Compare commits

..

1 Commits

Author SHA1 Message Date
Jonas Schäfer
a1ecb4ce80 Port to WTForms 3.x
Fixes #103.
2022-01-22 15:17:48 +01:00
20 changed files with 216 additions and 1802 deletions

View File

@@ -6,7 +6,8 @@ RUN set -eu; \
apt-get install -y --no-install-recommends \
python3 python3-pip python3-setuptools python3-wheel \
libpython3-dev \
make build-essential;
make build-essential \
netcat;
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt
@@ -16,8 +17,7 @@ COPY babel.cfg /opt/snikket-web-portal/babel.cfg
WORKDIR /opt/snikket-web-portal
RUN set -eu; \
pip3 install -r requirements.txt; \
RUN pip3 install -r requirements.txt; \
pip3 install -r build-requirements.txt; \
make;
@@ -33,22 +33,21 @@ ENV SNIKKET_WEB_PYENV=/etc/snikket-web-portal/env.py
ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
WORKDIR /opt/snikket-web-portal
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
RUN set -eu; \
export DEBIAN_FRONTEND=noninteractive ; \
apt-get update ; \
apt-get install -y --no-install-recommends \
python3 python3-pip python3-setuptools python3-wheel build-essential libpython3-dev netcat; \
pip3 install -r requirements.txt; \
apt-get remove -y --autoremove build-essential libpython3-dev; \
python3 python3-pip python3-setuptools python3-wheel; \
apt-get clean ; rm -rf /var/lib/apt/lists; \
pip3 install hypercorn; \
rm -rf /root/.cache;
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
WORKDIR /opt/snikket-web-portal
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
RUN pip3 install -r requirements.txt; rm -rf /root/.cache;
COPY --from=build /opt/snikket-web-portal/snikket_web/ /opt/snikket-web-portal/snikket_web
COPY babel.cfg /opt/snikket-web-portal/babel.cfg

View File

@@ -1,3 +1,4 @@
[python: snikket_web/**.py]
[jinja2: snikket_web/templates/**.html]
[jinja2: snikket_web/templates/**.j2]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View File

@@ -1,9 +1,9 @@
aiohttp~=3.6
quart~=0.17
quart~=0.11,<0.15
flask-wtf~=0.14
hsluv~=5.0
hsluv~=0.0.2
flask-babel~=1.0
email-validator~=1.1
environ-config~=20.0
wtforms~=2.3
wtforms~=3.0
typing-extensions

View File

@@ -18,8 +18,6 @@ from quart import (
jsonify,
)
import werkzeug.exceptions
import environ
from . import colour, infra
@@ -42,7 +40,7 @@ async def proc() -> typing.Dict[str, typing.Any]:
try:
user_info = await infra.client.get_user_info()
except (aiohttp.ClientError, werkzeug.exceptions.HTTPException):
except (aiohttp.ClientError, quart.exceptions.HTTPException):
user_info = {}
return {
@@ -107,16 +105,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response:
async def generic_http_error(
exc: werkzeug.exceptions.HTTPException,
exc: quart.exceptions.HTTPException,
) -> quart.Response:
return quart.Response(
await render_template(
"generic_http_error.html",
status=exc.code,
status=exc.status_code,
description=exc.description,
name=exc.name,
),
status=exc.code,
status=exc.status_code,
)
@@ -155,7 +153,6 @@ class AppConfig:
"it",
"pl",
"sv",
"zh_Hans_CN",
], converter=autosplit)
apple_store_url = environ.var(
"https://apps.apple.com/us/app/snikket/id1545164189",
@@ -202,19 +199,19 @@ def create_app() -> quart.Quart:
app.context_processor(proc)
app.register_error_handler(
aiohttp.ClientConnectorError,
backend_error_handler,
backend_error_handler, # type:ignore
)
app.register_error_handler(
werkzeug.exceptions.HTTPException,
quart.exceptions.HTTPException,
generic_http_error, # type:ignore
)
app.register_error_handler(
Exception,
generic_error_handler,
generic_error_handler, # type:ignore
)
@app.route("/")
async def index() -> werkzeug.Response:
async def index() -> quart.Response:
if infra.client.has_session:
return redirect(url_for('user.index'))

View File

@@ -7,12 +7,9 @@ from datetime import datetime
import aiohttp
import werkzeug.exceptions
import quart.flask_patch
import wtforms
import wtforms.fields.html5
from quart import (
Blueprint,
@@ -94,7 +91,7 @@ class EditUserForm(BaseForm):
@bp.route("/user/<localpart>/", methods=["GET", "POST"])
@client.require_admin_session()
async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]:
async def edit_user(localpart: str) -> typing.Union[quart.Response, str]:
target_user_info = await client.get_user_by_localpart(localpart)
form = EditUserForm()
@@ -149,7 +146,7 @@ class DeleteUserForm(BaseForm):
@bp.route("/user/<localpart>/delete", methods=["GET", "POST"])
@client.require_admin_session()
async def delete_user(localpart: str) -> typing.Union[str, werkzeug.Response]:
async def delete_user(localpart: str) -> typing.Union[str, quart.Response]:
target_user_info = await client.get_user_by_localpart(localpart)
form = DeleteUserForm()
if form.validate_on_submit():
@@ -188,7 +185,7 @@ async def debug_user(localpart: str) -> typing.Union[str, quart.Response]:
@client.require_admin_session()
async def user_password_reset_link(
id_: str,
) -> typing.Union[str, werkzeug.Response]:
) -> typing.Union[str, quart.Response]:
invite_info = await client.get_invite_by_id(
id_,
)
@@ -280,7 +277,7 @@ class InvitePost(BaseForm):
@bp.route("/invitations", methods=["GET", "POST"])
@client.require_admin_session()
async def invitations() -> typing.Union[str, werkzeug.Response]:
async def invitations() -> typing.Union[str, quart.Response]:
invites = sorted(
(
invite
@@ -326,7 +323,7 @@ class InviteForm(BaseForm):
@bp.route("/invitation/-/new", methods=["POST"])
@client.require_admin_session()
async def create_invite() -> typing.Union[str, werkzeug.Response]:
async def create_invite() -> typing.Union[str, quart.Response]:
form = InvitePost()
circles = await client.list_groups()
form.circles.choices = [
@@ -354,7 +351,7 @@ async def create_invite() -> typing.Union[str, werkzeug.Response]:
@bp.route("/invitation/<id_>", methods=["GET", "POST"])
@client.require_admin_session()
async def edit_invite(id_: str) -> typing.Union[str, werkzeug.Response]:
async def edit_invite(id_: str) -> typing.Union[str, quart.Response]:
try:
invite_info = await client.get_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
@@ -420,7 +417,7 @@ async def circles() -> str:
@bp.route("/circle/-/new", methods=["POST"])
@client.require_admin_session()
async def create_circle() -> typing.Union[str, werkzeug.Response]:
async def create_circle() -> typing.Union[str, quart.Response]:
create_form = CirclePost()
if create_form.validate_on_submit():
circle = await client.create_group(
@@ -466,7 +463,7 @@ class EditCircleForm(BaseForm):
@bp.route("/circle/<id_>", methods=["GET", "POST"])
@client.require_admin_session()
async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
async def edit_circle(id_: str) -> typing.Union[str, quart.Response]:
async with client.authenticated_session() as session:
try:
circle = await client.get_group_by_id(
@@ -628,7 +625,7 @@ class AnnouncementForm(BaseForm):
@bp.route("/system/", methods=["GET", "POST"])
@client.require_admin_session()
async def system() -> typing.Union[str, werkzeug.Response]:
async def system() -> typing.Union[str, quart.Response]:
form = AnnouncementForm()
if form.validate_on_submit():
@@ -659,7 +656,7 @@ async def system() -> typing.Union[str, werkzeug.Response]:
now = time.time()
try:
prosody_metrics = await client.get_system_metrics()
except werkzeug.exceptions.NotFound:
except quart.exceptions.NotFound:
# server does not offer the endpoint for whatever reason -- ignore
prosody_metrics = {}

View File

@@ -15,8 +15,6 @@ from quart import (
session as http_session,
)
import werkzeug
import wtforms
from flask_babel import lazy_gettext as _l, gettext
@@ -48,14 +46,14 @@ def apple_store_badge() -> str:
@bp.context_processor
def context() -> typing.Dict[str, typing.Any]:
def context() -> typing.Mapping[str, typing.Any]:
return {
"apple_store_badge": apple_store_badge,
}
@bp.route("/<id_>")
async def view_old(id_: str) -> werkzeug.Response:
async def view_old(id_: str) -> quart.Response:
return redirect(url_for(".view", id_=id_))
@@ -133,7 +131,7 @@ class RegisterForm(BaseForm):
@bp.route("/<id_>/register", methods=["GET", "POST"])
async def register(id_: str) -> typing.Union[str, werkzeug.Response]:
async def register(id_: str) -> typing.Union[str, quart.Response]:
try:
invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
@@ -201,7 +199,7 @@ class ResetForm(BaseForm):
@bp.route("/<id_>/reset", methods=["GET", "POST"])
async def reset(id_: str) -> typing.Union[str, werkzeug.Response]:
async def reset(id_: str) -> typing.Union[str, quart.Response]:
try:
invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
@@ -302,5 +300,5 @@ async def reset_success() -> str:
@bp.route("/-")
async def index() -> werkzeug.Response:
async def index() -> quart.Response:
return redirect(url_for("index"))

View File

@@ -18,8 +18,6 @@ from quart import (
flash,
)
import werkzeug.exceptions
import babel
import wtforms
@@ -34,7 +32,7 @@ bp = quart.Blueprint("main", __name__)
class LoginForm(BaseForm):
address = wtforms.TextField(
address = wtforms.StringField(
_l("Address"),
validators=[wtforms.validators.InputRequired()],
)
@@ -50,7 +48,7 @@ class LoginForm(BaseForm):
@bp.route("/-")
async def index() -> werkzeug.Response:
async def index() -> quart.Response:
return redirect(url_for("index"))
@@ -58,7 +56,7 @@ ERR_CREDENTIALS_INVALID = _l("Invalid username or password.")
@bp.route("/login", methods=["GET", "POST"])
async def login() -> typing.Union[str, werkzeug.Response]:
async def login() -> typing.Union[str, quart.Response]:
if client.has_session and (await client.test_session()):
return redirect(url_for('user.index'))
@@ -78,7 +76,7 @@ async def login() -> typing.Union[str, werkzeug.Response]:
password = form.password.data
try:
await client.login(jid, password)
except werkzeug.exceptions.Unauthorized:
except quart.exceptions.Unauthorized:
form.password.errors.append(ERR_CREDENTIALS_INVALID)
else:
await flash(
@@ -97,13 +95,14 @@ async def about() -> str:
if current_app.debug or client.is_admin_session:
version = _version.version
extra_versions["Quart"] = quart.__version__
extra_versions["aiohttp"] = aiohttp.__version__
extra_versions["babel"] = babel.__version__
extra_versions["wtforms"] = wtforms.__version__
extra_versions["flask-wtf"] = flask_wtf.__version__
try:
extra_versions["Prosody"] = await client.get_server_version()
except werkzeug.exceptions.Unauthorized:
except quart.exceptions.Unauthorized:
extra_versions["Prosody"] = "unknown"
return await render_template(

View File

@@ -19,9 +19,7 @@ from quart import (
current_app, _app_ctx_stack, session as http_session, abort, redirect,
url_for,
)
import quart
import werkzeug.exceptions
import quart.exceptions
from . import xmpputil
from .xmpputil import split_jid
@@ -388,16 +386,16 @@ class ProsodyClient:
) -> typing.Callable[
[typing.Callable[..., typing.Awaitable[T]]],
typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response, werkzeug.Response]]]]:
typing.Union[T, quart.Response]]]]:
def decorator(
f: typing.Callable[..., typing.Awaitable[T]],
) -> typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response, werkzeug.Response]]]:
typing.Union[T, quart.Response]]]:
@functools.wraps(f)
async def wrapped(
*args: typing.Any,
**kwargs: typing.Any,
) -> typing.Union[T, quart.Response, werkzeug.Response]:
) -> typing.Union[T, quart.Response]:
if not self.has_session or not (await self.test_session()):
redirect_to_value = redirect_to
if redirect_to_value is not False:
@@ -417,17 +415,17 @@ class ProsodyClient:
) -> typing.Callable[
[typing.Callable[..., typing.Awaitable[T]]],
typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response, werkzeug.Response]]]]:
typing.Union[T, quart.Response]]]]:
def decorator(
f: typing.Callable[..., typing.Awaitable[T]],
) -> typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response, werkzeug.Response]]]:
typing.Union[T, quart.Response]]]:
@functools.wraps(f)
@self.require_session(redirect_to=redirect_to)
async def wrapped(
*args: typing.Any,
**kwargs: typing.Any,
) -> typing.Union[T, quart.Response, werkzeug.Response]:
) -> typing.Union[T, quart.Response]:
if not self.is_admin_session:
raise abort(403, "This is not for you.")
@@ -494,7 +492,7 @@ class ProsodyClient:
session=session,
)
avatar_hash = avatar_info["sha1"]
except werkzeug.exceptions.HTTPException:
except quart.exceptions.HTTPException:
avatar_hash = None
return {
@@ -646,7 +644,7 @@ class ProsodyClient:
new_access_model,
)
))
except werkzeug.exceptions.NotFound:
except quart.exceptions.NotFound:
if ignore_not_found:
return
raise
@@ -776,7 +774,7 @@ class ProsodyClient:
session: aiohttp.ClientSession,
) -> str:
access_models = filter(
lambda x: not isinstance(x, werkzeug.exceptions.NotFound),
lambda x: not isinstance(x, quart.exceptions.NotFound),
await asyncio.gather(
self.get_avatar_access_model(session=session),
self.get_nickname_access_model(session=session),

View File

@@ -28,12 +28,12 @@
</div>
<div class="f-ebox">
{{ form.password.label }}
{{ form.password(autocomplete="new-password") }}
{{ form.password }}
<p class="field-desc weak">{% trans %}Enter a secure password that you do not use anywhere else.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.password_confirm.label }}
{{ form.password_confirm(autocomplete="new-password") }}
{{ form.password_confirm }}
</div>
<div class="f-bbox">
{%- call form_button("done", form.action_register, class="primary") -%}{%- endcall -%}

View File

@@ -17,11 +17,11 @@
{%- call render_errors(form) %}{% endcall -%}
<div class="f-ebox">
{{ form.password.label }}
{{ form.password(autocomplete="new-password") }}
{{ form.password }}
</div>
<div class="f-ebox">
{{ form.password_confirm.label }}
{{ form.password_confirm(autocomplete="new-password") }}
{{ form.password_confirm }}
</div>
<div class="f-bbox">
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}

View File

@@ -9,15 +9,15 @@
{%- endcall -%}
<div class="f-ebox">
{{ form.current_password.label(class="required") }}
{{ form.current_password(class=("has-error" if form.current_password.name in form.errors else ""), autocomplete="current-password") }}
{{ form.current_password(class=("has-error" if form.current_password.name in form.errors else "")) }}
</div>
<div class="f-ebox">
{{ form.new_password.label(class="required") }}
{{ form.new_password(autocomplete="new-password") }}
{{ form.new_password }}
</div>
<div class="f-ebox">
{{ form.new_password_confirm.label(class="required") }}
{{ form.new_password_confirm(class=("has-error" if form.new_password_confirm.name in form.errors else ""), autocomplete="new-password") }}
{{ form.new_password_confirm(class=("has-error" if form.new_password_confirm.name in form.errors else "")) }}
</div>
<div class="box warning">
<header>{% trans %}Warning{% endtrans %}</header>

View File

@@ -6,18 +6,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: translations@snikket.org\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2022-05-30 14:01+0000\n"
"Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n"
"Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/"
"da/>\n"
"PO-Revision-Date: 2021-04-02 19:01+0000\n"
"Last-Translator: Daniel Holmgaard <annoncer@protonmail.com>\n"
"Language-Team: Danish <https://i18n.sotecware.net/projects/snikket/web-"
"portal/da/>\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.8.1\n"
"X-Generator: Weblate 4.5.1\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
@@ -194,23 +194,23 @@ msgstr "Bruger fjernet fra cirkel"
#: snikket_web/admin.py:609
msgid "Message contents"
msgstr "Meddelelsens indhold"
msgstr ""
#: snikket_web/admin.py:615
msgid "Only send to online users"
msgstr "Send kun til online brugere"
msgstr ""
#: snikket_web/admin.py:619
msgid "Post to all users"
msgstr "Send til alle brugere"
msgstr ""
#: snikket_web/admin.py:623
msgid "Send preview to yourself"
msgstr "Send forhåndsvisning til dig selv"
msgstr ""
#: snikket_web/admin.py:645
msgid "Announcement sent!"
msgstr "Bekendgørelse sendt!"
msgstr ""
#: snikket_web/infra.py:51
msgid "Main"
@@ -221,8 +221,6 @@ msgid ""
"The account data you tried to import is too large to upload. Please contact "
"your Snikket operator."
msgstr ""
"De kontodata, du forsøgte at importere, er for store til at uploade. Kontakt "
"din Snikket-operatør."
#: snikket_web/invite.py:112
msgid "Username"
@@ -263,11 +261,11 @@ msgstr "Ændr adgangskode"
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr "Kontodatafil"
msgstr ""
#: snikket_web/invite.py:248
msgid "Import data"
msgstr "Importer data"
msgstr ""
#: snikket_web/invite.py:269
#, python-format
@@ -275,8 +273,6 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload "
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
"De kontodata, du forsøgte at importere, er i et ukendt format. Upload en XML-"
"fil i XEP-0227-format (forudsat format: %(mimetype)s)."
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
@@ -345,11 +341,11 @@ msgstr "Opdater profil"
#: snikket_web/user.py:82
msgid "Account data"
msgstr "Kontodata"
msgstr ""
#: snikket_web/user.py:86
msgid "Upload"
msgstr "Upload"
msgstr ""
#: snikket_web/user.py:111
msgid "Incorrect password."
@@ -373,11 +369,11 @@ msgstr "Profil opdateret"
#: snikket_web/user.py:184
msgid "Export"
msgstr "Exporter"
msgstr ""
#: snikket_web/user.py:202
msgid "You currently have no account data to export."
msgstr "Du har i øjeblikket ingen kontodata at eksportere."
msgstr ""
#: snikket_web/templates/_footer.html:4
#, python-format
@@ -666,7 +662,7 @@ msgstr "Cirkel medlemmer"
#: snikket_web/templates/admin_edit_circle.html:71
msgid "The user has been deleted from the server."
msgstr "Brugeren er blevet slettet fra serveren."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:71
#: snikket_web/templates/library.j2:108
@@ -880,20 +876,22 @@ msgstr "Håndter invitationer"
#: snikket_web/templates/admin_home.html:35
msgid "System health"
msgstr "Systemets sundhed"
msgstr ""
#: snikket_web/templates/admin_home.html:38
msgid "View the server status or send a broadcast message to all users."
msgstr "Vis serverstatus, eller send en udsendelsesmeddelelse til alle brugere."
msgstr ""
#: snikket_web/templates/admin_home.html:40
msgid "Send a broadcast message to all users."
msgstr "Send en udsendelsesmeddelelse til alle brugere."
msgstr ""
#: snikket_web/templates/admin_home.html:43
#: snikket_web/templates/admin_system.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage system"
msgstr "Håndter system"
msgstr "Håndter brugere"
#: snikket_web/templates/admin_home.html:48
msgid "Go back to your user's web portal page."
@@ -954,11 +952,11 @@ msgstr "Ødelæg link"
#: snikket_web/templates/admin_system.html:6
msgid "Overall system status"
msgstr "Samlet systemstatus"
msgstr ""
#: snikket_web/templates/admin_system.html:9
msgid "System load (5 minute average)"
msgstr "Systembelastning (5 minutters gennemsnit)"
msgstr ""
#: snikket_web/templates/admin_system.html:14
#: snikket_web/templates/admin_system.html:22
@@ -969,11 +967,11 @@ msgstr "Systembelastning (5 minutters gennemsnit)"
#: snikket_web/templates/admin_system.html:76
#: snikket_web/templates/admin_system.html:84
msgid "unknown"
msgstr "ukendt"
msgstr ""
#: snikket_web/templates/admin_system.html:17
msgid "Memory use"
msgstr "Forbrug af hukommelse"
msgstr ""
#: snikket_web/templates/admin_system.html:20
#, python-format
@@ -981,56 +979,54 @@ msgid ""
"%(percentage_global)s of %(mem_available)s. Of that, Snikket uses "
"%(percentage_snikket)s."
msgstr ""
"%(percentage_global)s af %(mem_available)s. Der af bruger Snikket "
"%(percentage_snikket)s."
#: snikket_web/templates/admin_system.html:27
msgid "Web portal status"
msgstr "Webportalens status"
msgstr ""
#: snikket_web/templates/admin_system.html:30
#: snikket_web/templates/admin_system.html:53
msgid "Version"
msgstr "Version"
msgstr ""
#: snikket_web/templates/admin_system.html:31
#: snikket_web/templates/admin_system.html:54
msgid "View all versions"
msgstr "Vis alle versioner"
msgstr ""
#: snikket_web/templates/admin_system.html:32
#: snikket_web/templates/admin_system.html:55
msgid "Average CPU use"
msgstr "Gennemsnitlig CPU-forbrug"
msgstr ""
#: snikket_web/templates/admin_system.html:40
#: snikket_web/templates/admin_system.html:63
msgid "Current memory use"
msgstr "Nuværende hukommelsesbrug"
msgstr ""
#: snikket_web/templates/admin_system.html:50
#, fuzzy
#| msgid "Snikket Web Portal"
msgid "Snikket server status"
msgstr "Snikket server status"
msgstr "Snikket Webportal"
#: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files"
msgstr "Lagerplads, der bruges af delte filer"
msgstr ""
#: snikket_web/templates/admin_system.html:79
msgid "Connected devices"
msgstr "Forbundet enheder"
msgstr ""
#: snikket_web/templates/admin_system.html:90
msgid "Broadcast message"
msgstr "Send besked"
msgstr ""
#: snikket_web/templates/admin_system.html:92
msgid ""
"This form allows you to send a message to all users currently online on your "
"Snikket server. Use it wisely."
msgstr ""
"Denne formular giver dig mulighed for at sende en besked til alle brugere, "
"der i øjeblikket er online på din Snikket-server. Brug den med omtanke."
#: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator."
@@ -1304,20 +1300,22 @@ msgid ""
"You can now safely close this page, or log in to the web portal to <a href="
"\"%(login_url)s\">manage your account</a>."
msgstr ""
"Du kan nu trygt lukke denne side eller logge ind på webportalen for at <a "
"href=\"%(login_url)s\">administrere din konto</a>."
#: snikket_web/templates/invite_success.html:21
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful"
msgstr "Importering lykkes"
msgstr "Operation lykkes"
#: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported."
msgstr "Tillykke! Dine kontodata er blevet importeret."
msgstr ""
#: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?"
msgstr "Flytte til Snikket?"
msgstr "Bruger Snikket appen"
#: snikket_web/templates/invite_success.html:27
msgid ""
@@ -1326,14 +1324,10 @@ msgid ""
"information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below."
msgstr ""
"Hvis du flytter fra en anden Snikket-platform eller en anden XMPP-kompatibel "
"tjeneste, kan du eventuelt importere dataene (kontakter, profiloplysninger "
"osv.) fra din tidligere konto. Når du har eksporteret dataene fra din "
"tidligere konto, skal du uploade dem ved hjælp af nedenstående formular."
#: snikket_web/templates/invite_success.html:30
msgid "Upload account data"
msgstr "Upload kontodata"
msgstr ""
#: snikket_web/templates/invite_view.html:6
#, python-format
@@ -1564,8 +1558,10 @@ msgstr "Rediger profil"
#: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data"
msgstr "Håndter dine data"
msgstr "Håndter brugere"
#: snikket_web/templates/user_home.html:39
msgid "Your Snikket"
@@ -1592,16 +1588,16 @@ msgstr ""
"af de forbundne enheder."
#: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account"
msgstr "Eksporter konto"
msgstr "Din konto"
#: snikket_web/templates/user_manage_data.html:9
msgid ""
"Download your account data as a file for backup purposes or to move your "
"account to another service."
msgstr ""
"Download dine kontodata som en fil til sikkerhedskopieringsformål eller for "
"at flytte din konto til en anden tjeneste."
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"

View File

@@ -6,18 +6,18 @@
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: translations@snikket.org\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2022-04-11 13:00+0000\n"
"Last-Translator: David Baraniak <admin@chipmnk.dev>\n"
"Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/"
"fr/>\n"
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
"Last-Translator: Link Mauve <linkmauve@linkmauve.fr>\n"
"Language-Team: French <https://i18n.sotecware.net/projects/snikket/web-"
"portal/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.8.1\n"
"X-Generator: Weblate 4.5.1\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
@@ -221,8 +221,6 @@ msgid ""
"The account data you tried to import is too large to upload. Please contact "
"your Snikket operator."
msgstr ""
"Les données du compte que vous avez essayé d'importer sont trop volumineuses "
"pour être téléchargées. Veuillez contacter votre opérateur Snikket."
#: snikket_web/invite.py:112
msgid "Username"
@@ -263,11 +261,11 @@ msgstr "Changer de mot de passe"
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr "Fichier de données du compte"
msgstr ""
#: snikket_web/invite.py:248
msgid "Import data"
msgstr "Importer les données"
msgstr ""
#: snikket_web/invite.py:269
#, python-format
@@ -275,9 +273,6 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload "
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
"Les données du compte que vous avez essayé d'importer sont dans un format "
"inconnu. Veuillez télécharger un fichier XML au format XEP-0227 (format "
"fourni : %(mimetype)s)."
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
@@ -346,11 +341,11 @@ msgstr "Mettre à jour le profil"
#: snikket_web/user.py:82
msgid "Account data"
msgstr "Données du compte"
msgstr ""
#: snikket_web/user.py:86
msgid "Upload"
msgstr "Télécharger"
msgstr ""
#: snikket_web/user.py:111
msgid "Incorrect password."
@@ -374,11 +369,11 @@ msgstr "Profil mis à jour"
#: snikket_web/user.py:184
msgid "Export"
msgstr "Exporter"
msgstr ""
#: snikket_web/user.py:202
msgid "You currently have no account data to export."
msgstr "Vous n'avez actuellement aucune donnée de compte à exporter."
msgstr ""
#: snikket_web/templates/_footer.html:4
#, python-format
@@ -1019,7 +1014,7 @@ msgstr "Statut du serveur Snikket"
#: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files"
msgstr "Stockage utilisé par les fichiers partagés"
msgstr ""
#: snikket_web/templates/admin_system.html:79
msgid "Connected devices"
@@ -1319,22 +1314,22 @@ msgid ""
"You can now safely close this page, or log in to the web portal to <a href="
"\"%(login_url)s\">manage your account</a>."
msgstr ""
"Vous pouvez maintenant fermer cette page en toute sécurité, ou vous "
"connecter au portail web pour <a href=\"%(login_url)s\">gérer votre "
"compte</a>."
#: snikket_web/templates/invite_success.html:21
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful"
msgstr "Importation réussie"
msgstr "Opération réussie"
#: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported."
msgstr ""
"Félicitations ! Les données de votre compte ont été importées avec succès."
#: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?"
msgstr "Nouveau utilisateur Snikket ?"
msgstr "En utilisant lapplication Snikket"
#: snikket_web/templates/invite_success.html:27
msgid ""
@@ -1343,15 +1338,10 @@ msgid ""
"information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below."
msgstr ""
"Si vous passez d'une autre instance de Snikket ou d'un autre service "
"compatible XMPP, vous pouvez éventuellement importer les données (contacts, "
"informations de profil, etc.) de votre ancien compte. Lorsque vous avez "
"exporté les données de votre ancien compte, téléchargez-les en utilisant le "
"formulaire ci-dessous."
#: snikket_web/templates/invite_success.html:30
msgid "Upload account data"
msgstr "Télécharger les données du compte"
msgstr ""
#: snikket_web/templates/invite_view.html:6
#, python-format
@@ -1406,9 +1396,10 @@ msgstr "Télécharger sur lApp Store"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr "Obtenez-le sur F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:35
#, fuzzy
msgid "Send to mobile device"
msgstr "Envoyer vers l'appareil"
@@ -1487,14 +1478,10 @@ msgid ""
"After downloading Snikket from the App Store, you have to return to this "
"invite link and tap on \"Open the app\" to proceed."
msgstr ""
"Après avoir téléchargé Snikket depuis l'App Store, vous devez revenir à ce "
"lien d'invitation et cliquer sur \"Ouvrir l'application\" pour continuer."
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr ""
"Téléchargez d'abord Snikket depuis l'App Store en utilisant le bouton ci-"
"dessous :"
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
@@ -1502,9 +1489,6 @@ msgid ""
"After the installation is complete, you can return to this page and tap the "
"\"Open the app\" button to continue with the setup:"
msgstr ""
"Une fois l'installation terminée, vous pouvez revenir à cette page et "
"appuyer sur le bouton \"Ouvrir l'application\" pour poursuivre la "
"configuration :"
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
@@ -1516,13 +1500,10 @@ msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
msgstr ""
"Après avoir installé Snikket via F-Droid, vous devez revenir à ce lien "
"d'invitation et appuyer sur \"Ouvrir l'application\" pour continuer."
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr ""
"Installez d'abord Snikket depuis F-Droid en utilisant le bouton ci-dessous :"
#: snikket_web/templates/library.j2:18
msgid "Copy link"
@@ -1589,8 +1570,10 @@ msgstr "Éditer votre profil"
#: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data"
msgstr "Gérer vos données"
msgstr "Gérer les utilisateurs"
#: snikket_web/templates/user_home.html:39
msgid "Your Snikket"
@@ -1618,16 +1601,16 @@ msgstr ""
"autres appareils connectés."
#: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account"
msgstr "Exportation du compte"
msgstr "Votre compte"
#: snikket_web/templates/user_manage_data.html:9
msgid ""
"Download your account data as a file for backup purposes or to move your "
"account to another service."
msgstr ""
"Téléchargez les données de votre compte sous forme d'un fichier à des fins "
"de sauvegarde ou pour transférer votre compte vers un autre service."
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"

View File

@@ -8,204 +8,204 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-05-30 20:51+0200\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.10.1\n"
"Generated-By: Babel 2.9.1\n"
#: snikket_web/admin.py:70 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_edit_circle.html:59
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr ""
#: snikket_web/admin.py:74 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/admin.py:72 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_edit_circle.html:60
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:63
msgid "Display name"
msgstr ""
#: snikket_web/admin.py:78 snikket_web/templates/admin_edit_user.html:32
#: snikket_web/admin.py:76 snikket_web/templates/admin_edit_user.html:32
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:80
#: snikket_web/admin.py:78
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:81
#: snikket_web/admin.py:79
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:82
#: snikket_web/admin.py:80
msgid "Administrator"
msgstr ""
#: snikket_web/admin.py:87
#: snikket_web/admin.py:85
msgid "Update user"
msgstr ""
#: snikket_web/admin.py:91
#: snikket_web/admin.py:89
msgid "Create password reset link"
msgstr ""
#: snikket_web/admin.py:109
#: snikket_web/admin.py:107
msgid "Password reset link created"
msgstr ""
#: snikket_web/admin.py:124
#: snikket_web/admin.py:122
msgid "User information updated."
msgstr ""
#: snikket_web/admin.py:146
#: snikket_web/admin.py:144
msgid "Delete user permanently"
msgstr ""
#: snikket_web/admin.py:159
#: snikket_web/admin.py:157
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:197
#: snikket_web/admin.py:195
msgid "Password reset link not found"
msgstr ""
#: snikket_web/admin.py:209
#: snikket_web/admin.py:207
msgid "Password reset link deleted"
msgstr ""
#: snikket_web/admin.py:229
#: snikket_web/admin.py:227
msgid "Invite to circle"
msgstr ""
#: snikket_web/admin.py:235
#: snikket_web/admin.py:233
msgid "At least one circle must be selected"
msgstr ""
#: snikket_web/admin.py:240
#: snikket_web/admin.py:238
msgid "Valid for"
msgstr ""
#: snikket_web/admin.py:242
#: snikket_web/admin.py:240
msgid "One hour"
msgstr ""
#: snikket_web/admin.py:243
#: snikket_web/admin.py:241
msgid "Twelve hours"
msgstr ""
#: snikket_web/admin.py:244
#: snikket_web/admin.py:242
msgid "One day"
msgstr ""
#: snikket_web/admin.py:245
#: snikket_web/admin.py:243
msgid "One week"
msgstr ""
#: snikket_web/admin.py:246
#: snikket_web/admin.py:244
msgid "Four weeks"
msgstr ""
#: snikket_web/admin.py:252 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:250 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr ""
#: snikket_web/admin.py:254 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:252 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr ""
#: snikket_web/admin.py:255 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:253 snikket_web/templates/library.j2:114
msgid "Group"
msgstr ""
#: snikket_web/admin.py:261
#: snikket_web/admin.py:259
msgid "New invitation link"
msgstr ""
#: snikket_web/admin.py:323
#: snikket_web/admin.py:321
msgid "Revoke"
msgstr ""
#: snikket_web/admin.py:347
#: snikket_web/admin.py:345
msgid "Invitation created"
msgstr ""
#: snikket_web/admin.py:363
#: snikket_web/admin.py:361
msgid "No such invitation exists"
msgstr ""
#: snikket_web/admin.py:378
#: snikket_web/admin.py:376
msgid "Invitation revoked"
msgstr ""
#: snikket_web/admin.py:395 snikket_web/admin.py:443
#: snikket_web/admin.py:393 snikket_web/admin.py:441
msgid "Name"
msgstr ""
#: snikket_web/admin.py:400 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:398 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr ""
#: snikket_web/admin.py:430
#: snikket_web/admin.py:428
msgid "Circle created"
msgstr ""
#: snikket_web/admin.py:448
#: snikket_web/admin.py:446
msgid "Select user"
msgstr ""
#: snikket_web/admin.py:453
#: snikket_web/admin.py:451
msgid "Update circle"
msgstr ""
#: snikket_web/admin.py:457
#: snikket_web/admin.py:455
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:463
#: snikket_web/admin.py:461
msgid "Add user"
msgstr ""
#: snikket_web/admin.py:479
#: snikket_web/admin.py:477
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:516
#: snikket_web/admin.py:514
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:522
#: snikket_web/admin.py:520
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:533
#: snikket_web/admin.py:531
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:542
#: snikket_web/admin.py:540
msgid "User removed from circle"
msgstr ""
#: snikket_web/admin.py:611
#: snikket_web/admin.py:609
msgid "Message contents"
msgstr ""
#: snikket_web/admin.py:617
#: snikket_web/admin.py:615
msgid "Only send to online users"
msgstr ""
#: snikket_web/admin.py:621
#: snikket_web/admin.py:619
msgid "Post to all users"
msgstr ""
#: snikket_web/admin.py:625
#: snikket_web/admin.py:623
msgid "Send preview to yourself"
msgstr ""
#: snikket_web/admin.py:647
#: snikket_web/admin.py:645
msgid "Announcement sent!"
msgstr ""
@@ -213,82 +213,82 @@ msgstr ""
msgid "Main"
msgstr ""
#: snikket_web/invite.py:35
#: snikket_web/invite.py:33
msgid ""
"The account data you tried to import is too large to upload. Please "
"contact your Snikket operator."
msgstr ""
#: snikket_web/invite.py:114
#: snikket_web/invite.py:112
msgid "Username"
msgstr ""
#: snikket_web/invite.py:118 snikket_web/invite.py:186 snikket_web/main.py:43
#: snikket_web/invite.py:116 snikket_web/invite.py:184 snikket_web/main.py:41
msgid "Password"
msgstr ""
#: snikket_web/invite.py:122 snikket_web/invite.py:190
#: snikket_web/invite.py:120 snikket_web/invite.py:188
msgid "Confirm password"
msgstr ""
#: snikket_web/invite.py:126 snikket_web/invite.py:194
#: snikket_web/invite.py:124 snikket_web/invite.py:192
msgid "The passwords must match."
msgstr ""
#: snikket_web/invite.py:131
#: snikket_web/invite.py:129
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:158
#: snikket_web/invite.py:156
msgid "That username is already taken."
msgstr ""
#: snikket_web/invite.py:162 snikket_web/invite.py:227
#: snikket_web/invite.py:160 snikket_web/invite.py:225
msgid "Registration was declined for unknown reasons."
msgstr ""
#: snikket_web/invite.py:166
#: snikket_web/invite.py:164
msgid "The username is not valid."
msgstr ""
#: snikket_web/invite.py:199 snikket_web/templates/user_home.html:32
#: snikket_web/invite.py:197 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr ""
#: snikket_web/invite.py:246
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr ""
#: snikket_web/invite.py:250
#: snikket_web/invite.py:248
msgid "Import data"
msgstr ""
#: snikket_web/invite.py:271
#: snikket_web/invite.py:269
#, python-format
msgid ""
"The account data you tried to import is in an unknown format. Please "
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
#: snikket_web/invite.py:291 snikket_web/templates/unauth.html:18
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
msgid "Error"
msgstr ""
#: snikket_web/main.py:38
#: snikket_web/main.py:36
msgid "Address"
msgstr ""
#: snikket_web/main.py:48
#: snikket_web/main.py:46
msgid "Sign in"
msgstr ""
#: snikket_web/main.py:57
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr ""
#: snikket_web/main.py:85
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ from quart import (
flash,
current_app,
)
import werkzeug.exceptions
import quart.exceptions
import wtforms
@@ -59,7 +59,7 @@ _ACCESS_MODEL_CHOICES = [
class ProfileForm(BaseForm):
nickname = wtforms.TextField(
nickname = wtforms.StringField(
_l("Display name"),
)
@@ -96,7 +96,7 @@ async def index() -> str:
@bp.route('/passwd', methods=["GET", "POST"])
@client.require_session()
async def change_pw() -> typing.Union[str, werkzeug.Response]:
async def change_pw() -> typing.Union[str, quart.Response]:
form = ChangePasswordForm()
if form.validate_on_submit():
try:
@@ -104,8 +104,8 @@ async def change_pw() -> typing.Union[str, werkzeug.Response]:
form.current_password.data,
form.new_password.data,
)
except (werkzeug.exceptions.Unauthorized,
werkzeug.exceptions.Forbidden):
except (quart.exceptions.Unauthorized,
quart.exceptions.Forbidden):
# server refused current password, set an appropriate error
form.current_password.errors.append(
_("Incorrect password."),
@@ -128,7 +128,7 @@ EAVATARTOOBIG = _l(
@bp.route("/profile", methods=["GET", "POST"])
@client.require_session()
async def profile() -> typing.Union[str, werkzeug.Response]:
async def profile() -> typing.Union[str, quart.Response]:
max_avatar_size = current_app.config["MAX_AVATAR_SIZE"]
form = ProfileForm()
@@ -221,7 +221,7 @@ async def manage_data() -> typing.Union[str, quart.Response]:
@bp.route("/logout", methods=["GET", "POST"])
@client.require_session()
async def logout() -> typing.Union[werkzeug.Response, str]:
async def logout() -> typing.Union[quart.Response, str]:
form = LogoutForm()
if form.validate_on_submit():
await client.logout()

View File

@@ -4,7 +4,7 @@ import typing
import xml.etree.ElementTree as ET
from quart import abort
import werkzeug.exceptions
import quart.exceptions
TAG_XMPP_ERROR = "error"
@@ -239,7 +239,7 @@ def extract_pubsub_item_get_reply(
) -> typing.Optional[ET.Element]:
try:
pubsub = extract_iq_reply(iq_tree, TAG_PUBSUB)
except werkzeug.exceptions.NotFound:
except quart.exceptions.NotFound:
return None
if pubsub is None: