Compare commits

...

17 Commits

27 changed files with 751 additions and 287 deletions

View File

@ -10,7 +10,7 @@ Serveur web (basé sur Flask) pour gérer sa collection de partitions musicales
- Thème sombre
- dashboard administrateur: gestion de tous les albums, partitions et utilisateurs
- [CLI](https://github.com/partitioncloud/partitioncloud-cli) uniquement à des fins de synchronisation. Il serait bon d'ajouter une BDD locale avec les UUIDs des partitions
- ~~Pas de Javascript~~ Complètement fonctionnel sans JavaScript, cela vient juste ajouter des [toutes petites améliorations](partitioncloud/static/main.js)
- ~~Pas de Javascript~~ Complètement fonctionnel sans JavaScript, cela vient juste ajouter des [toutes petites améliorations](partitioncloud/static/scripts)
## Points à noter
- Les partitions ajoutées sont accessibles à tous les utilisateurs depuis la recherche même si ils ne sont pas dans un album leur y donnant accès, pour limiter la redondance
@ -20,6 +20,26 @@ Serveur web (basé sur Flask) pour gérer sa collection de partitions musicales
## Installation
### Installation via Docker (recommandé)
```bash
# Clone this repo
git clone https://github.com/partitioncloud/partitioncloud-server.git
cd partitioncloud-server
# Create an image named "partitioncloud"
docker build -t partitioncloud .
# You can then run the container, replace $PORT with the port you want to be exposed
PORT=5000
docker run -d \
-p $PORT:5000 \
--restart=unless-stopped \
--name partitioncloud \
partitioncloud:latest
```
L'utilisateur par défaut est `root` avec le mot de passe `root`
### Installation manuelle
Installer le serveur
```bash
# Clone this repo

View File

@ -1,2 +1,5 @@
[extractors]
jinja2 = jinja2.ext:babel_extract
[python: partitioncloud/**.py]
[jinja2: partitioncloud/templates/**.html]

View File

@ -14,6 +14,12 @@ MAX_ONLINE_QUERIES=3
# Disable registration of new users via /auth/register (they can still be added by root)
DISABLE_REGISTER=False
# Disable account deletion for users (still possible for admins)
DISABLE_ACCOUNT_DELETION=False
# Set this to True if you want local search to be across all albums (not just those the user belong to)
PRIVATE_SEARCH=False
# Front URL of the application (for QRCodes generation)
BASE_URL="http://localhost:5000"
@ -33,3 +39,6 @@ LANGUAGES=['en', 'fr']
# Show Launch page
LAUNCH_PAGE=True
# Check if account is logged in before serving zipped album/groupe
ZIP_REQUIRE_LOGIN=True

View File

@ -50,7 +50,8 @@ def user_inspect(user_id):
"settings/index.html",
skip_old_password=True,
inspected_user=inspected_user,
user=current_user
user=current_user,
deletion_allowed=True
)

View File

@ -5,12 +5,12 @@ Albums module
import os
import pypdf
import shutil
from uuid import uuid4
from typing import TypeVar
from flask import (Blueprint, abort, flash, redirect, render_template,
request, session, current_app)
request, session, current_app, send_file, g, url_for)
from werkzeug.utils import secure_filename
from flask_babel import _
from .auth import login_required
@ -43,12 +43,18 @@ def search_page():
flash(_("Missing search query"))
return redirect("/albums")
user = User(user_id=session.get("user_id"))
query = request.form["query"]
nb_queries = abs(int(request.form["nb-queries"]))
search.flush_cache(current_app.instance_path)
partitions_local = search.local_search(query, utils.get_all_partitions())
user = User(user_id=session.get("user_id"))
partitions_list = None
if current_app.config["PRIVATE_SEARCH"]:
partitions_list = utils.get_all_partitions()
else:
partitions_list = user.get_accessible_partitions()
partitions_local = search.local_search(query, partitions_list)
if nb_queries > 0:
if user.access_level != 1:
@ -83,7 +89,7 @@ def get_album(uuid):
except LookupError:
return abort(404)
album.users = [User(user_id=i["id"]) for i in album.get_users()]
album.users = [User(user_id=u_id) for u_id in album.get_users()]
user = User(user_id=session.get("user_id"))
partitions = album.get_partitions()
if user.id is None:
@ -109,6 +115,30 @@ def qr_code(uuid):
return utils.get_qrcode(f"/albums/{uuid}")
@bp.route("/<uuid>/zip")
def zip_download(uuid):
"""
Télécharger un album comme fichier zip
"""
if g.user is None and current_app.config["ZIP_REQUIRE_LOGIN"]:
flash(_("You need to login to access this resource."))
return redirect(url_for("auth.login"))
try:
album = Album(uuid=uuid)
except LookupError:
try:
album = Album(uuid=utils.format_uuid(uuid))
return redirect(f"/albums/{utils.format_uuid(uuid)}")
except LookupError:
return abort(404)
return send_file(
album.to_zip(current_app.instance_path),
download_name=secure_filename(f"{album.name}.zip")
)
@bp.route("/create-album", methods=["POST"])
@login_required
def create_album_req():
@ -174,8 +204,9 @@ def quit_album(uuid):
"""
user = User(user_id=session.get("user_id"))
album = Album(uuid=uuid)
users = album.get_users()
if user.id not in [u["id"] for u in users]:
if user.id not in users:
flash(_("You are not a member of this album"))
return redirect(request.referrer)
@ -204,7 +235,7 @@ def delete_album(uuid):
users = album.get_users()
if len(users) > 1:
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 = _("You don't own this album.")
if user.access_level == 1:

View File

@ -2,6 +2,10 @@
Classe Album
"""
import os
import io
import zipfile
from werkzeug.utils import secure_filename
from ..db import get_db
from ..utils import new_uuid
@ -47,11 +51,11 @@ class Album():
def get_users(self, force_reload=False):
"""
Renvoie les utilisateurs liés à l'album
Renvoie les data["id"] des utilisateurs liés à l'album
"""
if self.users is None or force_reload:
db = get_db()
self.users = db.execute(
data = db.execute(
"""
SELECT * FROM user
JOIN contient_user ON user_id = user.id
@ -60,6 +64,7 @@ class Album():
""",
(self.uuid,)
).fetchall()
self.users = [i["id"] for i in data]
return self.users
def get_partitions(self):
@ -166,6 +171,23 @@ class Album():
db.commit()
def to_zip(self, instance_path):
data = io.BytesIO()
with zipfile.ZipFile(data, mode="w") as z:
for partition in self.get_partitions():
z.write(os.path.join(
instance_path,
"partitions",
f"{partition['uuid']}.pdf"
), arcname=secure_filename(partition['name']+".pdf")
)
# Spooling back to the beginning of the buffer
data.seek(0)
return data
def create(name: str) -> str:
"""Créer un nouvel album"""
db = get_db()

View File

@ -1,3 +1,12 @@
"""
Classe Groupe
"""
import io
import os
import zipfile
from werkzeug.utils import secure_filename
from ..db import get_db
from .album import Album
@ -66,21 +75,23 @@ class Groupe():
album.delete(instance_path)
def get_users(self):
def get_users(self, force_reload=False):
"""
Renvoie les data["id"] des utilisateurs liés au groupe
TODO: uniformiser le tout
"""
db = get_db()
return db.execute(
"""
SELECT * FROM user
JOIN groupe_contient_user ON user_id = user.id
JOIN groupe ON groupe.id = groupe_id
WHERE groupe.id = ?
""",
(self.id,)
).fetchall()
if self.users is None or force_reload:
db = get_db()
data = db.execute(
"""
SELECT * FROM user
JOIN groupe_contient_user ON user_id = user.id
JOIN groupe ON groupe.id = groupe_id
WHERE groupe.id = ?
""",
(self.id,)
).fetchall()
self.users = [i["id"] for i in data]
return self.users
def get_albums(self, force_reload=False):
"""
@ -116,3 +127,36 @@ class Groupe():
(self.id,)
).fetchall()
return [i["id"] for i in data]
def set_admin(self, user_id, value):
"""
Rend un utilisateur administrateur du groupe
"""
db = get_db()
data = db.execute(
"""
UPDATE groupe_contient_user
SET is_admin=?
WHERE user_id=? AND groupe_id=?
""",
(value, user_id, self.id)
)
db.commit()
def to_zip(self, instance_path):
data = io.BytesIO()
with zipfile.ZipFile(data, mode="w") as z:
for album in self.get_albums():
for partition in album.get_partitions():
z.write(os.path.join(
instance_path,
"partitions",
f"{partition['uuid']}.pdf"
), arcname=secure_filename(album.name)+"/"
+secure_filename(partition['name']+".pdf")
)
# Spooling back to the beginning of the buffer
data.seek(0)
return data

View File

@ -75,6 +75,16 @@ class Partition():
)
db.commit()
def update_file(self, file, instance_path):
partition_path = os.path.join(
instance_path,
"partitions",
f"{self.uuid}.pdf"
)
file.save(partition_path)
if os.path.exists(f"{instance_path}/cache/thumbnails/{self.uuid}.jpg"):
os.remove(f"{instance_path}/cache/thumbnails/{self.uuid}.jpg")
def get_user(self):
db = get_db()
user = db.execute(

View File

@ -33,6 +33,7 @@ class User():
self.albums = None
self.groupes = None
self.partitions = None
self.accessible_partitions = None
self.max_queries = 0
db = get_db()
@ -169,6 +170,44 @@ class User():
).fetchall()
return self.partitions
def get_accessible_partitions(self, force_reload=False):
if self.accessible_partitions is None or force_reload:
db = get_db()
if self.access_level == 1:
self.accessible_partitions = db.execute(
"""
SELECT * FROM partition
"""
).fetchall()
else:
self.accessible_partitions = db.execute(
"""
SELECT DISTINCT partition.uuid, partition.name,
partition.author, partition.body,
partition.user_id, partition.source
FROM partition
JOIN album
JOIN contient_partition
ON album.id=album_id
AND partition.uuid=partition_uuid
WHERE album.id IN (
SELECT album.id FROM album
JOIN contient_user
ON contient_user.user_id=?
AND album_id=album.id
UNION
SELECT DISTINCT album.id FROM album
JOIN groupe_contient_user
JOIN groupe_contient_album
ON groupe_contient_user.user_id=?
AND groupe_contient_album.album_id=album.id
AND groupe_contient_user.groupe_id=groupe_contient_album.groupe_id
)
""",
(self.id, self.id,),
).fetchall()
return self.accessible_partitions
def join_album(self, album_uuid):
db = get_db()
album = Album(uuid=album_uuid)

View File

@ -3,7 +3,8 @@
Groupe module
"""
from flask import (Blueprint, abort, flash, redirect, render_template,
request, session, current_app)
request, session, current_app, send_file, g, url_for)
from werkzeug.utils import secure_filename
from flask_babel import _
from .auth import login_required
@ -34,7 +35,7 @@ def get_groupe(uuid):
except LookupError:
return abort(404)
groupe.users = [User(user_id=i["id"]) for i in groupe.get_users()]
groupe.users = [User(user_id=u_id) for u_id in groupe.get_users()]
groupe.get_albums()
user = User(user_id=session.get("user_id"))
@ -130,7 +131,7 @@ def quit_groupe(uuid):
user = User(user_id=session.get("user_id"))
groupe = Groupe(uuid=uuid)
users = groupe.get_users()
if user.id not in [u["id"] for u in users]:
if user.id not in users:
flash(_("You are not a member of this group."))
return redirect(f"/groupe/{uuid}")
@ -139,6 +140,11 @@ def quit_groupe(uuid):
return redirect(f"/groupe/{uuid}#delete")
user.quit_groupe(groupe.uuid)
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")
@ -150,8 +156,7 @@ def delete_groupe(uuid):
user = User(user_id=session.get("user_id"))
error = None
users = groupe.get_users()
if len(users) > 1:
if len(groupe.get_users()) > 1:
error = _("You are not alone in this group.")
if user.access_level == 1 or user.id not in groupe.get_admins():
@ -184,7 +189,7 @@ def create_album_req(groupe_uuid):
if not name or name.strip() == "":
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 = _("You are not admin of this group.")
if error is None:
@ -240,7 +245,7 @@ def get_album(groupe_uuid, album_uuid):
user = User(user_id=session.get("user_id"))
# List of users without duplicate
users_id = list({i["id"] for i in album.get_users()+groupe.get_users()})
users_id = list(set(album.get_users()+groupe.get_users()))
album.users = [User(user_id=id) for id in users_id]
partitions = album.get_partitions()
@ -261,6 +266,31 @@ def get_album(groupe_uuid, album_uuid):
)
@bp.route("/<groupe_uuid>/zip")
def zip_download(groupe_uuid):
"""
Télécharger un groupe comme fichier zip
"""
if g.user is None and current_app.config["ZIP_REQUIRE_LOGIN"]:
flash(_("You need to login to access this resource."))
return redirect(url_for("auth.login"))
try:
groupe = Groupe(uuid=groupe_uuid)
except LookupError:
try:
groupe = Groupe(uuid=utils.format_uuid(groupe_uuid))
return redirect(f"/groupe/{utils.format_uuid(groupe_uuid)}/zip")
except LookupError:
return abort(404)
return send_file(
groupe.to_zip(current_app.instance_path),
download_name=secure_filename(f"{groupe.name}.zip")
)
@bp.route("/<groupe_uuid>/<album_uuid>/qr")
def groupe_qr_code(groupe_uuid, album_uuid):
return utils.get_qrcode(f"/groupe/{groupe_uuid}/{album_uuid}")

View File

@ -3,6 +3,7 @@
Partition module
"""
import os
import pypdf
from uuid import uuid4
from flask import (Blueprint, abort, send_file, render_template,
request, redirect, flash, session, current_app)
@ -160,6 +161,17 @@ def edit(uuid):
flash(error)
return redirect(f"/partition/{ uuid }/edit")
if request.files.get('file', None):
new_file = request.files["file"]
try:
pypdf.PdfReader(new_file)
new_file.seek(0)
except (pypdf.errors.PdfReadError, pypdf.errors.PdfStreamError):
flash(_("Invalid PDF file"))
return redirect(request.referrer)
partition.update_file(new_file, current_app.instance_path)
partition.update(
name=request.form["name"],
author=request.form["author"],

View File

@ -10,6 +10,7 @@ import os
import pypdf
import googlesearch
from unidecode import unidecode
from .db import get_db
@ -20,20 +21,20 @@ def local_search(query, partitions):
"""
Renvoie les 5 résultats les plus pertinents parmi une liste donnée
"""
query_words = [word.lower() for word in query.split(" ")]
query_words = [word.lower() for word in unidecode(query).split()]
def score_attribution(partition):
score = 0
for word in query_words:
if word != "":
if word in partition["name"].lower():
if word in unidecode(partition["name"]).lower():
score += 6
elif word in partition["author"].lower():
elif word in unidecode(partition["author"]).lower():
score += 4
elif word in partition["body"].lower():
elif word in unidecode(partition["body"]).lower():
score += 2
else:
score -= 1
for word in partition["name"].split(" "):
score -= 6
for word in unidecode(partition["name"]).split():
if word != "" and word.lower() not in query_words:
score -= 1
return score

View File

@ -27,7 +27,8 @@ def index():
return render_template(
"settings/index.html",
inspected_user=user,
user=user
user=user,
deletion_allowed=not current_app.config["DISABLE_ACCOUNT_DELETION"]
)
@ -48,6 +49,10 @@ def delete_account():
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]

View File

@ -718,3 +718,56 @@ midi-player {
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;
}

View File

@ -35,8 +35,8 @@
{% endif %}
{{ album.name }}
</h2>
{% if g.user %}
<div id="header-actions">
<div id="header-actions">
{% if g.user %}
<section id="users">
{% for album_user in album.users %}
<div class="user-profile-picture" style="background-color:{{ album_user.color }};" title="{{ album_user.username }}">
@ -44,25 +44,28 @@
</div>
{% endfor %}
</section>
<div class="dropdown dp1">
+
<div class="dropdown-content dp1">
{% if g.user %}
<a href="#add-partition">{{ _("Add a score") }}</a>
{% endif %}
{% if not_participant %}
<a href="/albums/{{ album.uuid }}/join">{{ _("Join") }}</a>
{% elif album.users | length > 1 %}
<a href="/albums/{{ album.uuid }}/quit">{{ _("Quit") }}</a>
{% endif %}
<a href="#share">{{ _("Share") }}</a>
{% if g.user.access_level == 1 or (not not_participant and album.users | length == 1) %}
<a id="delete-album" href="#delete">{{ _("Delete") }}</a>
{% endif %}
</div>
{% endif %}
<div class="dropdown dp1">
+
<div class="dropdown-content dp1">
{% if g.user %}
<a href="#add-partition">{{ _("Add a score") }}</a>
{% endif %}
{% if not_participant %}
<a href="/albums/{{ album.uuid }}/join">{{ _("Join") }}</a>
{% elif g.user and not not_participant %}
<a href="/albums/{{ album.uuid }}/quit">{{ _("Quit") }}</a>
{% endif %}
<a href="#share">{{ _("Share") }}</a>
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
<a href="/albums/{{ album.uuid }}/zip">{{ _("Download as zip") }}</a>
{% endif %}
{% if g.user.access_level == 1 or (g.user and not not_participant and album.users | length == 1) %}
<a id="delete-album" href="#delete">{{ _("Delete") }}</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
</header>
<hr/>
{% if partitions|length != 0 %}

View File

@ -7,7 +7,11 @@
{% if partition_uuid %}
<input name="partition-uuid" value="{{ partition_uuid }}" type="hidden">
{% else %}
<input name="file" type="file" accept=".pdf" required=""><br/>
{% block input_file %}
{% set required=true %}
{% set filetype=".pdf" %}
{% include 'components/input_file.html' %}
{% endblock %}
{% endif %}
<input type="submit" value="{{ _('Add') }}" />
</form>

View 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>

View File

@ -30,8 +30,8 @@
{% block content %}
<header id="album-header">
<h2 id="groupe-title">{{ groupe.name }}</h2>
{% if g.user %}
<div id="header-actions">
<div id="header-actions">
{% if g.user %}
<section id="users">
{% for groupe_user in groupe.users %}
<div class="user-profile-picture" style="background-color:{{ groupe_user.color }};" title="{{ groupe_user.username }}">
@ -39,23 +39,26 @@
</div>
{% endfor %}
</section>
<div class="dropdown dp1">
+
<div class="dropdown-content dp1">
{% if not_participant %}
<a href="/groupe/{{ groupe.uuid }}/join">{{ _("Join") }}</a>
{% elif groupe.users | length > 1 %}
<a href="/groupe/{{ groupe.uuid }}/quit">{{ _("Quit") }}</a>
{% endif %}
<a href="#share">{{ _("Share") }}</a>
{% if g.user.access_level == 1 or 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>
{% endif %}
<div class="dropdown dp1">
+
<div class="dropdown-content dp1">
{% if not_participant %}
<a href="/groupe/{{ groupe.uuid }}/join">{{ _("Join") }}</a>
{% elif g.user and not not_participant %}
<a href="/groupe/{{ groupe.uuid }}/quit">{{ _("Quit") }}</a>
{% endif %}
<a href="#share">{{ _("Share") }}</a>
{% if g.user or not config["ZIP_REQUIRE_LOGIN"] %}
<a href="/groupe/{{ groupe.uuid }}/zip">{{ _("Download as zip") }}</a>
{% endif %}
{% if g.user.access_level == 1 or (g.user and user.id in groupe.get_admins()) %}
<a href="#create-groupe-album">{{ _("Add an album") }}</a>
<a id="delete-album" href="#delete">{{ _("Delete") }}</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
</header>
<hr/>
{% if groupe.albums|length != 0 %}

View File

@ -9,7 +9,11 @@
<h2>{{ _("Add an attachment to %(name)s", name=partition.name) }}</h2>
<form action="/partition/{{ partition.uuid }}/add-attachment" method="post" enctype="multipart/form-data">
<input type="text" name="name" id="name" placeholder="{{ _('Name') }}"><br/>
<input name="file" type="file" accept=".mp3,.mid" required=""><br/>
{% block input_file %}
{% set required=true %}
{% set filetype=".mp3,.mid" %}
{% include 'components/input_file.html' %}
{% endblock %}
<input type="submit" value="{{ _('Add') }}">
</form>
<a href="#!" class="close-dialog">Close</a>
@ -55,7 +59,7 @@
{% endif %}
<br/>
{% if user %}
{% if g.user %}
<div class="centered">
<a href="#create-attachment"><button>{{ _("Add an attachment") }}</button></a>
</div>

View File

@ -52,7 +52,13 @@
<td>{{ _("File") }}</td>
<td><a href="/partition/{{ partition.uuid }}">
<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>
<td>{{ _("Title") }}</td>

View File

@ -13,7 +13,13 @@
<td>{{ _("File") }}</td>
<td><a href="/partition/{{ partition.uuid }}">
<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>
{% if partition.source != "unknown" and partition.source != "upload" %}
<tr>

View File

@ -35,8 +35,9 @@
<input type="hidden" id="user_id" name="user_id" value="{{ inspected_user.id }}">
<input type="Submit" value="{{ _('confirm') }}">
</form>
<h3>{{ _("Delete account") }}</h3>
<a href="#delete-account"><button class="red-confirm">{{ _("Delete account") }}</button></a>
{% if deletion_allowed %}
<h3>{{ _("Delete account") }}</h3>
<a href="#delete-account"><button class="red-confirm">{{ _("Delete account") }}</button></a>
{% endif %}
{% endblock %}

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-02-25 15:18+0100\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"
@ -28,96 +28,101 @@ msgstr "Created user %(username)s"
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:41
#: partitioncloud/modules/albums.py:43
msgid "Missing search query"
msgstr "Missing search query"
#: partitioncloud/modules/albums.py:123 partitioncloud/modules/groupe.py:71
#: partitioncloud/modules/groupe.py:185
#: 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:160
#: partitioncloud/modules/albums.py:192
msgid "This album does not exist."
msgstr "This album does not exist."
#: partitioncloud/modules/albums.py:163
#: partitioncloud/modules/albums.py:195
msgid "Album added to collection."
msgstr "Album added to collection."
#: partitioncloud/modules/albums.py:177 partitioncloud/modules/albums.py:240
#: partitioncloud/modules/albums.py:346
#: 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:181
#: 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:185
#: partitioncloud/modules/albums.py:217
msgid "Album quitted."
msgstr "Album quitted."
#: partitioncloud/modules/albums.py:204
#: 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:206
#: partitioncloud/modules/albums.py:238
msgid "You don't own this album."
msgstr "You don't own this album."
#: partitioncloud/modules/albums.py:217
#: partitioncloud/modules/albums.py:249
msgid "Album deleted."
msgstr "Album deleted."
#: partitioncloud/modules/albums.py:246 partitioncloud/modules/partition.py:153
#: partitioncloud/modules/partition.py:199
#: 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:248 partitioncloud/modules/partition.py:63
#: partitioncloud/modules/albums.py:280 partitioncloud/modules/partition.py:64
msgid "Missing file"
msgstr "Missing file"
#: partitioncloud/modules/albums.py:260
#: partitioncloud/modules/albums.py:292
msgid "Search results expired"
msgstr "Search results expired"
#: partitioncloud/modules/albums.py:326
#: 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:340
#: partitioncloud/modules/albums.py:378
msgid "Selecting an album is mandatory."
msgstr "Selecting an album is mandatory."
#: partitioncloud/modules/albums.py:342
#: partitioncloud/modules/albums.py:380
msgid "Selecting a score is mandatory."
msgstr "Selecting a score is mandatory."
#: partitioncloud/modules/albums.py:344
#: partitioncloud/modules/albums.py:382
msgid "Please specify a score type."
msgstr "Please specify a score type."
#: partitioncloud/modules/albums.py:366
#: partitioncloud/modules/albums.py:404
msgid "Score added"
msgstr "Score added"
#: partitioncloud/modules/albums.py:368
#: partitioncloud/modules/albums.py:406
msgid "Score is already in the album."
msgstr "Score is already in the album."
#: partitioncloud/modules/albums.py:380
#: partitioncloud/modules/albums.py:418
msgid "Unknown score type."
msgstr "Unknown score type."
#: partitioncloud/modules/auth.py:27 partitioncloud/modules/auth.py:54
msgid "You need to login to access this resource."
msgstr "You need to login to access this resource."
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:46
#: partitioncloud/modules/settings.py:69
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:50
#: partitioncloud/modules/settings.py:82
msgid "Missing rights."
msgstr "Missing rights."
@ -125,7 +130,7 @@ msgstr "Missing rights."
msgid "Missing username."
msgstr "Missing username."
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:81
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:96
msgid "Missing password."
msgstr "Missing password."
@ -146,99 +151,103 @@ msgstr "Successfully created new user. You can log in."
msgid "Incorrect username or password"
msgstr "Incorrect username or password"
#: partitioncloud/modules/groupe.py:120
#: partitioncloud/modules/groupe.py:121
msgid "Unknown group."
msgstr "Unknown group."
#: partitioncloud/modules/groupe.py:123
#: partitioncloud/modules/groupe.py:124
msgid "Group added to collection."
msgstr "Group added to collection."
#: partitioncloud/modules/groupe.py:134
#: 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:138
#: 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:142
#: partitioncloud/modules/groupe.py:143
msgid "Group quitted."
msgstr "Group quitted."
#: partitioncloud/modules/groupe.py:155
#: 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:166
#: partitioncloud/modules/groupe.py:167
msgid "Group deleted."
msgstr "Group deleted."
#: partitioncloud/modules/groupe.py:188
#: 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:58
#: partitioncloud/modules/partition.py:59
msgid "You don't own this score."
msgstr "You don't own this score."
#: partitioncloud/modules/partition.py:71
#: partitioncloud/modules/partition.py:72
msgid "Missing filename."
msgstr "Missing filename."
#: partitioncloud/modules/partition.py:76
#: partitioncloud/modules/partition.py:77
msgid "Unsupported file type."
msgstr "Unsupported file type."
#: partitioncloud/modules/partition.py:144
#: 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:155
#: partitioncloud/modules/partition.py:201
#: 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:157
#: partitioncloud/modules/partition.py:203
#: 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:169
#: partitioncloud/modules/partition.py:215
#: 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:230
#: 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:238
#: partitioncloud/modules/partition.py:250
msgid "Score deleted."
msgstr "Score deleted."
#: partitioncloud/modules/settings.py:37 partitioncloud/modules/settings.py:60
#: partitioncloud/modules/settings.py:40 partitioncloud/modules/settings.py:72
msgid "Missing user id."
msgstr "Missing user id."
#: partitioncloud/modules/settings.py:50
#: 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:73
#: partitioncloud/modules/settings.py:86
msgid "Missing old password."
msgstr "Missing old password."
#: partitioncloud/modules/settings.py:77
#: partitioncloud/modules/settings.py:90
msgid "Incorrect password."
msgstr "Incorrect password."
#: partitioncloud/modules/settings.py:85
#: partitioncloud/modules/settings.py:100
msgid "Password and its confirmation differ."
msgstr "Password and its confirmation differ."
#: partitioncloud/modules/settings.py:89
#: partitioncloud/modules/settings.py:104
msgid "Successfully updated password."
msgstr "Successfully updated password."
@ -379,7 +388,7 @@ msgid "Scores list"
msgstr "Scores list"
#: partitioncloud/templates/admin/partitions.html:31
#: partitioncloud/templates/albums/album.html:94
#: partitioncloud/templates/albums/album.html:97
msgid "No available scores"
msgstr "No available scores"
@ -397,35 +406,41 @@ 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:60
#: partitioncloud/templates/albums/album.html:64
#: partitioncloud/templates/albums/delete-album.html:8
#: partitioncloud/templates/groupe/index.html:20
#: partitioncloud/templates/groupe/index.html:53
#: partitioncloud/templates/groupe/index.html:57
#: partitioncloud/templates/partition/delete.html:10
#: partitioncloud/templates/partition/details.html:86
#: partitioncloud/templates/partition/edit.html:57
#: 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:51
#: partitioncloud/templates/albums/album.html:52
msgid "Add a score"
msgstr "Add a score"
#: partitioncloud/templates/albums/album.html:54
#: partitioncloud/templates/groupe/index.html:46
#: partitioncloud/templates/albums/album.html:55
#: partitioncloud/templates/groupe/index.html:47
msgid "Join"
msgstr "Join"
#: partitioncloud/templates/albums/album.html:56
#: partitioncloud/templates/groupe/index.html:48
#: partitioncloud/templates/albums/album.html:57
#: partitioncloud/templates/groupe/index.html:49
msgid "Quit"
msgstr "Quit"
#: partitioncloud/templates/albums/album.html:58
#: partitioncloud/templates/groupe/index.html:50
#: 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
@ -511,12 +526,20 @@ msgstr "author"
msgid "lyrics"
msgstr "lyrics"
#: partitioncloud/templates/components/add_partition.html:12
#: partitioncloud/templates/components/add_partition.html:16
#: partitioncloud/templates/groupe/index.html:11
#: partitioncloud/templates/partition/attachments.html:13
#: 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"
@ -530,15 +553,15 @@ msgstr "Delete group"
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:52
#: partitioncloud/templates/groupe/index.html:56
msgid "Add an album"
msgstr "Add an album"
#: partitioncloud/templates/groupe/index.html:74
#: partitioncloud/templates/groupe/index.html:77
msgid "Create one"
msgstr "Create one"
#: partitioncloud/templates/groupe/index.html:77
#: partitioncloud/templates/groupe/index.html:80
#, python-format
msgid "No available album. %(create)s"
msgstr "No available album. %(create)s"
@ -553,7 +576,7 @@ msgstr "Attachments of %(name)s"
msgid "Add an attachment to %(name)s"
msgstr "Add an attachment to %(name)s"
#: partitioncloud/templates/partition/attachments.html:22
#: partitioncloud/templates/partition/attachments.html:26
msgid ""
"No pdf viewer available in this browser.\n"
" You can use Firefox on Android."
@ -561,11 +584,11 @@ msgstr ""
"No pdf viewer available in this browser.\n"
" You can use Firefox on Android."
#: partitioncloud/templates/partition/attachments.html:46
#: 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:60
#: partitioncloud/templates/partition/attachments.html:64
msgid "Add an attachment"
msgstr "Add an attachment"
@ -595,45 +618,45 @@ msgstr "Type"
msgid "File"
msgstr "File"
#: partitioncloud/templates/partition/details.html:58
#: partitioncloud/templates/partition/details.html:59
#: partitioncloud/templates/partition/edit.html:29
#: partitioncloud/templates/partition/edit.html:30
#: 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:62
#: partitioncloud/templates/partition/details.html:63
#: partitioncloud/templates/partition/edit.html:33
#: partitioncloud/templates/partition/edit.html:34
#: 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:66
#: partitioncloud/templates/partition/details.html:67
#: partitioncloud/templates/partition/edit.html:37
#: partitioncloud/templates/partition/edit.html:38
#: 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:70
#: partitioncloud/templates/partition/edit.html:41
#: partitioncloud/templates/partition/details.html:76
#: partitioncloud/templates/partition/edit.html:47
msgid "Attachments"
msgstr "Attachments"
#: partitioncloud/templates/partition/details.html:75
#: partitioncloud/templates/partition/edit.html:46
#: 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:77
#: partitioncloud/templates/partition/edit.html:48
#: partitioncloud/templates/partition/details.html:83
#: partitioncloud/templates/partition/edit.html:54
msgid "Add one"
msgstr "Add one"
#: partitioncloud/templates/partition/details.html:83
#: partitioncloud/templates/partition/edit.html:54
#: partitioncloud/templates/partition/details.html:89
#: partitioncloud/templates/partition/edit.html:60
msgid "Update"
msgstr "Update"
@ -642,7 +665,51 @@ msgstr "Update"
msgid "Modify \"%(name)s\""
msgstr "Modify \"%(name)s\""
#: partitioncloud/templates/partition/edit.html:21
#: 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"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-02-25 15:18+0100\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"
@ -28,96 +28,101 @@ msgstr "Utilisateur %(username)s créé"
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:41
#: partitioncloud/modules/albums.py:43
msgid "Missing search query"
msgstr "Aucun terme de recherche spécifié."
#: partitioncloud/modules/albums.py:123 partitioncloud/modules/groupe.py:71
#: partitioncloud/modules/groupe.py:185
#: 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:160
#: partitioncloud/modules/albums.py:192
msgid "This album does not exist."
msgstr "Cet album n'existe pas."
#: partitioncloud/modules/albums.py:163
#: partitioncloud/modules/albums.py:195
msgid "Album added to collection."
msgstr "Album ajouté à la collection."
#: partitioncloud/modules/albums.py:177 partitioncloud/modules/albums.py:240
#: partitioncloud/modules/albums.py:346
#: 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:181
#: 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:185
#: partitioncloud/modules/albums.py:217
msgid "Album quitted."
msgstr "Album quitté."
#: partitioncloud/modules/albums.py:204
#: 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:206
#: partitioncloud/modules/albums.py:238
msgid "You don't own this album."
msgstr "Vous ne possédez pas cet album."
#: partitioncloud/modules/albums.py:217
#: partitioncloud/modules/albums.py:249
msgid "Album deleted."
msgstr "Album supprimé."
#: partitioncloud/modules/albums.py:246 partitioncloud/modules/partition.py:153
#: partitioncloud/modules/partition.py:199
#: 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:248 partitioncloud/modules/partition.py:63
#: partitioncloud/modules/albums.py:280 partitioncloud/modules/partition.py:64
msgid "Missing file"
msgstr "Aucun fichier n'a été fourni."
#: partitioncloud/modules/albums.py:260
#: partitioncloud/modules/albums.py:292
msgid "Search results expired"
msgstr "Les résultats de la recherche ont expiré."
#: partitioncloud/modules/albums.py:326
#: 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:340
#: 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:342
#: 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:344
#: 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:366
#: partitioncloud/modules/albums.py:404
msgid "Score added"
msgstr "Partition ajoutée."
#: partitioncloud/modules/albums.py:368
#: partitioncloud/modules/albums.py:406
msgid "Score is already in the album."
msgstr "Partition déjà dans l'album."
#: partitioncloud/modules/albums.py:380
#: partitioncloud/modules/albums.py:418
msgid "Unknown score type."
msgstr "Type de partition inconnu."
#: partitioncloud/modules/auth.py:27 partitioncloud/modules/auth.py:54
msgid "You need to login to access this resource."
msgstr "Vous devez être connecté pour accéder à cette page."
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:46
#: partitioncloud/modules/settings.py:69
#: partitioncloud/modules/auth.py:59 partitioncloud/modules/settings.py:50
#: partitioncloud/modules/settings.py:82
msgid "Missing rights."
msgstr "Droits insuffisants."
@ -125,7 +130,7 @@ msgstr "Droits insuffisants."
msgid "Missing username."
msgstr "Un nom d'utilisateur est requis."
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:81
#: partitioncloud/modules/auth.py:87 partitioncloud/modules/settings.py:96
msgid "Missing password."
msgstr "Un mot de passe est requis."
@ -148,99 +153,103 @@ msgstr "Utilisateur créé avec succès. Vous pouvez vous connecter."
msgid "Incorrect username or password"
msgstr "Nom d'utilisateur ou mot de passe incorrect."
#: partitioncloud/modules/groupe.py:120
#: partitioncloud/modules/groupe.py:121
msgid "Unknown group."
msgstr "Ce groupe n'existe pas."
#: partitioncloud/modules/groupe.py:123
#: partitioncloud/modules/groupe.py:124
msgid "Group added to collection."
msgstr "Groupe ajouté à la collection."
#: partitioncloud/modules/groupe.py:134
#: 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:138
#: 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:142
#: partitioncloud/modules/groupe.py:143
msgid "Group quitted."
msgstr "Groupe quitté."
#: partitioncloud/modules/groupe.py:155
#: 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:166
#: partitioncloud/modules/groupe.py:167
msgid "Group deleted."
msgstr "Groupe supprimé."
#: partitioncloud/modules/groupe.py:188
#: 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:58
#: partitioncloud/modules/partition.py:59
msgid "You don't own this score."
msgstr "Cette partition ne vous appartient pas"
#: partitioncloud/modules/partition.py:71
#: partitioncloud/modules/partition.py:72
msgid "Missing filename."
msgstr "Pas de nom de fichier"
#: partitioncloud/modules/partition.py:76
#: partitioncloud/modules/partition.py:77
msgid "Unsupported file type."
msgstr "Extension de fichier non supportée"
#: partitioncloud/modules/partition.py:144
#: 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:155
#: partitioncloud/modules/partition.py:201
#: 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:157
#: partitioncloud/modules/partition.py:203
#: 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:169
#: partitioncloud/modules/partition.py:215
#: 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:230
#: 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:238
#: partitioncloud/modules/partition.py:250
msgid "Score deleted."
msgstr "Partition supprimée."
#: partitioncloud/modules/settings.py:37 partitioncloud/modules/settings.py:60
#: partitioncloud/modules/settings.py:40 partitioncloud/modules/settings.py:72
msgid "Missing user id."
msgstr "Identifiant d'utilisateur manquant."
#: partitioncloud/modules/settings.py:50
msgid "User successfully deleted."
msgstr "Utilisateur supprimée."
#: 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:73
#: 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:77
#: partitioncloud/modules/settings.py:90
msgid "Incorrect password."
msgstr "Mot de passe incorrect."
#: partitioncloud/modules/settings.py:85
#: 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:89
#: partitioncloud/modules/settings.py:104
msgid "Successfully updated password."
msgstr "Mot de passe mis à jour."
@ -385,7 +394,7 @@ msgid "Scores list"
msgstr "Liste des partitions"
#: partitioncloud/templates/admin/partitions.html:31
#: partitioncloud/templates/albums/album.html:94
#: partitioncloud/templates/albums/album.html:97
msgid "No available scores"
msgstr "Aucune partition disponible"
@ -403,35 +412,41 @@ 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:60
#: partitioncloud/templates/albums/album.html:64
#: partitioncloud/templates/albums/delete-album.html:8
#: partitioncloud/templates/groupe/index.html:20
#: partitioncloud/templates/groupe/index.html:53
#: partitioncloud/templates/groupe/index.html:57
#: partitioncloud/templates/partition/delete.html:10
#: partitioncloud/templates/partition/details.html:86
#: partitioncloud/templates/partition/edit.html:57
#: 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:51
#: partitioncloud/templates/albums/album.html:52
msgid "Add a score"
msgstr "Ajouter une partition"
#: partitioncloud/templates/albums/album.html:54
#: partitioncloud/templates/groupe/index.html:46
#: partitioncloud/templates/albums/album.html:55
#: partitioncloud/templates/groupe/index.html:47
msgid "Join"
msgstr "Rejoindre"
#: partitioncloud/templates/albums/album.html:56
#: partitioncloud/templates/groupe/index.html:48
#: partitioncloud/templates/albums/album.html:57
#: partitioncloud/templates/groupe/index.html:49
msgid "Quit"
msgstr "Quitter"
#: partitioncloud/templates/albums/album.html:58
#: partitioncloud/templates/groupe/index.html:50
#: 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
@ -517,12 +532,20 @@ msgstr "auteur"
msgid "lyrics"
msgstr "paroles"
#: partitioncloud/templates/components/add_partition.html:12
#: partitioncloud/templates/components/add_partition.html:16
#: partitioncloud/templates/groupe/index.html:11
#: partitioncloud/templates/partition/attachments.html:13
#: 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"
@ -539,15 +562,15 @@ msgstr ""
" sous-jacents et leurs partitions si personne ne les a rejoints "
"(indépendamment du groupe)."
#: partitioncloud/templates/groupe/index.html:52
#: partitioncloud/templates/groupe/index.html:56
msgid "Add an album"
msgstr "Ajouter un album"
#: partitioncloud/templates/groupe/index.html:74
#: partitioncloud/templates/groupe/index.html:77
msgid "Create one"
msgstr "En créer un"
#: partitioncloud/templates/groupe/index.html:77
#: partitioncloud/templates/groupe/index.html:80
#, python-format
msgid "No available album. %(create)s"
msgstr "Aucun album disponible. %(create)s"
@ -562,7 +585,7 @@ msgstr "Attachments de %(name)s"
msgid "Add an attachment to %(name)s"
msgstr "Ajouter un attachment à %(name)s"
#: partitioncloud/templates/partition/attachments.html:22
#: partitioncloud/templates/partition/attachments.html:26
msgid ""
"No pdf viewer available in this browser.\n"
" You can use Firefox on Android."
@ -570,11 +593,11 @@ msgstr ""
"Impossible d'afficher le pdf dans ce navigateur.\n"
" Il est conseillé d'utiliser Firefox sur Android."
#: partitioncloud/templates/partition/attachments.html:46
#: 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:60
#: partitioncloud/templates/partition/attachments.html:64
msgid "Add an attachment"
msgstr "Ajouter un attachment"
@ -604,45 +627,45 @@ msgstr "Type d'ajout"
msgid "File"
msgstr "Fichier"
#: partitioncloud/templates/partition/details.html:58
#: partitioncloud/templates/partition/details.html:59
#: partitioncloud/templates/partition/edit.html:29
#: partitioncloud/templates/partition/edit.html:30
#: 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:62
#: partitioncloud/templates/partition/details.html:63
#: partitioncloud/templates/partition/edit.html:33
#: partitioncloud/templates/partition/edit.html:34
#: 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:66
#: partitioncloud/templates/partition/details.html:67
#: partitioncloud/templates/partition/edit.html:37
#: partitioncloud/templates/partition/edit.html:38
#: 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:70
#: partitioncloud/templates/partition/edit.html:41
#: partitioncloud/templates/partition/details.html:76
#: partitioncloud/templates/partition/edit.html:47
msgid "Attachments"
msgstr "Pièces jointes"
#: partitioncloud/templates/partition/details.html:75
#: partitioncloud/templates/partition/edit.html:46
#: 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:77
#: partitioncloud/templates/partition/edit.html:48
#: partitioncloud/templates/partition/details.html:83
#: partitioncloud/templates/partition/edit.html:54
msgid "Add one"
msgstr "En rajouter"
#: partitioncloud/templates/partition/details.html:83
#: partitioncloud/templates/partition/edit.html:54
#: partitioncloud/templates/partition/details.html:89
#: partitioncloud/templates/partition/edit.html:60
msgid "Update"
msgstr "Mettre à jour"
@ -651,7 +674,51 @@ msgstr "Mettre à jour"
msgid "Modify \"%(name)s\""
msgstr "Modifier \"%(name)s\""
#: partitioncloud/templates/partition/edit.html:21
#: 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"

View File

@ -4,3 +4,4 @@ google
colorama
pypdf
qrcode
unidecode

View File

@ -188,3 +188,10 @@ def install_babel():
def install_pypdf():
utils.install_package("pypdf")
"""
v1.10.*
"""
def install_unidecode():
utils.install_package("unidecode")

View File

@ -36,7 +36,8 @@ hooks = [
("v1.5.0", [("Move to instance directory", v1_hooks.move_instance)]),
("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.8.2", [("Install pypdf", v1_hooks.install_pypdf)]),
("v1.10.3", [("Install unidecode", v1_hooks.install_unidecode)]),
]
@ -201,11 +202,18 @@ if __name__ == "__main__":
"--restore",
help="restore from specific version backup, will not apply any hook (vx.y.z)",
)
parser.add_argument(
"-b",
"--backup",
help="backup current version, without running any hooks",
)
args = parser.parse_args()
config.instance = os.path.abspath(args.instance)
if args.restore is None:
migrate(args.current, args.target, skip_backup=args.skip_backup)
else:
if args.restore is not None:
restore(args.restore)
elif args.backup is not None:
backup_instance(args.backup, verbose=True)
else:
migrate(args.current, args.target, skip_backup=args.skip_backup)