mirror of
https://github.com/partitioncloud/partitioncloud-server.git
synced 2025-04-23 22:43:55 +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
|
# cache
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
|
|
||||||
|
# translations
|
||||||
|
**.mo
|
||||||
|
**.pot
|
||||||
|
|
||||||
# config
|
# config
|
||||||
.vscode/
|
.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
|
- Thème sombre
|
||||||
- dashboard administrateur: gestion de tous les albums, partitions et utilisateurs
|
- 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
|
- [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
|
## 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
|
- 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
|
||||||
|
|
||||||
|
### 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
|
Installer le serveur
|
||||||
```bash
|
```bash
|
||||||
# Clone this repo
|
# Clone this repo
|
||||||
@ -27,6 +47,7 @@ git clone https://github.com/partitioncloud/partitioncloud-server.git
|
|||||||
cd partitioncloud-server
|
cd partitioncloud-server
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
pybabel compile -d partitioncloud/translations
|
||||||
# Create database and folders
|
# Create database and folders
|
||||||
./make.sh init
|
./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
|
## TODO
|
||||||
- [ ] Modifier son mot de passe
|
- [x] Modifier son mot de passe
|
||||||
- [ ] Supprimer un utilisateur
|
- [x] Supprimer un utilisateur
|
||||||
- [ ] Ajouter config:DISABLE_DARK_MODE
|
- [ ] Ajouter config:DISABLE_DARK_MODE
|
||||||
- [x] Ajouter config:DISABLE_REGISTER
|
- [x] Ajouter config:DISABLE_REGISTER
|
||||||
- [ ] Ajouter config:ONLINE_SEARCH_BASE_QUERY pour la recherche google, actuellement 'filetype:pdf partition'
|
- [ ] 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 registration of new users via /auth/register (they can still be added by root)
|
||||||
DISABLE_REGISTER=False
|
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)
|
# Front URL of the application (for QRCodes generation)
|
||||||
BASE_URL="http://localhost:5000"
|
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,
|
# Keep in mind that this config option can only be loaded from default_config.py,
|
||||||
# as the custom config is stored in $INSTANCE_PATH/
|
# as the custom config is stored in $INSTANCE_PATH/
|
||||||
INSTANCE_PATH="instance"
|
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
|
35
make.sh
35
make.sh
@ -18,6 +18,7 @@ init () {
|
|||||||
printf "Souhaitez vous supprimer la base de données existante ? [y/n] "
|
printf "Souhaitez vous supprimer la base de données existante ? [y/n] "
|
||||||
read -r CONFIRMATION
|
read -r CONFIRMATION
|
||||||
[[ $CONFIRMATION == y ]] || exit 1
|
[[ $CONFIRMATION == y ]] || exit 1
|
||||||
|
rm "$INSTANCE_PATH/partitioncloud.sqlite"
|
||||||
fi
|
fi
|
||||||
sqlite3 "$INSTANCE_PATH/partitioncloud.sqlite" '.read partitioncloud/schema.sql'
|
sqlite3 "$INSTANCE_PATH/partitioncloud.sqlite" '.read partitioncloud/schema.sql'
|
||||||
echo "Base de données créé"
|
echo "Base de données créé"
|
||||||
@ -25,30 +26,54 @@ init () {
|
|||||||
echo "Utilisateur root:root ajouté"
|
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 () {
|
start () {
|
||||||
|
pybabel compile -d partitioncloud/translations/
|
||||||
flask run --port=$PORT
|
flask run --port=$PORT
|
||||||
}
|
}
|
||||||
|
|
||||||
production () {
|
production () {
|
||||||
FLASK_APP=partitioncloud /usr/bin/gunicorn \
|
pybabel compile -d partitioncloud/translations/
|
||||||
|
FLASK_APP=partitioncloud gunicorn \
|
||||||
wsgi:app \
|
wsgi:app \
|
||||||
--bind 0.0.0.0:$PORT
|
--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 () {
|
usage () {
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo -e "\t$0 init"
|
echo -e "\t$0 init"
|
||||||
echo -e "\t$0 start"
|
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
|
# Import config
|
||||||
source "default_config.py"
|
load_config "default_config.py"
|
||||||
[[ ! -x "$INSTANCE_PATH/config.py" ]] && source "$INSTANCE_PATH/config.py"
|
|
||||||
|
if test -f "instance/config.py"; then
|
||||||
|
load_config "instance/config.py"
|
||||||
|
fi
|
||||||
|
|
||||||
$1 ${*:2} # Call the function
|
$1 ${*:2} # Call the function
|
||||||
else
|
else
|
||||||
usage
|
usage
|
||||||
echo $(type "$1")
|
echo $RESULT
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -8,16 +8,23 @@ import datetime
|
|||||||
import subprocess
|
import subprocess
|
||||||
import importlib.util
|
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 werkzeug.security import generate_password_hash
|
||||||
|
from flask_babel import Babel, _
|
||||||
|
|
||||||
from .modules.utils import User, Album, get_all_albums
|
from .modules.utils import User, Album, get_all_albums, user_count, partition_count
|
||||||
from .modules import albums, auth, partition, admin, groupe, thumbnails
|
from .modules import albums, auth, partition, admin, groupe, thumbnails, logging, settings
|
||||||
from .modules.auth import admin_required, login_required
|
from .modules.auth import admin_required, login_required
|
||||||
from .modules.db import get_db
|
from .modules.db import get_db
|
||||||
|
|
||||||
app = Flask(__name__)
|
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():
|
def load_config():
|
||||||
app.config.from_object('default_config')
|
app.config.from_object('default_config')
|
||||||
@ -33,14 +40,20 @@ def load_config():
|
|||||||
".",
|
".",
|
||||||
os.path.join(app.instance_path, "config.py")
|
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)
|
user_config = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(user_config)
|
spec.loader.exec_module(user_config)
|
||||||
|
|
||||||
app.config.from_object(user_config)
|
app.config.from_object(user_config)
|
||||||
|
|
||||||
if os.path.abspath(app.config["INSTANCE_PATH"]) != app.instance_path:
|
if os.path.abspath(app.config["INSTANCE_PATH"]) != app.instance_path:
|
||||||
print("[ERROR] Using two different instance path. \
|
print(("[ERROR] Using two different instance path.\n"
|
||||||
\nPlease modify INSTANCE_PATH only in default_config.py and remove it from $INSTANCE_PATH/config.py")
|
"Please modify INSTANCE_PATH only in default_config.py ",
|
||||||
|
"and remove it from $INSTANCE_PATH/config.py"))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print("[WARNING] Using default config")
|
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():
|
def get_version():
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, check=True)
|
result = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, check=True)
|
||||||
@ -60,21 +85,37 @@ def get_version():
|
|||||||
|
|
||||||
|
|
||||||
load_config()
|
load_config()
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
app.register_blueprint(auth.bp)
|
app.register_blueprint(auth.bp)
|
||||||
app.register_blueprint(admin.bp)
|
app.register_blueprint(admin.bp)
|
||||||
app.register_blueprint(groupe.bp)
|
app.register_blueprint(groupe.bp)
|
||||||
app.register_blueprint(albums.bp)
|
app.register_blueprint(albums.bp)
|
||||||
|
app.register_blueprint(settings.bp)
|
||||||
app.register_blueprint(partition.bp)
|
app.register_blueprint(partition.bp)
|
||||||
app.register_blueprint(thumbnails.bp)
|
app.register_blueprint(thumbnails.bp)
|
||||||
|
|
||||||
__version__ = get_version()
|
__version__ = get_version()
|
||||||
|
|
||||||
|
logging.log([], logging.LogEntry.SERVER_RESTART)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
"""Redirect to home"""
|
"""Show launch page if enabled"""
|
||||||
return redirect("/albums/")
|
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"])
|
@app.route("/add-user", methods=["GET", "POST"])
|
||||||
@ -95,14 +136,20 @@ def add_user():
|
|||||||
if error is None:
|
if error is None:
|
||||||
# Success, go to the login page.
|
# Success, go to the login page.
|
||||||
user = User(name=username)
|
user = User(name=username)
|
||||||
|
|
||||||
|
logging.log(
|
||||||
|
[user.username, user.id, True, current_user.username],
|
||||||
|
logging.LogEntry.NEW_USER
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if album_uuid != "":
|
if album_uuid != "":
|
||||||
user.join_album(album_uuid)
|
user.join_album(album_uuid)
|
||||||
flash(f"Utilisateur {username} créé")
|
flash(_("Created user %(username)s", username=username))
|
||||||
return redirect("/albums")
|
return redirect(url_for("albums.index"))
|
||||||
except LookupError:
|
except LookupError:
|
||||||
flash(f"Cet album n'existe pas. L'utilisateur {username} a été créé")
|
flash(_("This album does not exists, but user %(username)s has been created", username=username))
|
||||||
return redirect("/albums")
|
return redirect(url_for("albums.index"))
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
return render_template("auth/register.html", albums=get_all_albums(), user=current_user)
|
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"""
|
"""Inject the version number in the template variables"""
|
||||||
if __version__ == "unknown":
|
if __version__ == "unknown":
|
||||||
return {"version": ''}
|
return {"version": ''}
|
||||||
return {"version": __version__}
|
return {"version": __version__, "lang": get_locale()}
|
||||||
|
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
"""
|
"""
|
||||||
Admin Panel
|
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 .db import get_db
|
||||||
from .auth import admin_required
|
from .auth import admin_required
|
||||||
@ -18,7 +19,6 @@ def index():
|
|||||||
Admin panel home page
|
Admin panel home page
|
||||||
"""
|
"""
|
||||||
current_user = User(user_id=session.get("user_id"))
|
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()
|
db = get_db()
|
||||||
users_id = db.execute(
|
users_id = db.execute(
|
||||||
"""
|
"""
|
||||||
@ -35,3 +35,46 @@ def index():
|
|||||||
users=users,
|
users=users,
|
||||||
user=current_user
|
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
|
Albums module
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import pypdf
|
||||||
import shutil
|
import shutil
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
from flask import (Blueprint, abort, flash, redirect, render_template,
|
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 .auth import login_required
|
||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .utils import User, Album
|
from .utils import User, Album
|
||||||
from . import search, utils
|
from . import search, utils, logging
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("albums", __name__, url_prefix="/albums")
|
bp = Blueprint("albums", __name__, url_prefix="/albums")
|
||||||
@ -37,15 +40,21 @@ def search_page():
|
|||||||
Résultats de recherche
|
Résultats de recherche
|
||||||
"""
|
"""
|
||||||
if "query" not in request.form or request.form["query"] == "":
|
if "query" not in request.form or request.form["query"] == "":
|
||||||
flash("Aucun terme de recherche spécifié.")
|
flash(_("Missing search query"))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
query = request.form["query"]
|
query = request.form["query"]
|
||||||
nb_queries = abs(int(request.form["nb-queries"]))
|
nb_queries = abs(int(request.form["nb-queries"]))
|
||||||
search.flush_cache(current_app.instance_path)
|
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 nb_queries > 0:
|
||||||
if user.access_level != 1:
|
if user.access_level != 1:
|
||||||
@ -80,7 +89,7 @@ def get_album(uuid):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
return abort(404)
|
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"))
|
user = User(user_id=session.get("user_id"))
|
||||||
partitions = album.get_partitions()
|
partitions = album.get_partitions()
|
||||||
if user.id is None:
|
if user.id is None:
|
||||||
@ -106,6 +115,30 @@ def qr_code(uuid):
|
|||||||
return utils.get_qrcode(f"/albums/{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"])
|
@bp.route("/create-album", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def create_album_req():
|
def create_album_req():
|
||||||
@ -116,8 +149,10 @@ def create_album_req():
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
|
user = User(user_id=session["user_id"])
|
||||||
|
|
||||||
if not name or name.strip() == "":
|
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:
|
if error is None:
|
||||||
uuid = utils.create_album(name)
|
uuid = utils.create_album(name)
|
||||||
@ -131,6 +166,8 @@ def create_album_req():
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
logging.log([album.name, album.uuid, user.username], logging.LogEntry.NEW_ALBUM)
|
||||||
|
|
||||||
if "response" in request.args and request.args["response"] == "json":
|
if "response" in request.args and request.args["response"] == "json":
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@ -152,10 +189,10 @@ def join_album(uuid):
|
|||||||
try:
|
try:
|
||||||
user.join_album(uuid)
|
user.join_album(uuid)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
flash("Cet album n'existe pas.")
|
flash(_("This album does not exist."))
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
flash("Album ajouté à la collection.")
|
flash(_("Album added to collection."))
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
|
|
||||||
@ -167,17 +204,18 @@ def quit_album(uuid):
|
|||||||
"""
|
"""
|
||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
album = Album(uuid=uuid)
|
album = Album(uuid=uuid)
|
||||||
|
|
||||||
users = album.get_users()
|
users = album.get_users()
|
||||||
if user.id not in [u["id"] for u in users]:
|
if user.id not in users:
|
||||||
flash("Vous ne faites pas partie de cet album")
|
flash(_("You are not a member of this album"))
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
if len(users) == 1:
|
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")
|
return redirect(f"/albums/{uuid}#delete")
|
||||||
|
|
||||||
user.quit_album(uuid)
|
user.quit_album(uuid)
|
||||||
flash("Album quitté.")
|
flash(_("Album quitted."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -196,9 +234,9 @@ def delete_album(uuid):
|
|||||||
error = None
|
error = None
|
||||||
users = album.get_users()
|
users = album.get_users()
|
||||||
if len(users) > 1:
|
if len(users) > 1:
|
||||||
error = "Vous n'êtes pas seul dans cet album."
|
error = _("You are not alone in this album.")
|
||||||
elif len(users) == 1 and users[0]["id"] != user.id:
|
elif len(users) == 1 and users[0] != user.id:
|
||||||
error = "Vous ne possédez pas cet album."
|
error = _("You don't own this album.")
|
||||||
|
|
||||||
if user.access_level == 1:
|
if user.access_level == 1:
|
||||||
error = None
|
error = None
|
||||||
@ -209,7 +247,7 @@ def delete_album(uuid):
|
|||||||
|
|
||||||
album.delete(current_app.instance_path)
|
album.delete(current_app.instance_path)
|
||||||
|
|
||||||
flash("Album supprimé.")
|
flash(_("Album deleted."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -217,7 +255,7 @@ def delete_album(uuid):
|
|||||||
@login_required
|
@login_required
|
||||||
def add_partition(album_uuid):
|
def add_partition(album_uuid):
|
||||||
"""
|
"""
|
||||||
Ajouter une partition à un album (par upload)
|
Ajouter une partition à un album (nouveau fichier)
|
||||||
"""
|
"""
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
def get_opt_string(dictionary: dict[T, str], key: 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
|
source = "upload" # source type: upload, unknown or url
|
||||||
|
|
||||||
if (not user.is_participant(album.uuid)) and (user.access_level != 1):
|
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)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
if "name" not in request.form:
|
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:
|
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:
|
elif "file" not in request.files:
|
||||||
partition_type = "uuid"
|
partition_type = "uuid"
|
||||||
search_uuid = request.form["partition-uuid"]
|
search_uuid = request.form["partition-uuid"]
|
||||||
@ -252,12 +290,18 @@ def add_partition(album_uuid):
|
|||||||
(search_uuid,)
|
(search_uuid,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if data is None:
|
if data is None:
|
||||||
error = "Les résultats de la recherche ont expiré."
|
error = _("Search results expired")
|
||||||
else:
|
else:
|
||||||
source = data["url"]
|
source = data["url"]
|
||||||
else:
|
else:
|
||||||
partition_type = "file"
|
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:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
return redirect(request.referrer)
|
return redirect(request.referrer)
|
||||||
@ -265,6 +309,7 @@ def add_partition(album_uuid):
|
|||||||
author = get_opt_string(request.form, "author")
|
author = get_opt_string(request.form, "author")
|
||||||
body = get_opt_string(request.form, "body")
|
body = get_opt_string(request.form, "body")
|
||||||
|
|
||||||
|
partition_uuid: str
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
partition_uuid = str(uuid4())
|
partition_uuid = str(uuid4())
|
||||||
@ -307,12 +352,17 @@ def add_partition(album_uuid):
|
|||||||
except db.IntegrityError:
|
except db.IntegrityError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logging.log(
|
||||||
|
[request.form["name"], partition_uuid, user.username],
|
||||||
|
logging.LogEntry.NEW_PARTITION
|
||||||
|
)
|
||||||
|
|
||||||
if "response" in request.args and request.args["response"] == "json":
|
if "response" in request.args and request.args["response"] == "json":
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"uuid": partition_uuid
|
"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}")
|
return redirect(f"/albums/{album.uuid}")
|
||||||
|
|
||||||
|
|
||||||
@ -320,19 +370,19 @@ def add_partition(album_uuid):
|
|||||||
@login_required
|
@login_required
|
||||||
def add_partition_from_search():
|
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"))
|
user = User(user_id=session.get("user_id"))
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
if "album-uuid" not in request.form:
|
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:
|
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:
|
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):
|
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:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
@ -352,9 +402,9 @@ def add_partition_from_search():
|
|||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
album.add_partition(request.form["partition-uuid"])
|
album.add_partition(request.form["partition-uuid"])
|
||||||
flash("Partition ajoutée.")
|
flash(_("Score added"))
|
||||||
else:
|
else:
|
||||||
flash("Partition déjà dans l'album.")
|
flash(_("Score is already in the album."))
|
||||||
|
|
||||||
return redirect(f"/albums/{album.uuid}")
|
return redirect(f"/albums/{album.uuid}")
|
||||||
|
|
||||||
@ -366,5 +416,5 @@ def add_partition_from_search():
|
|||||||
user=user
|
user=user
|
||||||
)
|
)
|
||||||
|
|
||||||
flash("Type de partition inconnu.")
|
flash(_("Unknown score type."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
@ -7,11 +7,13 @@ from typing import Optional
|
|||||||
|
|
||||||
from flask import (Blueprint, flash, g, redirect, render_template,
|
from flask import (Blueprint, flash, g, redirect, render_template,
|
||||||
request, session, url_for, current_app)
|
request, session, url_for, current_app)
|
||||||
|
from flask_babel import _
|
||||||
|
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .utils import User
|
from .utils import User
|
||||||
|
from . import logging
|
||||||
|
|
||||||
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ def login_required(view):
|
|||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
if g.user is None:
|
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 redirect(url_for("auth.login"))
|
||||||
|
|
||||||
return view(**kwargs)
|
return view(**kwargs)
|
||||||
@ -49,12 +51,12 @@ def admin_required(view):
|
|||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
if g.user is None:
|
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 redirect(url_for("auth.login"))
|
||||||
|
|
||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
if user.access_level != 1:
|
if user.access_level != 1:
|
||||||
flash("Droits insuffisants.")
|
flash(_("Missing rights."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
return view(**kwargs)
|
return view(**kwargs)
|
||||||
@ -80,9 +82,9 @@ def create_user(username: str, password: str) -> Optional[str]:
|
|||||||
"""Adds a new user to the database"""
|
"""Adds a new user to the database"""
|
||||||
error = None
|
error = None
|
||||||
if not username:
|
if not username:
|
||||||
error = "Un nom d'utilisateur est requis."
|
error = _("Missing username.")
|
||||||
elif not password:
|
elif not password:
|
||||||
error = "Un mot de passe est requis."
|
error = _("Missing password.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@ -95,7 +97,7 @@ def create_user(username: str, password: str) -> Optional[str]:
|
|||||||
except db.IntegrityError:
|
except db.IntegrityError:
|
||||||
# The username was already taken, which caused the
|
# The username was already taken, which caused the
|
||||||
# commit to fail. Show a validation error.
|
# 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
|
return error # may be None
|
||||||
|
|
||||||
@ -108,7 +110,7 @@ def register():
|
|||||||
password for security.
|
password for security.
|
||||||
"""
|
"""
|
||||||
if current_app.config["DISABLE_REGISTER"]:
|
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"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@ -120,7 +122,15 @@ def register():
|
|||||||
if error is not None:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
else:
|
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")
|
return render_template("auth/register.html")
|
||||||
|
|
||||||
@ -139,12 +149,16 @@ def login():
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
if (user is None) or not check_password_hash(user["password"], password):
|
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:
|
if error is None:
|
||||||
# store the user id in a new session and return to the index
|
# store the user id in a new session and return to the index
|
||||||
session.clear()
|
session.clear()
|
||||||
session["user_id"] = user["id"]
|
session["user_id"] = user["id"]
|
||||||
|
|
||||||
|
logging.log([username], logging.LogEntry.LOGIN)
|
||||||
|
|
||||||
return redirect(url_for("albums.index"))
|
return redirect(url_for("albums.index"))
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
@ -156,4 +170,4 @@ def login():
|
|||||||
def logout():
|
def logout():
|
||||||
"""Clear the current session, including the stored user id."""
|
"""Clear the current session, including the stored user id."""
|
||||||
session.clear()
|
session.clear()
|
||||||
return redirect(url_for("auth.login"))
|
return redirect("/")
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
Classe Album
|
Classe Album
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from ..db import get_db
|
from ..db import get_db
|
||||||
from ..utils import new_uuid
|
from ..utils import new_uuid
|
||||||
@ -47,11 +51,11 @@ class Album():
|
|||||||
|
|
||||||
def get_users(self, force_reload=False):
|
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:
|
if self.users is None or force_reload:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.users = db.execute(
|
data = db.execute(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM user
|
SELECT * FROM user
|
||||||
JOIN contient_user ON user_id = user.id
|
JOIN contient_user ON user_id = user.id
|
||||||
@ -60,6 +64,7 @@ class Album():
|
|||||||
""",
|
""",
|
||||||
(self.uuid,)
|
(self.uuid,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
self.users = [i["id"] for i in data]
|
||||||
return self.users
|
return self.users
|
||||||
|
|
||||||
def get_partitions(self):
|
def get_partitions(self):
|
||||||
@ -166,6 +171,23 @@ class Album():
|
|||||||
db.commit()
|
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:
|
def create(name: str) -> str:
|
||||||
"""Créer un nouvel album"""
|
"""Créer un nouvel album"""
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Classe Groupe
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from ..db import get_db
|
from ..db import get_db
|
||||||
from .album import Album
|
from .album import Album
|
||||||
|
|
||||||
@ -66,21 +75,23 @@ class Groupe():
|
|||||||
album.delete(instance_path)
|
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
|
Renvoie les data["id"] des utilisateurs liés au groupe
|
||||||
TODO: uniformiser le tout
|
|
||||||
"""
|
"""
|
||||||
db = get_db()
|
if self.users is None or force_reload:
|
||||||
return db.execute(
|
db = get_db()
|
||||||
"""
|
data = db.execute(
|
||||||
SELECT * FROM user
|
"""
|
||||||
JOIN groupe_contient_user ON user_id = user.id
|
SELECT * FROM user
|
||||||
JOIN groupe ON groupe.id = groupe_id
|
JOIN groupe_contient_user ON user_id = user.id
|
||||||
WHERE groupe.id = ?
|
JOIN groupe ON groupe.id = groupe_id
|
||||||
""",
|
WHERE groupe.id = ?
|
||||||
(self.id,)
|
""",
|
||||||
).fetchall()
|
(self.id,)
|
||||||
|
).fetchall()
|
||||||
|
self.users = [i["id"] for i in data]
|
||||||
|
return self.users
|
||||||
|
|
||||||
def get_albums(self, force_reload=False):
|
def get_albums(self, force_reload=False):
|
||||||
"""
|
"""
|
||||||
@ -116,3 +127,36 @@ class Groupe():
|
|||||||
(self.id,)
|
(self.id,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
return [i["id"] for i in data]
|
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()
|
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):
|
def get_user(self):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
user = db.execute(
|
user = db.execute(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from ..db import get_db
|
from ..db import get_db
|
||||||
from .album import Album
|
from .album import Album
|
||||||
@ -28,9 +29,11 @@ class User():
|
|||||||
def __init__(self, user_id=None, name=None):
|
def __init__(self, user_id=None, name=None):
|
||||||
self.id = user_id
|
self.id = user_id
|
||||||
self.username = name
|
self.username = name
|
||||||
|
self.password = None
|
||||||
self.albums = None
|
self.albums = None
|
||||||
self.groupes = None
|
self.groupes = None
|
||||||
self.partitions = None
|
self.partitions = None
|
||||||
|
self.accessible_partitions = None
|
||||||
self.max_queries = 0
|
self.max_queries = 0
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
@ -58,6 +61,7 @@ class User():
|
|||||||
|
|
||||||
self.id = data["id"]
|
self.id = data["id"]
|
||||||
self.username = data["username"]
|
self.username = data["username"]
|
||||||
|
self.password = data["password"]
|
||||||
self.access_level = data["access_level"]
|
self.access_level = data["access_level"]
|
||||||
self.color = self.get_color()
|
self.color = self.get_color()
|
||||||
if self.access_level == 1:
|
if self.access_level == 1:
|
||||||
@ -166,6 +170,44 @@ class User():
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return self.partitions
|
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):
|
def join_album(self, album_uuid):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
album = Album(uuid=album_uuid)
|
album = Album(uuid=album_uuid)
|
||||||
@ -198,12 +240,10 @@ class User():
|
|||||||
db.execute(
|
db.execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM contient_user
|
DELETE FROM contient_user
|
||||||
JOIN album
|
WHERE album_id IN (SELECT id FROM album WHERE uuid = ?)
|
||||||
ON album.id = album_id
|
AND user_id = ?
|
||||||
WHERE user_id = ?
|
|
||||||
AND album.uuid = ?
|
|
||||||
""",
|
""",
|
||||||
(self.id, album_uuid)
|
(album_uuid, self.id)
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@ -221,6 +261,49 @@ class User():
|
|||||||
)
|
)
|
||||||
db.commit()
|
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):
|
def get_color(self):
|
||||||
if len(colors) == 0:
|
if len(colors) == 0:
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
Groupe module
|
Groupe module
|
||||||
"""
|
"""
|
||||||
from flask import (Blueprint, abort, flash, redirect, render_template,
|
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 .auth import login_required
|
||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .utils import User, Album, Groupe
|
from .utils import User, Album, Groupe
|
||||||
from . import utils
|
from . import utils
|
||||||
|
from . import logging
|
||||||
|
|
||||||
bp = Blueprint("groupe", __name__, url_prefix="/groupe")
|
bp = Blueprint("groupe", __name__, url_prefix="/groupe")
|
||||||
|
|
||||||
@ -32,7 +35,7 @@ def get_groupe(uuid):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
return abort(404)
|
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()
|
groupe.get_albums()
|
||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
@ -63,8 +66,10 @@ def create_groupe():
|
|||||||
db = get_db()
|
db = get_db()
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
|
user = User(user_id=session["user_id"])
|
||||||
|
|
||||||
if not name or name.strip() == "":
|
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:
|
if error is None:
|
||||||
while True:
|
while True:
|
||||||
@ -93,6 +98,8 @@ def create_groupe():
|
|||||||
except db.IntegrityError:
|
except db.IntegrityError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logging.log([name, uuid, user.username], logging.LogEntry.NEW_GROUPE)
|
||||||
|
|
||||||
if "response" in request.args and request.args["response"] == "json":
|
if "response" in request.args and request.args["response"] == "json":
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@ -111,10 +118,10 @@ def join_groupe(uuid):
|
|||||||
try:
|
try:
|
||||||
user.join_groupe(uuid)
|
user.join_groupe(uuid)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
flash("Ce groupe n'existe pas.")
|
flash(_("Unknown group."))
|
||||||
return redirect(f"/groupe/{uuid}")
|
return redirect(f"/groupe/{uuid}")
|
||||||
|
|
||||||
flash("Groupe ajouté à la collection.")
|
flash(_("Group added to collection."))
|
||||||
return redirect(f"/groupe/{uuid}")
|
return redirect(f"/groupe/{uuid}")
|
||||||
|
|
||||||
|
|
||||||
@ -124,16 +131,21 @@ def quit_groupe(uuid):
|
|||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
groupe = Groupe(uuid=uuid)
|
groupe = Groupe(uuid=uuid)
|
||||||
users = groupe.get_users()
|
users = groupe.get_users()
|
||||||
if user.id not in [u["id"] for u in users]:
|
if user.id not in users:
|
||||||
flash("Vous ne faites pas partie de ce groupe")
|
flash(_("You are not a member of this group."))
|
||||||
return redirect(f"/groupe/{uuid}")
|
return redirect(f"/groupe/{uuid}")
|
||||||
|
|
||||||
if len(users) == 1:
|
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")
|
return redirect(f"/groupe/{uuid}#delete")
|
||||||
|
|
||||||
user.quit_groupe(groupe.uuid)
|
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")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -144,9 +156,8 @@ def delete_groupe(uuid):
|
|||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
error = None
|
error = None
|
||||||
users = groupe.get_users()
|
if len(groupe.get_users()) > 1:
|
||||||
if len(users) > 1:
|
error = _("You are not alone in this group.")
|
||||||
error = "Vous n'êtes pas seul dans ce groupe."
|
|
||||||
|
|
||||||
if user.access_level == 1 or user.id not in groupe.get_admins():
|
if user.access_level == 1 or user.id not in groupe.get_admins():
|
||||||
error = None
|
error = None
|
||||||
@ -157,7 +168,7 @@ def delete_groupe(uuid):
|
|||||||
|
|
||||||
groupe.delete(current_app.instance_path)
|
groupe.delete(current_app.instance_path)
|
||||||
|
|
||||||
flash("Groupe supprimé.")
|
flash(_("Group deleted."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -176,10 +187,10 @@ def create_album_req(groupe_uuid):
|
|||||||
error = None
|
error = None
|
||||||
|
|
||||||
if not name or name.strip() == "":
|
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():
|
if user.id not in groupe.get_admins() and user.access_level != 1:
|
||||||
error ="Vous n'êtes pas administrateur de ce groupe"
|
error = _("You are not admin of this group.")
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
uuid = utils.create_album(name)
|
uuid = utils.create_album(name)
|
||||||
@ -194,6 +205,8 @@ def create_album_req(groupe_uuid):
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
logging.log([album.name, album.uuid, user.username], logging.LogEntry.NEW_ALBUM)
|
||||||
|
|
||||||
if "response" in request.args and request.args["response"] == "json":
|
if "response" in request.args and request.args["response"] == "json":
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@ -232,7 +245,7 @@ def get_album(groupe_uuid, album_uuid):
|
|||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
# List of users without duplicate
|
# 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]
|
album.users = [User(user_id=id) for id in users_id]
|
||||||
|
|
||||||
partitions = album.get_partitions()
|
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")
|
@bp.route("/<groupe_uuid>/<album_uuid>/qr")
|
||||||
def groupe_qr_code(groupe_uuid, album_uuid):
|
def groupe_qr_code(groupe_uuid, album_uuid):
|
||||||
return utils.get_qrcode(f"/groupe/{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
|
Partition module
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import pypdf
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from flask import (Blueprint, abort, send_file, render_template,
|
from flask import (Blueprint, abort, send_file, render_template,
|
||||||
request, redirect, flash, session, current_app)
|
request, redirect, flash, session, current_app)
|
||||||
|
from flask_babel import _
|
||||||
|
|
||||||
from .db import get_db
|
from .db import get_db
|
||||||
from .auth import login_required, admin_required
|
from .auth import login_required, admin_required
|
||||||
@ -54,12 +56,12 @@ def add_attachment(uuid):
|
|||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
if user.id != partition.user_id and user.access_level != 1:
|
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)
|
return redirect(request.referrer)
|
||||||
|
|
||||||
error = None # À mettre au propre
|
error = None # À mettre au propre
|
||||||
if "file" not in request.files:
|
if "file" not in request.files:
|
||||||
error = "Aucun fichier n'a été fourni."
|
error = _("Missing file")
|
||||||
else:
|
else:
|
||||||
if "name" not in request.form or request.form["name"] == "":
|
if "name" not in request.form or request.form["name"] == "":
|
||||||
name = ".".join(request.files["file"].filename.split(".")[:-1])
|
name = ".".join(request.files["file"].filename.split(".")[:-1])
|
||||||
@ -67,12 +69,12 @@ def add_attachment(uuid):
|
|||||||
name = request.form["name"]
|
name = request.form["name"]
|
||||||
|
|
||||||
if name == "":
|
if name == "":
|
||||||
error = "Pas de nom de fichier"
|
error = _("Missing filename.")
|
||||||
else:
|
else:
|
||||||
filename = request.files["file"].filename
|
filename = request.files["file"].filename
|
||||||
ext = filename.split(".")[-1]
|
ext = filename.split(".")[-1]
|
||||||
if ext not in ["mid", "mp3"]:
|
if ext not in ["mid", "mp3"]:
|
||||||
error = "Extension de fichier non supportée"
|
error = _("Unsupported file type.")
|
||||||
|
|
||||||
if error is not None:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
@ -140,7 +142,7 @@ def edit(uuid):
|
|||||||
|
|
||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
if user.access_level != 1 and partition.user_id != 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")
|
return redirect("/albums")
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -149,23 +151,34 @@ def edit(uuid):
|
|||||||
error = None
|
error = None
|
||||||
|
|
||||||
if "name" not in request.form or request.form["name"].strip() == "":
|
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:
|
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:
|
elif "body" not in request.form:
|
||||||
error = "Des paroles sont requises (à minima nulles)"
|
error = _("Missing lyrics (can be null).")
|
||||||
|
|
||||||
if error is not None:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
return redirect(f"/partition/{ uuid }/edit")
|
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(
|
partition.update(
|
||||||
name=request.form["name"],
|
name=request.form["name"],
|
||||||
author=request.form["author"],
|
author=request.form["author"],
|
||||||
body=request.form["body"]
|
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")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -195,11 +208,11 @@ def details(uuid):
|
|||||||
error = None
|
error = None
|
||||||
|
|
||||||
if "name" not in request.form or request.form["name"].strip() == "":
|
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:
|
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:
|
elif "body" not in request.form:
|
||||||
error = "Des paroles sont requises (à minima nulles)"
|
error = _("Missing lyrics (can be null).")
|
||||||
|
|
||||||
if error is not None:
|
if error is not None:
|
||||||
flash(error)
|
flash(error)
|
||||||
@ -211,7 +224,7 @@ def details(uuid):
|
|||||||
body=request.form["body"]
|
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")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
@ -226,7 +239,7 @@ def delete(uuid):
|
|||||||
user = User(user_id=session.get("user_id"))
|
user = User(user_id=session.get("user_id"))
|
||||||
|
|
||||||
if user.access_level != 1 and partition.user_id != 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")
|
return redirect("/albums")
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -234,7 +247,7 @@ def delete(uuid):
|
|||||||
|
|
||||||
partition.delete(current_app.instance_path)
|
partition.delete(current_app.instance_path)
|
||||||
|
|
||||||
flash("Partition supprimée.")
|
flash(_("Score deleted."))
|
||||||
return redirect("/albums")
|
return redirect("/albums")
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ import threading
|
|||||||
import socket
|
import socket
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import pypdf
|
||||||
import googlesearch
|
import googlesearch
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
from .db import get_db
|
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
|
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):
|
def score_attribution(partition):
|
||||||
score = 0
|
score = 0
|
||||||
for word in query_words:
|
for word in query_words:
|
||||||
if word != "":
|
if word != "":
|
||||||
if word in partition["name"].lower():
|
if word in unidecode(partition["name"]).lower():
|
||||||
score += 6
|
score += 6
|
||||||
elif word in partition["author"].lower():
|
elif word in unidecode(partition["author"]).lower():
|
||||||
score += 4
|
score += 4
|
||||||
elif word in partition["body"].lower():
|
elif word in unidecode(partition["body"]).lower():
|
||||||
score += 2
|
score += 2
|
||||||
else:
|
else:
|
||||||
score -= 1
|
score -= 6
|
||||||
for word in partition["name"].split(" "):
|
for word in unidecode(partition["name"]).split():
|
||||||
if word != "" and word.lower() not in query_words:
|
if word != "" and word.lower() not in query_words:
|
||||||
score -= 1
|
score -= 1
|
||||||
return score
|
return score
|
||||||
@ -52,12 +54,17 @@ def local_search(query, partitions):
|
|||||||
def download_search_result(element, instance_path):
|
def download_search_result(element, instance_path):
|
||||||
uuid = element["uuid"]
|
uuid = element["uuid"]
|
||||||
url = element["url"]
|
url = element["url"]
|
||||||
|
filename = f"{instance_path}/search-partitions/{uuid}.pdf"
|
||||||
|
|
||||||
try:
|
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):
|
except (urllib.error.HTTPError, urllib.error.URLError,
|
||||||
with open(f"{instance_path}/search-partitions/{uuid}.pdf", 'a', encoding="utf8") as _:
|
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
|
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
|
Thumbnails
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import pypdf
|
||||||
|
|
||||||
from flask import current_app, abort, Blueprint, send_file
|
from flask import current_app, abort, Blueprint, send_file
|
||||||
|
|
||||||
from .db import get_db
|
|
||||||
from .auth import login_required
|
from .auth import login_required
|
||||||
|
|
||||||
bp = Blueprint("thumbnails", __name__, url_prefix="/thumbnails")
|
bp = Blueprint("thumbnails", __name__, url_prefix="/thumbnails")
|
||||||
@ -15,13 +15,18 @@ def generate_thumbnail(source, dest):
|
|||||||
"""
|
"""
|
||||||
Generates a thumbnail with 'convert' (ImageMagick)
|
Generates a thumbnail with 'convert' (ImageMagick)
|
||||||
"""
|
"""
|
||||||
os.system(
|
try:
|
||||||
f'/usr/bin/convert -thumbnail\
|
pypdf.PdfReader(source) # Check if file is really a PDF
|
||||||
"178^>" -background white -alpha \
|
except (pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
|
||||||
remove -crop 178x178+0+0 \
|
return
|
||||||
{source}[0] \
|
|
||||||
{dest}'
|
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):
|
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):
|
if not os.path.exists(thumbnail_file):
|
||||||
generate_thumbnail(partition_file, thumbnail_file)
|
generate_thumbnail(partition_file, thumbnail_file)
|
||||||
|
|
||||||
|
if not os.path.exists(thumbnail_file):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
return send_file(thumbnail_file)
|
return send_file(thumbnail_file)
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,3 +72,25 @@ def get_all_albums():
|
|||||||
"uuid": a["uuid"]
|
"uuid": a["uuid"]
|
||||||
} for a in albums
|
} 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",
|
"description": "Partitioncloud",
|
||||||
"display": "fullscreen",
|
"display": "fullscreen",
|
||||||
"icons": [
|
"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"));
|
||||||
|
}
|
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 {
|
input:checked#slide-sidebar~#content-container {
|
||||||
position: initial;
|
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'); */
|
/** @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) */
|
/** Various settings (variables) */
|
||||||
:root {
|
:root {
|
||||||
--sidebar-size: max(10vw, 160px);
|
--sidebar-size: max(10vw, 250px);
|
||||||
--sidebar-sz-plus10: calc(var(--sidebar-size) + 10px);
|
--sidebar-sz-plus10: calc(var(--sidebar-size) + 10px);
|
||||||
--sidebar-sz-moins20: calc(var(--sidebar-size) - 20px);
|
--sidebar-sz-moins20: calc(var(--sidebar-size) - 20px);
|
||||||
}
|
}
|
||||||
@ -401,6 +333,7 @@ img.partition-thumbnail {
|
|||||||
|
|
||||||
.user {
|
.user {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
@ -455,12 +388,13 @@ a#delete-album {
|
|||||||
transform: translateY(-17%);
|
transform: translateY(-17%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings-container>.user {
|
#settings-container > a > .user {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings-container>.user:hover {
|
#settings-container > a > .user:hover {
|
||||||
background-color: var(--color-mantle);
|
background-color: var(--color-mantle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,6 +540,8 @@ input[type="file"] {
|
|||||||
/** Dangerous buttons */
|
/** Dangerous buttons */
|
||||||
button#logout:hover,
|
button#logout:hover,
|
||||||
a#delete-album:hover,
|
a#delete-album:hover,
|
||||||
|
.red-confirm,
|
||||||
|
input[type="submit"].red-confirm,
|
||||||
#delete-partition {
|
#delete-partition {
|
||||||
background-color: var(--color-red);
|
background-color: var(--color-red);
|
||||||
color: var(--color-mantle);
|
color: var(--color-mantle);
|
||||||
@ -760,6 +696,9 @@ midi-player {
|
|||||||
margin: 20px;
|
margin: 20px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
|
width: 370px;
|
||||||
|
height: 370px;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#share-url {
|
#share-url {
|
||||||
@ -770,3 +709,65 @@ midi-player {
|
|||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
margin-top: 20px;
|
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 %}
|
{% block content %}
|
||||||
<h2>{% block title %}Panneau d'administration{% endblock %}</h2>
|
<h2>{% block title %}{{ _("Administration Panel") }}{% endblock %}</h2>
|
||||||
|
|
||||||
<div id="actions-rapides">
|
<div id="actions-rapides">
|
||||||
<a href="/add-user">
|
<a href="/add-user">
|
||||||
<div class="button">Ajouter un utilisateur</div>
|
<div class="button">{{ _("New user") }}</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/partition">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="x-scrollable">
|
<div class="x-scrollable">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Utilisateur</th>
|
<th scope="col">{{ _("User") }}</th>
|
||||||
<th scope="col">Albums</th>
|
<th scope="col">{{ _("Albums") }}</th>
|
||||||
<th scope="col">Partitions</th>
|
<th scope="col">{{ _("Scores") }}</th>
|
||||||
<th scope="col">Privilèges</th>
|
<th scope="col">{{ _("Admin privileges") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -31,7 +34,9 @@
|
|||||||
title="{{ user.username }}">
|
title="{{ user.username }}">
|
||||||
{{ user.username[0] | upper }}
|
{{ user.username[0] | upper }}
|
||||||
</div>
|
</div>
|
||||||
<div class="table-username">{{ user.username }}</div>
|
<div class="table-username">
|
||||||
|
<a href="/admin/user/{{ user.id }}">{{ user.username }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ user.albums | length }}</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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h1>{% block title %}Liste des partitions{% endblock %}</h1>
|
<h1>{% block title %}{{ _("Scores list") }}{% endblock %}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<a href="/partition/{{ partition['uuid'] }}">
|
<a href="/partition/{{ partition['uuid'] }}">
|
||||||
<div class="partition" id="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-description">
|
||||||
<div class="partition-name">{{ partition["name"] }}</div>
|
<div class="partition-name">{{ partition["name"] }}</div>
|
||||||
<div class="partition-author">{{ partition["author"] }}</div>
|
<div class="partition-author">{{ partition["author"] }}</div>
|
||||||
@ -28,6 +28,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>Aucune partition disponible</div>
|
<div>{{ _("No available scores") }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,15 +1,7 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Ajout de partition{% endblock %}
|
{% block title %}{{ _("New score") }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Ajouter une partition à {{ album.name }}</h2>
|
{% include 'components/add_partition.html' %}
|
||||||
|
|
||||||
<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>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -5,22 +5,15 @@
|
|||||||
|
|
||||||
{% block dialogs %}
|
{% block dialogs %}
|
||||||
<dialog id="add-partition">
|
<dialog id="add-partition">
|
||||||
<h2>Ajouter une partition à {{ album.name }}</h2>
|
{% include 'components/add_partition.html' %}
|
||||||
<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>
|
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="delete">
|
<dialog id="delete">
|
||||||
<h2>Supprimer l'album</h2>
|
<h2>{{ _("Delete l'album") }}</h2>
|
||||||
Êtes vous sûr de vouloir supprimer cet album ?
|
{{ _("Do you really want to delete this album?") }}
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<form method="post" action="/albums/{{ album.uuid }}/delete">
|
<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>
|
</form>
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
@ -38,12 +31,12 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<header id="album-header">
|
<header id="album-header">
|
||||||
<h2 id="album-title">
|
<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 %}
|
{% endif %}
|
||||||
{{ album.name }}
|
{{ album.name }}
|
||||||
</h2>
|
</h2>
|
||||||
{% if g.user %}
|
<div id="header-actions">
|
||||||
<div id="header-actions">
|
{% if g.user %}
|
||||||
<section id="users">
|
<section id="users">
|
||||||
{% for album_user in album.users %}
|
{% for album_user in album.users %}
|
||||||
<div class="user-profile-picture" style="background-color:{{ album_user.color }};" title="{{ album_user.username }}">
|
<div class="user-profile-picture" style="background-color:{{ album_user.color }};" title="{{ album_user.username }}">
|
||||||
@ -51,25 +44,28 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
<div class="dropdown dp1">
|
{% endif %}
|
||||||
+
|
<div class="dropdown dp1">
|
||||||
<div class="dropdown-content dp1">
|
+
|
||||||
{% if g.user %}
|
<div class="dropdown-content dp1">
|
||||||
<a href="#add-partition">Ajouter une partition</a>
|
{% if g.user %}
|
||||||
{% endif %}
|
<a href="#add-partition">{{ _("Add a score") }}</a>
|
||||||
{% if not_participant %}
|
{% endif %}
|
||||||
<a href="/albums/{{ album.uuid }}/join">Rejoindre</a>
|
{% if not_participant %}
|
||||||
{% elif album.users | length > 1 %}
|
<a href="/albums/{{ album.uuid }}/join">{{ _("Join") }}</a>
|
||||||
<a href="/albums/{{ album.uuid }}/quit">Quitter</a>
|
{% elif g.user and not not_participant %}
|
||||||
{% endif %}
|
<a href="/albums/{{ album.uuid }}/quit">{{ _("Quit") }}</a>
|
||||||
<a href="#share">Partager</a>
|
{% endif %}
|
||||||
{% if g.user.access_level == 1 or (not not_participant and album.users | length == 1) %}
|
<a href="#share">{{ _("Share") }}</a>
|
||||||
<a id="delete-album" href="#delete">Supprimer</a>
|
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
|
||||||
{% endif %}
|
<a href="/albums/{{ album.uuid }}/zip">{{ _("Download as zip") }}</a>
|
||||||
</div>
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<hr/>
|
<hr/>
|
||||||
{% if partitions|length != 0 %}
|
{% if partitions|length != 0 %}
|
||||||
@ -78,7 +74,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<a href="/partition/{{ partition['uuid'] }}">
|
<a href="/partition/{{ partition['uuid'] }}">
|
||||||
<div class="partition" id="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-description">
|
||||||
<div class="partition-name">{{ partition["name"] }}</div>
|
<div class="partition-name">{{ partition["name"] }}</div>
|
||||||
<div class="partition-author">{{ partition["author"] }}</div>
|
<div class="partition-author">{{ partition["author"] }}</div>
|
||||||
@ -98,6 +94,6 @@
|
|||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<br/>
|
<br/>
|
||||||
<section id="partitions-grid" style="display: inline;">Aucune partition disponible</section>
|
<section id="partitions-grid" style="display: inline;">{{ _("No available scores") }}</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Supprimer {{ album.name }}{% endblock %}
|
{% block title %}{{ _("Delete %(name)s", name=album.name) }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Êtes vous sûr de vouloir supprimer cet album ?
|
{{ _("Do you really want to delete this album?") }}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="submit" value="Supprimer">
|
<input type="submit" value="{{ _('Delete') }}">
|
||||||
</form>
|
</form>
|
||||||
<a class="button-href" href="/albums/{{ album.uuid }}">
|
<a class="button-href" href="/albums/{{ album.uuid }}">
|
||||||
<button id="cancel-deletion">Annuler</button>
|
<button id="cancel-deletion">{{ _("Cancel") }}</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,10 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Home{% endblock %}
|
{% block title %}{{ _("Home") }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
Bonjour <i><b>{{ user.username }}</b></i> !<br/>
|
{% set user_name %}
|
||||||
Aucun album sélectionné
|
<i><b>{{ user.username }}</b></i>
|
||||||
|
{% endset %}
|
||||||
|
{{ _("Hi %(user_name)s !", user_name=user_name) }}<br/>
|
||||||
|
{{ _("No album selected") }}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}
|
{% if partitions|length != 0 %}
|
||||||
<h3>Résultats dans la bibliothèque locale</h3>
|
<h3>{{ _("Results in current database") }}</h3>
|
||||||
<div id="partitions-grid">
|
<div id="partitions-grid">
|
||||||
{% for partition in partitions %}
|
{% for partition in partitions %}
|
||||||
<div class="partition-container">
|
<div class="partition-container">
|
||||||
<a href="/partition/{{ partition['uuid'] }}">
|
<a href="/partition/{{ partition['uuid'] }}">
|
||||||
<div class="partition" id="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-description">
|
||||||
<div class="partition-name">{{ partition["name"] }}</div>
|
<div class="partition-name">{{ partition["name"] }}</div>
|
||||||
<div class="partition-author">{{ partition["author"] }}</div>
|
<div class="partition-author">{{ partition["author"] }}</div>
|
||||||
@ -35,20 +35,20 @@
|
|||||||
</select>
|
</select>
|
||||||
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
||||||
<input type="hidden" value="local_file" name="partition-type">
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if google_results|length != 0 %}
|
{% if google_results|length != 0 %}
|
||||||
<h3>Résultats de la recherche en ligne</h3>
|
<h3>{{ _("Online search results") }}</h3>
|
||||||
<div id="partitions-grid">
|
<div id="partitions-grid">
|
||||||
{% for partition in google_results %}
|
{% for partition in google_results %}
|
||||||
<div class="partition-container">
|
<div class="partition-container">
|
||||||
<a href="/partition/search/{{ partition['uuid'] }}">
|
<a href="/partition/search/{{ partition['uuid'] }}">
|
||||||
<div class="partition" id="partition-{{ 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-description">
|
||||||
<div class="partition-name">{{ partition["name"] }}</div>
|
<div class="partition-name">{{ partition["name"] }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,13 +67,13 @@
|
|||||||
</select>
|
</select>
|
||||||
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
<input type="hidden" value="{{ partition['uuid'] }}" name="partition-uuid">
|
||||||
<input type="hidden" value="online_search" name="partition-type">
|
<input type="hidden" value="online_search" name="partition-type">
|
||||||
<input type="submit" value="Ajouter à l'album">
|
<input type="submit" value="{{ _('Add to album') }}">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if google_results|length == 0 and partitions|length == 0 %}
|
{% 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 %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% block title %}Connexion{% endblock %}</h2>
|
<h2>{% block title %}{{ _("Log in") }}{% endblock %}</h2>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="text" name="username" id="username" placeholder="Nom d'utilisateur" required><br/>
|
<input type="text" name="username" id="username" placeholder="{{ _('Username') }}" required><br/>
|
||||||
<input type="password" name="password" id="password" placeholder="Mot de passe" required><br/>
|
<input type="password" name="password" id="password" placeholder="{{ _('Password') }}" required><br/>
|
||||||
<input type="submit" value="Se connecter">
|
<input type="submit" value="{{ _('Log in') }}">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% 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">
|
<form method="post" id="add-user-form">
|
||||||
{% if g.user.access_level == 1 %}
|
{% if g.user.access_level == 1 %}
|
||||||
<!-- Uniquement pour /add-user -->
|
<!-- 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;">
|
<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 %}
|
{% for album in albums %}
|
||||||
<option value="{{ album['uuid'] }}">{{ album["name"] }}</option>
|
<option value="{{ album['uuid'] }}">{{ album["name"] }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select><br/>
|
</select><br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="text" name="username" id="username" placeholder="Nom d'utilisateur" required><br/>
|
<input type="text" name="username" id="username" placeholder="{{ _('Username') }}" required><br/>
|
||||||
<input type="password" name="password" id="password" placeholder="Mot de passe" required><br/>
|
<input type="password" name="password" id="password" placeholder="{{ _('Password') }}" required><br/>
|
||||||
<input type="submit" value="Créer un compte">
|
<input type="submit" value="{{ _('Create account') }}">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,12 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="{{ lang }}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
<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 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>
|
<title>{% block title %}{% endblock %} - PartitionCloud</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='mobile.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="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="apple-touch-icon" href="{{ url_for('static', filename='icons/512.png') }}">
|
||||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.webmanifest') }}" />
|
<link rel="manifest" href="{{ url_for('static', filename='manifest.webmanifest') }}" />
|
||||||
@ -20,21 +23,21 @@
|
|||||||
{% block dialogs %}{% endblock %}
|
{% block dialogs %}{% endblock %}
|
||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
<dialog id="create-album">
|
<dialog id="create-album">
|
||||||
<h2>Créer un nouvel album</h2>
|
<h2>{{ _("New Album") }}</h2>
|
||||||
<form action="/albums/create-album" method="post">
|
<form action="/albums/create-album" method="post">
|
||||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||||
<input type="submit" value="Créer">
|
<input type="submit" value="{{ _('Create') }}">
|
||||||
</form>
|
</form>
|
||||||
<br/>
|
<br/>
|
||||||
<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>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="create-groupe">
|
<dialog id="create-groupe">
|
||||||
<h2>Créer un nouveau groupe</h2>
|
<h2>{{ _("Create new group") }}</h2>
|
||||||
<form action="/groupe/create-groupe" method="post">
|
<form action="/groupe/create-groupe" method="post">
|
||||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||||
<input type="submit" value="Créer">
|
<input type="submit" value="{{ _('Create') }}">
|
||||||
</form>
|
</form>
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
@ -58,9 +61,9 @@
|
|||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
<form id="search-form" action="/albums/search" method="post">
|
<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>
|
<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) %}
|
{% for i in range(0, user.max_queries+1) %}
|
||||||
<option value="{{ i }}">{{ i }}</option>
|
<option value="{{ i }}">{{ i }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -68,11 +71,11 @@
|
|||||||
<input id="search-submit" type="submit" value="Go">
|
<input id="search-submit" type="submit" value="Go">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Albums</h2>
|
<h2>{{ _("Albums") }}</h2>
|
||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
<a href="#create-album">
|
<a href="#create-album">
|
||||||
<div class="create-button">
|
<div class="create-button">
|
||||||
Créer un album
|
{{ _("New album") }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -89,7 +92,7 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<div class="groupe-albums-cover">
|
<div class="groupe-albums-cover">
|
||||||
{% if groupe.get_albums() | length == 0 %}
|
{% if groupe.get_albums() | length == 0 %}
|
||||||
Aucun album
|
{{ _("No albums") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for album in groupe.get_albums() %}
|
{% for album in groupe.get_albums() %}
|
||||||
<a href="/groupe/{{ groupe.uuid }}/{{ album['uuid'] }}">
|
<a href="/groupe/{{ groupe.uuid }}/{{ album['uuid'] }}">
|
||||||
@ -108,7 +111,7 @@
|
|||||||
|
|
||||||
<section id="albums">
|
<section id="albums">
|
||||||
{% if user.get_albums() | length == 0 %}
|
{% 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 %}
|
{% else %}
|
||||||
{% for album in user.albums %}
|
{% for album in user.albums %}
|
||||||
<a href="/albums/{{ album['uuid'] }}">
|
<a href="/albums/{{ album['uuid'] }}">
|
||||||
@ -122,7 +125,7 @@
|
|||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<section id="sidebar-navigation">
|
<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>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -136,7 +139,7 @@
|
|||||||
<path d="M9 12h12l-3 -3"></path>
|
<path d="M9 12h12l-3 -3"></path>
|
||||||
<path d="M18 15l3 -3"></path>
|
<path d="M18 15l3 -3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Déconnexion
|
{{ _("Log out") }}
|
||||||
</button>
|
</button>
|
||||||
</a><br/>
|
</a><br/>
|
||||||
{% if g.user.access_level == 1 %}
|
{% if g.user.access_level == 1 %}
|
||||||
@ -151,21 +154,21 @@
|
|||||||
<path d="M17.27 20l-1.3 .75"></path>
|
<path d="M17.27 20l-1.3 .75"></path>
|
||||||
<path d="M15.97 17.25l1.3 .75"></path>
|
<path d="M15.97 17.25l1.3 .75"></path>
|
||||||
<path d="M20.733 20l1.3 .75"></path>
|
<path d="M20.733 20l1.3 .75"></path>
|
||||||
</svg>Panneau admin
|
</svg>{{ _("Admin Panel") }}
|
||||||
</button></a><br/>
|
</button></a><br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="user">
|
<a href="/settings"><div class="user">
|
||||||
<div class="user-profile-picture" style="background-color:{{ user.color }};"
|
<div class="user-profile-picture" style="background-color:{{ user.color }};"
|
||||||
title="{{ user.username }}">
|
title="{{ user.username }}">
|
||||||
{{ user.username[0] | upper }}
|
{{ user.username[0] | upper }}
|
||||||
</div>
|
</div>
|
||||||
<div class="username">{{ user.username }}</div>
|
<div class="username">{{ user.username }}</div>
|
||||||
</div>
|
</div></a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not config.DISABLE_REGISTER %}
|
{% 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 %}
|
{% endif %}
|
||||||
<a href="{{ url_for('auth.login') }}"><button>Se connecter</button></a>
|
<a href="{{ url_for('auth.login') }}"><button>{{ _("Log in") }}</button></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -185,5 +188,8 @@
|
|||||||
<div id="footer"><a href="https://github.com/partitioncloud/partitioncloud-server">PartitionCloud</a> {{ version }}</div>
|
<div id="footer"><a href="https://github.com/partitioncloud/partitioncloud-server">PartitionCloud</a> {{ version }}</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</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>
|
</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">
|
<dialog id="share">
|
||||||
<center>
|
<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 }}")'>
|
<div id="share-url" onclick='navigator.clipboard.writeText("{{ share_link }}")'>
|
||||||
{{ share_link }}
|
{{ share_link }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,20 +5,19 @@
|
|||||||
|
|
||||||
{% block dialogs %}
|
{% block dialogs %}
|
||||||
<dialog id="create-groupe-album">
|
<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">
|
<form action="/groupe/{{ groupe.uuid }}/create-album" method="post">
|
||||||
<input type="text" name="name" id="name" placeholder="Nom" required><br/>
|
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}" required><br/>
|
||||||
<input type="submit" value="Ajouter">
|
<input type="submit" value="{{ _('Add') }}">
|
||||||
</form>
|
</form>
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="delete">
|
<dialog id="delete">
|
||||||
<h2>Supprimer le groupe</h2>
|
<h2>{{ _("Delete group") }}</h2>
|
||||||
Êtes vous sûr de vouloir supprimer ce groupe ? Cela supprimera les albums
|
{{ _("Do you really want to delete this group and the albums it contains?") }}
|
||||||
sous-jacents et leurs partitions si personne ne les a rejoints (indépendamment du groupe).
|
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<form method="post" action="/groupe/{{ groupe.uuid }}/delete">
|
<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>
|
</form>
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
@ -31,8 +30,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<header id="album-header">
|
<header id="album-header">
|
||||||
<h2 id="groupe-title">{{ groupe.name }}</h2>
|
<h2 id="groupe-title">{{ groupe.name }}</h2>
|
||||||
{% if g.user %}
|
<div id="header-actions">
|
||||||
<div id="header-actions">
|
{% if g.user %}
|
||||||
<section id="users">
|
<section id="users">
|
||||||
{% for groupe_user in groupe.users %}
|
{% for groupe_user in groupe.users %}
|
||||||
<div class="user-profile-picture" style="background-color:{{ groupe_user.color }};" title="{{ groupe_user.username }}">
|
<div class="user-profile-picture" style="background-color:{{ groupe_user.color }};" title="{{ groupe_user.username }}">
|
||||||
@ -40,23 +39,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
<div class="dropdown dp1">
|
{% endif %}
|
||||||
+
|
<div class="dropdown dp1">
|
||||||
<div class="dropdown-content dp1">
|
+
|
||||||
{% if not_participant %}
|
<div class="dropdown-content dp1">
|
||||||
<a href="/groupe/{{ groupe.uuid }}/join">Rejoindre</a>
|
{% if not_participant %}
|
||||||
{% elif groupe.users | length > 1 %}
|
<a href="/groupe/{{ groupe.uuid }}/join">{{ _("Join") }}</a>
|
||||||
<a href="/groupe/{{ groupe.uuid }}/quit">Quitter</a>
|
{% elif g.user and not not_participant %}
|
||||||
{% endif %}
|
<a href="/groupe/{{ groupe.uuid }}/quit">{{ _("Quit") }}</a>
|
||||||
<a href="#share">Partager</a>
|
{% endif %}
|
||||||
{% if g.user.access_level == 1 or user.id in groupe.get_admins() %}
|
<a href="#share">{{ _("Share") }}</a>
|
||||||
<a href="#create-groupe-album">Ajouter un album</a>
|
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
|
||||||
<a id="delete-album" href="#delete">Supprimer</a>
|
<a href="/groupe/{{ groupe.uuid }}/zip">{{ _("Download as zip") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<hr/>
|
<hr/>
|
||||||
{% if groupe.albums|length != 0 %}
|
{% if groupe.albums|length != 0 %}
|
||||||
@ -71,6 +73,11 @@
|
|||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<br/>
|
<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 %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% 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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Attachments de {{ partition.name }}{% endblock %}
|
{% block title %}{{ _("Attachments of %(name)s", name=partition.name) }}{% endblock %}
|
||||||
|
|
||||||
{% block dialogs %}
|
{% block dialogs %}
|
||||||
<dialog id="create-attachment">
|
<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">
|
<form action="/partition/{{ partition.uuid }}/add-attachment" method="post" enctype="multipart/form-data">
|
||||||
<input type="text" name="name" id="name" placeholder="Nom"><br/>
|
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}"><br/>
|
||||||
<input name="file" type="file" accept=".mp3,.mid" required=""><br/>
|
{% block input_file %}
|
||||||
<input type="submit" value="Ajouter">
|
{% set required=true %}
|
||||||
|
{% set filetype=".mp3,.mid" %}
|
||||||
|
{% include 'components/input_file.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
<input type="submit" value="{{ _('Add') }}">
|
||||||
</form>
|
</form>
|
||||||
<a href="#!" class="close-dialog">Close</a>
|
<a href="#!" class="close-dialog">Close</a>
|
||||||
</dialog>
|
</dialog>
|
||||||
@ -19,8 +23,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<object id="pdf-embed" width="400" height="500" type="application/pdf" data="/partition/{{ partition.uuid }}">
|
<object id="pdf-embed" width="400" height="500" type="application/pdf" data="/partition/{{ partition.uuid }}">
|
||||||
<p>
|
<p>
|
||||||
Impossible d'afficher le pdf dans ce navigateur.
|
{{ _("No pdf viewer available in this browser.
|
||||||
Il est conseillé d'utiliser Firefox sur Android.
|
You can use Firefox on Android.") }}
|
||||||
</p>
|
</p>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
@ -43,7 +47,7 @@
|
|||||||
src="/partition/attachment/{{ attachment.uuid }}.mid"
|
src="/partition/attachment/{{ attachment.uuid }}.mid"
|
||||||
sound-font visualizer="#midi-visualizer" data-js-focus-visible>
|
sound-font visualizer="#midi-visualizer" data-js-focus-visible>
|
||||||
</midi-player>
|
</midi-player>
|
||||||
<noscript>MIDI support needs JavaScript</noscript>
|
<noscript>{{ _("JavaScript is mandatory to read MIDI files") }}</noscript>
|
||||||
</td>
|
</td>
|
||||||
<td>🎵 {{ attachment.name }}</td>
|
<td>🎵 {{ attachment.name }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -55,9 +59,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
{% if user %}
|
{% if g.user %}
|
||||||
<div class="centered">
|
<div class="centered">
|
||||||
<a href="#create-attachment"><button>Ajouter un attachment</button></a>
|
<a href="#create-attachment"><button>{{ _("Add an attachment") }}</button></a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h1>{% block title %}Supprimer {{ partition.name }}{% endblock %}</h1>
|
<h1>{% block title %}{{ _("Delete %(name)s", name=partition.name) }}{% endblock %}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Êtes vous sûr de vouloir supprimer cette partition ?
|
{{ _("Do you really want to delete this score?") }}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="submit" id="delete-partition" value="Supprimer">
|
<input type="submit" id="delete-partition" value="{{ _('Delete') }}">
|
||||||
</form>
|
</form>
|
||||||
<a class="button-href" href="/partition/{{ partition.uuid }}/edit">
|
<a class="button-href" href="/partition/{{ partition.uuid }}/edit">
|
||||||
<button id="cancel-deletion">Annuler</button>
|
<button id="cancel-deletion">{{ _("Cancel") }}</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,15 +1,15 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% block title %}Détails "{{ partition.name }}"{% endblock %}</h2>
|
<h2>{% block title %}{{ _('Details of "%(name)s"', name=partition.name)}}{% endblock %}</h2>
|
||||||
<br />
|
<br/>
|
||||||
|
|
||||||
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
Responsable de l'ajout
|
{{ _("Added by") }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if user is not none %}
|
{% if user is not none %}
|
||||||
@ -20,13 +20,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
inconnu
|
{{ _("Unknown") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
Type d'ajout
|
{{ _("Type") }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if partition.source == "unknown" or partition.source == "upload" %}
|
{% if partition.source == "unknown" or partition.source == "upload" %}
|
||||||
@ -38,7 +38,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
Albums
|
{{ _("Albums") }}
|
||||||
</td>
|
</td>
|
||||||
<td class="liste">
|
<td class="liste">
|
||||||
<ul>
|
<ul>
|
||||||
@ -49,38 +49,47 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Fichier</td>
|
<td>{{ _("File") }}</td>
|
||||||
<td><a href="/partition/{{ partition.uuid }}"><img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg"></a></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Titre</td>
|
<td>{{ _("Title") }}</td>
|
||||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="Titre" required /><br/></td>
|
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="{{ _('Title') }}" required /><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Auteur</td>
|
<td>{{ _("Author") }}</td>
|
||||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="Auteur" /><br/></td>
|
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="{{ _('Author') }}" /><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Paroles</td>
|
<td>{{ _("Lyrics") }}</td>
|
||||||
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
|
<td><textarea id="lyrics" name="body" type="text" placeholder="{{ _('Lyrics') }}">{{ partition.body }}</textarea><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pièces jointes</td>
|
<td>{{ _("Attachments") }}</td>
|
||||||
{% set _ = partition.load_attachments() %}
|
{{ partition.load_attachments() }}
|
||||||
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
||||||
{% if partition.attachments %}
|
{% if partition.attachments %}
|
||||||
Oui, {{ partition.attachments | length }}
|
{% set number=partition.attachments | length %}
|
||||||
|
{{ _("Yes, %(number)s", number=number) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
En rajouter
|
{{ _("Add one") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a></td>
|
</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" value="Mettre à jour" />
|
<input type="submit" value="{{ _('Update') }}" />
|
||||||
</form>
|
</form>
|
||||||
<a href="/partition/{{ partition.uuid }}/delete">
|
<a href="/partition/{{ partition.uuid }}/delete">
|
||||||
<button id="delete-partition">Supprimer</button>
|
<button id="delete-partition">{{ _("Delete") }}</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -3,22 +3,28 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% block title %}Modifier "{{ partition.name }}"{% endblock %}</h2>
|
<h2>{% block title %}{{ _("Modify \"%(name)s\"", name=partition.name) }}{% endblock %}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
<form action="/partition/{{ partition.uuid }}/edit" method="post" enctype="multipart/form-data">
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Fichier</td>
|
<td>{{ _("File") }}</td>
|
||||||
<td><a href="/partition/{{ partition.uuid }}">
|
<td><a href="/partition/{{ partition.uuid }}">
|
||||||
<img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg">
|
<img class="partition-thumbnail" src="/thumbnails/{{ partition.uuid }}.jpg" loading="lazy">
|
||||||
</a></td>
|
</a><br/>
|
||||||
|
{% block input_file %}
|
||||||
|
{% set required=false %}
|
||||||
|
{% set filetype=".pdf" %}
|
||||||
|
{% include 'components/input_file.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if partition.source != "unknown" and partition.source != "upload" %}
|
{% if partition.source != "unknown" and partition.source != "upload" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
Source
|
{{ _("Source") }}
|
||||||
</td>
|
</td>
|
||||||
<td class="partition-source">
|
<td class="partition-source">
|
||||||
<a href="{{ partition.source }}">{{ partition.source.split("/")[2] }}</a>
|
<a href="{{ partition.source }}">{{ partition.source.split("/")[2] }}</a>
|
||||||
@ -26,34 +32,35 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Titre</td>
|
<td>{{ _("Title") }}</td>
|
||||||
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="Titre" required /><br/></td>
|
<td><input name="name" type="text" value="{{ partition.name }}" placeholder="{{ _('Title') }}" required /><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Auteur</td>
|
<td>{{ _("Author") }}</td>
|
||||||
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="Auteur" /><br/></td>
|
<td><input name="author" type="text" value="{{ partition.author }}" placeholder="{{ _('Author') }}" /><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Paroles</td>
|
<td>{{ _("Lyrics") }}</td>
|
||||||
<td><textarea id="paroles" name="body" type="text" placeholder="Paroles">{{ partition.body }}</textarea><br/></td>
|
<td><textarea id="lyrics" name="body" type="text" placeholder="{{ _('Lyrics') }}">{{ partition.body }}</textarea><br/></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pièces jointes</td>
|
<td>{{ _("Attachments") }}</td>
|
||||||
{% set _ = partition.load_attachments() %}
|
{{ partition.load_attachments() }}
|
||||||
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
<td><a href="/partition/{{ partition.uuid }}/attachments">
|
||||||
{% if partition.attachments %}
|
{% if partition.attachments %}
|
||||||
Oui, {{ partition.attachments | length }}
|
{% set number=partition.attachments | length %}
|
||||||
|
{{ _("Yes, %(number)s", number=number) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
En rajouter
|
{{ _("Add one") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a></td>
|
</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" value="Mettre à jour" />
|
<input type="submit" value="{{ _('Update') }}" />
|
||||||
</form>
|
</form>
|
||||||
<a href="/partition/{{ partition.uuid }}/delete">
|
<a href="/partition/{{ partition.uuid }}/delete">
|
||||||
<button id="delete-partition">Supprimer</button>
|
<button id="delete-partition">{{ _("Delete") }}</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% 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
|
||||||
|
flask-babel
|
||||||
google
|
google
|
||||||
colorama
|
colorama
|
||||||
|
pypdf
|
||||||
qrcode
|
qrcode
|
||||||
|
unidecode
|
@ -1,6 +1,9 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
from colorama import Fore, Style
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
@ -39,3 +42,29 @@ def new_uuid():
|
|||||||
def format_uuid(uuid):
|
def format_uuid(uuid):
|
||||||
"""Format old uuid4 format"""
|
"""Format old uuid4 format"""
|
||||||
return uuid.upper()[:6]
|
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():
|
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():
|
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", "thumbnails"), exist_ok=True)
|
||||||
os.makedirs(os.path.join(config.instance, "cache", "search-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.4.1", [("Install qrcode", v1_hooks.install_qrcode)]),
|
||||||
("v1.5.0", [("Move to instance directory", v1_hooks.move_instance)]),
|
("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",
|
"--restore",
|
||||||
help="restore from specific version backup, will not apply any hook (vx.y.z)",
|
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()
|
args = parser.parse_args()
|
||||||
config.instance = os.path.abspath(args.instance)
|
config.instance = os.path.abspath(args.instance)
|
||||||
|
|
||||||
if args.restore is None:
|
if args.restore is not None:
|
||||||
migrate(args.current, args.target, skip_backup=args.skip_backup)
|
|
||||||
else:
|
|
||||||
restore(args.restore)
|
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