mirror of
https://github.com/partitioncloud/partitioncloud-server.git
synced 2025-04-23 14:33:54 +02:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
bc91cec93f | |||
ffbf1907ad | |||
009be93b7a | |||
4fd789436b | |||
b71953fd1b | |||
9aa156a9b4 | |||
f6690f2013 | |||
178a540791 | |||
90c70a8d42 | |||
dbe77d0ece | |||
d1812cdde7 | |||
52894d37ea | |||
1bd70c8653 | |||
0a1d3f0f1c | |||
40becb01ce | |||
988f85b134 | |||
7ae8a1939a | |||
bfb6a127f0 | |||
3cbc586c78 | |||
5d0535ef70 | |||
c702cb714e | |||
b9a5f92a56 | |||
3f83f1c44a | |||
9a6d08d2e1 | |||
ebc454f7a2 | |||
3f888c39d2 | |||
eb5e1edf5e | |||
bf48ed29d7 | |||
511a4b3626 | |||
a97070eb2e | |||
c219f28a37 | |||
74444871c0 | |||
7acb446837 | |||
210ab6c0d3 | |||
2ff7a515d5 | |||
99c9781767 | |||
191ffebd7e | |||
5a7c3ed09d | |||
56d01ee3e2 | |||
fb13e396e5 | |||
bb59dfe992 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,10 @@
|
||||
# cache
|
||||
**/__pycache__
|
||||
|
||||
# translations
|
||||
**.mo
|
||||
**.pot
|
||||
|
||||
# config
|
||||
.vscode/
|
||||
|
||||
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
|
||||
COPY . /app
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y python3-pip sqlite3 ghostscript
|
||||
|
||||
RUN rm /app/instance -rf
|
||||
RUN bash make.sh init
|
||||
RUN pip3 install -r requirements.txt
|
||||
RUN pip3 install gunicorn
|
||||
|
||||
|
||||
CMD [ "bash", "make.sh" , "production"]
|
52
README.md
52
README.md
@ -10,7 +10,7 @@ Serveur web (basé sur Flask) pour gérer sa collection de partitions musicales
|
||||
- Thème sombre
|
||||
- dashboard administrateur: gestion de tous les albums, partitions et utilisateurs
|
||||
- [CLI](https://github.com/partitioncloud/partitioncloud-cli) uniquement à des fins de synchronisation. Il serait bon d'ajouter une BDD locale avec les UUIDs des partitions
|
||||
- ~~Pas de Javascript~~ Complètement fonctionnel sans JavaScript, cela vient juste ajouter des [toutes petites améliorations](partitioncloud/static/main.js)
|
||||
- ~~Pas de Javascript~~ Complètement fonctionnel sans JavaScript, cela vient juste ajouter des [toutes petites améliorations](partitioncloud/static/scripts)
|
||||
|
||||
## Points à noter
|
||||
- Les partitions ajoutées sont accessibles à tous les utilisateurs depuis la recherche même si ils ne sont pas dans un album leur y donnant accès, pour limiter la redondance
|
||||
@ -20,6 +20,26 @@ Serveur web (basé sur Flask) pour gérer sa collection de partitions musicales
|
||||
|
||||
## Installation
|
||||
|
||||
### Installation via Docker (recommandé)
|
||||
|
||||
```bash
|
||||
# Clone this repo
|
||||
git clone https://github.com/partitioncloud/partitioncloud-server.git
|
||||
cd partitioncloud-server
|
||||
# Create an image named "partitioncloud"
|
||||
docker build -t partitioncloud .
|
||||
# You can then run the container, replace $PORT with the port you want to be exposed
|
||||
PORT=5000
|
||||
docker run -d \
|
||||
-p $PORT:5000 \
|
||||
--restart=unless-stopped \
|
||||
--name partitioncloud \
|
||||
partitioncloud:latest
|
||||
```
|
||||
L'utilisateur par défaut est `root` avec le mot de passe `root`
|
||||
|
||||
### Installation manuelle
|
||||
|
||||
Installer le serveur
|
||||
```bash
|
||||
# Clone this repo
|
||||
@ -27,6 +47,7 @@ git clone https://github.com/partitioncloud/partitioncloud-server.git
|
||||
cd partitioncloud-server
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
pybabel compile -d partitioncloud/translations
|
||||
# Create database and folders
|
||||
./make.sh init
|
||||
```
|
||||
@ -66,9 +87,34 @@ Modifier le fichier de configuration créé dans `instance/`
|
||||

|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
### Créer une nouvelle traduction
|
||||
|
||||
```bash
|
||||
# Extraire les données
|
||||
pybabel extract -F babel.cfg -k _l -o partitioncloud/translations/messages.pot .
|
||||
# Créer un nouveau fichier
|
||||
pybabel init -i partitioncloud/translations/messages.pot -d partitioncloud/translations/ -l $COUNTRY_CODE
|
||||
# Modifier translations/$COUNTRY_CODE/LC_MESSAGES/messages.po
|
||||
# Ajouter $COUNTRY_CODE dans default_config.py: LANGUAGES
|
||||
# Compiler les nouvelles translations avant de démarrer le serveur
|
||||
pybabel compile -d partitioncloud/translations/
|
||||
```
|
||||
|
||||
### Mettre à jour une traduction
|
||||
|
||||
```bash
|
||||
# Récupérer les données les plus récentes
|
||||
pybabel extract -F babel.cfg -k _l -o partitioncloud/translations/messages.pot .
|
||||
# Les ajouter aux traductions
|
||||
pybabel update -i partitioncloud/translations/messages.pot -d partitioncloud/translations/
|
||||
```
|
||||
|
||||
## TODO
|
||||
- [ ] Modifier son mot de passe
|
||||
- [ ] Supprimer un utilisateur
|
||||
- [x] Modifier son mot de passe
|
||||
- [x] Supprimer un utilisateur
|
||||
- [ ] Ajouter config:DISABLE_DARK_MODE
|
||||
- [x] Ajouter config:DISABLE_REGISTER
|
||||
- [ ] Ajouter config:ONLINE_SEARCH_BASE_QUERY pour la recherche google, actuellement 'filetype:pdf partition'
|
||||
- [x] Ajouter un Dockerfile
|
||||
|
5
babel.cfg
Normal file
5
babel.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[extractors]
|
||||
jinja2 = jinja2.ext:babel_extract
|
||||
|
||||
[python: partitioncloud/**.py]
|
||||
[jinja2: partitioncloud/templates/**.html]
|
@ -14,6 +14,12 @@ MAX_ONLINE_QUERIES=3
|
||||
# Disable registration of new users via /auth/register (they can still be added by root)
|
||||
DISABLE_REGISTER=False
|
||||
|
||||
# Disable account deletion for users (still possible for admins)
|
||||
DISABLE_ACCOUNT_DELETION=False
|
||||
|
||||
# Set this to True if you want local search to be across all albums (not just those the user belong to)
|
||||
PRIVATE_SEARCH=False
|
||||
|
||||
# Front URL of the application (for QRCodes generation)
|
||||
BASE_URL="http://localhost:5000"
|
||||
|
||||
@ -24,3 +30,15 @@ MAX_AGE=31
|
||||
# Keep in mind that this config option can only be loaded from default_config.py,
|
||||
# as the custom config is stored in $INSTANCE_PATH/
|
||||
INSTANCE_PATH="instance"
|
||||
|
||||
# Events to log
|
||||
ENABLED_LOGS=["NEW_GROUPE", "NEW_ALBUM", "NEW_PARTITION", "NEW_USER", "PASSWORD_CHANGE", "DELETE_ACCOUNT", "SERVER_RESTART", "FAILED_LOGIN"]
|
||||
|
||||
# Available languages
|
||||
LANGUAGES=['en', 'fr']
|
||||
|
||||
# Show Launch page
|
||||
LAUNCH_PAGE=True
|
||||
|
||||
# Check if account is logged in before serving zipped album/groupe
|
||||
ZIP_REQUIRE_LOGIN=True
|
37
make.sh
37
make.sh
@ -9,7 +9,7 @@ init () {
|
||||
mkdir -p "$INSTANCE_PATH/search-partitions"
|
||||
mkdir -p "$INSTANCE_PATH/cache/thumbnails"
|
||||
mkdir -p "$INSTANCE_PATH/cache/search-thumbnails"
|
||||
|
||||
|
||||
if ! test -f "$INSTANCE_PATH/config.py"; then
|
||||
echo "SECRET_KEY=\"$(python3 -c 'import secrets; print(secrets.token_hex())')\"" > "$INSTANCE_PATH/config.py"
|
||||
fi
|
||||
@ -18,6 +18,7 @@ init () {
|
||||
printf "Souhaitez vous supprimer la base de données existante ? [y/n] "
|
||||
read -r CONFIRMATION
|
||||
[[ $CONFIRMATION == y ]] || exit 1
|
||||
rm "$INSTANCE_PATH/partitioncloud.sqlite"
|
||||
fi
|
||||
sqlite3 "$INSTANCE_PATH/partitioncloud.sqlite" '.read partitioncloud/schema.sql'
|
||||
echo "Base de données créé"
|
||||
@ -25,30 +26,54 @@ init () {
|
||||
echo "Utilisateur root:root ajouté"
|
||||
}
|
||||
|
||||
translations () {
|
||||
# Rajouter les chaînes non traduites
|
||||
pybabel extract -F babel.cfg -k _l -o partitioncloud/translations/messages.pot .
|
||||
pybabel update -i partitioncloud/translations/messages.pot -d partitioncloud/translations/
|
||||
# Compiler
|
||||
pybabel compile -d partitioncloud/translations/
|
||||
}
|
||||
|
||||
start () {
|
||||
pybabel compile -d partitioncloud/translations/
|
||||
flask run --port=$PORT
|
||||
}
|
||||
|
||||
production () {
|
||||
FLASK_APP=partitioncloud /usr/bin/gunicorn \
|
||||
pybabel compile -d partitioncloud/translations/
|
||||
FLASK_APP=partitioncloud gunicorn \
|
||||
wsgi:app \
|
||||
--bind 0.0.0.0:$PORT
|
||||
}
|
||||
|
||||
load_config () {
|
||||
# Load variables PORT and INSTANCE_PATH
|
||||
eval $(cat $1 | grep -E "^PORT=")
|
||||
eval $(cat $1 | grep -E "^INSTANCE_PATH=")
|
||||
}
|
||||
|
||||
|
||||
usage () {
|
||||
echo "Usage:"
|
||||
echo -e "\t$0 init"
|
||||
echo -e "\t$0 start"
|
||||
echo -e "\t$0 production"
|
||||
echo -e "\t$0 translations"
|
||||
}
|
||||
|
||||
if [[ $1 && $(type "$1") = *"is a"*"function"* || $(type "$1") == *"est une fonction"* ]]; then
|
||||
|
||||
RESULT=$(type "$1")
|
||||
if [[ $1 && $RESULT = *"is a"*"function"* || $RESULT == *"est une fonction"* ]]; then
|
||||
# Import config
|
||||
source "default_config.py"
|
||||
[[ ! -x "$INSTANCE_PATH/config.py" ]] && source "$INSTANCE_PATH/config.py"
|
||||
load_config "default_config.py"
|
||||
|
||||
if test -f "instance/config.py"; then
|
||||
load_config "instance/config.py"
|
||||
fi
|
||||
|
||||
$1 ${*:2} # Call the function
|
||||
else
|
||||
usage
|
||||
echo $(type "$1")
|
||||
echo $RESULT
|
||||
exit 1
|
||||
fi
|
||||
|
@ -8,16 +8,23 @@ import datetime
|
||||
import subprocess
|
||||
import importlib.util
|
||||
|
||||
from flask import Flask, g, redirect, render_template, request, send_file, flash, session, abort
|
||||
from flask import Flask, g, redirect, render_template, request, send_file, flash, session, abort, url_for
|
||||
from werkzeug.security import generate_password_hash
|
||||
from flask_babel import Babel, _
|
||||
|
||||
from .modules.utils import User, Album, get_all_albums
|
||||
from .modules import albums, auth, partition, admin, groupe, thumbnails
|
||||
from .modules.utils import User, Album, get_all_albums, user_count, partition_count
|
||||
from .modules import albums, auth, partition, admin, groupe, thumbnails, logging, settings
|
||||
from .modules.auth import admin_required, login_required
|
||||
from .modules.db import get_db
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def get_locale():
|
||||
return request.accept_languages.best_match(app.config['LANGUAGES'])
|
||||
|
||||
babel = Babel(app, locale_selector=get_locale)
|
||||
|
||||
|
||||
|
||||
def load_config():
|
||||
app.config.from_object('default_config')
|
||||
@ -33,14 +40,20 @@ def load_config():
|
||||
".",
|
||||
os.path.join(app.instance_path, "config.py")
|
||||
)
|
||||
|
||||
if spec is None:
|
||||
print("[ERROR] Failed to load $INSTANCE_PATH/config.py")
|
||||
sys.exit(1)
|
||||
|
||||
user_config = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(user_config)
|
||||
|
||||
app.config.from_object(user_config)
|
||||
|
||||
if os.path.abspath(app.config["INSTANCE_PATH"]) != app.instance_path:
|
||||
print("[ERROR] Using two different instance path. \
|
||||
\nPlease modify INSTANCE_PATH only in default_config.py and remove it from $INSTANCE_PATH/config.py")
|
||||
print(("[ERROR] Using two different instance path.\n"
|
||||
"Please modify INSTANCE_PATH only in default_config.py ",
|
||||
"and remove it from $INSTANCE_PATH/config.py"))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("[WARNING] Using default config")
|
||||
@ -50,6 +63,18 @@ def load_config():
|
||||
)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.log_file = os.path.join(app.instance_path, "logs.txt")
|
||||
enabled = []
|
||||
for event in app.config["ENABLED_LOGS"]:
|
||||
try:
|
||||
enabled.append(logging.LogEntry.from_string(event))
|
||||
except KeyError:
|
||||
print(f"[ERROR] There is an error in your config: Unknown event {event}")
|
||||
|
||||
logging.enabled = enabled
|
||||
|
||||
|
||||
def get_version():
|
||||
try:
|
||||
result = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, check=True)
|
||||
@ -60,21 +85,37 @@ def get_version():
|
||||
|
||||
|
||||
load_config()
|
||||
setup_logging()
|
||||
|
||||
app.register_blueprint(auth.bp)
|
||||
app.register_blueprint(admin.bp)
|
||||
app.register_blueprint(groupe.bp)
|
||||
app.register_blueprint(albums.bp)
|
||||
app.register_blueprint(settings.bp)
|
||||
app.register_blueprint(partition.bp)
|
||||
app.register_blueprint(thumbnails.bp)
|
||||
|
||||
__version__ = get_version()
|
||||
|
||||
logging.log([], logging.LogEntry.SERVER_RESTART)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def home():
|
||||
"""Redirect to home"""
|
||||
return redirect("/albums/")
|
||||
"""Show launch page if enabled"""
|
||||
if g.user is None:
|
||||
if app.config["LAUNCH_PAGE"]:
|
||||
return redirect(url_for("launch_page"))
|
||||
return redirect(url_for("auth.login"))
|
||||
return redirect(url_for("albums.index"))
|
||||
|
||||
|
||||
@app.route("/launch")
|
||||
def launch_page():
|
||||
"""Show launch page if enabled"""
|
||||
if not app.config["LAUNCH_PAGE"]:
|
||||
return home()
|
||||
return render_template("launch.html", user_count=user_count(), partition_count=partition_count())
|
||||
|
||||
|
||||
@app.route("/add-user", methods=["GET", "POST"])
|
||||
@ -95,14 +136,20 @@ def add_user():
|
||||
if error is None:
|
||||
# Success, go to the login page.
|
||||
user = User(name=username)
|
||||
|
||||
logging.log(
|
||||
[user.username, user.id, True, current_user.username],
|
||||
logging.LogEntry.NEW_USER
|
||||
)
|
||||
|
||||
try:
|
||||
if album_uuid != "":
|
||||
user.join_album(album_uuid)
|
||||
flash(f"Utilisateur {username} créé")
|
||||
return redirect("/albums")
|
||||
flash(_("Created user %(username)s", username=username))
|
||||
return redirect(url_for("albums.index"))
|
||||
except LookupError:
|
||||
flash(f"Cet album n'existe pas. L'utilisateur {username} a été créé")
|
||||
return redirect("/albums")
|
||||
flash(_("This album does not exists, but user %(username)s has been created", username=username))
|
||||
return redirect(url_for("albums.index"))
|
||||
|
||||
flash(error)
|
||||
return render_template("auth/register.html", albums=get_all_albums(), user=current_user)
|
||||
@ -119,7 +166,7 @@ def inject_default_variables():
|
||||
"""Inject the version number in the template variables"""
|
||||
if __version__ == "unknown":
|
||||
return {"version": ''}
|
||||
return {"version": __version__}
|
||||
return {"version": __version__, "lang": get_locale()}
|
||||
|
||||
|
||||
@app.after_request
|
||||
|
@ -2,7 +2,8 @@
|
||||
"""
|
||||
Admin Panel
|
||||
"""
|
||||
from flask import Blueprint, render_template, session
|
||||
import os
|
||||
from flask import Blueprint, render_template, session, current_app, send_file
|
||||
|
||||
from .db import get_db
|
||||
from .auth import admin_required
|
||||
@ -18,7 +19,6 @@ def index():
|
||||
Admin panel home page
|
||||
"""
|
||||
current_user = User(user_id=session.get("user_id"))
|
||||
current_user.get_albums() # We need to do that before we close the db
|
||||
db = get_db()
|
||||
users_id = db.execute(
|
||||
"""
|
||||
@ -35,3 +35,46 @@ def index():
|
||||
users=users,
|
||||
user=current_user
|
||||
)
|
||||
|
||||
@bp.route("/user/<user_id>")
|
||||
@admin_required
|
||||
def user_inspect(user_id):
|
||||
"""
|
||||
Inspect user
|
||||
"""
|
||||
current_user = User(user_id=session.get("user_id"))
|
||||
db = get_db()
|
||||
inspected_user = User(user_id=user_id)
|
||||
|
||||
return render_template(
|
||||
"settings/index.html",
|
||||
skip_old_password=True,
|
||||
inspected_user=inspected_user,
|
||||
user=current_user,
|
||||
deletion_allowed=True
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/logs")
|
||||
@admin_required
|
||||
def logs():
|
||||
"""
|
||||
Admin panel logs page
|
||||
"""
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
return render_template(
|
||||
"admin/logs.html",
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/logs.txt")
|
||||
@admin_required
|
||||
def logs_txt():
|
||||
"""
|
||||
Admin panel logs page
|
||||
"""
|
||||
return send_file(
|
||||
os.path.join(current_app.instance_path, "logs.txt")
|
||||
)
|
||||
|
@ -3,17 +3,20 @@
|
||||
Albums module
|
||||
"""
|
||||
import os
|
||||
import pypdf
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
from typing import TypeVar
|
||||
|
||||
from flask import (Blueprint, abort, flash, redirect, render_template,
|
||||
request, session, current_app)
|
||||
request, session, current_app, send_file, g, url_for)
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_babel import _
|
||||
|
||||
from .auth import login_required
|
||||
from .db import get_db
|
||||
from .utils import User, Album
|
||||
from . import search, utils
|
||||
from . import search, utils, logging
|
||||
|
||||
|
||||
bp = Blueprint("albums", __name__, url_prefix="/albums")
|
||||
@ -37,15 +40,21 @@ def search_page():
|
||||
Résultats de recherche
|
||||
"""
|
||||
if "query" not in request.form or request.form["query"] == "":
|
||||
flash("Aucun terme de recherche spécifié.")
|
||||
flash(_("Missing search query"))
|
||||
return redirect("/albums")
|
||||
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
query = request.form["query"]
|
||||
nb_queries = abs(int(request.form["nb-queries"]))
|
||||
search.flush_cache(current_app.instance_path)
|
||||
partitions_local = search.local_search(query, utils.get_all_partitions())
|
||||
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
partitions_list = None
|
||||
if current_app.config["PRIVATE_SEARCH"]:
|
||||
partitions_list = utils.get_all_partitions()
|
||||
else:
|
||||
partitions_list = user.get_accessible_partitions()
|
||||
partitions_local = search.local_search(query, partitions_list)
|
||||
|
||||
if nb_queries > 0:
|
||||
if user.access_level != 1:
|
||||
@ -80,7 +89,7 @@ def get_album(uuid):
|
||||
except LookupError:
|
||||
return abort(404)
|
||||
|
||||
album.users = [User(user_id=i["id"]) for i in album.get_users()]
|
||||
album.users = [User(user_id=u_id) for u_id in album.get_users()]
|
||||
user = User(user_id=session.get("user_id"))
|
||||
partitions = album.get_partitions()
|
||||
if user.id is None:
|
||||
@ -106,6 +115,30 @@ def qr_code(uuid):
|
||||
return utils.get_qrcode(f"/albums/{uuid}")
|
||||
|
||||
|
||||
@bp.route("/<uuid>/zip")
|
||||
def zip_download(uuid):
|
||||
"""
|
||||
Télécharger un album comme fichier zip
|
||||
"""
|
||||
if g.user is None and current_app.config["ZIP_REQUIRE_LOGIN"]:
|
||||
flash(_("You need to login to access this resource."))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
try:
|
||||
album = Album(uuid=uuid)
|
||||
except LookupError:
|
||||
try:
|
||||
album = Album(uuid=utils.format_uuid(uuid))
|
||||
return redirect(f"/albums/{utils.format_uuid(uuid)}")
|
||||
except LookupError:
|
||||
return abort(404)
|
||||
|
||||
return send_file(
|
||||
album.to_zip(current_app.instance_path),
|
||||
download_name=secure_filename(f"{album.name}.zip")
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/create-album", methods=["POST"])
|
||||
@login_required
|
||||
def create_album_req():
|
||||
@ -116,8 +149,10 @@ def create_album_req():
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
user = User(user_id=session["user_id"])
|
||||
|
||||
if not name or name.strip() == "":
|
||||
error = "Un nom est requis. L'album n'a pas été créé"
|
||||
error = _("Missing name.")
|
||||
|
||||
if error is None:
|
||||
uuid = utils.create_album(name)
|
||||
@ -131,6 +166,8 @@ def create_album_req():
|
||||
)
|
||||
db.commit()
|
||||
|
||||
logging.log([album.name, album.uuid, user.username], logging.LogEntry.NEW_ALBUM)
|
||||
|
||||
if "response" in request.args and request.args["response"] == "json":
|
||||
return {
|
||||
"status": "ok",
|
||||
@ -152,10 +189,10 @@ def join_album(uuid):
|
||||
try:
|
||||
user.join_album(uuid)
|
||||
except LookupError:
|
||||
flash("Cet album n'existe pas.")
|
||||
flash(_("This album does not exist."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
flash("Album ajouté à la collection.")
|
||||
flash(_("Album added to collection."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@ -167,17 +204,18 @@ def quit_album(uuid):
|
||||
"""
|
||||
user = User(user_id=session.get("user_id"))
|
||||
album = Album(uuid=uuid)
|
||||
|
||||
users = album.get_users()
|
||||
if user.id not in [u["id"] for u in users]:
|
||||
flash("Vous ne faites pas partie de cet album")
|
||||
if user.id not in users:
|
||||
flash(_("You are not a member of this album"))
|
||||
return redirect(request.referrer)
|
||||
|
||||
if len(users) == 1:
|
||||
flash("Vous êtes seul dans cet album, le quitter entraînera sa suppression.")
|
||||
flash(_("You are alone here, quitting means deleting this album."))
|
||||
return redirect(f"/albums/{uuid}#delete")
|
||||
|
||||
user.quit_album(uuid)
|
||||
flash("Album quitté.")
|
||||
flash(_("Album quitted."))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -196,9 +234,9 @@ def delete_album(uuid):
|
||||
error = None
|
||||
users = album.get_users()
|
||||
if len(users) > 1:
|
||||
error = "Vous n'êtes pas seul dans cet album."
|
||||
elif len(users) == 1 and users[0]["id"] != user.id:
|
||||
error = "Vous ne possédez pas cet album."
|
||||
error = _("You are not alone in this album.")
|
||||
elif len(users) == 1 and users[0] != user.id:
|
||||
error = _("You don't own this album.")
|
||||
|
||||
if user.access_level == 1:
|
||||
error = None
|
||||
@ -209,7 +247,7 @@ def delete_album(uuid):
|
||||
|
||||
album.delete(current_app.instance_path)
|
||||
|
||||
flash("Album supprimé.")
|
||||
flash(_("Album deleted."))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -217,7 +255,7 @@ def delete_album(uuid):
|
||||
@login_required
|
||||
def add_partition(album_uuid):
|
||||
"""
|
||||
Ajouter une partition à un album (par upload)
|
||||
Ajouter une partition à un album (nouveau fichier)
|
||||
"""
|
||||
T = TypeVar("T")
|
||||
def get_opt_string(dictionary: dict[T, str], key: T):
|
||||
@ -232,15 +270,15 @@ def add_partition(album_uuid):
|
||||
source = "upload" # source type: upload, unknown or url
|
||||
|
||||
if (not user.is_participant(album.uuid)) and (user.access_level != 1):
|
||||
flash("Vous ne participez pas à cet album.")
|
||||
flash(_("You are not a member of this album"))
|
||||
return redirect(request.referrer)
|
||||
|
||||
error = None
|
||||
|
||||
if "name" not in request.form:
|
||||
error = "Un titre est requis."
|
||||
error = _("Missing title")
|
||||
elif "file" not in request.files and "partition-uuid" not in request.form:
|
||||
error = "Aucun fichier n'a été fourni."
|
||||
error = _("Missing file")
|
||||
elif "file" not in request.files:
|
||||
partition_type = "uuid"
|
||||
search_uuid = request.form["partition-uuid"]
|
||||
@ -252,12 +290,18 @@ def add_partition(album_uuid):
|
||||
(search_uuid,)
|
||||
).fetchone()
|
||||
if data is None:
|
||||
error = "Les résultats de la recherche ont expiré."
|
||||
error = _("Search results expired")
|
||||
else:
|
||||
source = data["url"]
|
||||
else:
|
||||
partition_type = "file"
|
||||
|
||||
try:
|
||||
pypdf.PdfReader(request.files["file"])
|
||||
request.files["file"].seek(0)
|
||||
except (pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
|
||||
error = _("Invalid PDF file")
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
return redirect(request.referrer)
|
||||
@ -265,6 +309,7 @@ def add_partition(album_uuid):
|
||||
author = get_opt_string(request.form, "author")
|
||||
body = get_opt_string(request.form, "body")
|
||||
|
||||
partition_uuid: str
|
||||
while True:
|
||||
try:
|
||||
partition_uuid = str(uuid4())
|
||||
@ -307,12 +352,17 @@ def add_partition(album_uuid):
|
||||
except db.IntegrityError:
|
||||
pass
|
||||
|
||||
logging.log(
|
||||
[request.form["name"], partition_uuid, user.username],
|
||||
logging.LogEntry.NEW_PARTITION
|
||||
)
|
||||
|
||||
if "response" in request.args and request.args["response"] == "json":
|
||||
return {
|
||||
"status": "ok",
|
||||
"uuid": partition_uuid
|
||||
}
|
||||
flash(f"Partition {request.form['name']} ajoutée")
|
||||
flash(_("Score %(partition_name)s added", partition_name=request.form['name']))
|
||||
return redirect(f"/albums/{album.uuid}")
|
||||
|
||||
|
||||
@ -320,19 +370,19 @@ def add_partition(album_uuid):
|
||||
@login_required
|
||||
def add_partition_from_search():
|
||||
"""
|
||||
Ajout d'une partition (depuis la recherche)
|
||||
Ajout d'une partition (depuis la recherche locale)
|
||||
"""
|
||||
user = User(user_id=session.get("user_id"))
|
||||
error = None
|
||||
|
||||
if "album-uuid" not in request.form:
|
||||
error = "Il est nécessaire de sélectionner un album."
|
||||
error = _("Selecting an album is mandatory.")
|
||||
elif "partition-uuid" not in request.form:
|
||||
error = "Il est nécessaire de sélectionner une partition."
|
||||
error = _("Selecting a score is mandatory.")
|
||||
elif "partition-type" not in request.form:
|
||||
error = "Il est nécessaire de spécifier un type de partition."
|
||||
error = _("Please specify a score type.")
|
||||
elif (not user.is_participant(request.form["album-uuid"])) and (user.access_level != 1):
|
||||
error = "Vous ne participez pas à cet album."
|
||||
error = _("You are not a member of this album")
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
@ -352,9 +402,9 @@ def add_partition_from_search():
|
||||
|
||||
if data is None:
|
||||
album.add_partition(request.form["partition-uuid"])
|
||||
flash("Partition ajoutée.")
|
||||
flash(_("Score added"))
|
||||
else:
|
||||
flash("Partition déjà dans l'album.")
|
||||
flash(_("Score is already in the album."))
|
||||
|
||||
return redirect(f"/albums/{album.uuid}")
|
||||
|
||||
@ -366,5 +416,5 @@ def add_partition_from_search():
|
||||
user=user
|
||||
)
|
||||
|
||||
flash("Type de partition inconnu.")
|
||||
flash(_("Unknown score type."))
|
||||
return redirect("/albums")
|
||||
|
@ -7,11 +7,13 @@ from typing import Optional
|
||||
|
||||
from flask import (Blueprint, flash, g, redirect, render_template,
|
||||
request, session, url_for, current_app)
|
||||
from flask_babel import _
|
||||
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
from .db import get_db
|
||||
from .utils import User
|
||||
from . import logging
|
||||
|
||||
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
|
||||
@ -22,7 +24,7 @@ def login_required(view):
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None:
|
||||
flash("Vous devez être connecté pour accéder à cette page.")
|
||||
flash(_("You need to login to access this resource."))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return view(**kwargs)
|
||||
@ -49,12 +51,12 @@ def admin_required(view):
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None:
|
||||
flash("Vous devez être connecté pour accéder à cette page.")
|
||||
flash(_("You need to login to access this resource."))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
user = User(user_id=session.get("user_id"))
|
||||
if user.access_level != 1:
|
||||
flash("Droits insuffisants.")
|
||||
flash(_("Missing rights."))
|
||||
return redirect("/albums")
|
||||
|
||||
return view(**kwargs)
|
||||
@ -80,9 +82,9 @@ def create_user(username: str, password: str) -> Optional[str]:
|
||||
"""Adds a new user to the database"""
|
||||
error = None
|
||||
if not username:
|
||||
error = "Un nom d'utilisateur est requis."
|
||||
error = _("Missing username.")
|
||||
elif not password:
|
||||
error = "Un mot de passe est requis."
|
||||
error = _("Missing password.")
|
||||
|
||||
try:
|
||||
db = get_db()
|
||||
@ -95,7 +97,7 @@ def create_user(username: str, password: str) -> Optional[str]:
|
||||
except db.IntegrityError:
|
||||
# The username was already taken, which caused the
|
||||
# commit to fail. Show a validation error.
|
||||
error = f"Le nom d'utilisateur {username} est déjà pris."
|
||||
error = _("Username %(username)s is not available.", username=username)
|
||||
|
||||
return error # may be None
|
||||
|
||||
@ -108,7 +110,7 @@ def register():
|
||||
password for security.
|
||||
"""
|
||||
if current_app.config["DISABLE_REGISTER"]:
|
||||
flash("L'enregistrement de nouveaux utilisateurs a été désactivé par l'administrateur.")
|
||||
flash(_("New users registration is disabled by owner."))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
if request.method == "POST":
|
||||
@ -120,7 +122,15 @@ def register():
|
||||
if error is not None:
|
||||
flash(error)
|
||||
else:
|
||||
flash("Utilisateur créé avec succès. Vous pouvez vous connecter.")
|
||||
user = User(name=username)
|
||||
|
||||
flash(_("Successfully created new user. You can log in."))
|
||||
|
||||
logging.log(
|
||||
[user.username, user.id, False],
|
||||
logging.LogEntry.NEW_USER
|
||||
)
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
return render_template("auth/register.html")
|
||||
|
||||
@ -139,12 +149,16 @@ def login():
|
||||
).fetchone()
|
||||
|
||||
if (user is None) or not check_password_hash(user["password"], password):
|
||||
error = "Nom d'utilisateur ou mot de passe incorrect."
|
||||
logging.log([username], logging.LogEntry.FAILED_LOGIN)
|
||||
error = _("Incorrect username or password")
|
||||
|
||||
if error is None:
|
||||
# store the user id in a new session and return to the index
|
||||
session.clear()
|
||||
session["user_id"] = user["id"]
|
||||
|
||||
logging.log([username], logging.LogEntry.LOGIN)
|
||||
|
||||
return redirect(url_for("albums.index"))
|
||||
|
||||
flash(error)
|
||||
@ -156,4 +170,4 @@ def login():
|
||||
def logout():
|
||||
"""Clear the current session, including the stored user id."""
|
||||
session.clear()
|
||||
return redirect(url_for("auth.login"))
|
||||
return redirect("/")
|
||||
|
@ -2,6 +2,10 @@
|
||||
Classe Album
|
||||
"""
|
||||
import os
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..db import get_db
|
||||
from ..utils import new_uuid
|
||||
@ -47,11 +51,11 @@ class Album():
|
||||
|
||||
def get_users(self, force_reload=False):
|
||||
"""
|
||||
Renvoie les utilisateurs liés à l'album
|
||||
Renvoie les data["id"] des utilisateurs liés à l'album
|
||||
"""
|
||||
if self.users is None or force_reload:
|
||||
db = get_db()
|
||||
self.users = db.execute(
|
||||
data = db.execute(
|
||||
"""
|
||||
SELECT * FROM user
|
||||
JOIN contient_user ON user_id = user.id
|
||||
@ -60,6 +64,7 @@ class Album():
|
||||
""",
|
||||
(self.uuid,)
|
||||
).fetchall()
|
||||
self.users = [i["id"] for i in data]
|
||||
return self.users
|
||||
|
||||
def get_partitions(self):
|
||||
@ -114,7 +119,7 @@ class Album():
|
||||
"""
|
||||
SELECT partition.uuid FROM partition
|
||||
WHERE NOT EXISTS (
|
||||
SELECT NULL FROM contient_partition
|
||||
SELECT NULL FROM contient_partition
|
||||
WHERE partition.uuid = partition_uuid
|
||||
)
|
||||
"""
|
||||
@ -142,7 +147,7 @@ class Album():
|
||||
WHERE uuid IN (
|
||||
SELECT partition.uuid FROM partition
|
||||
WHERE NOT EXISTS (
|
||||
SELECT NULL FROM contient_partition
|
||||
SELECT NULL FROM contient_partition
|
||||
WHERE partition.uuid = partition_uuid
|
||||
)
|
||||
)
|
||||
@ -165,6 +170,23 @@ class Album():
|
||||
)
|
||||
db.commit()
|
||||
|
||||
|
||||
def to_zip(self, instance_path):
|
||||
data = io.BytesIO()
|
||||
with zipfile.ZipFile(data, mode="w") as z:
|
||||
for partition in self.get_partitions():
|
||||
z.write(os.path.join(
|
||||
instance_path,
|
||||
"partitions",
|
||||
f"{partition['uuid']}.pdf"
|
||||
), arcname=secure_filename(partition['name']+".pdf")
|
||||
)
|
||||
|
||||
# Spooling back to the beginning of the buffer
|
||||
data.seek(0)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def create(name: str) -> str:
|
||||
"""Créer un nouvel album"""
|
||||
|
@ -1,3 +1,12 @@
|
||||
"""
|
||||
Classe Groupe
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..db import get_db
|
||||
from .album import Album
|
||||
|
||||
@ -66,21 +75,23 @@ class Groupe():
|
||||
album.delete(instance_path)
|
||||
|
||||
|
||||
def get_users(self):
|
||||
def get_users(self, force_reload=False):
|
||||
"""
|
||||
Renvoie les data["id"] des utilisateurs liés au groupe
|
||||
TODO: uniformiser le tout
|
||||
"""
|
||||
db = get_db()
|
||||
return db.execute(
|
||||
"""
|
||||
SELECT * FROM user
|
||||
JOIN groupe_contient_user ON user_id = user.id
|
||||
JOIN groupe ON groupe.id = groupe_id
|
||||
WHERE groupe.id = ?
|
||||
""",
|
||||
(self.id,)
|
||||
).fetchall()
|
||||
if self.users is None or force_reload:
|
||||
db = get_db()
|
||||
data = db.execute(
|
||||
"""
|
||||
SELECT * FROM user
|
||||
JOIN groupe_contient_user ON user_id = user.id
|
||||
JOIN groupe ON groupe.id = groupe_id
|
||||
WHERE groupe.id = ?
|
||||
""",
|
||||
(self.id,)
|
||||
).fetchall()
|
||||
self.users = [i["id"] for i in data]
|
||||
return self.users
|
||||
|
||||
def get_albums(self, force_reload=False):
|
||||
"""
|
||||
@ -116,3 +127,36 @@ class Groupe():
|
||||
(self.id,)
|
||||
).fetchall()
|
||||
return [i["id"] for i in data]
|
||||
|
||||
def set_admin(self, user_id, value):
|
||||
"""
|
||||
Rend un utilisateur administrateur du groupe
|
||||
"""
|
||||
db = get_db()
|
||||
data = db.execute(
|
||||
"""
|
||||
UPDATE groupe_contient_user
|
||||
SET is_admin=?
|
||||
WHERE user_id=? AND groupe_id=?
|
||||
""",
|
||||
(value, user_id, self.id)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
def to_zip(self, instance_path):
|
||||
data = io.BytesIO()
|
||||
with zipfile.ZipFile(data, mode="w") as z:
|
||||
for album in self.get_albums():
|
||||
for partition in album.get_partitions():
|
||||
z.write(os.path.join(
|
||||
instance_path,
|
||||
"partitions",
|
||||
f"{partition['uuid']}.pdf"
|
||||
), arcname=secure_filename(album.name)+"/"
|
||||
+secure_filename(partition['name']+".pdf")
|
||||
)
|
||||
|
||||
# Spooling back to the beginning of the buffer
|
||||
data.seek(0)
|
||||
|
||||
return data
|
@ -75,6 +75,16 @@ class Partition():
|
||||
)
|
||||
db.commit()
|
||||
|
||||
def update_file(self, file, instance_path):
|
||||
partition_path = os.path.join(
|
||||
instance_path,
|
||||
"partitions",
|
||||
f"{self.uuid}.pdf"
|
||||
)
|
||||
file.save(partition_path)
|
||||
if os.path.exists(f"{instance_path}/cache/thumbnails/{self.uuid}.jpg"):
|
||||
os.remove(f"{instance_path}/cache/thumbnails/{self.uuid}.jpg")
|
||||
|
||||
def get_user(self):
|
||||
db = get_db()
|
||||
user = db.execute(
|
||||
|
@ -1,4 +1,5 @@
|
||||
from flask import current_app
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from ..db import get_db
|
||||
from .album import Album
|
||||
@ -28,9 +29,11 @@ class User():
|
||||
def __init__(self, user_id=None, name=None):
|
||||
self.id = user_id
|
||||
self.username = name
|
||||
self.password = None
|
||||
self.albums = None
|
||||
self.groupes = None
|
||||
self.partitions = None
|
||||
self.accessible_partitions = None
|
||||
self.max_queries = 0
|
||||
|
||||
db = get_db()
|
||||
@ -58,6 +61,7 @@ class User():
|
||||
|
||||
self.id = data["id"]
|
||||
self.username = data["username"]
|
||||
self.password = data["password"]
|
||||
self.access_level = data["access_level"]
|
||||
self.color = self.get_color()
|
||||
if self.access_level == 1:
|
||||
@ -166,6 +170,44 @@ class User():
|
||||
).fetchall()
|
||||
return self.partitions
|
||||
|
||||
def get_accessible_partitions(self, force_reload=False):
|
||||
if self.accessible_partitions is None or force_reload:
|
||||
db = get_db()
|
||||
if self.access_level == 1:
|
||||
self.accessible_partitions = db.execute(
|
||||
"""
|
||||
SELECT * FROM partition
|
||||
"""
|
||||
).fetchall()
|
||||
else:
|
||||
self.accessible_partitions = db.execute(
|
||||
"""
|
||||
SELECT DISTINCT partition.uuid, partition.name,
|
||||
partition.author, partition.body,
|
||||
partition.user_id, partition.source
|
||||
FROM partition
|
||||
JOIN album
|
||||
JOIN contient_partition
|
||||
ON album.id=album_id
|
||||
AND partition.uuid=partition_uuid
|
||||
WHERE album.id IN (
|
||||
SELECT album.id FROM album
|
||||
JOIN contient_user
|
||||
ON contient_user.user_id=?
|
||||
AND album_id=album.id
|
||||
UNION
|
||||
SELECT DISTINCT album.id FROM album
|
||||
JOIN groupe_contient_user
|
||||
JOIN groupe_contient_album
|
||||
ON groupe_contient_user.user_id=?
|
||||
AND groupe_contient_album.album_id=album.id
|
||||
AND groupe_contient_user.groupe_id=groupe_contient_album.groupe_id
|
||||
)
|
||||
""",
|
||||
(self.id, self.id,),
|
||||
).fetchall()
|
||||
return self.accessible_partitions
|
||||
|
||||
def join_album(self, album_uuid):
|
||||
db = get_db()
|
||||
album = Album(uuid=album_uuid)
|
||||
@ -198,12 +240,10 @@ class User():
|
||||
db.execute(
|
||||
"""
|
||||
DELETE FROM contient_user
|
||||
JOIN album
|
||||
ON album.id = album_id
|
||||
WHERE user_id = ?
|
||||
AND album.uuid = ?
|
||||
WHERE album_id IN (SELECT id FROM album WHERE uuid = ?)
|
||||
AND user_id = ?
|
||||
""",
|
||||
(self.id, album_uuid)
|
||||
(album_uuid, self.id)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
@ -221,6 +261,49 @@ class User():
|
||||
)
|
||||
db.commit()
|
||||
|
||||
def update_password(self, new_password):
|
||||
db = get_db()
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE user SET password=?
|
||||
WHERE id=?
|
||||
""",
|
||||
(generate_password_hash(new_password), self.id)
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
def delete(self):
|
||||
instance_path = current_app.config["INSTANCE_PATH"]
|
||||
for groupe in self.get_groupes():
|
||||
self.quit_groupe(groupe.uuid)
|
||||
|
||||
if groupe.get_users() == []:
|
||||
groupe.delete(instance_path)
|
||||
|
||||
|
||||
for album_data in self.get_albums():
|
||||
uuid = album_data["uuid"]
|
||||
self.quit_album(uuid)
|
||||
|
||||
album = Album(uuid=uuid)
|
||||
if album.get_users() == []:
|
||||
album.delete(instance_path)
|
||||
|
||||
db = get_db()
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
DELETE FROM user
|
||||
WHERE id=?
|
||||
""",
|
||||
(self.id,)
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
def get_color(self):
|
||||
if len(colors) == 0:
|
||||
|
@ -3,12 +3,15 @@
|
||||
Groupe module
|
||||
"""
|
||||
from flask import (Blueprint, abort, flash, redirect, render_template,
|
||||
request, session, current_app)
|
||||
request, session, current_app, send_file, g, url_for)
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_babel import _
|
||||
|
||||
from .auth import login_required
|
||||
from .db import get_db
|
||||
from .utils import User, Album, Groupe
|
||||
from . import utils
|
||||
from . import logging
|
||||
|
||||
bp = Blueprint("groupe", __name__, url_prefix="/groupe")
|
||||
|
||||
@ -32,7 +35,7 @@ def get_groupe(uuid):
|
||||
except LookupError:
|
||||
return abort(404)
|
||||
|
||||
groupe.users = [User(user_id=i["id"]) for i in groupe.get_users()]
|
||||
groupe.users = [User(user_id=u_id) for u_id in groupe.get_users()]
|
||||
groupe.get_albums()
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
@ -63,8 +66,10 @@ def create_groupe():
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
user = User(user_id=session["user_id"])
|
||||
|
||||
if not name or name.strip() == "":
|
||||
error = "Un nom est requis. Le groupe n'a pas été créé"
|
||||
error = _("Missing name.")
|
||||
|
||||
if error is None:
|
||||
while True:
|
||||
@ -93,6 +98,8 @@ def create_groupe():
|
||||
except db.IntegrityError:
|
||||
pass
|
||||
|
||||
logging.log([name, uuid, user.username], logging.LogEntry.NEW_GROUPE)
|
||||
|
||||
if "response" in request.args and request.args["response"] == "json":
|
||||
return {
|
||||
"status": "ok",
|
||||
@ -111,10 +118,10 @@ def join_groupe(uuid):
|
||||
try:
|
||||
user.join_groupe(uuid)
|
||||
except LookupError:
|
||||
flash("Ce groupe n'existe pas.")
|
||||
flash(_("Unknown group."))
|
||||
return redirect(f"/groupe/{uuid}")
|
||||
|
||||
flash("Groupe ajouté à la collection.")
|
||||
flash(_("Group added to collection."))
|
||||
return redirect(f"/groupe/{uuid}")
|
||||
|
||||
|
||||
@ -124,16 +131,21 @@ def quit_groupe(uuid):
|
||||
user = User(user_id=session.get("user_id"))
|
||||
groupe = Groupe(uuid=uuid)
|
||||
users = groupe.get_users()
|
||||
if user.id not in [u["id"] for u in users]:
|
||||
flash("Vous ne faites pas partie de ce groupe")
|
||||
if user.id not in users:
|
||||
flash(_("You are not a member of this group."))
|
||||
return redirect(f"/groupe/{uuid}")
|
||||
|
||||
if len(users) == 1:
|
||||
flash("Vous êtes seul dans ce groupe, le quitter entraînera sa suppression.")
|
||||
flash(_("You are alone here, quitting means deleting this group."))
|
||||
return redirect(f"/groupe/{uuid}#delete")
|
||||
|
||||
user.quit_groupe(groupe.uuid)
|
||||
flash("Groupe quitté.")
|
||||
|
||||
if len(groupe.get_admins()) == 0: # On s'assure que le groupe contient toujours des administrateurs
|
||||
for user_id in groupe.get_users(force_reload=True):
|
||||
groupe.set_admin(user_id, True)
|
||||
|
||||
flash(_("Group quitted."))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -144,9 +156,8 @@ def delete_groupe(uuid):
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
error = None
|
||||
users = groupe.get_users()
|
||||
if len(users) > 1:
|
||||
error = "Vous n'êtes pas seul dans ce groupe."
|
||||
if len(groupe.get_users()) > 1:
|
||||
error = _("You are not alone in this group.")
|
||||
|
||||
if user.access_level == 1 or user.id not in groupe.get_admins():
|
||||
error = None
|
||||
@ -157,7 +168,7 @@ def delete_groupe(uuid):
|
||||
|
||||
groupe.delete(current_app.instance_path)
|
||||
|
||||
flash("Groupe supprimé.")
|
||||
flash(_("Group deleted."))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -176,10 +187,10 @@ def create_album_req(groupe_uuid):
|
||||
error = None
|
||||
|
||||
if not name or name.strip() == "":
|
||||
error = "Un nom est requis. L'album n'a pas été créé"
|
||||
error = _("Missing name.")
|
||||
|
||||
if user.id not in groupe.get_admins():
|
||||
error ="Vous n'êtes pas administrateur de ce groupe"
|
||||
if user.id not in groupe.get_admins() and user.access_level != 1:
|
||||
error = _("You are not admin of this group.")
|
||||
|
||||
if error is None:
|
||||
uuid = utils.create_album(name)
|
||||
@ -194,6 +205,8 @@ def create_album_req(groupe_uuid):
|
||||
)
|
||||
db.commit()
|
||||
|
||||
logging.log([album.name, album.uuid, user.username], logging.LogEntry.NEW_ALBUM)
|
||||
|
||||
if "response" in request.args and request.args["response"] == "json":
|
||||
return {
|
||||
"status": "ok",
|
||||
@ -232,7 +245,7 @@ def get_album(groupe_uuid, album_uuid):
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
# List of users without duplicate
|
||||
users_id = list({i["id"] for i in album.get_users()+groupe.get_users()})
|
||||
users_id = list(set(album.get_users()+groupe.get_users()))
|
||||
album.users = [User(user_id=id) for id in users_id]
|
||||
|
||||
partitions = album.get_partitions()
|
||||
@ -253,6 +266,31 @@ def get_album(groupe_uuid, album_uuid):
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<groupe_uuid>/zip")
|
||||
def zip_download(groupe_uuid):
|
||||
"""
|
||||
Télécharger un groupe comme fichier zip
|
||||
"""
|
||||
if g.user is None and current_app.config["ZIP_REQUIRE_LOGIN"]:
|
||||
flash(_("You need to login to access this resource."))
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
try:
|
||||
groupe = Groupe(uuid=groupe_uuid)
|
||||
except LookupError:
|
||||
try:
|
||||
groupe = Groupe(uuid=utils.format_uuid(groupe_uuid))
|
||||
return redirect(f"/groupe/{utils.format_uuid(groupe_uuid)}/zip")
|
||||
except LookupError:
|
||||
return abort(404)
|
||||
|
||||
|
||||
return send_file(
|
||||
groupe.to_zip(current_app.instance_path),
|
||||
download_name=secure_filename(f"{groupe.name}.zip")
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/<groupe_uuid>/<album_uuid>/qr")
|
||||
def groupe_qr_code(groupe_uuid, album_uuid):
|
||||
return utils.get_qrcode(f"/groupe/{groupe_uuid}/{album_uuid}")
|
||||
|
90
partitioncloud/modules/logging.py
Normal file
90
partitioncloud/modules/logging.py
Normal file
@ -0,0 +1,90 @@
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
from enum import Enum
|
||||
|
||||
global log_file
|
||||
global enabled
|
||||
|
||||
|
||||
class LogEntry(Enum):
|
||||
LOGIN = 1
|
||||
NEW_GROUPE = 2
|
||||
NEW_ALBUM = 3
|
||||
NEW_PARTITION = 4
|
||||
NEW_USER = 5
|
||||
PASSWORD_CHANGE = 6
|
||||
DELETE_ACCOUNT = 7
|
||||
SERVER_RESTART = 8
|
||||
FAILED_LOGIN = 9
|
||||
|
||||
def from_string(entry: str):
|
||||
mapping = {
|
||||
"LOGIN": LogEntry.LOGIN,
|
||||
"NEW_GROUPE": LogEntry.NEW_GROUPE,
|
||||
"NEW_ALBUM": LogEntry.NEW_ALBUM,
|
||||
"NEW_PARTITION": LogEntry.NEW_PARTITION,
|
||||
"NEW_USER": LogEntry.NEW_USER,
|
||||
"PASSWORD_CHANGE": LogEntry.PASSWORD_CHANGE,
|
||||
"DELETE_ACCOUNT": LogEntry.DELETE_ACCOUNT,
|
||||
"SERVER_RESTART": LogEntry.SERVER_RESTART,
|
||||
"FAILED_LOGIN": LogEntry.FAILED_LOGIN
|
||||
}
|
||||
# Will return KeyError if not available
|
||||
return mapping[entry]
|
||||
|
||||
|
||||
def add_entry(entry: str) -> None:
|
||||
date = datetime.now().strftime('%d-%b-%Y %H:%M:%S')
|
||||
|
||||
with open(log_file, 'a', encoding="utf8") as f:
|
||||
f.write(f"[{date}] {entry}\n")
|
||||
|
||||
|
||||
def log(content: list[Union[str, bool, int]], log_type: LogEntry) -> None:
|
||||
description: str = ""
|
||||
|
||||
if log_type not in enabled:
|
||||
return
|
||||
|
||||
match log_type:
|
||||
case LogEntry.LOGIN: # content = (user.name)
|
||||
description = f"Successful login for {content[0]}"
|
||||
|
||||
case LogEntry.NEW_GROUPE: # content = (groupe.name, groupe.id, user.name)
|
||||
description = f"{content[2]} added groupe '{content[0]}' ({content[1]})"
|
||||
|
||||
case LogEntry.NEW_ALBUM: # content = (album.name, album.id, user.name)
|
||||
description = f"{content[2]} added album '{content[0]}' ({content[1]})"
|
||||
|
||||
case LogEntry.NEW_PARTITION: # content = (partition.name, partition.uuid, user.name)
|
||||
description = f"{content[2]} added partition '{content[0]}' ({content[1]})"
|
||||
|
||||
case LogEntry.NEW_USER: # content = (user.name, user.id, from_register_page, admin.name if relevant)
|
||||
if not content[2]:
|
||||
description = f"New user {content[0]}[{content[1]}]"
|
||||
else:
|
||||
description = f"New user {content[0]}[{content[1]}] added by {content[3]}"
|
||||
|
||||
case LogEntry.PASSWORD_CHANGE: # content = (user.name, user.id, admin.name if relevant)
|
||||
if len(content) == 2:
|
||||
description = f"New password for {content[0]}[{content[1]}]"
|
||||
else:
|
||||
description = f"New password for {content[0]}[{content[1]}], changed by {content[2]}"
|
||||
|
||||
case LogEntry.DELETE_ACCOUNT: # content = (user.name, user.id, admin.name if relevant)
|
||||
if len(content) == 2:
|
||||
description = f"Account deleted {content[0]}[{content[1]}]"
|
||||
else:
|
||||
description = f"Account deleted {content[0]}[{content[1]}], by {content[2]}"
|
||||
|
||||
case LogEntry.SERVER_RESTART: # content = ()
|
||||
description = "Server just restarted"
|
||||
|
||||
case LogEntry.FAILED_LOGIN: # content = (user.name)
|
||||
description = f"Failed login for {content[0]}"
|
||||
|
||||
add_entry(description)
|
||||
|
||||
|
||||
log_file = "logs.txt"
|
||||
enabled = [i for i in LogEntry]
|
@ -3,9 +3,11 @@
|
||||
Partition module
|
||||
"""
|
||||
import os
|
||||
import pypdf
|
||||
from uuid import uuid4
|
||||
from flask import (Blueprint, abort, send_file, render_template,
|
||||
request, redirect, flash, session, current_app)
|
||||
from flask_babel import _
|
||||
|
||||
from .db import get_db
|
||||
from .auth import login_required, admin_required
|
||||
@ -54,12 +56,12 @@ def add_attachment(uuid):
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
if user.id != partition.user_id and user.access_level != 1:
|
||||
flash("Cette partition ne vous current_appartient pas")
|
||||
flash(_("You don't own this score."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
error = None # À mettre au propre
|
||||
if "file" not in request.files:
|
||||
error = "Aucun fichier n'a été fourni."
|
||||
error = _("Missing file")
|
||||
else:
|
||||
if "name" not in request.form or request.form["name"] == "":
|
||||
name = ".".join(request.files["file"].filename.split(".")[:-1])
|
||||
@ -67,12 +69,12 @@ def add_attachment(uuid):
|
||||
name = request.form["name"]
|
||||
|
||||
if name == "":
|
||||
error = "Pas de nom de fichier"
|
||||
error = _("Missing filename.")
|
||||
else:
|
||||
filename = request.files["file"].filename
|
||||
ext = filename.split(".")[-1]
|
||||
if ext not in ["mid", "mp3"]:
|
||||
error = "Extension de fichier non supportée"
|
||||
error = _("Unsupported file type.")
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
@ -140,7 +142,7 @@ def edit(uuid):
|
||||
|
||||
user = User(user_id=session.get("user_id"))
|
||||
if user.access_level != 1 and partition.user_id != user.id:
|
||||
flash("Vous n'êtes pas autorisé à modifier cette partition.")
|
||||
flash(_("You are not allowed to edit this file."))
|
||||
return redirect("/albums")
|
||||
|
||||
if request.method == "GET":
|
||||
@ -149,23 +151,34 @@ def edit(uuid):
|
||||
error = None
|
||||
|
||||
if "name" not in request.form or request.form["name"].strip() == "":
|
||||
error = "Un titre est requis."
|
||||
error = _("Missing title")
|
||||
elif "author" not in request.form:
|
||||
error = "Un nom d'auteur est requis (à minima nul)"
|
||||
error = _("Missing author in request body (can be null).")
|
||||
elif "body" not in request.form:
|
||||
error = "Des paroles sont requises (à minima nulles)"
|
||||
error = _("Missing lyrics (can be null).")
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
return redirect(f"/partition/{ uuid }/edit")
|
||||
|
||||
if request.files.get('file', None):
|
||||
new_file = request.files["file"]
|
||||
try:
|
||||
pypdf.PdfReader(new_file)
|
||||
new_file.seek(0)
|
||||
except (pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
|
||||
flash(_("Invalid PDF file"))
|
||||
return redirect(request.referrer)
|
||||
|
||||
partition.update_file(new_file, current_app.instance_path)
|
||||
|
||||
partition.update(
|
||||
name=request.form["name"],
|
||||
author=request.form["author"],
|
||||
body=request.form["body"]
|
||||
)
|
||||
|
||||
flash(f"Partition {request.form['name']} modifiée avec succès.")
|
||||
flash(_("Successfully modified %(name)s", name=request.form['name']))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -195,11 +208,11 @@ def details(uuid):
|
||||
error = None
|
||||
|
||||
if "name" not in request.form or request.form["name"].strip() == "":
|
||||
error = "Un titre est requis."
|
||||
error = _("Missing title")
|
||||
elif "author" not in request.form:
|
||||
error = "Un nom d'auteur est requis (à minima nul)"
|
||||
error = _("Missing author in request body (can be null).")
|
||||
elif "body" not in request.form:
|
||||
error = "Des paroles sont requises (à minima nulles)"
|
||||
error = _("Missing lyrics (can be null).")
|
||||
|
||||
if error is not None:
|
||||
flash(error)
|
||||
@ -211,7 +224,7 @@ def details(uuid):
|
||||
body=request.form["body"]
|
||||
)
|
||||
|
||||
flash(f"Partition {request.form['name']} modifiée avec succès.")
|
||||
flash(_("Successfully modified %(name)s", name=request.form['name']))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
@ -226,7 +239,7 @@ def delete(uuid):
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
if user.access_level != 1 and partition.user_id != user.id:
|
||||
flash("Vous n'êtes pas autorisé à supprimer cette partition.")
|
||||
flash(_("You are not allowed to delete this score."))
|
||||
return redirect("/albums")
|
||||
|
||||
if request.method == "GET":
|
||||
@ -234,7 +247,7 @@ def delete(uuid):
|
||||
|
||||
partition.delete(current_app.instance_path)
|
||||
|
||||
flash("Partition supprimée.")
|
||||
flash(_("Score deleted."))
|
||||
return redirect("/albums")
|
||||
|
||||
|
||||
|
@ -8,7 +8,9 @@ import threading
|
||||
import socket
|
||||
import os
|
||||
|
||||
import pypdf
|
||||
import googlesearch
|
||||
from unidecode import unidecode
|
||||
|
||||
from .db import get_db
|
||||
|
||||
@ -19,20 +21,20 @@ def local_search(query, partitions):
|
||||
"""
|
||||
Renvoie les 5 résultats les plus pertinents parmi une liste donnée
|
||||
"""
|
||||
query_words = [word.lower() for word in query.split(" ")]
|
||||
query_words = [word.lower() for word in unidecode(query).split()]
|
||||
def score_attribution(partition):
|
||||
score = 0
|
||||
for word in query_words:
|
||||
if word != "":
|
||||
if word in partition["name"].lower():
|
||||
if word in unidecode(partition["name"]).lower():
|
||||
score += 6
|
||||
elif word in partition["author"].lower():
|
||||
elif word in unidecode(partition["author"]).lower():
|
||||
score += 4
|
||||
elif word in partition["body"].lower():
|
||||
elif word in unidecode(partition["body"]).lower():
|
||||
score += 2
|
||||
else:
|
||||
score -= 1
|
||||
for word in partition["name"].split(" "):
|
||||
score -= 6
|
||||
for word in unidecode(partition["name"]).split():
|
||||
if word != "" and word.lower() not in query_words:
|
||||
score -= 1
|
||||
return score
|
||||
@ -52,12 +54,17 @@ def local_search(query, partitions):
|
||||
def download_search_result(element, instance_path):
|
||||
uuid = element["uuid"]
|
||||
url = element["url"]
|
||||
filename = f"{instance_path}/search-partitions/{uuid}.pdf"
|
||||
|
||||
try:
|
||||
urllib.request.urlretrieve(url, f"{instance_path}/search-partitions/{uuid}.pdf")
|
||||
urllib.request.urlretrieve(url, filename)
|
||||
pypdf.PdfReader(filename)
|
||||
|
||||
except (urllib.error.HTTPError, urllib.error.URLError):
|
||||
with open(f"{instance_path}/search-partitions/{uuid}.pdf", 'a', encoding="utf8") as _:
|
||||
except (urllib.error.HTTPError, urllib.error.URLError,
|
||||
pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
with open(filename, 'a', encoding="utf8") as _:
|
||||
pass # Create empty file
|
||||
|
||||
|
||||
|
106
partitioncloud/modules/settings.py
Normal file
106
partitioncloud/modules/settings.py
Normal file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
User Settings
|
||||
"""
|
||||
import os
|
||||
from flask import Blueprint, render_template, session, current_app, send_file, request, flash, redirect
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
from flask_babel import _
|
||||
|
||||
from .db import get_db
|
||||
from .auth import login_required
|
||||
from .utils import User
|
||||
from . import logging
|
||||
|
||||
|
||||
bp = Blueprint("settings", __name__, url_prefix="/settings")
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
"""
|
||||
Settings page
|
||||
"""
|
||||
user = User(user_id=session.get("user_id"))
|
||||
|
||||
return render_template(
|
||||
"settings/index.html",
|
||||
inspected_user=user,
|
||||
user=user,
|
||||
deletion_allowed=not current_app.config["DISABLE_ACCOUNT_DELETION"]
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/delete-account", methods=["POST"])
|
||||
@login_required
|
||||
def delete_account():
|
||||
log_data = None
|
||||
if "user_id" not in request.form:
|
||||
flash(_("Missing user id."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
cur_user = User(user_id=session.get("user_id"))
|
||||
user_id = request.form["user_id"]
|
||||
mod_user = User(user_id=user_id)
|
||||
|
||||
if cur_user.access_level != 1:
|
||||
log_data = [mod_user.username, mod_user.id]
|
||||
if cur_user.id != mod_user.id:
|
||||
flash(_("Missing rights."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
if current_app.config["DISABLE_ACCOUNT_DELETION"]:
|
||||
flash(_("You are not allowed to delete your account."))
|
||||
return redirect(request.referrer)
|
||||
else:
|
||||
log_data = [mod_user.username, mod_user.id, cur_user.username]
|
||||
|
||||
mod_user.delete()
|
||||
flash(_("User successfully deleted."))
|
||||
logging.log(log_data, logging.LogEntry.DELETE_ACCOUNT)
|
||||
if cur_user.id == mod_user.id:
|
||||
return redirect("/")
|
||||
return redirect("/admin")
|
||||
|
||||
|
||||
@bp.route("/change-password", methods=["POST"])
|
||||
@login_required
|
||||
def change_password():
|
||||
log_data = None
|
||||
if "user_id" not in request.form:
|
||||
flash(_("Missing user id."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
cur_user = User(user_id=session.get("user_id"))
|
||||
user_id = request.form["user_id"]
|
||||
mod_user = User(user_id=user_id)
|
||||
|
||||
if cur_user.access_level != 1:
|
||||
log_data = [mod_user.username, mod_user.id]
|
||||
if cur_user.id != mod_user.id:
|
||||
flash(_("Missing rights."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
if "old_password" not in request.form:
|
||||
flash(_("Missing old password."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
if not check_password_hash(mod_user.password, request.form["old_password"]):
|
||||
flash(_("Incorrect password."))
|
||||
return redirect(request.referrer)
|
||||
else:
|
||||
log_data = [mod_user.username, mod_user.id, cur_user.username]
|
||||
|
||||
if "new_password" not in request.form or "confirm_new_password" not in request.form:
|
||||
flash(_("Missing password."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
if request.form["new_password"] != request.form["confirm_new_password"]:
|
||||
flash(_("Password and its confirmation differ."))
|
||||
return redirect(request.referrer)
|
||||
|
||||
mod_user.update_password(request.form["new_password"])
|
||||
flash(_("Successfully updated password."))
|
||||
logging.log(log_data, logging.LogEntry.PASSWORD_CHANGE)
|
||||
return redirect(request.referrer)
|
@ -2,10 +2,10 @@
|
||||
Thumbnails
|
||||
"""
|
||||
import os
|
||||
import pypdf
|
||||
|
||||
from flask import current_app, abort, Blueprint, send_file
|
||||
|
||||
from .db import get_db
|
||||
from .auth import login_required
|
||||
|
||||
bp = Blueprint("thumbnails", __name__, url_prefix="/thumbnails")
|
||||
@ -15,13 +15,18 @@ def generate_thumbnail(source, dest):
|
||||
"""
|
||||
Generates a thumbnail with 'convert' (ImageMagick)
|
||||
"""
|
||||
os.system(
|
||||
f'/usr/bin/convert -thumbnail\
|
||||
"178^>" -background white -alpha \
|
||||
remove -crop 178x178+0+0 \
|
||||
{source}[0] \
|
||||
{dest}'
|
||||
try:
|
||||
pypdf.PdfReader(source) # Check if file is really a PDF
|
||||
except (pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
|
||||
return
|
||||
|
||||
command = (
|
||||
f"gs -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 \
|
||||
-dAlignToPixels=0 -dGridFitTT=2 -sDEVICE=png16m -dBackgroundColor=16#FFFFFF -dTextAlphaBits=4 \
|
||||
-dGraphicsAlphaBits=4 -r72x72 -dPrinted=false -dFirstPage=1 -dPDFFitPage -g356x356 \
|
||||
-dLastPage=1 -sOutputFile={dest} {source}"
|
||||
)
|
||||
os.system(command)
|
||||
|
||||
def serve_thumbnail(partition_file, thumbnail_file):
|
||||
"""
|
||||
@ -33,6 +38,9 @@ def serve_thumbnail(partition_file, thumbnail_file):
|
||||
if not os.path.exists(thumbnail_file):
|
||||
generate_thumbnail(partition_file, thumbnail_file)
|
||||
|
||||
if not os.path.exists(thumbnail_file):
|
||||
abort(404)
|
||||
|
||||
return send_file(thumbnail_file)
|
||||
|
||||
|
||||
@ -55,4 +63,4 @@ def regular_thumbnail(uuid):
|
||||
return serve_thumbnail(
|
||||
os.path.join(current_app.instance_path, "partitions", f"{uuid}.pdf"),
|
||||
os.path.join(current_app.instance_path, "cache", "thumbnails", f"{uuid}.jpg")
|
||||
)
|
||||
)
|
||||
|
@ -72,3 +72,25 @@ def get_all_albums():
|
||||
"uuid": a["uuid"]
|
||||
} for a in albums
|
||||
]
|
||||
|
||||
|
||||
def user_count():
|
||||
db = get_db()
|
||||
count = db.execute(
|
||||
"""
|
||||
SELECT COUNT(*) as count FROM user
|
||||
"""
|
||||
).fetchone()
|
||||
|
||||
return count[0]
|
||||
|
||||
|
||||
def partition_count():
|
||||
db = get_db()
|
||||
count = db.execute(
|
||||
"""
|
||||
SELECT COUNT(*) FROM partition
|
||||
"""
|
||||
).fetchone()
|
||||
|
||||
return count[0]
|
BIN
partitioncloud/static/images/dark-preview.png
Normal file
BIN
partitioncloud/static/images/dark-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 146 KiB |
BIN
partitioncloud/static/images/light-preview.png
Normal file
BIN
partitioncloud/static/images/light-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
@ -1,5 +1,5 @@
|
||||
{
|
||||
"background_color": "#eff1f5",
|
||||
"background_color": "#1E1E2E",
|
||||
"description": "Partitioncloud",
|
||||
"display": "fullscreen",
|
||||
"icons": [
|
||||
|
20
partitioncloud/static/scripts/logs.js
Normal file
20
partitioncloud/static/scripts/logs.js
Normal file
@ -0,0 +1,20 @@
|
||||
let logsEmbed = document.getElementById("logs-embed");
|
||||
|
||||
logsEmbed.addEventListener("load", () => {
|
||||
var cssLink = document.createElement("link");
|
||||
|
||||
cssLink.href = "/static/style/logs.css";
|
||||
cssLink.rel = "stylesheet";
|
||||
cssLink.type = "text/css";
|
||||
|
||||
// add css
|
||||
logsEmbed.contentDocument.head.appendChild(cssLink);
|
||||
|
||||
// Scroll to bottom
|
||||
logsEmbed.contentWindow.scrollTo(0, logsEmbed.contentDocument.body.scrollHeight);
|
||||
});
|
||||
|
||||
// check if the iframe is already loaded (happened with FF Android)
|
||||
if (logsEmbed.contentDocument.readyState == "complete") {
|
||||
logsEmbed.dispatchEvent(new Event("load"));
|
||||
}
|
@ -17,7 +17,7 @@ async function hideSidebarNoAnim () {
|
||||
/* The transition needs to be invisible as if it was loaded that way */
|
||||
content_container.style.transitionDuration = "0s";
|
||||
sidebar_indicator.style.transitionDuration = "0s";
|
||||
|
||||
|
||||
sidebar_toggle.checked = true;
|
||||
|
||||
/* We need to set a sleep because we want to reset the transition duration only once it ended*/
|
65
partitioncloud/static/style/colors.css
Normal file
65
partitioncloud/static/style/colors.css
Normal file
@ -0,0 +1,65 @@
|
||||
/** Color Schemes */
|
||||
/* Themes used: Catppuccin Latte & Moccha
|
||||
* https://github.com/catppuccin/catppuccin */
|
||||
|
||||
/* Dark theme: Catpuccin Mocha */
|
||||
:root {
|
||||
--color-rosewater: #f5e0dc;
|
||||
--color-flamingo: #f2cdcd;
|
||||
--color-pink: #f5c2e7;
|
||||
--color-mauve: #cba6f7;
|
||||
--color-red: #f38ba8;
|
||||
--color-maroon: #eba0ac;
|
||||
--color-peach: #fab387;
|
||||
--color-yellow: #f9e2af;
|
||||
--color-green: #a6e3a1;
|
||||
--color-teal: #94e2d5;
|
||||
--color-sky: #89dceb;
|
||||
--color-sapphire: #74c7ec;
|
||||
--color-blue: #89b4fa;
|
||||
--color-lavender: #b4befe;
|
||||
--color-text: #cdd6f4;
|
||||
--color-subtext1: #bac2de;
|
||||
--color-subtext0: #a6adc8;
|
||||
--color-overlay2: #9399b2;
|
||||
--color-overlay1: #7f849c;
|
||||
--color-overlay0: #6c7086;
|
||||
--color-surface2: #585b70;
|
||||
--color-surface1: #45475a;
|
||||
--color-surface0: #313244;
|
||||
--color-base: #1e1e2e;
|
||||
--color-mantle: #181825;
|
||||
--color-crust: #11111b;
|
||||
}
|
||||
|
||||
/* Light theme: Catppuccin Latte */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-rosewater: #dc8a78;
|
||||
--color-flamingo: #dd7878;
|
||||
--color-pink: #ea76cb;
|
||||
--color-mauve: #8839ef;
|
||||
--color-red: #d20f39;
|
||||
--color-maroon: #e64553;
|
||||
--color-peach: #fe640b;
|
||||
--color-yellow: #df8e1d;
|
||||
--color-green: #40a02b;
|
||||
--color-teal: #179299;
|
||||
--color-sky: #04a5e5;
|
||||
--color-sapphire: #209fb5;
|
||||
--color-blue: #1e66f5;
|
||||
--color-lavender: #7287fd;
|
||||
--color-text: #4c4f69;
|
||||
--color-subtext1: #5c5f77;
|
||||
--color-subtext0: #6c6f85;
|
||||
--color-overlay2: #7c7f93;
|
||||
--color-overlay1: #8c8fa1;
|
||||
--color-overlay0: #9ca0b0;
|
||||
--color-surface2: #acb0be;
|
||||
--color-surface1: #bcc0cc;
|
||||
--color-surface0: #ccd0da;
|
||||
--color-base: #eff1f5;
|
||||
--color-mantle: #e6e9ef;
|
||||
--color-crust: #dce0e8;
|
||||
}
|
||||
}
|
110
partitioncloud/static/style/launch.css
Normal file
110
partitioncloud/static/style/launch.css
Normal file
@ -0,0 +1,110 @@
|
||||
@import url('/static/style/colors.css');
|
||||
|
||||
|
||||
* {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--color-subtext1);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-base);
|
||||
}
|
||||
|
||||
.no-color-link {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
font-weight: bold;
|
||||
font-size: 0.95em;
|
||||
|
||||
border: var(--color-subtext0);
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
|
||||
background-color: var(--color-subtext0);
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
button.blue {
|
||||
background-color: var(--color-blue);
|
||||
border-color: var(--color-blue);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--color-crust);
|
||||
border-color: var(--color-blue);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#login {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#logo-container {
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 5vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#instance-stats {
|
||||
margin: 35px 0px;
|
||||
color: var(--color-subtext1);
|
||||
}
|
||||
|
||||
img.preview {
|
||||
max-width: 85vw;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
border-color: var(--color-overlay1);
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
img#light-preview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img#dark-preview {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
img#light-preview {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
img#dark-preview {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
color: var(--color-subtext1);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
10
partitioncloud/static/style/logs.css
Normal file
10
partitioncloud/static/style/logs.css
Normal file
@ -0,0 +1,10 @@
|
||||
@import url('/static/style/colors.css');
|
||||
|
||||
body {
|
||||
background-color: var(--color-crust);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
}
|
@ -19,4 +19,8 @@
|
||||
input:checked#slide-sidebar~#content-container {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
input:checked#slide-sidebar~#footer,#content-container {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
@ -1,78 +1,10 @@
|
||||
@import url('/static/style/colors.css');
|
||||
/** @import url('https://www.augustin64.fr/static/font/iosevka.css'); */
|
||||
|
||||
|
||||
/** Color Schemes */
|
||||
/* Themes used: Catppuccin Latte & Moccha
|
||||
* https://github.com/catppuccin/catppuccin */
|
||||
|
||||
/* Dark theme: Catpuccin Mocha */
|
||||
:root {
|
||||
--color-rosewater: #f5e0dc;
|
||||
--color-flamingo: #f2cdcd;
|
||||
--color-pink: #f5c2e7;
|
||||
--color-mauve: #cba6f7;
|
||||
--color-red: #f38ba8;
|
||||
--color-maroon: #eba0ac;
|
||||
--color-peach: #fab387;
|
||||
--color-yellow: #f9e2af;
|
||||
--color-green: #a6e3a1;
|
||||
--color-teal: #94e2d5;
|
||||
--color-sky: #89dceb;
|
||||
--color-sapphire: #74c7ec;
|
||||
--color-blue: #89b4fa;
|
||||
--color-lavender: #b4befe;
|
||||
--color-text: #cdd6f4;
|
||||
--color-subtext1: #bac2de;
|
||||
--color-subtext0: #a6adc8;
|
||||
--color-overlay2: #9399b2;
|
||||
--color-overlay1: #7f849c;
|
||||
--color-overlay0: #6c7086;
|
||||
--color-surface2: #585b70;
|
||||
--color-surface1: #45475a;
|
||||
--color-surface0: #313244;
|
||||
--color-base: #1e1e2e;
|
||||
--color-mantle: #181825;
|
||||
--color-crust: #11111b;
|
||||
|
||||
/* --font-family: Iosevka Web; /* Specify the font here */
|
||||
}
|
||||
|
||||
/* Light theme: Catppuccin Latte */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-rosewater: #dc8a78;
|
||||
--color-flamingo: #dd7878;
|
||||
--color-pink: #ea76cb;
|
||||
--color-mauve: #8839ef;
|
||||
--color-red: #d20f39;
|
||||
--color-maroon: #e64553;
|
||||
--color-peach: #fe640b;
|
||||
--color-yellow: #df8e1d;
|
||||
--color-green: #40a02b;
|
||||
--color-teal: #179299;
|
||||
--color-sky: #04a5e5;
|
||||
--color-sapphire: #209fb5;
|
||||
--color-blue: #1e66f5;
|
||||
--color-lavender: #7287fd;
|
||||
--color-text: #4c4f69;
|
||||
--color-subtext1: #5c5f77;
|
||||
--color-subtext0: #6c6f85;
|
||||
--color-overlay2: #7c7f93;
|
||||
--color-overlay1: #8c8fa1;
|
||||
--color-overlay0: #9ca0b0;
|
||||
--color-surface2: #acb0be;
|
||||
--color-surface1: #bcc0cc;
|
||||
--color-surface0: #ccd0da;
|
||||
--color-base: #eff1f5;
|
||||
--color-mantle: #e6e9ef;
|
||||
--color-crust: #dce0e8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Various settings (variables) */
|
||||
:root {
|
||||
--sidebar-size: max(10vw, 160px);
|
||||
--sidebar-size: max(10vw, 250px);
|
||||
--sidebar-sz-plus10: calc(var(--sidebar-size) + 10px);
|
||||
--sidebar-sz-moins20: calc(var(--sidebar-size) - 20px);
|
||||
}
|
||||
@ -401,6 +333,7 @@ img.partition-thumbnail {
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.username {
|
||||
@ -455,12 +388,13 @@ a#delete-album {
|
||||
transform: translateY(-17%);
|
||||
}
|
||||
|
||||
#settings-container>.user {
|
||||
#settings-container > a > .user {
|
||||
margin-top: 6px;
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#settings-container>.user:hover {
|
||||
#settings-container > a > .user:hover {
|
||||
background-color: var(--color-mantle);
|
||||
}
|
||||
|
||||
@ -606,6 +540,8 @@ input[type="file"] {
|
||||
/** Dangerous buttons */
|
||||
button#logout:hover,
|
||||
a#delete-album:hover,
|
||||
.red-confirm,
|
||||
input[type="submit"].red-confirm,
|
||||
#delete-partition {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-mantle);
|
||||
@ -720,7 +656,7 @@ td {
|
||||
/** Attachment page */
|
||||
#pdf-embed {
|
||||
margin: auto;
|
||||
|
||||
|
||||
width: 100%;
|
||||
width: -moz-available;
|
||||
width: -webkit-fill-available;
|
||||
@ -760,6 +696,9 @@ midi-player {
|
||||
margin: 20px;
|
||||
margin-top: 50px;
|
||||
border-radius: 15px;
|
||||
width: 370px;
|
||||
height: 370px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#share-url {
|
||||
@ -770,3 +709,65 @@ midi-player {
|
||||
margin-bottom: 100px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#logs-embed {
|
||||
margin: auto;
|
||||
height: 80vh;
|
||||
width: 95%;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--color-crust);
|
||||
}
|
||||
|
||||
/** Input[file] */
|
||||
.file-area {
|
||||
position: relative;
|
||||
}
|
||||
.file-area input[type=file] {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-area .inner-file-area {
|
||||
padding: 30px;
|
||||
background: var(--color-mantle);
|
||||
border: 2px dashed var(--color-red);
|
||||
text-align: center;
|
||||
transition: background 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.file-area .inner-file-area .success {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-area:hover > .inner-file-area {
|
||||
background: var(--color-surface0);
|
||||
}
|
||||
|
||||
.file-area input[type=file]:valid + .inner-file-area {
|
||||
border-color: var(--color-green);
|
||||
}
|
||||
.file-area input[type=file]:not(:required) + .inner-file-area {
|
||||
border-color: var(--color-blue);
|
||||
}
|
||||
|
||||
.file-area input[type=file]:valid + .inner-file-area .success {
|
||||
display: inline-block;
|
||||
}
|
||||
.file-area input[type=file]:valid + .inner-file-area .default {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-area input[type=file]:not(:required) + .inner-file-area .success {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-area input[type=file]:not(:required) + .inner-file-area .default {
|
||||
display: inline-block;
|
||||
}
|
@ -2,24 +2,27 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Panneau d'administration{% endblock %}</h2>
|
||||
<h2>{% block title %}{{ _("Administration Panel") }}{% endblock %}</h2>
|
||||
|
||||
<div id="actions-rapides">
|
||||
<a href="/add-user">
|
||||
<div class="button">Ajouter un utilisateur</div>
|
||||
<div class="button">{{ _("New user") }}</div>
|
||||
</a>
|
||||
<a href="/partition">
|
||||
<div class="button">Voir toutes les partitions</div>
|
||||
<div class="button">{{ _("See scores") }}</div>
|
||||
</a>
|
||||
<a href="/admin/logs">
|
||||
<div class="button">{{ _("See logs") }}</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="x-scrollable">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Utilisateur</th>
|
||||
<th scope="col">Albums</th>
|
||||
<th scope="col">Partitions</th>
|
||||
<th scope="col">Privilèges</th>
|
||||
<th scope="col">{{ _("User") }}</th>
|
||||
<th scope="col">{{ _("Albums") }}</th>
|
||||
<th scope="col">{{ _("Scores") }}</th>
|
||||
<th scope="col">{{ _("Admin privileges") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -31,7 +34,9 @@
|
||||
title="{{ user.username }}">
|
||||
{{ user.username[0] | upper }}
|
||||
</div>
|
||||
<div class="table-username">{{ user.username }}</div>
|
||||
<div class="table-username">
|
||||
<a href="/admin/user/{{ user.id }}">{{ user.username }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ user.albums | length }}</td>
|
||||
|
10
partitioncloud/templates/admin/logs.html
Normal file
10
partitioncloud/templates/admin/logs.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% set scripts=["scripts/logs.js"] %}
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}{{ _("Logs") }}{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<iframe type="text/plain" id="logs-embed" src="/admin/logs.txt" frameborder="0" width="100%" height="100%"></iframe>
|
||||
{% endblock %}
|
@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Liste des partitions{% endblock %}</h1>
|
||||
<h1>{% block title %}{{ _("Scores list") }}{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@ -11,7 +11,7 @@
|
||||
<div>
|
||||
<a href="/partition/{{ partition['uuid'] }}">
|
||||
<div class="partition" id="partition-{{ partition['uuid'] }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg" loading="lazy">
|
||||
<div class="partition-description">
|
||||
<div class="partition-name">{{ partition["name"] }}</div>
|
||||
<div class="partition-author">{{ partition["author"] }}</div>
|
||||
@ -28,6 +28,6 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div>Aucune partition disponible</div>
|
||||
<div>{{ _("No available scores") }}</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -1,15 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Ajout de partition{% endblock %}
|
||||
{% block title %}{{ _("New score") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Ajouter une partition à {{ album.name }}</h2>
|
||||
|
||||
<form action="/albums/{{ album.uuid }}/add-partition" method="post" enctype="multipart/form-data">
|
||||
<input name="name" type="text" placeholder="titre" required/><br/>
|
||||
<input name="author" type="text" placeholder="auteur"/><br/>
|
||||
<textarea id="paroles" name="body" type="text" placeholder="paroles"></textarea><br/>
|
||||
<input name="partition-uuid" value="{{ partition_uuid }}" type="hidden">
|
||||
<input type="submit" value="Ajouter" />
|
||||
</form>
|
||||
{% include 'components/add_partition.html' %}
|
||||
{% endblock %}
|
@ -5,22 +5,15 @@
|
||||
|
||||
{% block dialogs %}
|
||||
<dialog id="add-partition">
|
||||
<h2>Ajouter une partition à {{ album.name }}</h2>
|
||||
<form action="/albums/{{ album.uuid }}/add-partition" method="post" enctype="multipart/form-data">
|
||||
<input name="name" type="text" required="" placeholder="Titre"><br/>
|
||||
<input name="author" type="text" placeholder="Auteur"><br/>
|
||||
<textarea id="paroles" name="body" type="text" placeholder="Paroles"></textarea><br/>
|
||||
<input name="file" type="file" accept=".pdf" required=""><br/>
|
||||
<input type="submit" value="Ajouter">
|
||||
</form>
|
||||
{% include 'components/add_partition.html' %}
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
<dialog id="delete">
|
||||
<h2>Supprimer l'album</h2>
|
||||
Êtes vous sûr de vouloir supprimer cet album ?
|
||||
<h2>{{ _("Delete l'album") }}</h2>
|
||||
{{ _("Do you really want to delete this album?") }}
|
||||
<br/><br/>
|
||||
<form method="post" action="/albums/{{ album.uuid }}/delete">
|
||||
<input type="submit" style="background-color: var(--color-red);" value="Supprimer">
|
||||
<input type="submit" style="background-color: var(--color-red);" value="{{ _('Delete') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
@ -38,12 +31,12 @@
|
||||
{% block content %}
|
||||
<header id="album-header">
|
||||
<h2 id="album-title">
|
||||
{% if groupe %}<a href="/groupe/{{ groupe.uuid }}">{{ groupe.name}}</a> /
|
||||
{% if groupe %}<a href="/groupe/{{ groupe.uuid }}">{{ groupe.name }}</a> /
|
||||
{% endif %}
|
||||
{{ album.name }}
|
||||
</h2>
|
||||
{% if g.user %}
|
||||
<div id="header-actions">
|
||||
<div id="header-actions">
|
||||
{% if g.user %}
|
||||
<section id="users">
|
||||
{% for album_user in album.users %}
|
||||
<div class="user-profile-picture" style="background-color:{{ album_user.color }};" title="{{ album_user.username }}">
|
||||
@ -51,25 +44,28 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<div class="dropdown dp1">
|
||||
+
|
||||
<div class="dropdown-content dp1">
|
||||
{% if g.user %}
|
||||
<a href="#add-partition">Ajouter une partition</a>
|
||||
{% endif %}
|
||||
{% if not_participant %}
|
||||
<a href="/albums/{{ album.uuid }}/join">Rejoindre</a>
|
||||
{% elif album.users | length > 1 %}
|
||||
<a href="/albums/{{ album.uuid }}/quit">Quitter</a>
|
||||
{% endif %}
|
||||
<a href="#share">Partager</a>
|
||||
{% if g.user.access_level == 1 or (not not_participant and album.users | length == 1) %}
|
||||
<a id="delete-album" href="#delete">Supprimer</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="dropdown dp1">
|
||||
+
|
||||
<div class="dropdown-content dp1">
|
||||
{% if g.user %}
|
||||
<a href="#add-partition">{{ _("Add a score") }}</a>
|
||||
{% endif %}
|
||||
{% if not_participant %}
|
||||
<a href="/albums/{{ album.uuid }}/join">{{ _("Join") }}</a>
|
||||
{% elif g.user and not not_participant %}
|
||||
<a href="/albums/{{ album.uuid }}/quit">{{ _("Quit") }}</a>
|
||||
{% endif %}
|
||||
<a href="#share">{{ _("Share") }}</a>
|
||||
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
|
||||
<a href="/albums/{{ album.uuid }}/zip">{{ _("Download as zip") }}</a>
|
||||
{% endif %}
|
||||
{% if g.user.access_level == 1 or (g.user and not not_participant and album.users | length == 1) %}
|
||||
<a id="delete-album" href="#delete">{{ _("Delete") }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
<hr/>
|
||||
{% if partitions|length != 0 %}
|
||||
@ -78,7 +74,7 @@
|
||||
<div>
|
||||
<a href="/partition/{{ partition['uuid'] }}">
|
||||
<div class="partition" id="partition-{{ partition['uuid'] }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg" loading="lazy">
|
||||
<div class="partition-description">
|
||||
<div class="partition-name">{{ partition["name"] }}</div>
|
||||
<div class="partition-author">{{ partition["author"] }}</div>
|
||||
@ -98,6 +94,6 @@
|
||||
</section>
|
||||
{% else %}
|
||||
<br/>
|
||||
<section id="partitions-grid" style="display: inline;">Aucune partition disponible</section>
|
||||
<section id="partitions-grid" style="display: inline;">{{ _("No available scores") }}</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -1,14 +1,14 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Supprimer {{ album.name }}{% endblock %}
|
||||
{% block title %}{{ _("Delete %(name)s", name=album.name) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Êtes vous sûr de vouloir supprimer cet album ?
|
||||
{{ _("Do you really want to delete this album?") }}
|
||||
<form method="post">
|
||||
<input type="submit" value="Supprimer">
|
||||
<input type="submit" value="{{ _('Delete') }}">
|
||||
</form>
|
||||
<a class="button-href" href="/albums/{{ album.uuid }}">
|
||||
<button id="cancel-deletion">Annuler</button>
|
||||
<button id="cancel-deletion">{{ _("Cancel") }}</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
@ -1,10 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
{% block title %}{{ _("Home") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div style="text-align: center;">
|
||||
Bonjour <i><b>{{ user.username }}</b></i> !<br/>
|
||||
Aucun album sélectionné
|
||||
{% set user_name %}
|
||||
<i><b>{{ user.username }}</b></i>
|
||||
{% endset %}
|
||||
{{ _("Hi %(user_name)s !", user_name=user_name) }}<br/>
|
||||
{{ _("No album selected") }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -2,15 +2,15 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Résultats de la recherche "{{ query }}"{% endblock %}</h2>
|
||||
<h2>{% block title %}{{ _('Search results for "%(query)s"', query=query)}}{% endblock %}</h2>
|
||||
{% if partitions|length != 0 %}
|
||||
<h3>Résultats dans la bibliothèque locale</h3>
|
||||
<h3>{{ _("Results in current database") }}</h3>
|
||||
<div id="partitions-grid">
|
||||
{% for partition in partitions %}
|
||||
<div class="partition-container">
|
||||
<a href="/partition/{{ partition['uuid'] }}">
|
||||
<div class="partition" id="partition-{{ partition['uuid'] }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition['uuid'] }}.jpg" loading="lazy">
|
||||
<div class="partition-description">
|
||||
<div class="partition-name">{{ partition["name"] }}</div>
|
||||
<div class="partition-author">{{ partition["author"] }}</div>
|
||||
@ -35,20 +35,20 @@
|
||||
</select>
|
||||
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
||||
<input type="hidden" value="local_file" name="partition-type">
|
||||
<input type="submit" value="Ajouter à l'album" class="add-to-album">
|
||||
<input type="submit" value="{{ _('Add to album') }}" class="add-to-album">
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if google_results|length != 0 %}
|
||||
<h3>Résultats de la recherche en ligne</h3>
|
||||
<h3>{{ _("Online search results") }}</h3>
|
||||
<div id="partitions-grid">
|
||||
{% for partition in google_results %}
|
||||
<div class="partition-container">
|
||||
<a href="/partition/search/{{ partition['uuid'] }}">
|
||||
<div class="partition" id="partition-{{ partition['uuid'] }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/search/{{ partition['uuid'] }}.jpg">
|
||||
<img class="partition-thumbnail" src="/thumbnails/search/{{ partition['uuid'] }}.jpg" loading="lazy">
|
||||
<div class="partition-description">
|
||||
<div class="partition-name">{{ partition["name"] }}</div>
|
||||
</div>
|
||||
@ -67,13 +67,13 @@
|
||||
</select>
|
||||
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
||||
<input type="hidden" value="online_search" name="partition-type">
|
||||
<input type="submit" value="Ajouter à l'album">
|
||||
<input type="submit" value="{{ _('Add to album') }}">
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if google_results|length == 0 and partitions|length == 0 %}
|
||||
Aucun résultat. Essayez d'augmenter le nombre de recherches en ligne ou d'affiner votre recherche.
|
||||
{{ _("No results available. Try to tweak your query or increase the amount of online searches.") }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -2,11 +2,11 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Connexion{% endblock %}</h2>
|
||||
<h2>{% block title %}{{ _("Log in") }}{% endblock %}</h2>
|
||||
|
||||
<form method="post">
|
||||
<input type="text" name="username" id="username" placeholder="Nom d'utilisateur" required><br/>
|
||||
<input type="password" name="password" id="password" placeholder="Mot de passe" required><br/>
|
||||
<input type="submit" value="Se connecter">
|
||||
<input type="text" name="username" id="username" placeholder="{{ _('Username') }}" required><br/>
|
||||
<input type="password" name="password" id="password" placeholder="{{ _('Password') }}" required><br/>
|
||||
<input type="submit" value="{{ _('Log in') }}">
|
||||
</form>
|
||||
{% endblock %}
|
@ -2,21 +2,21 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Créer un compte{% endblock %}</h2>
|
||||
<h2>{% block title %}{{ _("Create account") }}{% endblock %}</h2>
|
||||
|
||||
<form method="post" id="add-user-form">
|
||||
{% if g.user.access_level == 1 %}
|
||||
<!-- Uniquement pour /add-user -->
|
||||
<label for="album_uuid">Ajouter à un album: </label><br/>
|
||||
<label for="album_uuid">{{ _("Add to album:") }}</label><br/>
|
||||
<select name="album_uuid" id="album_uuid" form="add-user-form" style="margin-bottom:15px;">
|
||||
<option value="">Aucun</option>
|
||||
<option value="">{{ _("None") }}</option>
|
||||
{% for album in albums %}
|
||||
<option value="{{ album['uuid'] }}">{{ album["name"] }}</option>
|
||||
{% endfor %}
|
||||
</select><br/>
|
||||
{% endif %}
|
||||
<input type="text" name="username" id="username" placeholder="Nom d'utilisateur" required><br/>
|
||||
<input type="password" name="password" id="password" placeholder="Mot de passe" required><br/>
|
||||
<input type="submit" value="Créer un compte">
|
||||
<input type="text" name="username" id="username" placeholder="{{ _('Username') }}" required><br/>
|
||||
<input type="password" name="password" id="password" placeholder="{{ _('Password') }}" required><br/>
|
||||
<input type="submit" value="{{ _('Create account') }}">
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,12 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="{{ lang }}">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="description" content="{{ self.title() }}" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#eff1f5">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1e1e2e">
|
||||
<title>{% block title %}{% endblock %} - PartitionCloud</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='mobile.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/mobile.css') }}">
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='icons/512.png') }}" />
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/512.png') }}">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.webmanifest') }}" />
|
||||
@ -15,26 +18,26 @@
|
||||
|
||||
<body>
|
||||
<div id="dialogs">
|
||||
<!-- This div contains needed needed dialogs for the page
|
||||
<!-- This div contains needed needed dialogs for the page
|
||||
They will only appear if the user clicks a button for one of them -->
|
||||
{% block dialogs %}{% endblock %}
|
||||
{% if g.user %}
|
||||
<dialog id="create-album">
|
||||
<h2>Créer un nouvel album</h2>
|
||||
<h2>{{ _("New Album") }}</h2>
|
||||
<form action="/albums/create-album" method="post">
|
||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
||||
<input type="submit" value="Créer">
|
||||
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||
<input type="submit" value="{{ _('Create') }}">
|
||||
</form>
|
||||
<br/>
|
||||
<br/>
|
||||
Je souhaite créer plusieurs albums et pouvoir tous les partager avec un seul lien. <a href="#create-groupe">Créer un groupe</a>.
|
||||
{{ _("I want to create a collection of albums.") }} <a href="#create-groupe">{{ _("Create group") }}</a>.
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
<dialog id="create-groupe">
|
||||
<h2>Créer un nouveau groupe</h2>
|
||||
<h2>{{ _("Create new group") }}</h2>
|
||||
<form action="/groupe/create-groupe" method="post">
|
||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
||||
<input type="submit" value="Créer">
|
||||
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||
<input type="submit" value="{{ _('Create') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
@ -58,9 +61,9 @@
|
||||
<div id="sidebar">
|
||||
{% if g.user %}
|
||||
<form id="search-form" action="/albums/search" method="post">
|
||||
<input type="search" id="search-bar" required="" placeholder="Rechercher" name="query">
|
||||
<input type="search" id="search-bar" required="" placeholder="{{ _('Search') }}" name="query">
|
||||
<br>
|
||||
<select id="nb-queries" name="nb-queries" title="Nombre de recherches en ligne">
|
||||
<select id="nb-queries" name="nb-queries" title="{{ _('Number of online searches') }}">
|
||||
{% for i in range(0, user.max_queries+1) %}
|
||||
<option value="{{ i }}">{{ i }}</option>
|
||||
{% endfor %}
|
||||
@ -68,11 +71,11 @@
|
||||
<input id="search-submit" type="submit" value="Go">
|
||||
</form>
|
||||
{% endif %}
|
||||
<h2>Albums</h2>
|
||||
<h2>{{ _("Albums") }}</h2>
|
||||
{% if g.user %}
|
||||
<a href="#create-album">
|
||||
<div class="create-button">
|
||||
Créer un album
|
||||
{{ _("New album") }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -89,7 +92,7 @@
|
||||
</summary>
|
||||
<div class="groupe-albums-cover">
|
||||
{% if groupe.get_albums() | length == 0 %}
|
||||
Aucun album
|
||||
{{ _("No albums") }}
|
||||
{% else %}
|
||||
{% for album in groupe.get_albums() %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/{{ album['uuid'] }}">
|
||||
@ -108,7 +111,7 @@
|
||||
|
||||
<section id="albums">
|
||||
{% if user.get_albums() | length == 0 %}
|
||||
<div style="text-align: center;"><i>Aucun album disponible</i></div>
|
||||
<div style="text-align: center;"><i>{{ _("No album available") }}</i></div>
|
||||
{% else %}
|
||||
{% for album in user.albums %}
|
||||
<a href="/albums/{{ album['uuid'] }}">
|
||||
@ -122,7 +125,7 @@
|
||||
</section>
|
||||
{% else %}
|
||||
<section id="sidebar-navigation">
|
||||
<div style="text-align: center;"><i>Connectez vous pour avoir accès à vos albums</i></div>
|
||||
<div style="text-align: center;"><i>{{ _("Log in to see your albums") }}</i></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
@ -136,7 +139,7 @@
|
||||
<path d="M9 12h12l-3 -3"></path>
|
||||
<path d="M18 15l3 -3"></path>
|
||||
</svg>
|
||||
Déconnexion
|
||||
{{ _("Log out") }}
|
||||
</button>
|
||||
</a><br/>
|
||||
{% if g.user.access_level == 1 %}
|
||||
@ -151,21 +154,21 @@
|
||||
<path d="M17.27 20l-1.3 .75"></path>
|
||||
<path d="M15.97 17.25l1.3 .75"></path>
|
||||
<path d="M20.733 20l1.3 .75"></path>
|
||||
</svg>Panneau admin
|
||||
</svg>{{ _("Admin Panel") }}
|
||||
</button></a><br/>
|
||||
{% endif %}
|
||||
<div class="user">
|
||||
<a href="/settings"><div class="user">
|
||||
<div class="user-profile-picture" style="background-color:{{ user.color }};"
|
||||
title="{{ user.username }}">
|
||||
{{ user.username[0] | upper }}
|
||||
</div>
|
||||
<div class="username">{{ user.username }}</div>
|
||||
</div>
|
||||
</div></a>
|
||||
{% else %}
|
||||
{% if not config.DISABLE_REGISTER %}
|
||||
<a href="{{ url_for('auth.register') }}"><button>Créer un compte</button></a>
|
||||
<a href="{{ url_for('auth.register') }}"><button>{{ _("Create account") }}</button></a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('auth.login') }}"><button>Se connecter</button></a>
|
||||
<a href="{{ url_for('auth.login') }}"><button>{{ _("Log in") }}</button></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -185,5 +188,8 @@
|
||||
<div id="footer"><a href="https://github.com/partitioncloud/partitioncloud-server">PartitionCloud</a> {{ version }}</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="{{ url_for('static', filename='main.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/main.js') }}"></script>
|
||||
{% for script in scripts %}
|
||||
<script src="{{ url_for('static', filename=script) }}"></script>
|
||||
{% endfor %}
|
||||
</html>
|
17
partitioncloud/templates/components/add_partition.html
Normal file
17
partitioncloud/templates/components/add_partition.html
Normal file
@ -0,0 +1,17 @@
|
||||
<h2>{{ _("Add a score to %(name)s", name=album.name) }}</h2>
|
||||
|
||||
<form action="/albums/{{ album.uuid }}/add-partition" method="post" enctype="multipart/form-data">
|
||||
<input name="name" type="text" placeholder="{{ _('title') }}" required/><br/>
|
||||
<input name="author" type="text" placeholder="{{ _('author') }}"/><br/>
|
||||
<textarea id="lyrics" name="body" type="text" placeholder="{{ _('lyrics') }}"></textarea><br/>
|
||||
{% if partition_uuid %}
|
||||
<input name="partition-uuid" value="{{ partition_uuid }}" type="hidden">
|
||||
{% else %}
|
||||
{% block input_file %}
|
||||
{% set required=true %}
|
||||
{% set filetype=".pdf" %}
|
||||
{% include 'components/input_file.html' %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
<input type="submit" value="{{ _('Add') }}" />
|
||||
</form>
|
7
partitioncloud/templates/components/input_file.html
Normal file
7
partitioncloud/templates/components/input_file.html
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="file-area">
|
||||
<input name="file" type="file" accept="{{ filetype }}" {% if required %}required=""{% endif %}>
|
||||
<div class="inner-file-area">
|
||||
<div class="success">{{ _("Your file is selected.") }}</div>
|
||||
<div class="default">{{ _("Select or drag & drop your file") }} ({{ filetype }}).</div>
|
||||
</div>
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
<dialog id="share">
|
||||
<center>
|
||||
<img src="{{ share_qrlink }}" id="share-qrcode"><br/>
|
||||
<img src="{{ share_qrlink }}" id="share-qrcode" loading="lazy"><br/>
|
||||
<div id="share-url" onclick='navigator.clipboard.writeText("{{ share_link }}")'>
|
||||
{{ share_link }}
|
||||
</div>
|
||||
|
@ -5,20 +5,19 @@
|
||||
|
||||
{% block dialogs %}
|
||||
<dialog id="create-groupe-album">
|
||||
<h2>Créer un nouvel album dans le groupe {{ groupe.name }}</h2>
|
||||
<h2>{{ _("Add an album to group %(name)s", name=groupe.name) }}</h2>
|
||||
<form action="/groupe/{{ groupe.uuid }}/create-album" method="post">
|
||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
||||
<input type="submit" value="Ajouter">
|
||||
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||
<input type="submit" value="{{ _('Add') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
<dialog id="delete">
|
||||
<h2>Supprimer le groupe</h2>
|
||||
Êtes vous sûr de vouloir supprimer ce groupe ? Cela supprimera les albums
|
||||
sous-jacents et leurs partitions si personne ne les a rejoints (indépendamment du groupe).
|
||||
<h2>{{ _("Delete group") }}</h2>
|
||||
{{ _("Do you really want to delete this group and the albums it contains?") }}
|
||||
<br/><br/>
|
||||
<form method="post" action="/groupe/{{ groupe.uuid }}/delete">
|
||||
<input type="submit" style="background-color: var(--color-red);" value="Supprimer">
|
||||
<input type="submit" style="background-color: var(--color-red);" value="{{ _('Delete') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
@ -31,8 +30,8 @@
|
||||
{% block content %}
|
||||
<header id="album-header">
|
||||
<h2 id="groupe-title">{{ groupe.name }}</h2>
|
||||
{% if g.user %}
|
||||
<div id="header-actions">
|
||||
<div id="header-actions">
|
||||
{% if g.user %}
|
||||
<section id="users">
|
||||
{% for groupe_user in groupe.users %}
|
||||
<div class="user-profile-picture" style="background-color:{{ groupe_user.color }};" title="{{ groupe_user.username }}">
|
||||
@ -40,23 +39,26 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<div class="dropdown dp1">
|
||||
+
|
||||
<div class="dropdown-content dp1">
|
||||
{% if not_participant %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/join">Rejoindre</a>
|
||||
{% elif groupe.users | length > 1 %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/quit">Quitter</a>
|
||||
{% endif %}
|
||||
<a href="#share">Partager</a>
|
||||
{% if g.user.access_level == 1 or user.id in groupe.get_admins() %}
|
||||
<a href="#create-groupe-album">Ajouter un album</a>
|
||||
<a id="delete-album" href="#delete">Supprimer</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="dropdown dp1">
|
||||
+
|
||||
<div class="dropdown-content dp1">
|
||||
{% if not_participant %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/join">{{ _("Join") }}</a>
|
||||
{% elif g.user and not not_participant %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/quit">{{ _("Quit") }}</a>
|
||||
{% endif %}
|
||||
<a href="#share">{{ _("Share") }}</a>
|
||||
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
|
||||
<a href="/groupe/{{ groupe.uuid }}/zip">{{ _("Download as zip") }}</a>
|
||||
{% endif %}
|
||||
{% if g.user.access_level == 1 or (g.user and user.id in groupe.get_admins()) %}
|
||||
<a href="#create-groupe-album">{{ _("Add an album") }}</a>
|
||||
<a id="delete-album" href="#delete">{{ _("Delete") }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
<hr/>
|
||||
{% if groupe.albums|length != 0 %}
|
||||
@ -71,6 +73,11 @@
|
||||
</section>
|
||||
{% else %}
|
||||
<br/>
|
||||
<div id="albums-grid" style="display: inline;">Aucun album disponible. <a href="#create-groupe-album">En créer un</a></div>
|
||||
{% set create %}
|
||||
<a href="#create-groupe-album">{{ _("Create one") }}</a>
|
||||
{% endset %}
|
||||
<div id="albums-grid" style="display: inline;">
|
||||
{{ _("No available album. %(create)s", create=create) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
62
partitioncloud/templates/launch.html
Normal file
62
partitioncloud/templates/launch.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="description" content="PartitionCloud launch page" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#eff1f5">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1e1e2e">
|
||||
<title>PartitionCloud</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style/launch.css') }}">
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='icons/512.png') }}" />
|
||||
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/512.png') }}">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.webmanifest') }}" />
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<a id="logo-container" href="/">
|
||||
<img src="/static/icons/icon.png" width="60px" height="auto" alt="Logo">
|
||||
</a>
|
||||
<div>
|
||||
<a href="/auth/login">
|
||||
<button class="blue" id="login">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" class="iconify inline" data-icon="fluent:key-24-filled" style="vertical-align: -0.125em; transform: rotate(360deg);"><path fill="currentColor" d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55a6.243 6.243 0 0 1-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0a1.25 1.25 0 0 0 2.5 0Z"></path></svg>
|
||||
{{ _("Log in") }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1>{{ _("PartitionCloud is an open-source score library server, to help you in all your musical activities") }}</h1>
|
||||
<div id="actions">
|
||||
<a href="/auth/login" class="no-color-link">
|
||||
<button class="blue">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3" /><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3" /><path d="M15 9m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
|
||||
{{ _("Let's go !") }}
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://github.com/partitioncloud/partitioncloud-server" class="no-color-link">
|
||||
<button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M12 8m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M12 16m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M12 15v-6" /><path d="M15 11l-2 -2" /><path d="M11 7l-1.9 -1.9" /><path d="M13.446 2.6l7.955 7.954a2.045 2.045 0 0 1 0 2.892l-7.955 7.955a2.045 2.045 0 0 1 -2.892 0l-7.955 -7.955a2.045 2.045 0 0 1 0 -2.892l7.955 -7.955a2.045 2.045 0 0 1 2.892 0z" /></svg>
|
||||
{{ _("Check code") }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div id="instance-stats">
|
||||
{% set user_bold %}
|
||||
<b>{{ user_count }}</b>
|
||||
{% endset %}
|
||||
{% set partition_bold %}
|
||||
<b>{{ partition_count }}</b>
|
||||
{% endset %}
|
||||
{{ _("This instance is used by %(users)s users with a total of %(scores)s scores.", users=user_bold, scores=partition_bold) }}
|
||||
</div>
|
||||
<img class="preview" id="dark-preview" src="/static/images/dark-preview.png" loading="lazy">
|
||||
<img class="preview" id="light-preview" src="/static/images/light-preview.png" loading="lazy">
|
||||
</main>
|
||||
<footer>{{ version }}</footer>
|
||||
</body>
|
||||
</html>
|
@ -2,15 +2,19 @@
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Attachments de {{ partition.name }}{% endblock %}
|
||||
{% block title %}{{ _("Attachments of %(name)s", name=partition.name) }}{% endblock %}
|
||||
|
||||
{% block dialogs %}
|
||||
<dialog id="create-attachment">
|
||||
<h2>Ajouter un attachment à {{ partition.name }}</h2>
|
||||
<h2>{{ _("Add an attachment to %(name)s", name=partition.name) }}</h2>
|
||||
<form action="/partition/{{ partition.uuid }}/add-attachment" method="post" enctype="multipart/form-data">
|
||||
<input type="text" name="name" id="name" placeholder="Nom"><br/>
|
||||
<input name="file" type="file" accept=".mp3,.mid" required=""><br/>
|
||||
<input type="submit" value="Ajouter">
|
||||
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}"><br/>
|
||||
{% block input_file %}
|
||||
{% set required=true %}
|
||||
{% set filetype=".mp3,.mid" %}
|
||||
{% include 'components/input_file.html' %}
|
||||
{% endblock %}
|
||||
<input type="submit" value="{{ _('Add') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
@ -19,15 +23,15 @@
|
||||
{% block content %}
|
||||
<object id="pdf-embed" width="400" height="500" type="application/pdf" data="/partition/{{ partition.uuid }}">
|
||||
<p>
|
||||
Impossible d'afficher le pdf dans ce navigateur.
|
||||
Il est conseillé d'utiliser Firefox sur Android.
|
||||
{{ _("No pdf viewer available in this browser.
|
||||
You can use Firefox on Android.") }}
|
||||
</p>
|
||||
</object>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/combine/npm/tone@14.7.58,npm/@magenta/music@1.23.1/es6/core.js,npm/focus-visible@5,npm/html-midi-player@1.5.0"></script>
|
||||
<midi-visualizer type="staff" id="midi-visualizer"></midi-visualizer>
|
||||
|
||||
|
||||
|
||||
{% if partition.attachments | length > 0 %}
|
||||
<div id="attachments">
|
||||
<table>
|
||||
@ -38,12 +42,12 @@
|
||||
<td><audio controls src="/partition/attachment/{{ attachment.uuid }}.mp3"></td>
|
||||
<td>🎙️ {{ attachment.name }}</td>
|
||||
{% elif attachment.filetype == "mid" %}
|
||||
|
||||
|
||||
<td><midi-player
|
||||
src="/partition/attachment/{{ attachment.uuid }}.mid"
|
||||
sound-font visualizer="#midi-visualizer" data-js-focus-visible>
|
||||
</midi-player>
|
||||
<noscript>MIDI support needs JavaScript</noscript>
|
||||
<noscript>{{ _("JavaScript is mandatory to read MIDI files") }}</noscript>
|
||||
</td>
|
||||
<td>🎵 {{ attachment.name }}</td>
|
||||
{% endif %}
|
||||
@ -55,9 +59,9 @@
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
{% if user %}
|
||||
{% if g.user %}
|
||||
<div class="centered">
|
||||
<a href="#create-attachment"><button>Ajouter un attachment</button></a>
|
||||
<a href="#create-attachment"><button>{{ _("Add an attachment") }}</button></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -1,16 +1,16 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block header %}
|
||||
<h1>{% block title %}Supprimer {{ partition.name }}{% endblock %}</h1>
|
||||
<h1>{% block title %}{{ _("Delete %(name)s", name=partition.name) }}{% endblock %}</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Êtes vous sûr de vouloir supprimer cette partition ?
|
||||
{{ _("Do you really want to delete this score?") }}
|
||||
<form method="post">
|
||||
<input type="submit" id="delete-partition" value="Supprimer">
|
||||
<input type="submit" id="delete-partition" value="{{ _('Delete') }}">
|
||||
</form>
|
||||
<a class="button-href" href="/partition/{{ partition.uuid }}/edit">
|
||||
<button id="cancel-deletion">Annuler</button>
|
||||
<button id="cancel-deletion">{{ _("Cancel") }}</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
@ -1,15 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Détails "{{ partition.name }}"{% endblock %}</h2>
|
||||
<br />
|
||||
<h2>{% block title %}{{ _('Details of "%(name)s"', name=partition.name)}}{% endblock %}</h2>
|
||||
<br/>
|
||||
|
||||
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Responsable de l'ajout
|
||||
{{ _("Added by") }}
|
||||
</td>
|
||||
<td>
|
||||
{% if user is not none %}
|
||||
@ -20,13 +20,13 @@
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
inconnu
|
||||
{{ _("Unknown") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Type d'ajout
|
||||
{{ _("Type") }}
|
||||
</td>
|
||||
<td>
|
||||
{% if partition.source == "unknown" or partition.source == "upload" %}
|
||||
@ -38,7 +38,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Albums
|
||||
{{ _("Albums") }}
|
||||
</td>
|
||||
<td class="liste">
|
||||
<ul>
|
||||
@ -49,38 +49,47 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fichier</td>
|
||||
<td><a href="/partition/{{ partition.uuid }}"><img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg"></a></td>
|
||||
<td>{{ _("File") }}</td>
|
||||
<td><a href="/partition/{{ partition.uuid }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg" loading="lazy">
|
||||
</a><br/>
|
||||
{% block input_file %}
|
||||
{% set required=false %}
|
||||
{% set filetype=".pdf" %}
|
||||
{% include 'components/input_file.html' %}
|
||||
{% endblock %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Titre</td>
|
||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="Titre" required /><br/></td>
|
||||
<td>{{ _("Title") }}</td>
|
||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="{{ _('Title') }}" required /><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Auteur</td>
|
||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="Auteur" /><br/></td>
|
||||
<td>{{ _("Author") }}</td>
|
||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="{{ _('Author') }}" /><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Paroles</td>
|
||||
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
|
||||
<td>{{ _("Lyrics") }}</td>
|
||||
<td><textarea id="lyrics" name="body" type="text" placeholder="{{ _('Lyrics') }}">{{ partition.body }}</textarea><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pièces jointes</td>
|
||||
{% set _ = partition.load_attachments() %}
|
||||
<td>{{ _("Attachments") }}</td>
|
||||
{{ partition.load_attachments() }}
|
||||
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
||||
{% if partition.attachments %}
|
||||
Oui, {{ partition.attachments | length }}
|
||||
{% set number=partition.attachments | length %}
|
||||
{{ _("Yes, %(number)s", number=number) }}
|
||||
{% else %}
|
||||
En rajouter
|
||||
{{ _("Add one") }}
|
||||
{% endif %}
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="submit" value="Mettre à jour" />
|
||||
<input type="submit" value="{{ _('Update') }}" />
|
||||
</form>
|
||||
<a href="/partition/{{ partition.uuid }}/delete">
|
||||
<button id="delete-partition">Supprimer</button>
|
||||
<button id="delete-partition">{{ _("Delete") }}</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
@ -3,22 +3,28 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% block title %}Modifier "{{ partition.name }}"{% endblock %}</h2>
|
||||
<h2>{% block title %}{{ _("Modify \"%(name)s\"", name=partition.name) }}{% endblock %}</h2>
|
||||
<br/>
|
||||
|
||||
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Fichier</td>
|
||||
<td>{{ _("File") }}</td>
|
||||
<td><a href="/partition/{{ partition.uuid }}">
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg">
|
||||
</a></td>
|
||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg" loading="lazy">
|
||||
</a><br/>
|
||||
{% block input_file %}
|
||||
{% set required=false %}
|
||||
{% set filetype=".pdf" %}
|
||||
{% include 'components/input_file.html' %}
|
||||
{% endblock %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if partition.source != "unknown" and partition.source != "upload" %}
|
||||
<tr>
|
||||
<td>
|
||||
Source
|
||||
{{ _("Source") }}
|
||||
</td>
|
||||
<td class="partition-source">
|
||||
<a href="{{ partition.source }}">{{ partition.source.split("/")[2] }}</a>
|
||||
@ -26,34 +32,35 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Titre</td>
|
||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="Titre" required /><br/></td>
|
||||
<td>{{ _("Title") }}</td>
|
||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="{{ _('Title') }}" required /><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Auteur</td>
|
||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="Auteur" /><br/></td>
|
||||
<td>{{ _("Author") }}</td>
|
||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="{{ _('Author') }}" /><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Paroles</td>
|
||||
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
|
||||
<td>{{ _("Lyrics") }}</td>
|
||||
<td><textarea id="lyrics" name="body" type="text" placeholder="{{ _('Lyrics') }}">{{ partition.body }}</textarea><br/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pièces jointes</td>
|
||||
{% set _ = partition.load_attachments() %}
|
||||
<td>{{ _("Attachments") }}</td>
|
||||
{{ partition.load_attachments() }}
|
||||
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
||||
{% if partition.attachments %}
|
||||
Oui, {{ partition.attachments | length }}
|
||||
{% set number=partition.attachments | length %}
|
||||
{{ _("Yes, %(number)s", number=number) }}
|
||||
{% else %}
|
||||
En rajouter
|
||||
{{ _("Add one") }}
|
||||
{% endif %}
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="submit" value="Mettre à jour" />
|
||||
<input type="submit" value="{{ _('Update') }}" />
|
||||
</form>
|
||||
<a href="/partition/{{ partition.uuid }}/delete">
|
||||
<button id="delete-partition">Supprimer</button>
|
||||
<button id="delete-partition">{{ _("Delete") }}</button>
|
||||
</a>
|
||||
|
||||
{% endblock %}
|
43
partitioncloud/templates/settings/index.html
Normal file
43
partitioncloud/templates/settings/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ _("Settings") }}{% endblock %}
|
||||
|
||||
|
||||
{% block dialogs %}
|
||||
<dialog id="delete-account">
|
||||
<h2>{{ _("Delete account") }}</h2>
|
||||
{% set username %}
|
||||
<b>{{ inspected_user.username }}</b>
|
||||
{% endset %}
|
||||
{% set irreversible_bold %}
|
||||
<b>irreversible</b>
|
||||
{% endset %}
|
||||
{{ _("Do you really want to delete %(username)s's account ? This action is %(irreversible_bold)s.", username=username, irreversible_bold=irreversible_bold) }}
|
||||
<br/><br/>
|
||||
<form method="post" action="/settings/delete-account">
|
||||
<input type="hidden" id="user_id" name="user_id" value="{{ inspected_user.id }}">
|
||||
<input type="submit" class="red-confirm" value="{{ _('Delete') }}">
|
||||
</form>
|
||||
<a href="#!" class="close-dialog">Close</a>
|
||||
</dialog>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{{ _("User %(username)s has %(album_count)s albums", username=inspected_user.username, album_count=(inspected_user.get_albums() | length)) }}
|
||||
<form action="/settings/change-password" method="post">
|
||||
<h3>{{ _("Change password") }}</h3>
|
||||
{% if not skip_old_password %}
|
||||
<input type="password" id="old-password" name="old_password" placeholder="{{ _('old password') }}"/><br/>
|
||||
{% endif %}
|
||||
<input type="password" id="new-password" name="new_password" placeholder="{{ _('new password') }}"/><br/>
|
||||
<input type="password" id="confirm-new-password" name="confirm_new_password" placeholder="{{ _('confirm new password') }}"/><br/>
|
||||
<input type="hidden" id="user_id" name="user_id" value="{{ inspected_user.id }}">
|
||||
<input type="Submit" value="{{ _('confirm') }}">
|
||||
</form>
|
||||
{% if deletion_allowed %}
|
||||
<h3>{{ _("Delete account") }}</h3>
|
||||
<a href="#delete-account"><button class="red-confirm">{{ _("Delete account") }}</button></a>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
715
partitioncloud/translations/en/LC_MESSAGES/messages.po
Normal file
715
partitioncloud/translations/en/LC_MESSAGES/messages.po
Normal file
@ -0,0 +1,715 @@
|
||||
# English translations for PROJECT.
|
||||
# Copyright (C) 2024 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-04-10 18:47+0200\n"
|
||||
"PO-Revision-Date: 2024-01-22 15:38+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.14.0\n"
|
||||
|
||||
#: partitioncloud/__init__.py:148
|
||||
#, python-format
|
||||
msgid "Created user %(username)s"
|
||||
msgstr "Created user %(username)s"
|
||||
|
||||
#: partitioncloud/__init__.py:151
|
||||
#, python-format
|
||||
msgid "This album does not exists, but user %(username)s has been created"
|
||||
msgstr "This album does not exists, but user %(username)s has been created"
|
||||
|
||||
#: partitioncloud/modules/albums.py:43
|
||||
msgid "Missing search query"
|
||||
msgstr "Missing search query"
|
||||
|
||||
#: partitioncloud/modules/albums.py:124 partitioncloud/modules/auth.py:27
|
||||
#: partitioncloud/modules/auth.py:54 partitioncloud/modules/groupe.py:271
|
||||
msgid "You need to login to access this resource."
|
||||
msgstr "You need to login to access this resource."
|
||||
|
||||
#: partitioncloud/modules/albums.py:155 partitioncloud/modules/groupe.py:72
|
||||
#: partitioncloud/modules/groupe.py:186
|
||||
msgid "Missing name."
|
||||
msgstr "Missing name."
|
||||
|
||||
#: partitioncloud/modules/albums.py:192
|
||||
msgid "This album does not exist."
|
||||
msgstr "This album does not exist."
|
||||
|
||||
#: partitioncloud/modules/albums.py:195
|
||||
msgid "Album added to collection."
|
||||
msgstr "Album added to collection."
|
||||
|
||||
#: partitioncloud/modules/albums.py:209 partitioncloud/modules/albums.py:272
|
||||
#: partitioncloud/modules/albums.py:384
|
||||
msgid "You are not a member of this album"
|
||||
msgstr "You are not a member of this album"
|
||||
|
||||
#: partitioncloud/modules/albums.py:213
|
||||
msgid "You are alone here, quitting means deleting this album."
|
||||
msgstr "You are alone here, quitting means deleting this album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:217
|
||||
msgid "Album quitted."
|
||||
msgstr "Album quitted."
|
||||
|
||||
#: partitioncloud/modules/albums.py:236
|
||||
msgid "You are not alone in this album."
|
||||
msgstr "You are not alone in this album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:238
|
||||
msgid "You don't own this album."
|
||||
msgstr "You don't own this album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:249
|
||||
msgid "Album deleted."
|
||||
msgstr "Album deleted."
|
||||
|
||||
#: partitioncloud/modules/albums.py:278 partitioncloud/modules/partition.py:154
|
||||
#: partitioncloud/modules/partition.py:211
|
||||
msgid "Missing title"
|
||||
msgstr "Missing title"
|
||||
|
||||
#: partitioncloud/modules/albums.py:280 partitioncloud/modules/partition.py:64
|
||||
msgid "Missing file"
|
||||
msgstr "Missing file"
|
||||
|
||||
#: partitioncloud/modules/albums.py:292
|
||||
msgid "Search results expired"
|
||||
msgstr "Search results expired"
|
||||
|
||||
#: partitioncloud/modules/albums.py:302 partitioncloud/modules/partition.py:170
|
||||
msgid "Invalid PDF file"
|
||||
msgstr ""
|
||||
|
||||
#: partitioncloud/modules/albums.py:364
|
||||
#, python-format
|
||||
msgid "Score %(partition_name)s added"
|
||||
msgstr "Score %(partition_name)s added"
|
||||
|
||||
#: partitioncloud/modules/albums.py:378
|
||||
msgid "Selecting an album is mandatory."
|
||||
msgstr "Selecting an album is mandatory."
|
||||
|
||||
#: partitioncloud/modules/albums.py:380
|
||||
msgid "Selecting a score is mandatory."
|
||||
msgstr "Selecting a score is mandatory."
|
||||
|
||||
#: partitioncloud/modules/albums.py:382
|
||||
msgid "Please specify a score type."
|
||||
msgstr "Please specify a score type."
|
||||
|
||||
#: partitioncloud/modules/albums.py:404
|
||||
msgid "Score added"
|
||||
msgstr "Score added"
|
||||
|
||||
#: partitioncloud/modules/albums.py:406
|
||||
msgid "Score is already in the album."
|
||||
msgstr "Score is already in the album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:418
|
||||
msgid "Unknown score type."
|
||||
msgstr "Unknown score type."
|
||||
|
||||
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:50
|
||||
#: partitioncloud/modules/settings.py:82
|
||||
msgid "Missing rights."
|
||||
msgstr "Missing rights."
|
||||
|
||||
#: partitioncloud/modules/auth.py:85
|
||||
msgid "Missing username."
|
||||
msgstr "Missing username."
|
||||
|
||||
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:96
|
||||
msgid "Missing password."
|
||||
msgstr "Missing password."
|
||||
|
||||
#: partitioncloud/modules/auth.py:100
|
||||
#, python-format
|
||||
msgid "Username %(username)s is not available."
|
||||
msgstr "Username %(username)s is not available."
|
||||
|
||||
#: partitioncloud/modules/auth.py:113
|
||||
msgid "New users registration is disabled by owner."
|
||||
msgstr "New users registration is disabled by owner."
|
||||
|
||||
#: partitioncloud/modules/auth.py:127
|
||||
msgid "Successfully created new user. You can log in."
|
||||
msgstr "Successfully created new user. You can log in."
|
||||
|
||||
#: partitioncloud/modules/auth.py:153
|
||||
msgid "Incorrect username or password"
|
||||
msgstr "Incorrect username or password"
|
||||
|
||||
#: partitioncloud/modules/groupe.py:121
|
||||
msgid "Unknown group."
|
||||
msgstr "Unknown group."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:124
|
||||
msgid "Group added to collection."
|
||||
msgstr "Group added to collection."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:135
|
||||
msgid "You are not a member of this group."
|
||||
msgstr "You are not a member of this group."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:139
|
||||
msgid "You are alone here, quitting means deleting this group."
|
||||
msgstr "You are alone here, quitting means deleting this group."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:143
|
||||
msgid "Group quitted."
|
||||
msgstr "Group quitted."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:156
|
||||
msgid "You are not alone in this group."
|
||||
msgstr "You are not alone in this group."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:167
|
||||
msgid "Group deleted."
|
||||
msgstr "Group deleted."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:189
|
||||
msgid "You are not admin of this group."
|
||||
msgstr "You are not admin of this group."
|
||||
|
||||
#: partitioncloud/modules/partition.py:59
|
||||
msgid "You don't own this score."
|
||||
msgstr "You don't own this score."
|
||||
|
||||
#: partitioncloud/modules/partition.py:72
|
||||
msgid "Missing filename."
|
||||
msgstr "Missing filename."
|
||||
|
||||
#: partitioncloud/modules/partition.py:77
|
||||
msgid "Unsupported file type."
|
||||
msgstr "Unsupported file type."
|
||||
|
||||
#: partitioncloud/modules/partition.py:145
|
||||
msgid "You are not allowed to edit this file."
|
||||
msgstr "You are not allowed to edit this file."
|
||||
|
||||
#: partitioncloud/modules/partition.py:156
|
||||
#: partitioncloud/modules/partition.py:213
|
||||
msgid "Missing author in request body (can be null)."
|
||||
msgstr "Missing author in request body (can be null)."
|
||||
|
||||
#: partitioncloud/modules/partition.py:158
|
||||
#: partitioncloud/modules/partition.py:215
|
||||
msgid "Missing lyrics (can be null)."
|
||||
msgstr "Missing lyrics (can be null)."
|
||||
|
||||
#: partitioncloud/modules/partition.py:181
|
||||
#: partitioncloud/modules/partition.py:227
|
||||
#, python-format
|
||||
msgid "Successfully modified %(name)s"
|
||||
msgstr "Successfully modified %(name)s"
|
||||
|
||||
#: partitioncloud/modules/partition.py:242
|
||||
msgid "You are not allowed to delete this score."
|
||||
msgstr "You are not allowed to delete this score."
|
||||
|
||||
#: partitioncloud/modules/partition.py:250
|
||||
msgid "Score deleted."
|
||||
msgstr "Score deleted."
|
||||
|
||||
#: partitioncloud/modules/settings.py:40 partitioncloud/modules/settings.py:72
|
||||
msgid "Missing user id."
|
||||
msgstr "Missing user id."
|
||||
|
||||
#: partitioncloud/modules/settings.py:54
|
||||
msgid "You are not allowed to delete your account."
|
||||
msgstr "You are not allowed to delete your account."
|
||||
|
||||
#: partitioncloud/modules/settings.py:60
|
||||
msgid "User successfully deleted."
|
||||
msgstr "User successfully deleted."
|
||||
|
||||
#: partitioncloud/modules/settings.py:86
|
||||
msgid "Missing old password."
|
||||
msgstr "Missing old password."
|
||||
|
||||
#: partitioncloud/modules/settings.py:90
|
||||
msgid "Incorrect password."
|
||||
msgstr "Incorrect password."
|
||||
|
||||
#: partitioncloud/modules/settings.py:100
|
||||
msgid "Password and its confirmation differ."
|
||||
msgstr "Password and its confirmation differ."
|
||||
|
||||
#: partitioncloud/modules/settings.py:104
|
||||
msgid "Successfully updated password."
|
||||
msgstr "Successfully updated password."
|
||||
|
||||
#: partitioncloud/templates/base.html:26
|
||||
msgid "New Album"
|
||||
msgstr "New Album"
|
||||
|
||||
#: partitioncloud/templates/base.html:28 partitioncloud/templates/base.html:39
|
||||
#: partitioncloud/templates/groupe/index.html:10
|
||||
#: partitioncloud/templates/partition/attachments.html:11
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: partitioncloud/templates/base.html:29 partitioncloud/templates/base.html:40
|
||||
msgid "Create"
|
||||
msgstr "Create"
|
||||
|
||||
#: partitioncloud/templates/base.html:33
|
||||
msgid "I want to create a collection of albums."
|
||||
msgstr "I want to create a collection of albums."
|
||||
|
||||
#: partitioncloud/templates/base.html:33
|
||||
msgid "Create group"
|
||||
msgstr "Create group"
|
||||
|
||||
#: partitioncloud/templates/base.html:37
|
||||
msgid "Create new group"
|
||||
msgstr "Create new group"
|
||||
|
||||
#: partitioncloud/templates/base.html:64
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
|
||||
#: partitioncloud/templates/base.html:66
|
||||
msgid "Number of online searches"
|
||||
msgstr "Number of online searches"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:23
|
||||
#: partitioncloud/templates/base.html:74
|
||||
#: partitioncloud/templates/partition/details.html:41
|
||||
msgid "Albums"
|
||||
msgstr "Albums"
|
||||
|
||||
#: partitioncloud/templates/base.html:78
|
||||
msgid "New album"
|
||||
msgstr "New album"
|
||||
|
||||
#: partitioncloud/templates/base.html:95
|
||||
msgid "No albums"
|
||||
msgstr "No albums"
|
||||
|
||||
#: partitioncloud/templates/base.html:114
|
||||
msgid "No album available"
|
||||
msgstr "No album available"
|
||||
|
||||
#: partitioncloud/templates/base.html:128
|
||||
msgid "Log in to see your albums"
|
||||
msgstr "Log in to see your albums"
|
||||
|
||||
#: partitioncloud/templates/base.html:142
|
||||
msgid "Log out"
|
||||
msgstr "Log out"
|
||||
|
||||
#: partitioncloud/templates/base.html:157
|
||||
msgid "Admin Panel"
|
||||
msgstr "Admin Panel"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:5
|
||||
#: partitioncloud/templates/auth/register.html:20
|
||||
#: partitioncloud/templates/base.html:169
|
||||
msgid "Create account"
|
||||
msgstr "Create account"
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:5
|
||||
#: partitioncloud/templates/auth/login.html:10
|
||||
#: partitioncloud/templates/base.html:171
|
||||
#: partitioncloud/templates/launch.html:26
|
||||
msgid "Log in"
|
||||
msgstr "Log in"
|
||||
|
||||
#: partitioncloud/templates/launch.html:33
|
||||
msgid ""
|
||||
"PartitionCloud is an open-source score library server, to help you in all"
|
||||
" your musical activities"
|
||||
msgstr ""
|
||||
"PartitionCloud is an open-source score library server, to help you in all"
|
||||
" your musical activities"
|
||||
|
||||
#: partitioncloud/templates/launch.html:38
|
||||
msgid "Let's go !"
|
||||
msgstr "Let's go !"
|
||||
|
||||
#: partitioncloud/templates/launch.html:44
|
||||
msgid "Check code"
|
||||
msgstr "Check code"
|
||||
|
||||
#: partitioncloud/templates/launch.html:55
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This instance is used by %(users)s users with a total of %(scores)s "
|
||||
"scores."
|
||||
msgstr "This instance has %(users)s users with a total of %(scores)s scores."
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:5
|
||||
msgid "Administration Panel"
|
||||
msgstr "Administration Panel"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:9
|
||||
msgid "New user"
|
||||
msgstr "New user"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:12
|
||||
msgid "See scores"
|
||||
msgstr "See scores"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:15
|
||||
msgid "See logs"
|
||||
msgstr "See logs"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:22
|
||||
msgid "User"
|
||||
msgstr "User"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:24
|
||||
msgid "Scores"
|
||||
msgstr "Scores"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:25
|
||||
msgid "Admin privileges"
|
||||
msgstr "Admin privileges"
|
||||
|
||||
#: partitioncloud/templates/admin/logs.html:5
|
||||
msgid "Logs"
|
||||
msgstr "Logs"
|
||||
|
||||
#: partitioncloud/templates/admin/partitions.html:4
|
||||
msgid "Scores list"
|
||||
msgstr "Scores list"
|
||||
|
||||
#: partitioncloud/templates/admin/partitions.html:31
|
||||
#: partitioncloud/templates/albums/album.html:97
|
||||
msgid "No available scores"
|
||||
msgstr "No available scores"
|
||||
|
||||
#: partitioncloud/templates/albums/add-partition.html:3
|
||||
msgid "New score"
|
||||
msgstr "New score"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:12
|
||||
msgid "Delete l'album"
|
||||
msgstr "Delete album"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:13
|
||||
#: partitioncloud/templates/albums/delete-album.html:6
|
||||
msgid "Do you really want to delete this album?"
|
||||
msgstr "Do you really want to delete this album?"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:16
|
||||
#: partitioncloud/templates/albums/album.html:64
|
||||
#: partitioncloud/templates/albums/delete-album.html:8
|
||||
#: partitioncloud/templates/groupe/index.html:20
|
||||
#: partitioncloud/templates/groupe/index.html:57
|
||||
#: partitioncloud/templates/partition/delete.html:10
|
||||
#: partitioncloud/templates/partition/details.html:92
|
||||
#: partitioncloud/templates/partition/edit.html:63
|
||||
#: partitioncloud/templates/settings/index.html:19
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:52
|
||||
msgid "Add a score"
|
||||
msgstr "Add a score"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:55
|
||||
#: partitioncloud/templates/groupe/index.html:47
|
||||
msgid "Join"
|
||||
msgstr "Join"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:57
|
||||
#: partitioncloud/templates/groupe/index.html:49
|
||||
msgid "Quit"
|
||||
msgstr "Quit"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:59
|
||||
#: partitioncloud/templates/groupe/index.html:51
|
||||
msgid "Share"
|
||||
msgstr "Share"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:61
|
||||
#: partitioncloud/templates/groupe/index.html:53
|
||||
msgid "Download as zip"
|
||||
msgstr "Download as zip"
|
||||
|
||||
#: partitioncloud/templates/albums/delete-album.html:3
|
||||
#: partitioncloud/templates/partition/delete.html:4
|
||||
#, python-format
|
||||
msgid "Delete %(name)s"
|
||||
msgstr "Delete %(name)s"
|
||||
|
||||
#: partitioncloud/templates/albums/delete-album.html:11
|
||||
#: partitioncloud/templates/partition/delete.html:13
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:3
|
||||
msgid "Home"
|
||||
msgstr "Home"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:10
|
||||
#, python-format
|
||||
msgid "Hi %(user_name)s !"
|
||||
msgstr "Hi %(user_name)s !"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:11
|
||||
msgid "No album selected"
|
||||
msgstr "No album selected"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:5
|
||||
#, python-format
|
||||
msgid "Search results for \"%(query)s\""
|
||||
msgstr "Search results for \"%(query)s\""
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:7
|
||||
msgid "Results in current database"
|
||||
msgstr "Results in current database"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:38
|
||||
#: partitioncloud/templates/albums/search.html:70
|
||||
msgid "Add to album"
|
||||
msgstr "Add to album"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:45
|
||||
msgid "Online search results"
|
||||
msgstr ""
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:77
|
||||
msgid ""
|
||||
"No results available. Try to tweak your query or increase the amount of "
|
||||
"online searches."
|
||||
msgstr ""
|
||||
"No results available. Try to tweak your query or increase the amount of "
|
||||
"online searches."
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:8
|
||||
#: partitioncloud/templates/auth/register.html:18
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:9
|
||||
#: partitioncloud/templates/auth/register.html:19
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:10
|
||||
msgid "Add to album:"
|
||||
msgstr "Add to album:"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:12
|
||||
msgid "None"
|
||||
msgstr "None"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:1
|
||||
#, python-format
|
||||
msgid "Add a score to %(name)s"
|
||||
msgstr "Add a score to %(name)s"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:4
|
||||
msgid "title"
|
||||
msgstr "title"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:5
|
||||
msgid "author"
|
||||
msgstr "author"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:6
|
||||
msgid "lyrics"
|
||||
msgstr "lyrics"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:16
|
||||
#: partitioncloud/templates/groupe/index.html:11
|
||||
#: partitioncloud/templates/partition/attachments.html:17
|
||||
msgid "Add"
|
||||
msgstr "Add"
|
||||
|
||||
#: partitioncloud/templates/components/input_file.html:4
|
||||
msgid "Your file is selected."
|
||||
msgstr "Your file is selected."
|
||||
|
||||
#: partitioncloud/templates/components/input_file.html:5
|
||||
msgid "Select or drag & drop your file"
|
||||
msgstr "Select or drag & drop your file"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:8
|
||||
#, python-format
|
||||
msgid "Add an album to group %(name)s"
|
||||
msgstr "Add an album to group %(name)s"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:16
|
||||
msgid "Delete group"
|
||||
msgstr "Delete group"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:17
|
||||
msgid "Do you really want to delete this group and the albums it contains?"
|
||||
msgstr "Do you really want to delete this group and the albums it contains?"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:56
|
||||
msgid "Add an album"
|
||||
msgstr "Add an album"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:77
|
||||
msgid "Create one"
|
||||
msgstr "Create one"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:80
|
||||
#, python-format
|
||||
msgid "No available album. %(create)s"
|
||||
msgstr "No available album. %(create)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:5
|
||||
#, python-format
|
||||
msgid "Attachments of %(name)s"
|
||||
msgstr "Attachments of %(name)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:9
|
||||
#, python-format
|
||||
msgid "Add an attachment to %(name)s"
|
||||
msgstr "Add an attachment to %(name)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:26
|
||||
msgid ""
|
||||
"No pdf viewer available in this browser.\n"
|
||||
" You can use Firefox on Android."
|
||||
msgstr ""
|
||||
"No pdf viewer available in this browser.\n"
|
||||
" You can use Firefox on Android."
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:50
|
||||
msgid "JavaScript is mandatory to read MIDI files"
|
||||
msgstr "JavaScript is mandatory to read MIDI files"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:64
|
||||
msgid "Add an attachment"
|
||||
msgstr "Add an attachment"
|
||||
|
||||
#: partitioncloud/templates/partition/delete.html:8
|
||||
msgid "Do you really want to delete this score?"
|
||||
msgstr "Do you really want to delete this score?"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:4
|
||||
#, python-format
|
||||
msgid "Details of \"%(name)s\""
|
||||
msgstr "Details of \"%(name)s\""
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:12
|
||||
msgid "Added by"
|
||||
msgstr "Added by"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:23
|
||||
msgid "Unknown"
|
||||
msgstr "Unknown"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:29
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:52
|
||||
#: partitioncloud/templates/partition/edit.html:13
|
||||
msgid "File"
|
||||
msgstr "File"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:64
|
||||
#: partitioncloud/templates/partition/details.html:65
|
||||
#: partitioncloud/templates/partition/edit.html:35
|
||||
#: partitioncloud/templates/partition/edit.html:36
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:68
|
||||
#: partitioncloud/templates/partition/details.html:69
|
||||
#: partitioncloud/templates/partition/edit.html:39
|
||||
#: partitioncloud/templates/partition/edit.html:40
|
||||
msgid "Author"
|
||||
msgstr "Author"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:72
|
||||
#: partitioncloud/templates/partition/details.html:73
|
||||
#: partitioncloud/templates/partition/edit.html:43
|
||||
#: partitioncloud/templates/partition/edit.html:44
|
||||
msgid "Lyrics"
|
||||
msgstr "Lyrics"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:76
|
||||
#: partitioncloud/templates/partition/edit.html:47
|
||||
msgid "Attachments"
|
||||
msgstr "Attachments"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:81
|
||||
#: partitioncloud/templates/partition/edit.html:52
|
||||
#, python-format
|
||||
msgid "Yes, %(number)s"
|
||||
msgstr "Yes, %(number)s"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:83
|
||||
#: partitioncloud/templates/partition/edit.html:54
|
||||
msgid "Add one"
|
||||
msgstr "Add one"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:89
|
||||
#: partitioncloud/templates/partition/edit.html:60
|
||||
msgid "Update"
|
||||
msgstr "Update"
|
||||
|
||||
#: partitioncloud/templates/partition/edit.html:6
|
||||
#, python-format
|
||||
msgid "Modify \"%(name)s\""
|
||||
msgstr "Modify \"%(name)s\""
|
||||
|
||||
#: partitioncloud/templates/partition/edit.html:27
|
||||
msgid "Source"
|
||||
msgstr "Source"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:3
|
||||
msgid "Settings"
|
||||
msgstr "Settings"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:8
|
||||
#: partitioncloud/templates/settings/index.html:39
|
||||
#: partitioncloud/templates/settings/index.html:40
|
||||
msgid "Delete account"
|
||||
msgstr "Delete account"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:15
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Do you really want to delete %(username)s's account ? This action is "
|
||||
"%(irreversible_bold)s."
|
||||
msgstr ""
|
||||
"Do you really want to delete %(username)s's account ? This action is "
|
||||
"%(irreversible_bold)s."
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:27
|
||||
#, python-format
|
||||
msgid "User %(username)s has %(album_count)s albums"
|
||||
msgstr "User %(username)s has %(album_count)s albums"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:29
|
||||
msgid "Change password"
|
||||
msgstr "Change password"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:31
|
||||
msgid "old password"
|
||||
msgstr "old password"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:33
|
||||
msgid "new password"
|
||||
msgstr "new password"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:34
|
||||
msgid "confirm new password"
|
||||
msgstr "confirm new password"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:36
|
||||
msgid "confirm"
|
||||
msgstr "confirm"
|
||||
|
724
partitioncloud/translations/fr/LC_MESSAGES/messages.po
Normal file
724
partitioncloud/translations/fr/LC_MESSAGES/messages.po
Normal file
@ -0,0 +1,724 @@
|
||||
# French translations for PROJECT.
|
||||
# Copyright (C) 2024 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2024-04-10 18:47+0200\n"
|
||||
"PO-Revision-Date: 2024-01-22 15:24+0100\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: fr\n"
|
||||
"Language-Team: fr <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.14.0\n"
|
||||
|
||||
#: partitioncloud/__init__.py:148
|
||||
#, python-format
|
||||
msgid "Created user %(username)s"
|
||||
msgstr "Utilisateur %(username)s créé"
|
||||
|
||||
#: partitioncloud/__init__.py:151
|
||||
#, python-format
|
||||
msgid "This album does not exists, but user %(username)s has been created"
|
||||
msgstr "Cet album n'existe pas. L'utilisateur %(username)s a été créé"
|
||||
|
||||
#: partitioncloud/modules/albums.py:43
|
||||
msgid "Missing search query"
|
||||
msgstr "Aucun terme de recherche spécifié."
|
||||
|
||||
#: partitioncloud/modules/albums.py:124 partitioncloud/modules/auth.py:27
|
||||
#: partitioncloud/modules/auth.py:54 partitioncloud/modules/groupe.py:271
|
||||
msgid "You need to login to access this resource."
|
||||
msgstr "Vous devez être connecté pour accéder à cette page."
|
||||
|
||||
#: partitioncloud/modules/albums.py:155 partitioncloud/modules/groupe.py:72
|
||||
#: partitioncloud/modules/groupe.py:186
|
||||
msgid "Missing name."
|
||||
msgstr "Un nom est requis."
|
||||
|
||||
#: partitioncloud/modules/albums.py:192
|
||||
msgid "This album does not exist."
|
||||
msgstr "Cet album n'existe pas."
|
||||
|
||||
#: partitioncloud/modules/albums.py:195
|
||||
msgid "Album added to collection."
|
||||
msgstr "Album ajouté à la collection."
|
||||
|
||||
#: partitioncloud/modules/albums.py:209 partitioncloud/modules/albums.py:272
|
||||
#: partitioncloud/modules/albums.py:384
|
||||
msgid "You are not a member of this album"
|
||||
msgstr "Vous ne faites pas partie de cet album"
|
||||
|
||||
#: partitioncloud/modules/albums.py:213
|
||||
msgid "You are alone here, quitting means deleting this album."
|
||||
msgstr "Vous êtes seul dans cet album, le quitter entraînera sa suppression."
|
||||
|
||||
#: partitioncloud/modules/albums.py:217
|
||||
msgid "Album quitted."
|
||||
msgstr "Album quitté."
|
||||
|
||||
#: partitioncloud/modules/albums.py:236
|
||||
msgid "You are not alone in this album."
|
||||
msgstr "Vous n'êtes pas seul dans cet album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:238
|
||||
msgid "You don't own this album."
|
||||
msgstr "Vous ne possédez pas cet album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:249
|
||||
msgid "Album deleted."
|
||||
msgstr "Album supprimé."
|
||||
|
||||
#: partitioncloud/modules/albums.py:278 partitioncloud/modules/partition.py:154
|
||||
#: partitioncloud/modules/partition.py:211
|
||||
msgid "Missing title"
|
||||
msgstr "Un titre est requis."
|
||||
|
||||
#: partitioncloud/modules/albums.py:280 partitioncloud/modules/partition.py:64
|
||||
msgid "Missing file"
|
||||
msgstr "Aucun fichier n'a été fourni."
|
||||
|
||||
#: partitioncloud/modules/albums.py:292
|
||||
msgid "Search results expired"
|
||||
msgstr "Les résultats de la recherche ont expiré."
|
||||
|
||||
#: partitioncloud/modules/albums.py:302 partitioncloud/modules/partition.py:170
|
||||
msgid "Invalid PDF file"
|
||||
msgstr "Fichier PDF invalide"
|
||||
|
||||
#: partitioncloud/modules/albums.py:364
|
||||
#, python-format
|
||||
msgid "Score %(partition_name)s added"
|
||||
msgstr "Partition %(partition_name)s ajoutée"
|
||||
|
||||
#: partitioncloud/modules/albums.py:378
|
||||
msgid "Selecting an album is mandatory."
|
||||
msgstr "Il est nécessaire de sélectionner un album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:380
|
||||
msgid "Selecting a score is mandatory."
|
||||
msgstr "Il est nécessaire de sélectionner une partition."
|
||||
|
||||
#: partitioncloud/modules/albums.py:382
|
||||
msgid "Please specify a score type."
|
||||
msgstr "Il est nécessaire de spécifier un type de partition."
|
||||
|
||||
#: partitioncloud/modules/albums.py:404
|
||||
msgid "Score added"
|
||||
msgstr "Partition ajoutée."
|
||||
|
||||
#: partitioncloud/modules/albums.py:406
|
||||
msgid "Score is already in the album."
|
||||
msgstr "Partition déjà dans l'album."
|
||||
|
||||
#: partitioncloud/modules/albums.py:418
|
||||
msgid "Unknown score type."
|
||||
msgstr "Type de partition inconnu."
|
||||
|
||||
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:50
|
||||
#: partitioncloud/modules/settings.py:82
|
||||
msgid "Missing rights."
|
||||
msgstr "Droits insuffisants."
|
||||
|
||||
#: partitioncloud/modules/auth.py:85
|
||||
msgid "Missing username."
|
||||
msgstr "Un nom d'utilisateur est requis."
|
||||
|
||||
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:96
|
||||
msgid "Missing password."
|
||||
msgstr "Un mot de passe est requis."
|
||||
|
||||
#: partitioncloud/modules/auth.py:100
|
||||
#, python-format
|
||||
msgid "Username %(username)s is not available."
|
||||
msgstr "Le nom d'utilisateur %(username)s est déjà pris."
|
||||
|
||||
#: partitioncloud/modules/auth.py:113
|
||||
msgid "New users registration is disabled by owner."
|
||||
msgstr ""
|
||||
"L'enregistrement de nouveaux utilisateurs a été désactivé par "
|
||||
"l'administrateur."
|
||||
|
||||
#: partitioncloud/modules/auth.py:127
|
||||
msgid "Successfully created new user. You can log in."
|
||||
msgstr "Utilisateur créé avec succès. Vous pouvez vous connecter."
|
||||
|
||||
#: partitioncloud/modules/auth.py:153
|
||||
msgid "Incorrect username or password"
|
||||
msgstr "Nom d'utilisateur ou mot de passe incorrect."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:121
|
||||
msgid "Unknown group."
|
||||
msgstr "Ce groupe n'existe pas."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:124
|
||||
msgid "Group added to collection."
|
||||
msgstr "Groupe ajouté à la collection."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:135
|
||||
msgid "You are not a member of this group."
|
||||
msgstr "Vous ne faites pas partie de ce groupe"
|
||||
|
||||
#: partitioncloud/modules/groupe.py:139
|
||||
msgid "You are alone here, quitting means deleting this group."
|
||||
msgstr "Vous êtes seul dans ce groupe, le quitter entraînera sa suppression."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:143
|
||||
msgid "Group quitted."
|
||||
msgstr "Groupe quitté."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:156
|
||||
msgid "You are not alone in this group."
|
||||
msgstr "Vous n'êtes pas seul dans ce groupe."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:167
|
||||
msgid "Group deleted."
|
||||
msgstr "Groupe supprimé."
|
||||
|
||||
#: partitioncloud/modules/groupe.py:189
|
||||
msgid "You are not admin of this group."
|
||||
msgstr "Vous n'êtes pas administrateur de ce groupe"
|
||||
|
||||
#: partitioncloud/modules/partition.py:59
|
||||
msgid "You don't own this score."
|
||||
msgstr "Cette partition ne vous appartient pas"
|
||||
|
||||
#: partitioncloud/modules/partition.py:72
|
||||
msgid "Missing filename."
|
||||
msgstr "Pas de nom de fichier"
|
||||
|
||||
#: partitioncloud/modules/partition.py:77
|
||||
msgid "Unsupported file type."
|
||||
msgstr "Extension de fichier non supportée"
|
||||
|
||||
#: partitioncloud/modules/partition.py:145
|
||||
msgid "You are not allowed to edit this file."
|
||||
msgstr "Vous n'êtes pas autorisé à modifier cette partition."
|
||||
|
||||
#: partitioncloud/modules/partition.py:156
|
||||
#: partitioncloud/modules/partition.py:213
|
||||
msgid "Missing author in request body (can be null)."
|
||||
msgstr "Un nom d'auteur est requis (à minima nul)"
|
||||
|
||||
#: partitioncloud/modules/partition.py:158
|
||||
#: partitioncloud/modules/partition.py:215
|
||||
msgid "Missing lyrics (can be null)."
|
||||
msgstr "Des paroles sont requises (à minima nulles)"
|
||||
|
||||
#: partitioncloud/modules/partition.py:181
|
||||
#: partitioncloud/modules/partition.py:227
|
||||
#, python-format
|
||||
msgid "Successfully modified %(name)s"
|
||||
msgstr "Partition %(name)s modifiée avec succès."
|
||||
|
||||
#: partitioncloud/modules/partition.py:242
|
||||
msgid "You are not allowed to delete this score."
|
||||
msgstr "Vous n'êtes pas autorisé à supprimer cette partition."
|
||||
|
||||
#: partitioncloud/modules/partition.py:250
|
||||
msgid "Score deleted."
|
||||
msgstr "Partition supprimée."
|
||||
|
||||
#: partitioncloud/modules/settings.py:40 partitioncloud/modules/settings.py:72
|
||||
msgid "Missing user id."
|
||||
msgstr "Identifiant d'utilisateur manquant."
|
||||
|
||||
#: partitioncloud/modules/settings.py:54
|
||||
msgid "You are not allowed to delete your account."
|
||||
msgstr "Vous n'êtes pas autorisé à supprimer votre compte."
|
||||
|
||||
#: partitioncloud/modules/settings.py:60
|
||||
msgid "User successfully deleted."
|
||||
msgstr "Utilisateur supprimé."
|
||||
|
||||
#: partitioncloud/modules/settings.py:86
|
||||
msgid "Missing old password."
|
||||
msgstr "Ancien mot de passe manquant."
|
||||
|
||||
#: partitioncloud/modules/settings.py:90
|
||||
msgid "Incorrect password."
|
||||
msgstr "Mot de passe incorrect."
|
||||
|
||||
#: partitioncloud/modules/settings.py:100
|
||||
msgid "Password and its confirmation differ."
|
||||
msgstr "Le mot de passe et sa confirmation diffèrent"
|
||||
|
||||
#: partitioncloud/modules/settings.py:104
|
||||
msgid "Successfully updated password."
|
||||
msgstr "Mot de passe mis à jour."
|
||||
|
||||
#: partitioncloud/templates/base.html:26
|
||||
msgid "New Album"
|
||||
msgstr "Créer un nouvel album"
|
||||
|
||||
#: partitioncloud/templates/base.html:28 partitioncloud/templates/base.html:39
|
||||
#: partitioncloud/templates/groupe/index.html:10
|
||||
#: partitioncloud/templates/partition/attachments.html:11
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: partitioncloud/templates/base.html:29 partitioncloud/templates/base.html:40
|
||||
msgid "Create"
|
||||
msgstr "Créer"
|
||||
|
||||
#: partitioncloud/templates/base.html:33
|
||||
msgid "I want to create a collection of albums."
|
||||
msgstr ""
|
||||
"Je souhaite créer plusieurs albums et pouvoir tous les partager avec un "
|
||||
"seul lien."
|
||||
|
||||
#: partitioncloud/templates/base.html:33
|
||||
msgid "Create group"
|
||||
msgstr "Créer un groupe"
|
||||
|
||||
#: partitioncloud/templates/base.html:37
|
||||
msgid "Create new group"
|
||||
msgstr "Créer un nouveau groupe"
|
||||
|
||||
#: partitioncloud/templates/base.html:64
|
||||
msgid "Search"
|
||||
msgstr "Rechercher"
|
||||
|
||||
#: partitioncloud/templates/base.html:66
|
||||
msgid "Number of online searches"
|
||||
msgstr "Nombre de recherches en ligne"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:23
|
||||
#: partitioncloud/templates/base.html:74
|
||||
#: partitioncloud/templates/partition/details.html:41
|
||||
msgid "Albums"
|
||||
msgstr "Albums"
|
||||
|
||||
#: partitioncloud/templates/base.html:78
|
||||
msgid "New album"
|
||||
msgstr "Créer un album"
|
||||
|
||||
#: partitioncloud/templates/base.html:95
|
||||
msgid "No albums"
|
||||
msgstr "Aucun album disponible"
|
||||
|
||||
#: partitioncloud/templates/base.html:114
|
||||
msgid "No album available"
|
||||
msgstr "Aucun album disponible"
|
||||
|
||||
#: partitioncloud/templates/base.html:128
|
||||
msgid "Log in to see your albums"
|
||||
msgstr "Connectez vous pour avoir accès à vos albums"
|
||||
|
||||
#: partitioncloud/templates/base.html:142
|
||||
msgid "Log out"
|
||||
msgstr "Déconnexion"
|
||||
|
||||
#: partitioncloud/templates/base.html:157
|
||||
msgid "Admin Panel"
|
||||
msgstr "Panneau admin"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:5
|
||||
#: partitioncloud/templates/auth/register.html:20
|
||||
#: partitioncloud/templates/base.html:169
|
||||
msgid "Create account"
|
||||
msgstr "Créer un compte"
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:5
|
||||
#: partitioncloud/templates/auth/login.html:10
|
||||
#: partitioncloud/templates/base.html:171
|
||||
#: partitioncloud/templates/launch.html:26
|
||||
msgid "Log in"
|
||||
msgstr "Se connecter"
|
||||
|
||||
#: partitioncloud/templates/launch.html:33
|
||||
msgid ""
|
||||
"PartitionCloud is an open-source score library server, to help you in all"
|
||||
" your musical activities"
|
||||
msgstr ""
|
||||
"PartitionCloud est une bibliothèque de partitions open-source, pour vous "
|
||||
"aider dans toutes vos activités musicales"
|
||||
|
||||
#: partitioncloud/templates/launch.html:38
|
||||
msgid "Let's go !"
|
||||
msgstr "C'est parti !"
|
||||
|
||||
#: partitioncloud/templates/launch.html:44
|
||||
msgid "Check code"
|
||||
msgstr "Voir le code"
|
||||
|
||||
#: partitioncloud/templates/launch.html:55
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This instance is used by %(users)s users with a total of %(scores)s "
|
||||
"scores."
|
||||
msgstr ""
|
||||
"Cette instance est utilisée par %(users)s personnes avec un total de "
|
||||
"%(scores)s partitions."
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:5
|
||||
msgid "Administration Panel"
|
||||
msgstr "Panneau d'administration"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:9
|
||||
msgid "New user"
|
||||
msgstr "Nouvel utilisateur"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:12
|
||||
msgid "See scores"
|
||||
msgstr "Voir les partitions"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:15
|
||||
msgid "See logs"
|
||||
msgstr "Voir les logs"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:22
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:24
|
||||
msgid "Scores"
|
||||
msgstr "Partitions"
|
||||
|
||||
#: partitioncloud/templates/admin/index.html:25
|
||||
msgid "Admin privileges"
|
||||
msgstr "Privilèges"
|
||||
|
||||
#: partitioncloud/templates/admin/logs.html:5
|
||||
msgid "Logs"
|
||||
msgstr "Logs"
|
||||
|
||||
#: partitioncloud/templates/admin/partitions.html:4
|
||||
msgid "Scores list"
|
||||
msgstr "Liste des partitions"
|
||||
|
||||
#: partitioncloud/templates/admin/partitions.html:31
|
||||
#: partitioncloud/templates/albums/album.html:97
|
||||
msgid "No available scores"
|
||||
msgstr "Aucune partition disponible"
|
||||
|
||||
#: partitioncloud/templates/albums/add-partition.html:3
|
||||
msgid "New score"
|
||||
msgstr "Ajout de partition"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:12
|
||||
msgid "Delete l'album"
|
||||
msgstr "Supprimer l'album"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:13
|
||||
#: partitioncloud/templates/albums/delete-album.html:6
|
||||
msgid "Do you really want to delete this album?"
|
||||
msgstr "Êtes vous sûr de vouloir supprimer cet album ?"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:16
|
||||
#: partitioncloud/templates/albums/album.html:64
|
||||
#: partitioncloud/templates/albums/delete-album.html:8
|
||||
#: partitioncloud/templates/groupe/index.html:20
|
||||
#: partitioncloud/templates/groupe/index.html:57
|
||||
#: partitioncloud/templates/partition/delete.html:10
|
||||
#: partitioncloud/templates/partition/details.html:92
|
||||
#: partitioncloud/templates/partition/edit.html:63
|
||||
#: partitioncloud/templates/settings/index.html:19
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:52
|
||||
msgid "Add a score"
|
||||
msgstr "Ajouter une partition"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:55
|
||||
#: partitioncloud/templates/groupe/index.html:47
|
||||
msgid "Join"
|
||||
msgstr "Rejoindre"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:57
|
||||
#: partitioncloud/templates/groupe/index.html:49
|
||||
msgid "Quit"
|
||||
msgstr "Quitter"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:59
|
||||
#: partitioncloud/templates/groupe/index.html:51
|
||||
msgid "Share"
|
||||
msgstr "Partager"
|
||||
|
||||
#: partitioncloud/templates/albums/album.html:61
|
||||
#: partitioncloud/templates/groupe/index.html:53
|
||||
msgid "Download as zip"
|
||||
msgstr "Télécharger le zip"
|
||||
|
||||
#: partitioncloud/templates/albums/delete-album.html:3
|
||||
#: partitioncloud/templates/partition/delete.html:4
|
||||
#, python-format
|
||||
msgid "Delete %(name)s"
|
||||
msgstr "Supprimer %(name)s"
|
||||
|
||||
#: partitioncloud/templates/albums/delete-album.html:11
|
||||
#: partitioncloud/templates/partition/delete.html:13
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:3
|
||||
msgid "Home"
|
||||
msgstr "Accueil"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:10
|
||||
#, python-format
|
||||
msgid "Hi %(user_name)s !"
|
||||
msgstr "Bonjour %(user_name)s !"
|
||||
|
||||
#: partitioncloud/templates/albums/index.html:11
|
||||
msgid "No album selected"
|
||||
msgstr "Aucun album sélectionné"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:5
|
||||
#, python-format
|
||||
msgid "Search results for \"%(query)s\""
|
||||
msgstr "Résultats de la recherche pour \"%(query)s\""
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:7
|
||||
msgid "Results in current database"
|
||||
msgstr "Résultats dans la recherche locale"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:38
|
||||
#: partitioncloud/templates/albums/search.html:70
|
||||
msgid "Add to album"
|
||||
msgstr "Ajouter à un album"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:45
|
||||
msgid "Online search results"
|
||||
msgstr "Résultats de la recherche en ligne"
|
||||
|
||||
#: partitioncloud/templates/albums/search.html:77
|
||||
msgid ""
|
||||
"No results available. Try to tweak your query or increase the amount of "
|
||||
"online searches."
|
||||
msgstr ""
|
||||
"Aucun résultat disponible. Essayez d'affiner votre recherche ou "
|
||||
"d'augmenter le nombre de résultats en ligne"
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:8
|
||||
#: partitioncloud/templates/auth/register.html:18
|
||||
msgid "Username"
|
||||
msgstr "Nom d'utilisateur"
|
||||
|
||||
#: partitioncloud/templates/auth/login.html:9
|
||||
#: partitioncloud/templates/auth/register.html:19
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:10
|
||||
msgid "Add to album:"
|
||||
msgstr "Ajouter à un album:"
|
||||
|
||||
#: partitioncloud/templates/auth/register.html:12
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:1
|
||||
#, python-format
|
||||
msgid "Add a score to %(name)s"
|
||||
msgstr "Ajouter une partition à %(name)s"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:4
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:5
|
||||
msgid "author"
|
||||
msgstr "auteur"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:6
|
||||
msgid "lyrics"
|
||||
msgstr "paroles"
|
||||
|
||||
#: partitioncloud/templates/components/add_partition.html:16
|
||||
#: partitioncloud/templates/groupe/index.html:11
|
||||
#: partitioncloud/templates/partition/attachments.html:17
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: partitioncloud/templates/components/input_file.html:4
|
||||
msgid "Your file is selected."
|
||||
msgstr "Fichier sélectionné."
|
||||
|
||||
#: partitioncloud/templates/components/input_file.html:5
|
||||
msgid "Select or drag & drop your file"
|
||||
msgstr "Sélectionner ou déposer un fichier"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:8
|
||||
#, python-format
|
||||
msgid "Add an album to group %(name)s"
|
||||
msgstr "Ajouter un album au groupe %(name)s"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:16
|
||||
msgid "Delete group"
|
||||
msgstr "Supprimer le groupe"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:17
|
||||
msgid "Do you really want to delete this group and the albums it contains?"
|
||||
msgstr ""
|
||||
"Êtes vous sûr de vouloir supprimer ce groupe ? Cela supprimera les albums"
|
||||
" sous-jacents et leurs partitions si personne ne les a rejoints "
|
||||
"(indépendamment du groupe)."
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:56
|
||||
msgid "Add an album"
|
||||
msgstr "Ajouter un album"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:77
|
||||
msgid "Create one"
|
||||
msgstr "En créer un"
|
||||
|
||||
#: partitioncloud/templates/groupe/index.html:80
|
||||
#, python-format
|
||||
msgid "No available album. %(create)s"
|
||||
msgstr "Aucun album disponible. %(create)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:5
|
||||
#, python-format
|
||||
msgid "Attachments of %(name)s"
|
||||
msgstr "Attachments de %(name)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:9
|
||||
#, python-format
|
||||
msgid "Add an attachment to %(name)s"
|
||||
msgstr "Ajouter un attachment à %(name)s"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:26
|
||||
msgid ""
|
||||
"No pdf viewer available in this browser.\n"
|
||||
" You can use Firefox on Android."
|
||||
msgstr ""
|
||||
"Impossible d'afficher le pdf dans ce navigateur.\n"
|
||||
" Il est conseillé d'utiliser Firefox sur Android."
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:50
|
||||
msgid "JavaScript is mandatory to read MIDI files"
|
||||
msgstr "JavaScript est nécessaire pour lire les fichiers MIDI"
|
||||
|
||||
#: partitioncloud/templates/partition/attachments.html:64
|
||||
msgid "Add an attachment"
|
||||
msgstr "Ajouter un attachment"
|
||||
|
||||
#: partitioncloud/templates/partition/delete.html:8
|
||||
msgid "Do you really want to delete this score?"
|
||||
msgstr "Êtes vous sûr de vouloir supprimer cette partition ?"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:4
|
||||
#, python-format
|
||||
msgid "Details of \"%(name)s\""
|
||||
msgstr "Détails de \"%(name)s\""
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:12
|
||||
msgid "Added by"
|
||||
msgstr "Responsable de l'ajout"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:23
|
||||
msgid "Unknown"
|
||||
msgstr "Inconnu"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:29
|
||||
msgid "Type"
|
||||
msgstr "Type d'ajout"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:52
|
||||
#: partitioncloud/templates/partition/edit.html:13
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:64
|
||||
#: partitioncloud/templates/partition/details.html:65
|
||||
#: partitioncloud/templates/partition/edit.html:35
|
||||
#: partitioncloud/templates/partition/edit.html:36
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:68
|
||||
#: partitioncloud/templates/partition/details.html:69
|
||||
#: partitioncloud/templates/partition/edit.html:39
|
||||
#: partitioncloud/templates/partition/edit.html:40
|
||||
msgid "Author"
|
||||
msgstr "Auteur"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:72
|
||||
#: partitioncloud/templates/partition/details.html:73
|
||||
#: partitioncloud/templates/partition/edit.html:43
|
||||
#: partitioncloud/templates/partition/edit.html:44
|
||||
msgid "Lyrics"
|
||||
msgstr "Paroles"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:76
|
||||
#: partitioncloud/templates/partition/edit.html:47
|
||||
msgid "Attachments"
|
||||
msgstr "Pièces jointes"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:81
|
||||
#: partitioncloud/templates/partition/edit.html:52
|
||||
#, python-format
|
||||
msgid "Yes, %(number)s"
|
||||
msgstr "Oui, %(number)s"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:83
|
||||
#: partitioncloud/templates/partition/edit.html:54
|
||||
msgid "Add one"
|
||||
msgstr "En rajouter"
|
||||
|
||||
#: partitioncloud/templates/partition/details.html:89
|
||||
#: partitioncloud/templates/partition/edit.html:60
|
||||
msgid "Update"
|
||||
msgstr "Mettre à jour"
|
||||
|
||||
#: partitioncloud/templates/partition/edit.html:6
|
||||
#, python-format
|
||||
msgid "Modify \"%(name)s\""
|
||||
msgstr "Modifier \"%(name)s\""
|
||||
|
||||
#: partitioncloud/templates/partition/edit.html:27
|
||||
msgid "Source"
|
||||
msgstr "Source"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:3
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:8
|
||||
#: partitioncloud/templates/settings/index.html:39
|
||||
#: partitioncloud/templates/settings/index.html:40
|
||||
msgid "Delete account"
|
||||
msgstr "Supprimer le compte"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:15
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Do you really want to delete %(username)s's account ? This action is "
|
||||
"%(irreversible_bold)s."
|
||||
msgstr ""
|
||||
"Souhaitez-vous vraiment supprimer le compte de %(username)s ? Cette "
|
||||
"action est %(irreversible_bold)s."
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:27
|
||||
#, python-format
|
||||
msgid "User %(username)s has %(album_count)s albums"
|
||||
msgstr "L'utilisateur %(username)s a %(album_count)s albums"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:29
|
||||
msgid "Change password"
|
||||
msgstr "Changer de mot de passe"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:31
|
||||
msgid "old password"
|
||||
msgstr "ancien mot de passe"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:33
|
||||
msgid "new password"
|
||||
msgstr "nouveau mot de passe"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:34
|
||||
msgid "confirm new password"
|
||||
msgstr "confirmer le nouveau mot de passe"
|
||||
|
||||
#: partitioncloud/templates/settings/index.html:36
|
||||
msgid "confirm"
|
||||
msgstr "confirmer"
|
||||
|
@ -1,4 +1,7 @@
|
||||
flask
|
||||
flask-babel
|
||||
google
|
||||
colorama
|
||||
qrcode
|
||||
pypdf
|
||||
qrcode
|
||||
unidecode
|
@ -2,4 +2,4 @@
|
||||
Config parameters, shared between files
|
||||
"""
|
||||
|
||||
instance = "instance"
|
||||
instance = "instance"
|
||||
|
@ -1,6 +1,9 @@
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
import string
|
||||
import sqlite3
|
||||
from colorama import Fore, Style
|
||||
|
||||
from . import config
|
||||
|
||||
@ -39,3 +42,29 @@ def new_uuid():
|
||||
def format_uuid(uuid):
|
||||
"""Format old uuid4 format"""
|
||||
return uuid.upper()[:6]
|
||||
|
||||
|
||||
def install_package(package):
|
||||
print(f"\nThe following python package needs to be installed: {Style.BRIGHT}{Fore.YELLOW}{package}{Style.RESET_ALL}")
|
||||
print(f"1. Install with {Style.BRIGHT}pip{Style.RESET_ALL} (automatic)")
|
||||
print(f"2. Install manually (other package manager)")
|
||||
option = input("Select an option: ")
|
||||
try:
|
||||
choice = int(option)
|
||||
|
||||
if choice == 1:
|
||||
return_value = os.system(f"pip install {package} -qq")
|
||||
if return_value == 0:
|
||||
return
|
||||
print(f"{Fore.RED}Installation with pip failed{Style.RESET_ALL}")
|
||||
sys.exit(return_value)
|
||||
|
||||
elif choice == 2:
|
||||
input("Install via you preferred option, and hit [Enter] when done")
|
||||
return
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
print(f"{Fore.RED}Please enter a valid option{Style.RESET_ALL}")
|
||||
return install_package(package)
|
@ -54,7 +54,7 @@ def add_attachments():
|
||||
|
||||
|
||||
def install_colorama():
|
||||
os.system("pip install colorama -qq")
|
||||
utils.install_package("colorama")
|
||||
|
||||
|
||||
"""
|
||||
@ -144,7 +144,7 @@ def base_url_parameter_added():
|
||||
|
||||
|
||||
def install_qrcode():
|
||||
os.system("pip install qrcode -qq")
|
||||
utils.install_package("qrcode")
|
||||
|
||||
|
||||
"""
|
||||
@ -171,3 +171,27 @@ def move_thumbnails():
|
||||
|
||||
os.makedirs(os.path.join(config.instance, "cache", "thumbnails"), exist_ok=True)
|
||||
os.makedirs(os.path.join(config.instance, "cache", "search-thumbnails"), exist_ok=True)
|
||||
|
||||
|
||||
"""
|
||||
v1.7.*
|
||||
"""
|
||||
|
||||
|
||||
def install_babel():
|
||||
utils.install_package("flask-babel")
|
||||
|
||||
|
||||
"""
|
||||
v1.8.*
|
||||
"""
|
||||
|
||||
def install_pypdf():
|
||||
utils.install_package("pypdf")
|
||||
|
||||
"""
|
||||
v1.10.*
|
||||
"""
|
||||
|
||||
def install_unidecode():
|
||||
utils.install_package("unidecode")
|
@ -34,7 +34,10 @@ hooks = [
|
||||
),
|
||||
("v1.4.1", [("Install qrcode", v1_hooks.install_qrcode)]),
|
||||
("v1.5.0", [("Move to instance directory", v1_hooks.move_instance)]),
|
||||
("v1.5.1", [("Move thumbnails", v1_hooks.move_thumbnails)])
|
||||
("v1.5.1", [("Move thumbnails", v1_hooks.move_thumbnails)]),
|
||||
("v1.7.0", [("Install babel", v1_hooks.install_babel)]),
|
||||
("v1.8.2", [("Install pypdf", v1_hooks.install_pypdf)]),
|
||||
("v1.10.3", [("Install unidecode", v1_hooks.install_unidecode)]),
|
||||
]
|
||||
|
||||
|
||||
@ -199,11 +202,18 @@ if __name__ == "__main__":
|
||||
"--restore",
|
||||
help="restore from specific version backup, will not apply any hook (vx.y.z)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--backup",
|
||||
help="backup current version, without running any hooks",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
config.instance = os.path.abspath(args.instance)
|
||||
|
||||
if args.restore is None:
|
||||
migrate(args.current, args.target, skip_backup=args.skip_backup)
|
||||
else:
|
||||
if args.restore is not None:
|
||||
restore(args.restore)
|
||||
elif args.backup is not None:
|
||||
backup_instance(args.backup, verbose=True)
|
||||
else:
|
||||
migrate(args.current, args.target, skip_backup=args.skip_backup)
|
||||
|
Loading…
x
Reference in New Issue
Block a user