From 72cf5a3d5e5deeee5c2f08c1d0b9ac3f0d5ec585 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 10 Jul 2024 12:24:41 +0200 Subject: [PATCH 1/4] Introduce honeypot for login/registering/password changing --- core/templates/core/login.jinja | 1 + core/templates/core/password_reset.jinja | 1 + core/templates/core/register.jinja | 1 + core/templatetags/extensions.py | 58 ++++++++++++++++++++++++ core/tests.py | 32 ++++++++++++- core/views/user.py | 9 +++- poetry.lock | 36 ++++++++++----- pyproject.toml | 1 + sith/settings.py | 10 +++- 9 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 core/templatetags/extensions.py diff --git a/core/templates/core/login.jinja b/core/templates/core/login.jinja index b76bc46c..4e56613d 100644 --- a/core/templates/core/login.jinja +++ b/core/templates/core/login.jinja @@ -33,6 +33,7 @@ {% endif %} {% csrf_token %} + {% render_honeypot_field %}
diff --git a/core/templates/core/password_reset.jinja b/core/templates/core/password_reset.jinja index f54a255d..b0a63fc3 100644 --- a/core/templates/core/password_reset.jinja +++ b/core/templates/core/password_reset.jinja @@ -3,6 +3,7 @@ {% block content %}
{% csrf_token %} +{% render_honeypot_field %} {{ form.as_p() }}
diff --git a/core/templates/core/register.jinja b/core/templates/core/register.jinja index 681d2d48..4702f49c 100644 --- a/core/templates/core/register.jinja +++ b/core/templates/core/register.jinja @@ -23,6 +23,7 @@ {% else %}
{% csrf_token %} + {% render_honeypot_field %} {{ form }}
diff --git a/core/templatetags/extensions.py b/core/templatetags/extensions.py new file mode 100644 index 00000000..6c2aa4ca --- /dev/null +++ b/core/templatetags/extensions.py @@ -0,0 +1,58 @@ +# +# Copyright 2024 +# - Sli +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# +from typing import Callable + +import honeypot.templatetags.honeypot as honeypot_filters +from django.template.loader import render_to_string +from jinja2 import Environment, nodes +from jinja2.ext import Extension +from jinja2.parser import Parser + + +class HoneypotExtension(Extension): + """ + Wrapper around the honeypot extension tag + Known limitation: doesn't support arguments + + Usage: {% render_honeypot_field %} + """ + + tags = {"render_honeypot_field"} + + def __init__(self, environment: Environment) -> None: + environment.globals["render_honeypot_field"] = ( + honeypot_filters.render_honeypot_field + ) + self.environment = environment + + def parse(self, parser: Parser) -> nodes.Output: + lineno = parser.stream.expect("name:render_honeypot_field").lineno + call = self.call_method( + "_render", + [nodes.Name("render_honeypot_field", "load", lineno=lineno)], + lineno=lineno, + ) + return nodes.Output([nodes.MarkSafe(call)]) + + def _render(self, render_honeypot_field: Callable[[str | None], str]): + return render_to_string("honeypot/honeypot_field.html", render_honeypot_field()) diff --git a/core/tests.py b/core/tests.py index b476c588..f4163388 100644 --- a/core/tests.py +++ b/core/tests.py @@ -36,6 +36,7 @@ class TestUserRegistration: @pytest.fixture() def valid_payload(self): return { + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, "first_name": "this user does not exist (yet)", "last_name": "this user does not exist (yet)", "email": "i-dont-exist-yet@git.an", @@ -68,6 +69,13 @@ class TestUserRegistration: assert response.status_code == 200 assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + def test_register_honeypot_fail(self, client, valid_payload): + payload = valid_payload | { + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "random" + } + response = client.post(reverse("core:register"), payload) + assert response.status_code == 400 + def test_register_user_form_fail_already_exists(self, client, valid_payload): """Should not register a user correctly if it already exists.""" # create the user, then try to create it again @@ -90,7 +98,11 @@ class TestUserLogin: response = client.post( reverse("core:login"), - {"username": user.username, "password": "wrong-password"}, + { + "username": user.username, + "password": "wrong-password", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, + }, ) assert response.status_code == 200 assert ( @@ -98,12 +110,28 @@ class TestUserLogin: "et votre mot de passe ne correspondent pas. Merci de réessayer.

" ) in str(response.content.decode()) + def test_login_honeypot(self, client, user): + response = client.post( + reverse("core:login"), + { + "username": user.username, + "password": "wrong-password", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "incorrect", + }, + ) + assert response.status_code == 400 + def test_login_success(self, client, user): """ Should login a user correctly """ response = client.post( - reverse("core:login"), {"username": user.username, "password": "plop"} + reverse("core:login"), + { + "username": user.username, + "password": "plop", + settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE, + }, ) assertRedirects(response, reverse("core:index")) diff --git a/core/views/user.py b/core/views/user.py index 4c91b8ff..6952a8b1 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -36,6 +36,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.template.response import TemplateResponse from django.urls import reverse, reverse_lazy +from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ from django.views.generic import ( CreateView, @@ -46,6 +47,7 @@ from django.views.generic import ( ) from django.views.generic.dates import MonthMixin, YearMixin from django.views.generic.edit import UpdateView +from honeypot.decorators import check_honeypot from api.views.sas import all_pictures_of_user from core.models import Gift, Preferences, SithFile, User @@ -69,6 +71,7 @@ from subscription.models import Subscription from trombi.views import UserTrombiForm +@method_decorator(check_honeypot, name="post") class SithLoginView(views.LoginView): """ The login View @@ -124,9 +127,10 @@ def password_root_change(request, user_id): ) +@method_decorator(check_honeypot, name="post") class SithPasswordResetView(views.PasswordResetView): """ - Allows someone to enter an email adresse for resetting password + Allows someone to enter an email address for resetting password """ template_name = "core/password_reset.jinja" @@ -153,12 +157,13 @@ class SithPasswordResetConfirmView(views.PasswordResetConfirmView): class SithPasswordResetCompleteView(views.PasswordResetCompleteView): """ - Confirm the password has sucessfully been reset + Confirm the password has successfully been reset """ template_name = "core/password_reset_complete.jinja" +@check_honeypot def register(request): context = {} if request.method == "POST": diff --git a/poetry.lock b/poetry.lock index 0cb95720..2c875fcd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -426,13 +426,13 @@ files = [ [[package]] name = "django" -version = "4.2.13" +version = "4.2.14" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, - {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, + {file = "Django-4.2.14-py3-none-any.whl", hash = "sha256:3ec32bc2c616ab02834b9cac93143a7dc1cdcd5b822d78ac95fc20a38c534240"}, + {file = "Django-4.2.14.tar.gz", hash = "sha256:fc6919875a6226c7ffcae1a7d51e0f2ceaf6f160393180818f6c95f51b1e7b96"}, ] [package.dependencies] @@ -477,17 +477,17 @@ test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytes [[package]] name = "django-debug-toolbar" -version = "4.3.0" +version = "4.4.5" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" files = [ - {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"}, - {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"}, + {file = "django_debug_toolbar-4.4.5-py3-none-any.whl", hash = "sha256:91425606673ee674d780f7aeedf3595c264eb382dcf41f55c6779577900904c0"}, + {file = "django_debug_toolbar-4.4.5.tar.gz", hash = "sha256:8298ce966b4c8fc71430082dd4739ef2badb5f867734e1973a413c4ab2ea81b7"}, ] [package.dependencies] -django = ">=3.2.4" +django = ">=4.2.9" sqlparse = ">=0.2" [[package]] @@ -508,6 +508,20 @@ packaging = "*" elasticsearch = ["elasticsearch (>=5,<8)"] testing = ["coverage", "geopy (==2)", "pysolr (>=3.7)", "python-dateutil", "requests", "whoosh (>=2.5.4,<3.0)"] +[[package]] +name = "django-honeypot" +version = "1.2.0" +description = "Django honeypot field utilities" +optional = false +python-versions = "<4.0,>=3.8" +files = [ + {file = "django_honeypot-1.2.0-py3-none-any.whl", hash = "sha256:53dd5f8dd96ef1bb7e31b5514c0dc2caae9577e78ebdf03ca4e0f304a7422aba"}, + {file = "django_honeypot-1.2.0.tar.gz", hash = "sha256:25fca02e786aec26649bd13b37a95c846e09ab3cfc10f28db2f7dfaa77b9b9c6"}, +] + +[package.dependencies] +Django = ">=3.2,<5.1" + [[package]] name = "django-jinja" version = "2.11.0" @@ -669,13 +683,13 @@ python-dateutil = ">=2.7" [[package]] name = "identify" -version = "2.5.36" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, - {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -1867,4 +1881,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8524ed5f593973edf05b3c01010c1f2345b7e799089c3e38274304bdedf8b3cb" +content-hash = "51820883f41bdf40f00296b722ebdd9ac386e43ef1424ef990b29bac579ecbab" diff --git a/pyproject.toml b/pyproject.toml index a52ffc52..abd42aca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ django-countries = "^7.5.1" dict2xml = "^1.7.3" Sphinx = "^5" # Needed for building xapian tomli = "^2.0.1" +django-honeypot = "^1.2.0" [tool.poetry.group.dev.dependencies] django-debug-toolbar = "^4.0.0" diff --git a/sith/settings.py b/sith/settings.py index 42d7b8d9..ab311a85 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -1,5 +1,5 @@ # -# Copyright 2016,2017 +# Copyright 2016,2017,2024 # - Skia # - Sli # @@ -17,7 +17,7 @@ # details. # # You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place - Suite 330, Boston, MA 02111-1307, USA. # # @@ -54,6 +54,10 @@ os.environ["HTTPS"] = "off" # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" +# Those values are to be changed in production to be more effective +HONEYPOT_FIELD_NAME = "body2" +HONEYPOT_VALUE = "content" + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules @@ -75,6 +79,7 @@ INSTALLED_APPS = ( "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.sites", + "honeypot", "django_jinja", "rest_framework", "ajax_select", @@ -143,6 +148,7 @@ TEMPLATES = [ "django_jinja.builtins.extensions.UrlsExtension", "django_jinja.builtins.extensions.StaticFilesExtension", "django_jinja.builtins.extensions.DjangoFiltersExtension", + "core.templatetags.extensions.HoneypotExtension", ], "filters": { "markdown": "core.templatetags.renderer.markdown", From e15bcfae072bf93071810ba00ee26e000a03412d Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 10 Jul 2024 16:24:01 +0200 Subject: [PATCH 2/4] Send an email when creating an account via POST /register --- core/templates/core/register.jinja | 19 +- .../core/register_confirm_mail.jinja | 17 + core/tests.py | 59 +- core/urls.py | 2 +- core/views/forms.py | 8 - core/views/user.py | 65 +- locale/fr/LC_MESSAGES/django.po | 1440 +++++++++-------- 7 files changed, 846 insertions(+), 764 deletions(-) create mode 100644 core/templates/core/register_confirm_mail.jinja diff --git a/core/templates/core/register.jinja b/core/templates/core/register.jinja index 4702f49c..c80d2f98 100644 --- a/core/templates/core/register.jinja +++ b/core/templates/core/register.jinja @@ -15,18 +15,11 @@ {% block content %}

{% trans %}Register{% endtrans %}

- {% if user_registered %} - {% trans user_name=user_registered.get_display_name() %}Welcome {{ user_name }}!{% endtrans %}
- {% trans %}You successfully registered and you will soon receive a confirmation mail.{% endtrans %}
- {% trans username=user_registered.username %}Your username is {{ username }}.{% endtrans %}
- - {% else %} -
- {% csrf_token %} - {% render_honeypot_field %} - {{ form }} - -
- {% endif %} +
+ {% csrf_token %} + {% render_honeypot_field %} + {{ form }} + +
{% endblock %} \ No newline at end of file diff --git a/core/templates/core/register_confirm_mail.jinja b/core/templates/core/register_confirm_mail.jinja new file mode 100644 index 00000000..9b313035 --- /dev/null +++ b/core/templates/core/register_confirm_mail.jinja @@ -0,0 +1,17 @@ +{% autoescape off %} +{% trans %}You're receiving this email because you created an account on the AE website.{% endtrans %} + +{% trans %}Your username, in case it was not given to you: {% endtrans %} {{ username }} + +{% trans %} +As this is the website of the students of the AE, by the students of the AE, +for the students of the AE, you won't be able to do many things without subscribing to the AE. +To make a contribution, contact a member of the association's board, either directly or by email at ae@utbm.fr. +{% endtrans %} + +{% trans %}Wishing you a good experience among us! {% endtrans %} + +{% trans %}The AE team{% endtrans %} + +{% endautoescape %} + diff --git a/core/tests.py b/core/tests.py index f4163388..a5e885bf 100644 --- a/core/tests.py +++ b/core/tests.py @@ -15,11 +15,14 @@ from datetime import date, timedelta from pathlib import Path +from smtplib import SMTPException import freezegun import pytest +from django.core import mail from django.core.cache import cache -from django.test import TestCase +from django.core.mail import EmailMessage +from django.test import Client, TestCase from django.urls import reverse from django.utils.timezone import now from pytest_django.asserts import assertInHTML, assertRedirects @@ -48,26 +51,35 @@ class TestUserRegistration: def test_register_user_form_ok(self, client, valid_payload): """Should register a user correctly.""" + assert not User.objects.filter(email=valid_payload["email"]).exists() response = client.post(reverse("core:register"), valid_payload) - assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_OK" in str(response.content) + assertRedirects(response, reverse("core:index")) + assert len(mail.outbox) == 1 + assert mail.outbox[0].subject == "Création de votre compte AE" + assert User.objects.filter(email=valid_payload["email"]).exists() @pytest.mark.parametrize( - "payload_edit", + ("payload_edit", "expected_error"), [ - {"password2": "not the same as password1"}, - {"email": "not-an-email"}, - {"first_name": ""}, - {"last_name": ""}, - {"captcha_1": "WRONG_CAPTCHA"}, + ( + {"password2": "not the same as password1"}, + "Les deux mots de passe ne correspondent pas.", + ), + ({"email": "not-an-email"}, "Saisissez une adresse e-mail valide."), + ({"first_name": ""}, "Ce champ est obligatoire."), + ({"last_name": ""}, "Ce champ est obligatoire."), + ({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"), ], ) - def test_register_user_form_fail(self, client, valid_payload, payload_edit): + def test_register_user_form_fail( + self, client, valid_payload, payload_edit, expected_error + ): """Should not register a user correctly.""" payload = valid_payload | payload_edit response = client.post(reverse("core:register"), payload) assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + error_html = f'
  • {expected_error}
' + assertInHTML(error_html, str(response.content.decode())) def test_register_honeypot_fail(self, client, valid_payload): payload = valid_payload | { @@ -76,13 +88,34 @@ class TestUserRegistration: response = client.post(reverse("core:register"), payload) assert response.status_code == 400 - def test_register_user_form_fail_already_exists(self, client, valid_payload): + def test_register_user_form_fail_already_exists( + self, client: Client, valid_payload + ): """Should not register a user correctly if it already exists.""" # create the user, then try to create it again client.post(reverse("core:register"), valid_payload) response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 - assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + error_html = "
  • Un objet User avec ce champ Adresse email existe déjà.
  • " + assertInHTML(error_html, str(response.content.decode())) + + def test_register_fail_with_not_existing_email( + self, client: Client, valid_payload, monkeypatch + ): + """Test that, when email is valid but doesn't actually exist, registration fails""" + + def always_fail(*_args, **_kwargs): + raise SMTPException + + monkeypatch.setattr(EmailMessage, "send", always_fail) + + response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 + error_html = ( + "
  • Nous n'avons pas réussi à vérifier que cette adresse mail existe.
  • " + ) + assertInHTML(error_html, str(response.content.decode())) @pytest.mark.django_db diff --git a/core/urls.py b/core/urls.py index 709346e4..a776cb94 100644 --- a/core/urls.py +++ b/core/urls.py @@ -75,7 +75,7 @@ urlpatterns = [ SithPasswordResetCompleteView.as_view(), name="password_reset_complete", ), - path("register/", register, name="register"), + path("register/", UserCreationView.as_view(), name="register"), # Group handling path("group/", GroupListView.as_view(), name="group_list"), path("group/new/", GroupCreateView.as_view(), name="group_new"), diff --git a/core/views/forms.py b/core/views/forms.py index 1ee9bda2..811b13d5 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -194,14 +194,6 @@ class RegisteringForm(UserCreationForm): model = User fields = ("first_name", "last_name", "email") - def save(self, *, commit=True): - user = super().save(commit=False) - user.set_password(self.cleaned_data["password1"]) - user.generate_username() - if commit: - user.save() - return user - class UserProfileForm(forms.ModelForm): """ diff --git a/core/views/user.py b/core/views/user.py index 6952a8b1..5a01a90b 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -23,17 +23,18 @@ # # This file contains all the views that concern the user model -import logging from datetime import date, timedelta +from smtplib import SMTPException from django.conf import settings -from django.contrib.auth import views +from django.contrib.auth import login, views from django.contrib.auth.forms import PasswordChangeForm from django.core.exceptions import PermissionDenied, ValidationError from django.forms import CheckboxSelectMultiple from django.forms.models import modelform_factory from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import get_object_or_404, redirect +from django.template.loader import render_to_string from django.template.response import TemplateResponse from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator @@ -46,7 +47,7 @@ from django.views.generic import ( TemplateView, ) from django.views.generic.dates import MonthMixin, YearMixin -from django.views.generic.edit import UpdateView +from django.views.generic.edit import FormView, UpdateView from honeypot.decorators import check_honeypot from api.views.sas import all_pictures_of_user @@ -80,6 +81,7 @@ class SithLoginView(views.LoginView): template_name = "core/login.jinja" authentication_form = LoginForm form_class = PasswordChangeForm + redirect_authenticated_user = True class SithPasswordChangeView(views.PasswordChangeView): @@ -163,28 +165,41 @@ class SithPasswordResetCompleteView(views.PasswordResetCompleteView): template_name = "core/password_reset_complete.jinja" -@check_honeypot -def register(request): - context = {} - if request.method == "POST": - form = RegisteringForm(request.POST) - if form.is_valid(): - logging.debug( - "Registering " - + form.cleaned_data["first_name"] - + form.cleaned_data["last_name"] +@method_decorator(check_honeypot, name="post") +class UserCreationView(FormView): + success_url = reverse_lazy("core:index") + form_class = RegisteringForm + template_name = "core/register.jinja" + + def form_valid(self, form): + # Just knowing that the user gave sound data isn't enough, + # we must also know if the given email actually exists. + # This step must happen after the whole validation has been made, + # but before saving the user, while being tightly coupled + # to the request/response cycle. + # Thus this is here. + user: User = form.save(commit=False) + username = user.generate_username() + try: + user.email_user( + "Création de votre compte AE", + render_to_string( + "core/register_confirm_mail.jinja", context={"username": username} + ), ) - u = form.save() - context["user_registered"] = u - context["tests"] = "TEST_REGISTER_USER_FORM_OK" - form = RegisteringForm() - else: - context["error"] = "Erreur" - context["tests"] = "TEST_REGISTER_USER_FORM_FAIL" - else: - form = RegisteringForm() - context["form"] = form.as_p() - return render(request, "core/register.jinja", context) + except SMTPException: + # if the email couldn't be sent, it's likely to be + # that the given email doesn't exist (which means it's either a typo or a bot). + # It may also be a genuine bug, but that's less likely to happen + # and wouldn't be critical as the favoured way to create an account + # is to contact an AE board member + form.add_error( + "email", _("We couldn't verify that this email actually exists") + ) + return super().form_invalid(form) + user = form.save() + login(self.request, user) + return super().form_valid(form) class UserTabsMixin(TabedViewMixin): diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 884375a2..1c3a693e 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -2,11 +2,11 @@ # Copyright (C) 2016 # This file is distributed under the same license as the Sith package. # Skia , 2016 -# +# msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-04 10:44+0200\n" +"POT-Creation-Date: 2024-07-10 16:10+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -16,166 +16,166 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: accounting/models.py:53 accounting/models.py:102 accounting/models.py:137 -#: accounting/models.py:212 club/models.py:50 com/models.py:287 -#: com/models.py:306 counter/models.py:212 counter/models.py:247 -#: counter/models.py:363 forum/models.py:58 launderette/models.py:30 -#: launderette/models.py:87 launderette/models.py:127 stock/models.py:39 -#: stock/models.py:62 stock/models.py:104 stock/models.py:132 +#: accounting/models.py:52 accounting/models.py:101 accounting/models.py:136 +#: accounting/models.py:211 club/models.py:54 com/models.py:286 +#: com/models.py:305 counter/models.py:212 counter/models.py:247 +#: counter/models.py:363 forum/models.py:60 launderette/models.py:29 +#: launderette/models.py:86 launderette/models.py:126 stock/models.py:38 +#: stock/models.py:61 stock/models.py:103 stock/models.py:131 msgid "name" msgstr "nom" -#: accounting/models.py:54 +#: accounting/models.py:53 msgid "street" msgstr "rue" -#: accounting/models.py:55 +#: accounting/models.py:54 msgid "city" msgstr "ville" -#: accounting/models.py:56 +#: accounting/models.py:55 msgid "postcode" msgstr "code postal" -#: accounting/models.py:57 +#: accounting/models.py:56 msgid "country" msgstr "pays" -#: accounting/models.py:58 core/models.py:361 +#: accounting/models.py:57 core/models.py:360 msgid "phone" msgstr "téléphone" -#: accounting/models.py:59 +#: accounting/models.py:58 msgid "email" msgstr "email" -#: accounting/models.py:60 +#: accounting/models.py:59 msgid "website" msgstr "site internet" -#: accounting/models.py:63 +#: accounting/models.py:62 msgid "company" msgstr "entreprise" -#: accounting/models.py:103 +#: accounting/models.py:102 msgid "iban" msgstr "IBAN" -#: accounting/models.py:104 +#: accounting/models.py:103 msgid "account number" msgstr "numéro de compte" -#: accounting/models.py:108 accounting/models.py:141 club/models.py:356 -#: com/models.py:77 com/models.py:272 com/models.py:312 counter/models.py:265 -#: counter/models.py:365 trombi/models.py:217 +#: accounting/models.py:107 accounting/models.py:140 club/models.py:356 +#: com/models.py:76 com/models.py:271 com/models.py:311 counter/models.py:265 +#: counter/models.py:365 trombi/models.py:215 msgid "club" msgstr "club" -#: accounting/models.py:113 +#: accounting/models.py:112 msgid "Bank account" msgstr "Compte en banque" -#: accounting/models.py:147 +#: accounting/models.py:146 msgid "bank account" msgstr "compte en banque" -#: accounting/models.py:152 +#: accounting/models.py:151 msgid "Club account" msgstr "Compte club" -#: accounting/models.py:199 +#: accounting/models.py:198 #, python-format msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:210 club/models.py:362 counter/models.py:883 -#: election/models.py:18 launderette/models.py:192 +#: accounting/models.py:209 club/models.py:362 counter/models.py:883 +#: election/models.py:18 launderette/models.py:185 msgid "start date" msgstr "date de début" -#: accounting/models.py:211 club/models.py:363 counter/models.py:884 +#: accounting/models.py:210 club/models.py:363 counter/models.py:884 #: election/models.py:19 msgid "end date" msgstr "date de fin" -#: accounting/models.py:213 +#: accounting/models.py:212 msgid "is closed" msgstr "est fermé" -#: accounting/models.py:218 accounting/models.py:551 +#: accounting/models.py:217 accounting/models.py:542 msgid "club account" msgstr "compte club" -#: accounting/models.py:221 accounting/models.py:287 counter/models.py:54 +#: accounting/models.py:220 accounting/models.py:286 counter/models.py:54 #: counter/models.py:601 msgid "amount" msgstr "montant" -#: accounting/models.py:222 +#: accounting/models.py:221 msgid "effective_amount" msgstr "montant effectif" -#: accounting/models.py:225 +#: accounting/models.py:224 msgid "General journal" msgstr "Classeur" -#: accounting/models.py:279 +#: accounting/models.py:278 msgid "number" msgstr "numéro" -#: accounting/models.py:284 +#: accounting/models.py:283 msgid "journal" msgstr "classeur" -#: accounting/models.py:288 core/models.py:914 core/models.py:1477 +#: accounting/models.py:287 core/models.py:916 core/models.py:1477 #: core/models.py:1525 core/models.py:1554 core/models.py:1580 #: counter/models.py:611 counter/models.py:706 counter/models.py:919 -#: eboutic/models.py:58 eboutic/models.py:227 forum/models.py:314 -#: forum/models.py:414 stock/models.py:103 +#: eboutic/models.py:57 eboutic/models.py:226 forum/models.py:314 +#: forum/models.py:413 stock/models.py:102 msgid "date" msgstr "date" -#: accounting/models.py:289 counter/models.py:214 counter/models.py:920 -#: pedagogy/models.py:218 stock/models.py:106 +#: accounting/models.py:288 counter/models.py:214 counter/models.py:920 +#: pedagogy/models.py:217 stock/models.py:105 msgid "comment" msgstr "commentaire" -#: accounting/models.py:291 counter/models.py:613 counter/models.py:708 +#: accounting/models.py:290 counter/models.py:613 counter/models.py:708 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" -#: accounting/models.py:296 +#: accounting/models.py:295 msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:301 eboutic/models.py:320 +#: accounting/models.py:300 eboutic/models.py:319 msgid "invoice" msgstr "facture" -#: accounting/models.py:306 +#: accounting/models.py:305 msgid "is done" msgstr "est fait" -#: accounting/models.py:310 +#: accounting/models.py:309 msgid "simple type" msgstr "type simplifié" -#: accounting/models.py:318 accounting/models.py:487 +#: accounting/models.py:317 accounting/models.py:481 msgid "accounting type" msgstr "type comptable" -#: accounting/models.py:326 accounting/models.py:475 accounting/models.py:512 -#: accounting/models.py:547 core/models.py:1553 core/models.py:1581 +#: accounting/models.py:325 accounting/models.py:469 accounting/models.py:506 +#: accounting/models.py:538 core/models.py:1553 core/models.py:1581 #: counter/models.py:672 msgid "label" msgstr "étiquette" -#: accounting/models.py:332 +#: accounting/models.py:331 msgid "target type" msgstr "type de cible" -#: accounting/models.py:335 club/models.py:528 +#: accounting/models.py:334 club/models.py:525 #: club/templates/club/club_members.jinja:17 #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 @@ -187,7 +187,7 @@ msgstr "type de cible" msgid "User" msgstr "Utilisateur" -#: accounting/models.py:336 club/models.py:427 +#: accounting/models.py:335 club/models.py:424 #: club/templates/club/club_detail.jinja:12 #: com/templates/com/mailing_admin.jinja:11 #: com/templates/com/news_admin_list.jinja:23 @@ -211,36 +211,36 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:337 core/views/user.py:277 +#: accounting/models.py:336 core/views/user.py:297 msgid "Account" msgstr "Compte" -#: accounting/models.py:338 +#: accounting/models.py:337 msgid "Company" msgstr "Entreprise" -#: accounting/models.py:339 core/models.py:308 sith/settings.py:398 +#: accounting/models.py:338 core/models.py:307 sith/settings.py:403 #: stock/templates/stock/shopping_list_items.jinja:37 msgid "Other" msgstr "Autre" -#: accounting/models.py:342 +#: accounting/models.py:341 msgid "target id" msgstr "id de la cible" -#: accounting/models.py:344 +#: accounting/models.py:343 msgid "target label" msgstr "nom de la cible" -#: accounting/models.py:349 +#: accounting/models.py:348 msgid "linked operation" msgstr "opération liée" -#: accounting/models.py:369 +#: accounting/models.py:380 msgid "The date must be set." msgstr "La date doit être indiquée." -#: accounting/models.py:373 +#: accounting/models.py:384 #, python-format msgid "" "The date can not be before the start date of the journal, which is\n" @@ -249,16 +249,16 @@ msgstr "" "La date ne peut pas être avant la date de début du journal, qui est\n" "%(start_date)s." -#: accounting/models.py:383 +#: accounting/models.py:394 msgid "Target does not exists" msgstr "La cible n'existe pas." -#: accounting/models.py:386 +#: accounting/models.py:397 msgid "Please add a target label if you set no existing target" msgstr "" "Merci d'ajouter un nom de cible si vous ne spécifiez pas de cible existante" -#: accounting/models.py:391 +#: accounting/models.py:402 msgid "" "You need to provide ether a simplified accounting type or a standard " "accounting type" @@ -266,41 +266,41 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:257 pedagogy/models.py:45 +#: accounting/models.py:461 counter/models.py:257 pedagogy/models.py:44 msgid "code" msgstr "code" -#: accounting/models.py:471 +#: accounting/models.py:465 msgid "An accounting type code contains only numbers" msgstr "Un code comptable ne contient que des numéros" -#: accounting/models.py:477 +#: accounting/models.py:471 msgid "movement type" msgstr "type de mouvement" -#: accounting/models.py:479 +#: accounting/models.py:473 #: accounting/templates/accounting/journal_statement_nature.jinja:9 #: accounting/templates/accounting/journal_statement_person.jinja:12 -#: accounting/views.py:594 +#: accounting/views.py:593 msgid "Credit" msgstr "Crédit" -#: accounting/models.py:480 +#: accounting/models.py:474 #: accounting/templates/accounting/journal_statement_nature.jinja:28 #: accounting/templates/accounting/journal_statement_person.jinja:40 -#: accounting/views.py:594 +#: accounting/views.py:593 msgid "Debit" msgstr "Débit" -#: accounting/models.py:481 +#: accounting/models.py:475 msgid "Neutral" msgstr "Neutre" -#: accounting/models.py:516 +#: accounting/models.py:510 msgid "simplified accounting types" msgstr "type simplifié" -#: accounting/models.py:521 +#: accounting/models.py:515 msgid "simplified type" msgstr "type simplifié" @@ -379,7 +379,7 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:218 pedagogy/templates/pedagogy/guide.jinja:67 +#: launderette/views.py:217 pedagogy/templates/pedagogy/guide.jinja:67 #: pedagogy/templates/pedagogy/guide.jinja:90 #: pedagogy/templates/pedagogy/guide.jinja:126 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 @@ -393,7 +393,7 @@ msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:80 core/views/user.py:196 sas/templates/sas/picture.jinja:79 +#: club/views.py:79 core/views/user.py:216 sas/templates/sas/picture.jinja:79 msgid "Infos" msgstr "Infos" @@ -412,7 +412,7 @@ msgstr "Nouveau compte club" #: accounting/templates/accounting/bank_account_details.jinja:27 #: accounting/templates/accounting/bank_account_list.jinja:22 #: accounting/templates/accounting/club_account_details.jinja:58 -#: accounting/templates/accounting/journal_details.jinja:92 club/views.py:126 +#: accounting/templates/accounting/journal_details.jinja:92 club/views.py:125 #: com/templates/com/news_admin_list.jinja:39 #: com/templates/com/news_admin_list.jinja:68 #: com/templates/com/news_admin_list.jinja:115 @@ -427,7 +427,7 @@ msgstr "Nouveau compte club" #: com/templates/com/weekmail.jinja:61 core/templates/core/file.jinja:38 #: core/templates/core/group_list.jinja:24 core/templates/core/page.jinja:35 #: core/templates/core/poster_list.jinja:40 -#: core/templates/core/user_tools.jinja:71 core/views/user.py:226 +#: core/templates/core/user_tools.jinja:71 core/views/user.py:246 #: counter/templates/counter/cash_summary_list.jinja:53 #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 @@ -530,7 +530,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:444 +#: sith/settings.py:449 msgid "Closed" msgstr "Fermé" @@ -629,7 +629,7 @@ msgstr "No" #: counter/templates/counter/last_ops.jinja:20 #: counter/templates/counter/last_ops.jinja:45 #: counter/templates/counter/refilling_list.jinja:16 -#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:364 +#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:357 #: stock/templates/stock/stock_shopping_list.jinja:25 #: stock/templates/stock/stock_shopping_list.jinja:54 #: trombi/templates/trombi/user_profile.jinja:40 @@ -653,7 +653,7 @@ msgid "Target" msgstr "Cible" #: accounting/templates/accounting/journal_details.jinja:38 -#: core/views/forms.py:95 +#: core/views/forms.py:94 msgid "Code" msgstr "Code" @@ -667,7 +667,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1078 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1077 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -799,7 +799,7 @@ msgstr "Sauver" #: accounting/templates/accounting/refound_account.jinja:4 #: accounting/templates/accounting/refound_account.jinja:9 -#: accounting/views.py:925 +#: accounting/views.py:924 msgid "Refound account" msgstr "Remboursement de compte" @@ -820,189 +820,189 @@ msgstr "Types simplifiés" msgid "New simplified type" msgstr "Nouveau type simplifié" -#: accounting/views.py:239 accounting/views.py:249 accounting/views.py:569 +#: accounting/views.py:238 accounting/views.py:248 accounting/views.py:568 msgid "Journal" msgstr "Classeur" -#: accounting/views.py:259 +#: accounting/views.py:258 msgid "Statement by nature" msgstr "Bilan par nature" -#: accounting/views.py:269 +#: accounting/views.py:268 msgid "Statement by person" msgstr "Bilan par personne" -#: accounting/views.py:279 +#: accounting/views.py:278 msgid "Accounting statement" msgstr "Bilan comptable" -#: accounting/views.py:383 +#: accounting/views.py:382 msgid "Link this operation to the target account" msgstr "Lier cette opération au compte cible" -#: accounting/views.py:413 +#: accounting/views.py:412 msgid "The target must be set." msgstr "La cible doit être indiquée." -#: accounting/views.py:428 +#: accounting/views.py:427 msgid "The amount must be set." msgstr "Le montant doit être indiqué." -#: accounting/views.py:563 accounting/views.py:569 +#: accounting/views.py:562 accounting/views.py:568 msgid "Operation" msgstr "Opération" -#: accounting/views.py:578 +#: accounting/views.py:577 msgid "Financial proof: " msgstr "Justificatif de libellé : " -#: accounting/views.py:581 +#: accounting/views.py:580 #, python-format msgid "Club: %(club_name)s" msgstr "Club : %(club_name)s" -#: accounting/views.py:586 +#: accounting/views.py:585 #, python-format msgid "Label: %(op_label)s" msgstr "Libellé : %(op_label)s" -#: accounting/views.py:589 +#: accounting/views.py:588 #, python-format msgid "Date: %(date)s" msgstr "Date : %(date)s" -#: accounting/views.py:597 +#: accounting/views.py:596 #, python-format msgid "Amount: %(amount).2f €" msgstr "Montant : %(amount).2f €" -#: accounting/views.py:612 +#: accounting/views.py:611 msgid "Debtor" msgstr "Débiteur" -#: accounting/views.py:612 +#: accounting/views.py:611 msgid "Creditor" msgstr "Créditeur" -#: accounting/views.py:617 +#: accounting/views.py:616 msgid "Comment:" msgstr "Commentaire :" -#: accounting/views.py:642 +#: accounting/views.py:641 msgid "Signature:" msgstr "Signature :" -#: accounting/views.py:710 +#: accounting/views.py:709 msgid "General statement" msgstr "Bilan général" -#: accounting/views.py:717 +#: accounting/views.py:716 msgid "No label operations" msgstr "Opérations sans étiquette" -#: accounting/views.py:881 +#: accounting/views.py:880 msgid "Refound this account" msgstr "Rembourser ce compte" -#: club/forms.py:58 club/forms.py:190 +#: club/forms.py:57 club/forms.py:189 msgid "Users to add" msgstr "Utilisateurs à ajouter" -#: club/forms.py:59 club/forms.py:191 core/views/group.py:52 +#: club/forms.py:58 club/forms.py:190 core/views/group.py:51 msgid "Search users to add (one or more)." msgstr "Recherche les utilisateurs à ajouter (un ou plus)." -#: club/forms.py:68 +#: club/forms.py:67 msgid "New Mailing" msgstr "Nouvelle mailing liste" -#: club/forms.py:69 +#: club/forms.py:68 msgid "Subscribe" msgstr "S'abonner" -#: club/forms.py:70 club/forms.py:83 com/templates/com/news_admin_list.jinja:40 +#: club/forms.py:69 club/forms.py:82 com/templates/com/news_admin_list.jinja:40 #: com/templates/com/news_admin_list.jinja:116 #: com/templates/com/news_admin_list.jinja:198 #: com/templates/com/news_admin_list.jinja:274 msgid "Remove" msgstr "Retirer" -#: club/forms.py:73 launderette/views.py:220 +#: club/forms.py:72 launderette/views.py:219 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" -#: club/forms.py:113 club/tests.py:742 +#: club/forms.py:112 club/tests.py:741 msgid "This field is required" msgstr "Ce champ est obligatoire" -#: club/forms.py:125 club/forms.py:250 club/tests.py:755 +#: club/forms.py:124 club/forms.py:249 club/tests.py:754 msgid "One of the selected users doesn't exist" msgstr "Un des utilisateurs sélectionné n'existe pas" -#: club/forms.py:129 club/tests.py:772 +#: club/forms.py:128 club/tests.py:771 msgid "One of the selected users doesn't have an email address" msgstr "Un des utilisateurs sélectionnés n'a pas d'adresse email" -#: club/forms.py:140 +#: club/forms.py:139 msgid "An action is required" msgstr "Une action est requise" -#: club/forms.py:151 club/tests.py:729 +#: club/forms.py:150 club/tests.py:728 msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:159 counter/forms.py:165 +#: club/forms.py:158 counter/forms.py:173 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:160 com/views.py:81 com/views.py:196 counter/forms.py:166 -#: election/views.py:167 subscription/views.py:39 +#: club/forms.py:159 com/views.py:80 com/views.py:195 counter/forms.py:174 +#: election/views.py:167 subscription/views.py:38 msgid "End date" msgstr "Date de fin" -#: club/forms.py:163 club/templates/club/club_sellings.jinja:21 +#: club/forms.py:162 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:149 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:148 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:170 counter/views.py:776 +#: club/forms.py:169 counter/views.py:775 msgid "Products" msgstr "Produits" -#: club/forms.py:175 counter/views.py:781 +#: club/forms.py:174 counter/views.py:780 msgid "Archived products" msgstr "Produits archivés" -#: club/forms.py:232 club/templates/club/club_members.jinja:22 +#: club/forms.py:231 club/templates/club/club_members.jinja:22 #: club/templates/club/club_members.jinja:48 #: core/templates/core/user_clubs.jinja:31 msgid "Mark as old" msgstr "Marquer comme ancien" -#: club/forms.py:254 +#: club/forms.py:253 msgid "User must be subscriber to take part to a club" msgstr "L'utilisateur doit être cotisant pour faire partie d'un club" -#: club/forms.py:258 core/views/group.py:71 +#: club/forms.py:257 core/views/group.py:70 msgid "You can not add the same user twice" msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" -#: club/forms.py:279 +#: club/forms.py:278 msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:290 sas/views.py:119 sas/views.py:191 sas/views.py:290 +#: club/forms.py:289 sas/views.py:118 sas/views.py:185 sas/views.py:284 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" -#: club/models.py:55 +#: club/models.py:59 msgid "unix name" msgstr "nom unix" -#: club/models.py:62 +#: club/models.py:66 msgid "" "Enter a valid unix name. This value may contain only letters, numbers ./-/_ " "characters." @@ -1010,82 +1010,78 @@ msgstr "" "Entrez un nom UNIX valide. Cette valeur peut contenir uniquement des " "lettres, des nombres, et les caractères ./-/_" -#: club/models.py:67 +#: club/models.py:71 msgid "A club with that unix name already exists." msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:70 +#: club/models.py:74 msgid "logo" msgstr "logo" -#: club/models.py:72 +#: club/models.py:76 msgid "is active" msgstr "actif" -#: club/models.py:74 +#: club/models.py:78 msgid "short description" msgstr "description courte" -#: club/models.py:76 core/models.py:363 +#: club/models.py:80 core/models.py:362 msgid "address" msgstr "Adresse" -#: club/models.py:97 core/models.py:274 +#: club/models.py:97 core/models.py:273 msgid "home" msgstr "home" -#: club/models.py:121 +#: club/models.py:149 msgid "You can not make loops in clubs" msgstr "Vous ne pouvez pas faire de boucles dans les clubs" -#: club/models.py:145 +#: club/models.py:173 msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." #: club/models.py:348 counter/models.py:874 counter/models.py:910 -#: eboutic/models.py:54 eboutic/models.py:223 election/models.py:191 -#: launderette/models.py:141 launderette/models.py:211 sas/models.py:240 -#: trombi/models.py:213 +#: eboutic/models.py:53 eboutic/models.py:222 election/models.py:191 +#: launderette/models.py:140 launderette/models.py:204 sas/models.py:231 +#: trombi/models.py:211 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:365 core/models.py:327 election/models.py:186 -#: election/models.py:222 trombi/models.py:218 +#: club/models.py:365 core/models.py:326 election/models.py:186 +#: election/models.py:222 trombi/models.py:216 msgid "role" msgstr "rôle" -#: club/models.py:370 core/models.py:85 counter/models.py:213 +#: club/models.py:370 core/models.py:84 counter/models.py:213 #: counter/models.py:248 election/models.py:15 election/models.py:119 -#: election/models.py:196 forum/models.py:59 forum/models.py:243 +#: election/models.py:196 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" -#: club/models.py:382 -msgid "past member" -msgstr "Anciens membres" - -#: club/models.py:434 club/models.py:534 +#: club/models.py:431 club/models.py:531 msgid "Email address" msgstr "Adresse email" -#: club/models.py:442 +#: club/models.py:439 msgid "Enter a valid address. Only the root of the address is needed." msgstr "" "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." -#: club/models.py:446 com/models.py:85 com/models.py:322 core/models.py:915 +#: club/models.py:443 com/models.py:84 com/models.py:321 core/models.py:917 msgid "is moderated" msgstr "est modéré" -#: club/models.py:450 com/models.py:89 com/models.py:326 +#: club/models.py:447 com/models.py:88 com/models.py:325 msgid "moderator" msgstr "modérateur" -#: club/models.py:457 +#: club/models.py:474 msgid "This mailing list already exists." msgstr "Cette liste de diffusion existe déjà." -#: club/models.py:520 club/templates/club/mailing.jinja:23 +#: club/models.py:517 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" @@ -1093,7 +1089,7 @@ msgstr "Liste de diffusion" msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:549 club/tests.py:800 +#: club/models.py:549 club/tests.py:799 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" @@ -1151,8 +1147,8 @@ msgid "There are no members in this club." msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:80 -#: core/templates/core/file_detail.jinja:19 core/views/forms.py:343 -#: launderette/views.py:218 trombi/templates/trombi/detail.jinja:19 +#: core/templates/core/file_detail.jinja:19 core/views/forms.py:334 +#: launderette/views.py:217 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1347,153 +1343,153 @@ msgstr "Aucune page n'existe pour ce club" msgid "Club stats" msgstr "Statistiques du club" -#: club/views.py:90 +#: club/views.py:89 msgid "Members" msgstr "Membres" -#: club/views.py:99 +#: club/views.py:98 msgid "Old members" msgstr "Anciens membres" -#: club/views.py:109 core/templates/core/page.jinja:33 +#: club/views.py:108 core/templates/core/page.jinja:33 msgid "History" msgstr "Historique" -#: club/views.py:117 core/templates/core/base.jinja:95 core/views/user.py:219 +#: club/views.py:116 core/templates/core/base.jinja:96 core/views/user.py:239 #: sas/templates/sas/picture.jinja:100 trombi/views.py:62 msgid "Tools" msgstr "Outils" -#: club/views.py:137 +#: club/views.py:136 msgid "Edit club page" msgstr "Éditer la page de club" -#: club/views.py:146 club/views.py:472 +#: club/views.py:145 club/views.py:471 #, fuzzy #| msgid "Selling" msgid "Sellings" msgstr "Vente" -#: club/views.py:153 +#: club/views.py:152 msgid "Mailing list" msgstr "Listes de diffusion" -#: club/views.py:162 com/views.py:131 +#: club/views.py:161 com/views.py:130 msgid "Posters list" msgstr "Liste d'affiches" -#: club/views.py:172 counter/templates/counter/counter_list.jinja:21 +#: club/views.py:171 counter/templates/counter/counter_list.jinja:21 #: counter/templates/counter/counter_list.jinja:43 #: counter/templates/counter/counter_list.jinja:59 msgid "Props" msgstr "Propriétés" -#: com/models.py:45 +#: com/models.py:44 msgid "alert message" msgstr "message d'alerte" -#: com/models.py:46 +#: com/models.py:45 msgid "info message" msgstr "message d'info" -#: com/models.py:47 +#: com/models.py:46 msgid "weekmail destinations" msgstr "destinataires du weekmail" -#: com/models.py:60 +#: com/models.py:59 msgid "Notice" msgstr "Information" -#: com/models.py:61 +#: com/models.py:60 msgid "Event" msgstr "Événement" -#: com/models.py:62 +#: com/models.py:61 msgid "Weekly" msgstr "Hebdomadaire" -#: com/models.py:63 +#: com/models.py:62 msgid "Call" msgstr "Appel" -#: com/models.py:70 com/models.py:179 com/models.py:261 election/models.py:14 -#: election/models.py:118 election/models.py:158 forum/models.py:254 -#: forum/models.py:312 pedagogy/models.py:100 +#: com/models.py:69 com/models.py:178 com/models.py:260 election/models.py:14 +#: election/models.py:118 election/models.py:158 forum/models.py:256 +#: forum/models.py:312 pedagogy/models.py:99 msgid "title" msgstr "titre" -#: com/models.py:71 +#: com/models.py:70 msgid "summary" msgstr "résumé" -#: com/models.py:72 com/models.py:262 trombi/models.py:197 +#: com/models.py:71 com/models.py:261 trombi/models.py:192 msgid "content" msgstr "contenu" -#: com/models.py:74 core/models.py:1523 launderette/models.py:95 -#: launderette/models.py:135 launderette/models.py:194 stock/models.py:79 -#: stock/models.py:136 +#: com/models.py:73 core/models.py:1523 launderette/models.py:94 +#: launderette/models.py:134 launderette/models.py:187 stock/models.py:78 +#: stock/models.py:135 msgid "type" msgstr "type" -#: com/models.py:82 com/models.py:266 pedagogy/models.py:60 -#: pedagogy/models.py:210 trombi/models.py:187 +#: com/models.py:81 com/models.py:265 pedagogy/models.py:59 +#: pedagogy/models.py:209 trombi/models.py:182 msgid "author" msgstr "auteur" -#: com/models.py:157 +#: com/models.py:156 msgid "news_date" msgstr "date de la nouvelle" -#: com/models.py:160 +#: com/models.py:159 msgid "start_date" msgstr "date de début" -#: com/models.py:161 +#: com/models.py:160 msgid "end_date" msgstr "date de fin" -#: com/models.py:180 +#: com/models.py:179 msgid "intro" msgstr "intro" -#: com/models.py:181 +#: com/models.py:180 msgid "joke" msgstr "blague" -#: com/models.py:182 +#: com/models.py:181 msgid "protip" msgstr "astuce" -#: com/models.py:183 +#: com/models.py:182 msgid "conclusion" msgstr "conclusion" -#: com/models.py:184 +#: com/models.py:183 msgid "sent" msgstr "envoyé" -#: com/models.py:257 +#: com/models.py:256 msgid "weekmail" msgstr "weekmail" -#: com/models.py:275 +#: com/models.py:274 msgid "rank" msgstr "rang" -#: com/models.py:308 core/models.py:880 core/models.py:930 +#: com/models.py:307 core/models.py:882 core/models.py:932 msgid "file" msgstr "fichier" -#: com/models.py:320 +#: com/models.py:319 msgid "display time" msgstr "temps d'affichage" -#: com/models.py:348 +#: com/models.py:350 msgid "Begin date should be before end date" msgstr "La date de début doit être avant celle de fin" -#: com/templates/com/mailing_admin.jinja:4 com/views.py:124 +#: com/templates/com/mailing_admin.jinja:4 com/views.py:123 #: core/templates/core/user_tools.jinja:144 msgid "Mailing lists administration" msgstr "Administration des mailing listes" @@ -1563,7 +1559,7 @@ msgstr "Informations affichées" #: com/templates/com/news_admin_list.jinja:248 #: com/templates/com/news_admin_list.jinja:285 #: launderette/templates/launderette/launderette_admin.jinja:42 -#: launderette/views.py:225 +#: launderette/views.py:224 msgid "Type" msgstr "Type" @@ -1577,7 +1573,7 @@ msgstr "Type" #: com/templates/com/news_admin_list.jinja:286 #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 #: forum/templates/forum/forum.jinja:28 forum/templates/forum/forum.jinja:47 -#: forum/templates/forum/main.jinja:30 forum/views.py:243 +#: forum/templates/forum/main.jinja:30 forum/views.py:242 #: pedagogy/templates/pedagogy/guide.jinja:60 msgid "Title" msgstr "Titre" @@ -1648,7 +1644,7 @@ msgid "Calls to moderate" msgstr "Appels à modérer" #: com/templates/com/news_admin_list.jinja:242 -#: core/templates/core/base.jinja:210 +#: core/templates/core/base.jinja:211 msgid "Events" msgstr "Événements" @@ -1740,7 +1736,7 @@ msgstr "Anniversaires" msgid "%(age)s year old" msgstr "%(age)s ans" -#: com/templates/com/news_list.jinja:156 com/tests.py:102 com/tests.py:112 +#: com/templates/com/news_list.jinja:156 com/tests.py:101 com/tests.py:111 msgid "You need an up to date subscription to access this content" msgstr "Votre cotisation doit être à jour pour accéder à cette section" @@ -1809,7 +1805,7 @@ msgid "Slideshow" msgstr "Diaporama" #: com/templates/com/weekmail.jinja:5 com/templates/com/weekmail.jinja:9 -#: com/views.py:101 core/templates/core/user_tools.jinja:137 +#: com/views.py:100 core/templates/core/user_tools.jinja:137 msgid "Weekmail" msgstr "Weekmail" @@ -1852,7 +1848,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:11 -#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:218 +#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:217 #: pedagogy/templates/pedagogy/uv_detail.jinja:12 #: pedagogy/templates/pedagogy/uv_detail.jinja:21 #: stock/templates/stock/shopping_list_items.jinja:9 @@ -1906,90 +1902,90 @@ msgstr "Astuce" msgid "Final word" msgstr "Le mot de la fin" -#: com/views.py:74 +#: com/views.py:73 msgid "Format: 16:9 | Resolution: 1920x1080" msgstr "Format : 16:9 | Résolution : 1920x1080" -#: com/views.py:77 com/views.py:195 election/views.py:166 -#: subscription/views.py:36 +#: com/views.py:76 com/views.py:194 election/views.py:166 +#: subscription/views.py:35 msgid "Start date" msgstr "Date de début" -#: com/views.py:96 +#: com/views.py:95 msgid "Communication administration" msgstr "Administration de la communication" -#: com/views.py:107 core/templates/core/user_tools.jinja:138 +#: com/views.py:106 core/templates/core/user_tools.jinja:138 msgid "Weekmail destinations" msgstr "Destinataires du Weekmail" -#: com/views.py:111 +#: com/views.py:110 msgid "Info message" msgstr "Message d'info" -#: com/views.py:117 +#: com/views.py:116 msgid "Alert message" msgstr "Message d'alerte" -#: com/views.py:138 +#: com/views.py:137 msgid "Screens list" msgstr "Liste d'écrans" -#: com/views.py:197 +#: com/views.py:196 msgid "Until" msgstr "Jusqu'à" -#: com/views.py:199 +#: com/views.py:198 msgid "Automoderation" msgstr "Automodération" -#: com/views.py:206 com/views.py:210 com/views.py:224 +#: com/views.py:205 com/views.py:209 com/views.py:223 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: com/views.py:220 +#: com/views.py:219 msgid "You crazy? You can not finish an event before starting it." msgstr "T'es fou? Un événement ne peut pas finir avant même de commencer." -#: com/views.py:459 +#: com/views.py:443 msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" -#: com/views.py:474 +#: com/views.py:458 msgid "Weekmail of the " msgstr "Weekmail du " -#: com/views.py:584 +#: com/views.py:562 msgid "" "You must be a board member of the selected club to post in the Weekmail." msgstr "" "Vous devez êtres un membre du bureau du club sélectionné pour poster dans le " "Weekmail." -#: core/models.py:80 +#: core/models.py:79 msgid "meta group status" msgstr "status du meta-groupe" -#: core/models.py:82 +#: core/models.py:81 msgid "Whether a group is a meta group or not" msgstr "Si un groupe est un meta-groupe ou pas" -#: core/models.py:171 +#: core/models.py:170 #, python-format msgid "%(value)s is not a valid promo (between 0 and %(end)s)" msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)" -#: core/models.py:227 +#: core/models.py:226 msgid "username" msgstr "nom d'utilisateur" -#: core/models.py:231 +#: core/models.py:230 msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." msgstr "" "Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:237 +#: core/models.py:236 msgid "" "Enter a valid username. This value may contain only letters, numbers and ./" "+/-/_ characters." @@ -1997,43 +1993,43 @@ msgstr "" "Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:243 +#: core/models.py:242 msgid "A user with that username already exists." msgstr "Un utilisateur de ce nom existe déjà" -#: core/models.py:245 +#: core/models.py:244 msgid "first name" msgstr "Prénom" -#: core/models.py:246 +#: core/models.py:245 msgid "last name" msgstr "Nom" -#: core/models.py:247 +#: core/models.py:246 msgid "email address" msgstr "adresse email" -#: core/models.py:248 +#: core/models.py:247 msgid "date of birth" msgstr "date de naissance" -#: core/models.py:249 +#: core/models.py:248 msgid "nick name" msgstr "surnom" -#: core/models.py:251 +#: core/models.py:250 msgid "staff status" msgstr "status \"staff\"" -#: core/models.py:253 +#: core/models.py:252 msgid "Designates whether the user can log into this admin site." msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site." -#: core/models.py:256 +#: core/models.py:255 msgid "active" msgstr "actif" -#: core/models.py:259 +#: core/models.py:258 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -2041,163 +2037,163 @@ msgstr "" "Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "lieu de supprimer les comptes." -#: core/models.py:263 +#: core/models.py:262 msgid "date joined" msgstr "date d'inscription" -#: core/models.py:264 +#: core/models.py:263 msgid "last update" msgstr "dernière mise à jour" -#: core/models.py:266 +#: core/models.py:265 msgid "superuser" msgstr "super-utilisateur" -#: core/models.py:268 +#: core/models.py:267 msgid "Designates whether this user is a superuser. " msgstr "Est-ce que l'utilisateur est super-utilisateur." -#: core/models.py:282 +#: core/models.py:281 msgid "profile" msgstr "profil" -#: core/models.py:290 +#: core/models.py:289 msgid "avatar" msgstr "avatar" -#: core/models.py:298 +#: core/models.py:297 msgid "scrub" msgstr "blouse" -#: core/models.py:304 +#: core/models.py:303 msgid "sex" msgstr "Genre" -#: core/models.py:308 +#: core/models.py:307 msgid "Man" msgstr "Homme" -#: core/models.py:308 +#: core/models.py:307 msgid "Woman" msgstr "Femme" -#: core/models.py:310 +#: core/models.py:309 msgid "pronouns" msgstr "pronoms" -#: core/models.py:312 +#: core/models.py:311 msgid "tshirt size" msgstr "taille de t-shirt" -#: core/models.py:315 +#: core/models.py:314 msgid "-" msgstr "-" -#: core/models.py:316 +#: core/models.py:315 msgid "XS" msgstr "XS" -#: core/models.py:317 +#: core/models.py:316 msgid "S" msgstr "S" -#: core/models.py:318 +#: core/models.py:317 msgid "M" msgstr "M" -#: core/models.py:319 +#: core/models.py:318 msgid "L" msgstr "L" -#: core/models.py:320 +#: core/models.py:319 msgid "XL" msgstr "XL" -#: core/models.py:321 +#: core/models.py:320 msgid "XXL" msgstr "XXL" -#: core/models.py:322 +#: core/models.py:321 msgid "XXXL" msgstr "XXXL" -#: core/models.py:330 +#: core/models.py:329 msgid "Student" msgstr "Étudiant" -#: core/models.py:331 +#: core/models.py:330 msgid "Administrative agent" msgstr "Personnel administratif" -#: core/models.py:332 +#: core/models.py:331 msgid "Teacher" msgstr "Enseignant" -#: core/models.py:333 +#: core/models.py:332 msgid "Agent" msgstr "Personnel" -#: core/models.py:334 +#: core/models.py:333 msgid "Doctor" msgstr "Doctorant" -#: core/models.py:335 +#: core/models.py:334 msgid "Former student" msgstr "Ancien étudiant" -#: core/models.py:336 +#: core/models.py:335 msgid "Service" msgstr "Service" -#: core/models.py:342 +#: core/models.py:341 msgid "department" msgstr "département" -#: core/models.py:349 +#: core/models.py:348 msgid "dpt option" msgstr "Filière" -#: core/models.py:351 pedagogy/models.py:73 pedagogy/models.py:302 +#: core/models.py:350 pedagogy/models.py:72 pedagogy/models.py:301 msgid "semester" msgstr "semestre" -#: core/models.py:352 +#: core/models.py:351 msgid "quote" msgstr "citation" -#: core/models.py:353 +#: core/models.py:352 msgid "school" msgstr "école" -#: core/models.py:355 +#: core/models.py:354 msgid "promo" msgstr "promo" -#: core/models.py:358 +#: core/models.py:357 msgid "forum signature" msgstr "signature du forum" -#: core/models.py:360 +#: core/models.py:359 msgid "second email address" msgstr "adresse email secondaire" -#: core/models.py:362 +#: core/models.py:361 msgid "parent phone" msgstr "téléphone des parents" -#: core/models.py:365 +#: core/models.py:364 msgid "parent address" msgstr "adresse des parents" -#: core/models.py:368 +#: core/models.py:367 msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:569 +#: core/models.py:568 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:709 core/templates/core/macros.jinja:75 +#: core/models.py:708 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 #: core/templates/core/user_detail.jinja:104 #: core/templates/core/user_detail.jinja:105 @@ -2226,101 +2222,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:833 +#: core/models.py:832 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:840 +#: core/models.py:839 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:841 +#: core/models.py:840 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:843 +#: core/models.py:842 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:846 +#: core/models.py:845 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:869 +#: core/models.py:871 msgid "file name" msgstr "nom du fichier" -#: core/models.py:873 core/models.py:1245 +#: core/models.py:875 core/models.py:1252 msgid "parent" msgstr "parent" -#: core/models.py:887 +#: core/models.py:889 msgid "compressed file" msgstr "version allégée" -#: core/models.py:894 +#: core/models.py:896 msgid "thumbnail" msgstr "miniature" -#: core/models.py:902 core/models.py:919 +#: core/models.py:904 core/models.py:921 msgid "owner" msgstr "propriétaire" -#: core/models.py:906 core/models.py:1266 core/views/files.py:224 +#: core/models.py:908 core/models.py:1269 core/views/files.py:223 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:909 core/models.py:1269 core/views/files.py:227 +#: core/models.py:911 core/models.py:1272 core/views/files.py:226 msgid "view group" msgstr "groupe de vue" -#: core/models.py:911 +#: core/models.py:913 msgid "is folder" msgstr "est un dossier" -#: core/models.py:912 +#: core/models.py:914 msgid "mime type" msgstr "type mime" -#: core/models.py:913 +#: core/models.py:915 msgid "size" msgstr "taille" -#: core/models.py:924 +#: core/models.py:926 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:926 +#: core/models.py:928 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:998 +#: core/models.py:1025 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:1000 core/models.py:1004 +#: core/models.py:1027 core/models.py:1031 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:1007 +#: core/models.py:1034 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:1018 +#: core/models.py:1045 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:1035 +#: core/models.py:1062 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1228 +#: core/models.py:1235 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1234 +#: core/models.py:1241 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2328,27 +2324,27 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1252 +#: core/models.py:1259 msgid "page name" msgstr "nom de la page" -#: core/models.py:1261 +#: core/models.py:1264 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1274 +#: core/models.py:1277 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1281 +#: core/models.py:1284 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1311 +#: core/models.py:1343 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1314 +#: core/models.py:1346 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" @@ -2396,18 +2392,18 @@ msgstr "500, Erreur Serveur" msgid "Welcome!" msgstr "Bienvenue !" -#: core/templates/core/base.jinja:53 core/templates/core/login.jinja:8 -#: core/templates/core/login.jinja:18 core/templates/core/login.jinja:50 +#: core/templates/core/base.jinja:54 core/templates/core/login.jinja:8 +#: core/templates/core/login.jinja:18 core/templates/core/login.jinja:51 #: core/templates/core/password_reset_complete.jinja:5 msgid "Login" msgstr "Connexion" -#: core/templates/core/base.jinja:54 core/templates/core/register.jinja:7 -#: core/templates/core/register.jinja:16 core/templates/core/register.jinja:27 +#: core/templates/core/base.jinja:55 core/templates/core/register.jinja:7 +#: core/templates/core/register.jinja:16 core/templates/core/register.jinja:22 msgid "Register" msgstr "Inscription" -#: core/templates/core/base.jinja:60 core/templates/core/base.jinja:61 +#: core/templates/core/base.jinja:61 core/templates/core/base.jinja:62 #: forum/templates/forum/macros.jinja:171 #: forum/templates/forum/macros.jinja:175 #: matmat/templates/matmat/search_form.jinja:37 @@ -2417,52 +2413,52 @@ msgstr "Inscription" msgid "Search" msgstr "Recherche" -#: core/templates/core/base.jinja:96 +#: core/templates/core/base.jinja:97 msgid "Logout" msgstr "Déconnexion" -#: core/templates/core/base.jinja:144 +#: core/templates/core/base.jinja:145 msgid "You do not have any unread notification" msgstr "Vous n'avez aucune notification non lue" -#: core/templates/core/base.jinja:149 +#: core/templates/core/base.jinja:150 msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:152 +#: core/templates/core/base.jinja:153 #: forum/templates/forum/last_unread.jinja:17 msgid "Mark all as read" msgstr "Marquer tout comme lu" -#: core/templates/core/base.jinja:200 +#: core/templates/core/base.jinja:201 msgid "Main" msgstr "Accueil" -#: core/templates/core/base.jinja:202 +#: core/templates/core/base.jinja:203 msgid "Associations & Clubs" msgstr "Associations & Clubs" -#: core/templates/core/base.jinja:204 +#: core/templates/core/base.jinja:205 msgid "AE" msgstr "L'AE" -#: core/templates/core/base.jinja:205 +#: core/templates/core/base.jinja:206 msgid "AE's clubs" msgstr "Les clubs de L'AE" -#: core/templates/core/base.jinja:206 +#: core/templates/core/base.jinja:207 msgid "Others UTBM's Associations" msgstr "Les autres associations de l'UTBM" -#: core/templates/core/base.jinja:187 core/templates/core/user_tools.jinja:118 +#: core/templates/core/base.jinja:213 core/templates/core/user_tools.jinja:180 msgid "Elections" msgstr "Élections" -#: core/templates/core/base.jinja:213 +#: core/templates/core/base.jinja:214 msgid "Big event" msgstr "Grandes Activités" -#: core/templates/core/base.jinja:216 +#: core/templates/core/base.jinja:217 #: forum/templates/forum/favorite_topics.jinja:14 #: forum/templates/forum/last_unread.jinja:14 #: forum/templates/forum/macros.jinja:90 forum/templates/forum/main.jinja:6 @@ -2471,93 +2467,93 @@ msgstr "Grandes Activités" msgid "Forum" msgstr "Forum" -#: core/templates/core/base.jinja:217 +#: core/templates/core/base.jinja:218 msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:218 counter/models.py:373 +#: core/templates/core/base.jinja:219 counter/models.py:373 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:397 sith/settings.py:405 +#: sith/settings.py:402 sith/settings.py:410 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:220 +#: core/templates/core/base.jinja:221 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:222 +#: core/templates/core/base.jinja:223 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:223 launderette/models.py:39 +#: core/templates/core/base.jinja:224 launderette/models.py:38 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:224 core/templates/core/file.jinja:20 -#: core/views/files.py:110 +#: core/templates/core/base.jinja:225 core/templates/core/file.jinja:20 +#: core/views/files.py:109 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:225 core/templates/core/user_tools.jinja:171 +#: core/templates/core/base.jinja:226 core/templates/core/user_tools.jinja:171 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:229 +#: core/templates/core/base.jinja:230 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:231 +#: core/templates/core/base.jinja:232 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:232 +#: core/templates/core/base.jinja:233 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:236 +#: core/templates/core/base.jinja:237 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:238 +#: core/templates/core/base.jinja:239 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:239 core/templates/core/base.jinja:279 +#: core/templates/core/base.jinja:240 core/templates/core/base.jinja:280 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:240 +#: core/templates/core/base.jinja:241 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:280 +#: core/templates/core/base.jinja:281 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:281 +#: core/templates/core/base.jinja:282 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:282 +#: core/templates/core/base.jinja:283 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:283 +#: core/templates/core/base.jinja:284 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:286 +#: core/templates/core/base.jinja:287 msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" -#: core/templates/core/base.jinja:292 +#: core/templates/core/base.jinja:293 #, fuzzy #| msgid "Site version:" msgid "Sith version:" @@ -2745,11 +2741,11 @@ msgstr "" "Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Merci de " "réessayer." -#: core/templates/core/login.jinja:55 +#: core/templates/core/login.jinja:56 msgid "Lost password?" msgstr "Mot de passe perdu ?" -#: core/templates/core/login.jinja:57 +#: core/templates/core/login.jinja:58 msgid "Create account" msgstr "Créer un compte" @@ -2770,7 +2766,7 @@ msgstr "Cotisant jusqu'au %(subscription_end)s" msgid "Account number: " msgstr "Numéro de compte : " -#: core/templates/core/macros.jinja:91 launderette/models.py:215 +#: core/templates/core/macros.jinja:91 launderette/models.py:208 msgid "Slot" msgstr "Créneau" @@ -2828,6 +2824,7 @@ msgstr "" "passe :" #: core/templates/core/new_user_email.jinja:8 +#: core/templates/core/register_confirm_mail.jinja:4 msgid "Your username, in case it was not given to you: " msgstr "Votre nom d'utilisateur, si il ne vous a pas été donné :" @@ -2849,6 +2846,7 @@ msgid "Thanks for subscribing! " msgstr "Merci d'avoir cotisé !" #: core/templates/core/new_user_email.jinja:14 +#: core/templates/core/register_confirm_mail.jinja:14 msgid "The AE team" msgstr "L'équipe AE" @@ -2914,7 +2912,7 @@ msgstr "Changer" msgid "You successfully changed your password!" msgstr "Vous avez correctement changé votre mot de passe !" -#: core/templates/core/password_reset.jinja:7 +#: core/templates/core/password_reset.jinja:8 #: core/templates/core/password_reset_confirm.jinja:8 msgid "Reset" msgstr "Reset" @@ -2981,22 +2979,36 @@ msgstr "Merci d'utiliser notre site !" msgid "The %(site_name)s team" msgstr "L'équipe de %(site_name)s" -#: core/templates/core/register.jinja:19 -#, python-format -msgid "Welcome %(user_name)s!" -msgstr "Bienvenue, %(user_name)s!" - -#: core/templates/core/register.jinja:20 +#: core/templates/core/register_confirm_mail.jinja:2 +#, fuzzy +#| msgid "" +#| "You're receiving this email because you subscribed to the UTBM student " +#| "association." msgid "" -"You successfully registered and you will soon receive a confirmation mail." +"You're receiving this email because you created an account on the AE website." msgstr "" -"Vous vous êtes correctement enregistré, et vous devriez recevoir rapidement " -"un email de confirmation." +"Vous avez reçu cet email parce que vous avez créé un compte sur " +"le site web de l'Association des Étudiants de l'UTBM." -#: core/templates/core/register.jinja:21 -#, python-format -msgid "Your username is %(username)s." -msgstr "Votre nom d'utilisateur est %(username)s." +#: core/templates/core/register_confirm_mail.jinja:6 +msgid "" +"\n" +"As this is the website of the students of the AE, by the students of the " +"AE,\n" +"for the students of the AE, you won't be able to do many things without " +"subscribing to the AE.\n" +"To make a contribution, contact a member of the association's board, either " +"directly or by email at ae@utbm.fr.\n" +msgstr "" +"\n" +"Ceci étant le site des étudiants de l'AE, par les étudiants de l'AE, " +"pour les étudiants de l'AE, vous n'aurez pas accès à tout sans être cotisant. " +"Pour cotiser, veuillez contacter un membre du bureau de l'AE, " +"soit en personne, soit par mail à l'adresse ae@utbm.fr.\n" + +#: core/templates/core/register_confirm_mail.jinja:12 +msgid "Wishing you a good experience among us! " +msgstr "En vous souhaitant une bonne expérience parmi nous !" #: core/templates/core/search.jinja:6 msgid "Search result" @@ -3006,7 +3018,7 @@ msgstr "Résultat de la recherche" msgid "Users" msgstr "Utilisateurs" -#: core/templates/core/search.jinja:18 core/views/user.py:241 +#: core/templates/core/search.jinja:18 core/views/user.py:261 msgid "Clubs" msgstr "Clubs" @@ -3063,7 +3075,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:58 counter/views.py:801 +#: core/templates/core/user_tools.jinja:58 counter/views.py:800 msgid "Etickets" msgstr "Etickets" @@ -3246,7 +3258,7 @@ msgstr "Voir l'arbre des ancêtres" msgid "No godfathers / godmothers" msgstr "Pas de famille" -#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:463 +#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:483 msgid "Godchildren" msgstr "Fillots / Fillotes" @@ -3324,7 +3336,7 @@ msgid "Error downloading your pictures" msgstr "Erreur de téléchargement de vos photos" #: core/templates/core/user_preferences.jinja:8 -#: core/templates/core/user_preferences.jinja:13 core/views/user.py:233 +#: core/templates/core/user_preferences.jinja:13 core/views/user.py:253 msgid "Preferences" msgstr "Préférences" @@ -3389,7 +3401,7 @@ msgstr "Achats" msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:43 counter/forms.py:176 +#: core/templates/core/user_stats.jinja:43 counter/forms.py:184 msgid "Product" msgstr "Produit" @@ -3406,7 +3418,7 @@ msgstr "Outils utilisateurs" msgid "Sith management" msgstr "Gestion de Sith" -#: core/templates/core/user_tools.jinja:21 core/views/user.py:249 +#: core/templates/core/user_tools.jinja:21 core/views/user.py:269 msgid "Groups" msgstr "Groupes" @@ -3434,8 +3446,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:139 -#: counter/views.py:771 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:147 +#: counter/views.py:770 msgid "Counters" msgstr "Comptoirs" @@ -3452,16 +3464,16 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:791 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:790 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:57 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:796 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:795 msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:72 core/views/user.py:268 +#: core/templates/core/user_tools.jinja:72 core/views/user.py:288 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:56 @@ -3555,117 +3567,117 @@ msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" msgid "Trombi tools" msgstr "Outils Trombi" -#: core/templatetags/renderer.py:77 +#: core/templatetags/renderer.py:78 #, python-format msgid "%(nb_days)d day, %(remainder)s" msgid_plural "%(nb_days)d days, %(remainder)s" msgstr[0] "" msgstr[1] "" -#: core/views/files.py:107 +#: core/views/files.py:106 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" -#: core/views/files.py:127 +#: core/views/files.py:126 #, python-format msgid "Error creating folder %(folder_name)s: %(msg)s" msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s" -#: core/views/files.py:147 core/views/forms.py:308 core/views/forms.py:315 -#: sas/views.py:83 +#: core/views/files.py:146 core/views/forms.py:299 core/views/forms.py:306 +#: sas/views.py:82 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" -#: core/views/files.py:229 sas/views.py:367 +#: core/views/files.py:228 sas/views.py:360 msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: core/views/forms.py:88 +#: core/views/forms.py:87 msgid "Heading" msgstr "Titre" -#: core/views/forms.py:89 +#: core/views/forms.py:88 msgid "Italic" msgstr "Italique" -#: core/views/forms.py:90 +#: core/views/forms.py:89 msgid "Bold" msgstr "Gras" -#: core/views/forms.py:91 +#: core/views/forms.py:90 msgid "Strikethrough" msgstr "Barré" -#: core/views/forms.py:92 +#: core/views/forms.py:91 msgid "Underline" msgstr "Souligné" -#: core/views/forms.py:93 +#: core/views/forms.py:92 msgid "Superscript" msgstr "Exposant" -#: core/views/forms.py:94 +#: core/views/forms.py:93 msgid "Subscript" msgstr "Indice" -#: core/views/forms.py:96 +#: core/views/forms.py:95 msgid "Quote" msgstr "Citation" -#: core/views/forms.py:97 +#: core/views/forms.py:96 msgid "Unordered list" msgstr "Liste non ordonnée" -#: core/views/forms.py:98 +#: core/views/forms.py:97 msgid "Ordered list" msgstr "Liste ordonnée" -#: core/views/forms.py:99 +#: core/views/forms.py:98 msgid "Insert image" msgstr "Insérer image" -#: core/views/forms.py:100 +#: core/views/forms.py:99 msgid "Insert link" msgstr "Insérer lien" -#: core/views/forms.py:101 +#: core/views/forms.py:100 msgid "Insert table" msgstr "Insérer tableau" -#: core/views/forms.py:102 +#: core/views/forms.py:101 msgid "Clean block" msgstr "Nettoyer bloc" -#: core/views/forms.py:103 +#: core/views/forms.py:102 msgid "Toggle preview" msgstr "Activer la prévisualisation" -#: core/views/forms.py:104 +#: core/views/forms.py:103 msgid "Toggle side by side" msgstr "Activer la vue côte à côte" -#: core/views/forms.py:105 +#: core/views/forms.py:104 msgid "Toggle fullscreen" msgstr "Activer le plein écran" -#: core/views/forms.py:106 +#: core/views/forms.py:105 msgid "Markdown guide" msgstr "Guide markdown" -#: core/views/forms.py:122 core/views/forms.py:130 +#: core/views/forms.py:121 core/views/forms.py:129 msgid "Choose file" msgstr "Choisir un fichier" -#: core/views/forms.py:146 core/views/forms.py:154 +#: core/views/forms.py:145 core/views/forms.py:153 msgid "Choose user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:186 +#: core/views/forms.py:185 msgid "Username, email, or account number" msgstr "Nom d'utilisateur, email, ou numéro de compte AE" -#: core/views/forms.py:254 +#: core/views/forms.py:245 msgid "" "Profile: you need to be visible on the picture, in order to be recognized (e." "g. by the barmen)" @@ -3673,88 +3685,92 @@ msgstr "" "Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "(par exemple par les barmen)" -#: core/views/forms.py:256 +#: core/views/forms.py:247 msgid "Avatar: used on the forum" msgstr "Avatar : utilisé sur le forum" -#: core/views/forms.py:257 +#: core/views/forms.py:248 msgid "Scrub: let other know how your scrub looks like!" msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !" -#: core/views/forms.py:319 +#: core/views/forms.py:310 msgid "Bad image format, only jpeg, png, and gif are accepted" msgstr "Mauvais format d'image, seuls les jpeg, png, et gif sont acceptés" -#: core/views/forms.py:340 +#: core/views/forms.py:331 msgid "Godfather / Godmother" msgstr "Parrain / Marraine" -#: core/views/forms.py:341 +#: core/views/forms.py:332 msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:346 counter/forms.py:55 trombi/views.py:156 +#: core/views/forms.py:337 counter/forms.py:63 trombi/views.py:156 msgid "Select user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:359 core/views/forms.py:377 election/models.py:24 +#: core/views/forms.py:350 core/views/forms.py:368 election/models.py:24 #: election/views.py:150 msgid "edit groups" msgstr "groupe d'édition" -#: core/views/forms.py:362 core/views/forms.py:380 election/models.py:31 +#: core/views/forms.py:353 core/views/forms.py:371 election/models.py:31 #: election/views.py:153 msgid "view groups" msgstr "groupe de vue" -#: core/views/group.py:44 +#: core/views/group.py:43 msgid "Users to remove from group" msgstr "Utilisateurs à retirer du groupe" -#: core/views/group.py:51 +#: core/views/group.py:50 msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/user.py:201 core/views/user.py:465 core/views/user.py:467 +#: core/views/user.py:198 +msgid "We couldn't verify that this email actually exists" +msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe." + +#: core/views/user.py:221 core/views/user.py:485 core/views/user.py:487 msgid "Family" msgstr "Famille" -#: core/views/user.py:206 sas/templates/sas/album.jinja:84 +#: core/views/user.py:226 sas/templates/sas/album.jinja:84 #: trombi/templates/trombi/export.jinja:25 #: trombi/templates/trombi/user_profile.jinja:11 msgid "Pictures" msgstr "Photos" -#: core/views/user.py:214 +#: core/views/user.py:234 msgid "Galaxy" msgstr "Galaxie" -#: core/views/user.py:612 +#: core/views/user.py:632 msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:389 counter/models.py:880 -#: counter/models.py:916 launderette/models.py:33 stock/models.py:42 +#: counter/app.py:30 counter/models.py:389 counter/models.py:880 +#: counter/models.py:916 launderette/models.py:32 stock/models.py:41 msgid "counter" msgstr "comptoir" -#: counter/forms.py:38 +#: counter/forms.py:46 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:77 +#: counter/forms.py:85 msgid "User not found" msgstr "Utilisateur non trouvé" -#: counter/forms.py:125 +#: counter/forms.py:133 msgid "Parent product" msgstr "Produit parent" -#: counter/forms.py:131 +#: counter/forms.py:139 msgid "Buying groups" msgstr "Groupes d'achat" -#: counter/migrations/0013_customer_recorded_products.py:26 +#: counter/migrations/0013_customer_recorded_products.py:25 msgid "Ecocup regularization" msgstr "Régularization des ecocups" @@ -3774,7 +3790,7 @@ msgstr "client" msgid "customers" msgstr "clients" -#: counter/models.py:138 counter/views.py:316 +#: counter/models.py:72 counter/views.py:315 msgid "Not enough money" msgstr "Solde insuffisant" @@ -3846,7 +3862,7 @@ msgstr "groupe d'achat" msgid "archived" msgstr "archivé" -#: counter/models.py:283 counter/models.py:1017 +#: counter/models.py:283 counter/models.py:1020 msgid "product" msgstr "produit" @@ -3870,7 +3886,7 @@ msgstr "Bureau" msgid "sellers" msgstr "vendeurs" -#: counter/models.py:384 launderette/models.py:205 +#: counter/models.py:384 launderette/models.py:198 msgid "token" msgstr "jeton" @@ -3886,11 +3902,11 @@ msgstr "est validé" msgid "refilling" msgstr "rechargement" -#: counter/models.py:690 eboutic/models.py:280 +#: counter/models.py:690 eboutic/models.py:279 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:691 counter/models.py:998 eboutic/models.py:281 +#: counter/models.py:691 counter/models.py:998 eboutic/models.py:280 msgid "quantity" msgstr "quantité" @@ -3898,8 +3914,8 @@ msgstr "quantité" msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:710 sith/settings.py:390 sith/settings.py:395 -#: sith/settings.py:415 +#: counter/models.py:710 sith/settings.py:395 sith/settings.py:400 +#: sith/settings.py:420 msgid "Credit card" msgstr "Carte bancaire" @@ -3907,16 +3923,16 @@ msgstr "Carte bancaire" msgid "selling" msgstr "vente" -#: counter/models.py:745 +#: counter/models.py:827 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:746 +#: counter/models.py:828 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:748 counter/models.py:771 +#: counter/models.py:830 counter/models.py:853 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3964,27 +3980,27 @@ msgstr "Vrai si c'est un chèque, sinon Faux." msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1021 +#: counter/models.py:1024 msgid "banner" msgstr "bannière" -#: counter/models.py:1023 +#: counter/models.py:1026 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1025 +#: counter/models.py:1028 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1027 +#: counter/models.py:1030 msgid "secret" msgstr "secret" -#: counter/models.py:1085 +#: counter/models.py:1071 msgid "uid" msgstr "uid" -#: counter/models.py:1090 +#: counter/models.py:1076 msgid "student cards" msgstr "cartes étudiante" @@ -4040,7 +4056,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1079 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1078 msgid "Emptied" msgstr "Coffre vidé" @@ -4266,164 +4282,164 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:170 +#: counter/views.py:169 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:186 +#: counter/views.py:185 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:203 +#: counter/views.py:202 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:256 +#: counter/views.py:255 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:258 +#: counter/views.py:257 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:263 +#: counter/views.py:262 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:307 +#: counter/views.py:306 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:310 +#: counter/views.py:309 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:313 +#: counter/views.py:312 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:613 +#: counter/views.py:612 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" -#: counter/views.py:765 +#: counter/views.py:764 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:767 +#: counter/views.py:766 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:786 +#: counter/views.py:785 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1036 +#: counter/views.py:1035 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1037 +#: counter/views.py:1036 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1038 +#: counter/views.py:1037 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1039 +#: counter/views.py:1038 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1040 +#: counter/views.py:1039 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1041 +#: counter/views.py:1040 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1042 +#: counter/views.py:1041 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1043 +#: counter/views.py:1042 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1044 +#: counter/views.py:1043 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1046 +#: counter/views.py:1045 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1049 counter/views.py:1055 counter/views.py:1061 -#: counter/views.py:1067 counter/views.py:1073 +#: counter/views.py:1048 counter/views.py:1054 counter/views.py:1060 +#: counter/views.py:1066 counter/views.py:1072 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1052 counter/views.py:1058 counter/views.py:1064 -#: counter/views.py:1070 counter/views.py:1076 +#: counter/views.py:1051 counter/views.py:1057 counter/views.py:1063 +#: counter/views.py:1069 counter/views.py:1075 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1632 +#: counter/views.py:1627 msgid "people(s)" msgstr "personne(s)" -#: eboutic/forms.py:107 +#: eboutic/forms.py:106 msgid "You have no basket." msgstr "Vous n'avez pas de panier." -#: eboutic/forms.py:120 +#: eboutic/forms.py:119 msgid "The request was badly formatted." msgstr "La requête a été mal formatée." -#: eboutic/forms.py:126 +#: eboutic/forms.py:125 msgid "The basket cookie was badly formatted." msgstr "Le cookie du panier a été mal formaté." -#: eboutic/forms.py:130 +#: eboutic/forms.py:129 msgid "Your basket is empty." msgstr "Votre panier est vide" -#: eboutic/forms.py:141 +#: eboutic/forms.py:140 #, python-format msgid "%(name)s : this product does not exist." msgstr "%(name)s : ce produit n'existe pas." -#: eboutic/forms.py:150 +#: eboutic/forms.py:149 #, python-format msgid "%(name)s : this product does not exist or may no longer be available." msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." -#: eboutic/forms.py:157 +#: eboutic/forms.py:156 #, python-format msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:228 +#: eboutic/models.py:227 msgid "validated" msgstr "validé" -#: eboutic/models.py:238 +#: eboutic/models.py:240 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:277 +#: eboutic/models.py:276 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:278 +#: eboutic/models.py:277 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:279 +#: eboutic/models.py:278 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:296 +#: eboutic/models.py:295 msgid "basket" msgstr "panier" @@ -4465,7 +4481,7 @@ msgstr "" msgid "this page" msgstr "cette page" -#: eboutic/templates/eboutic/eboutic_main.jinja:124 +#: eboutic/templates/eboutic/eboutic_main.jinja:126 msgid "There are no items available for sale" msgstr "Aucun article n'est disponible à la vente" @@ -4684,35 +4700,35 @@ msgstr "Début des candidatures" msgid "End candidature" msgstr "Fin des candidatures" -#: forum/models.py:60 +#: forum/models.py:62 msgid "is a category" msgstr "est une catégorie" -#: forum/models.py:71 +#: forum/models.py:73 msgid "owner club" msgstr "club propriétaire" -#: forum/models.py:88 +#: forum/models.py:90 msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:93 forum/models.py:250 +#: forum/models.py:95 forum/models.py:252 msgid "the last message" msgstr "le dernier message" -#: forum/models.py:97 +#: forum/models.py:99 msgid "number of topics" msgstr "nombre de sujets" -#: forum/models.py:187 +#: forum/models.py:195 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:245 +#: forum/models.py:247 msgid "subscribed users" msgstr "utilisateurs abonnés" -#: forum/models.py:255 +#: forum/models.py:257 msgid "number of messages" msgstr "nombre de messages" @@ -4728,23 +4744,23 @@ msgstr "lecteurs" msgid "is deleted" msgstr "est supprimé" -#: forum/models.py:401 +#: forum/models.py:400 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:402 +#: forum/models.py:401 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:403 +#: forum/models.py:402 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:415 +#: forum/models.py:414 msgid "action" msgstr "action" -#: forum/models.py:434 +#: forum/models.py:436 msgid "last read date" msgstr "dernière date de lecture" @@ -4828,56 +4844,56 @@ msgstr "Enlever des favoris" msgid "Mark as favorite" msgstr "Ajouter aux favoris" -#: forum/views.py:189 +#: forum/views.py:188 msgid "Apply rights and club owner recursively" msgstr "Appliquer les droits et le club propriétaire récursivement" -#: forum/views.py:409 +#: forum/views.py:408 #, python-format msgid "%(author)s said" msgstr "Citation de %(author)s" -#: galaxy/models.py:57 +#: galaxy/models.py:56 msgid "star owner" msgstr "propriétaire de l'étoile" -#: galaxy/models.py:62 +#: galaxy/models.py:61 msgid "star mass" msgstr "masse de l'étoile" -#: galaxy/models.py:67 +#: galaxy/models.py:66 msgid "the galaxy this star belongs to" msgstr "la galaxie à laquelle cette étoile appartient" -#: galaxy/models.py:103 +#: galaxy/models.py:102 msgid "galaxy star 1" msgstr "étoile 1" -#: galaxy/models.py:109 +#: galaxy/models.py:108 msgid "galaxy star 2" msgstr "étoile 2" -#: galaxy/models.py:114 +#: galaxy/models.py:113 msgid "distance" msgstr "distance" -#: galaxy/models.py:116 +#: galaxy/models.py:115 msgid "Distance separating star1 and star2" msgstr "Distance séparant étoile 1 et étoile 2" -#: galaxy/models.py:119 +#: galaxy/models.py:118 msgid "family score" msgstr "score de famille" -#: galaxy/models.py:123 +#: galaxy/models.py:122 msgid "pictures score" msgstr "score de photos" -#: galaxy/models.py:127 +#: galaxy/models.py:126 msgid "clubs score" msgstr "score de club" -#: galaxy/models.py:179 +#: galaxy/models.py:181 msgid "The galaxy current state" msgstr "L'état actuel de la galaxie" @@ -4886,35 +4902,35 @@ msgstr "L'état actuel de la galaxie" msgid "%(user_name)s's Galaxy" msgstr "Galaxie de %(user_name)s" -#: galaxy/views.py:49 +#: galaxy/views.py:48 msgid "This citizen has not yet joined the galaxy" msgstr "Ce citoyen n'a pas encore rejoint la galaxie" -#: launderette/models.py:91 launderette/models.py:131 +#: launderette/models.py:90 launderette/models.py:130 msgid "launderette" msgstr "laverie" -#: launderette/models.py:97 +#: launderette/models.py:96 msgid "is working" msgstr "fonctionne" -#: launderette/models.py:100 +#: launderette/models.py:99 msgid "Machine" msgstr "Machine" -#: launderette/models.py:137 +#: launderette/models.py:136 msgid "borrow date" msgstr "date d'emprunt" -#: launderette/models.py:148 +#: launderette/models.py:147 msgid "Token" msgstr "Jeton" -#: launderette/models.py:154 +#: launderette/models.py:159 msgid "Token name can not be blank" msgstr "Le nom du jeton ne peut pas être vide" -#: launderette/models.py:199 +#: launderette/models.py:192 msgid "machine" msgstr "machine" @@ -4943,12 +4959,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:626 +#: sith/settings.py:631 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:626 +#: sith/settings.py:631 msgid "Drying" msgstr "Séchage" @@ -4973,25 +4989,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:232 +#: launderette/views.py:231 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:252 launderette/views.py:274 +#: launderette/views.py:251 launderette/views.py:273 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:263 +#: launderette/views.py:262 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:330 +#: launderette/views.py:329 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:442 +#: launderette/views.py:441 msgid "Token not found" msgstr "Jeton non trouvé" @@ -5016,27 +5032,27 @@ msgstr "Recherche inversée" msgid "Quick search" msgstr "Recherche rapide" -#: matmat/views.py:71 +#: matmat/views.py:70 msgid "Last/First name or nickname" msgstr "Nom de famille, prénom ou surnom" -#: pedagogy/forms.py:84 +#: pedagogy/forms.py:83 msgid "Do not vote" msgstr "Ne pas voter" -#: pedagogy/forms.py:133 +#: pedagogy/forms.py:132 msgid "This user has already commented on this UV" msgstr "Cet utilisateur a déjà commenté cette UV" -#: pedagogy/forms.py:169 +#: pedagogy/forms.py:168 msgid "Accepted reports" msgstr "Signalements acceptés" -#: pedagogy/forms.py:176 +#: pedagogy/forms.py:175 msgid "Denied reports" msgstr "Signalements refusés" -#: pedagogy/models.py:52 +#: pedagogy/models.py:51 msgid "" "The code of an UV must only contains uppercase characters without accent and " "numbers" @@ -5044,103 +5060,103 @@ msgstr "" "Le code d'une UV doit seulement contenir des caractères majuscule sans " "accents et nombres" -#: pedagogy/models.py:66 +#: pedagogy/models.py:65 msgid "credit type" msgstr "type de crédit" -#: pedagogy/models.py:71 pedagogy/models.py:101 +#: pedagogy/models.py:70 pedagogy/models.py:100 msgid "uv manager" msgstr "gestionnaire d'uv" -#: pedagogy/models.py:79 +#: pedagogy/models.py:78 msgid "language" msgstr "langue" -#: pedagogy/models.py:85 +#: pedagogy/models.py:84 msgid "credits" msgstr "crédits" -#: pedagogy/models.py:93 +#: pedagogy/models.py:92 msgid "departmenmt" msgstr "département" -#: pedagogy/models.py:102 +#: pedagogy/models.py:101 msgid "objectives" msgstr "objectifs" -#: pedagogy/models.py:103 +#: pedagogy/models.py:102 msgid "program" msgstr "programme" -#: pedagogy/models.py:104 +#: pedagogy/models.py:103 msgid "skills" msgstr "compétences" -#: pedagogy/models.py:105 +#: pedagogy/models.py:104 msgid "key concepts" msgstr "concepts clefs" -#: pedagogy/models.py:110 +#: pedagogy/models.py:109 msgid "hours CM" msgstr "heures CM" -#: pedagogy/models.py:117 +#: pedagogy/models.py:116 msgid "hours TD" msgstr "heures TD" -#: pedagogy/models.py:124 +#: pedagogy/models.py:123 msgid "hours TP" msgstr "heures TP" -#: pedagogy/models.py:131 +#: pedagogy/models.py:130 msgid "hours THE" msgstr "heures THE" -#: pedagogy/models.py:138 +#: pedagogy/models.py:137 msgid "hours TE" msgstr "heures TE" -#: pedagogy/models.py:216 pedagogy/models.py:290 +#: pedagogy/models.py:215 pedagogy/models.py:289 msgid "uv" msgstr "UE" -#: pedagogy/models.py:220 +#: pedagogy/models.py:219 msgid "global grade" msgstr "note globale" -#: pedagogy/models.py:227 +#: pedagogy/models.py:226 msgid "utility grade" msgstr "note d'utilité" -#: pedagogy/models.py:234 +#: pedagogy/models.py:233 msgid "interest grade" msgstr "note d'intérêt" -#: pedagogy/models.py:241 +#: pedagogy/models.py:240 msgid "teaching grade" msgstr "note d'enseignement" -#: pedagogy/models.py:248 +#: pedagogy/models.py:247 msgid "work load grade" msgstr "note de charge de travail" -#: pedagogy/models.py:254 +#: pedagogy/models.py:253 msgid "publish date" msgstr "date de publication" -#: pedagogy/models.py:296 +#: pedagogy/models.py:295 msgid "grade" msgstr "note" -#: pedagogy/models.py:316 +#: pedagogy/models.py:318 msgid "report" msgstr "signaler" -#: pedagogy/models.py:322 +#: pedagogy/models.py:324 msgid "reporter" msgstr "signalant" -#: pedagogy/models.py:325 +#: pedagogy/models.py:327 msgid "reason" msgstr "raison" @@ -5264,7 +5280,7 @@ msgstr "Concepts clefs" msgid "UE manager: " msgstr "Gestionnaire d'UE : " -#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:405 +#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:404 msgid "" "You already posted a comment on this UV. If you want to comment again, " "please modify or delete your previous comment." @@ -5277,7 +5293,7 @@ msgid "Leave comment" msgstr "Laisser un commentaire" #: pedagogy/templates/pedagogy/uv_detail.jinja:146 -#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:263 +#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:262 #: trombi/templates/trombi/export.jinja:70 msgid "Comments" msgstr "Commentaires" @@ -5340,19 +5356,19 @@ msgstr "Fusionner deux utilisateurs" msgid "Merge" msgstr "Fusion" -#: rootplace/views.py:155 +#: rootplace/views.py:154 msgid "User that will be kept" msgstr "Utilisateur qui sera conservé" -#: rootplace/views.py:158 +#: rootplace/views.py:157 msgid "User that will be deleted" msgstr "Utilisateur qui sera supprimé" -#: rootplace/views.py:164 +#: rootplace/views.py:163 msgid "User to be selected" msgstr "Utilisateur à sélectionner" -#: sas/models.py:248 +#: sas/models.py:239 msgid "picture" msgstr "photo" @@ -5409,421 +5425,421 @@ msgstr "Demander le retrait" msgid "People" msgstr "Personne(s)" -#: sas/views.py:39 +#: sas/views.py:38 msgid "Add a new album" msgstr "Ajouter un nouvel album" -#: sas/views.py:42 +#: sas/views.py:41 msgid "Upload images" msgstr "Envoyer les images" -#: sas/views.py:60 +#: sas/views.py:59 #, python-format msgid "Error creating album %(album)s: %(msg)s" msgstr "Erreur de création de l'album %(album)s : %(msg)s" -#: sas/views.py:95 trombi/templates/trombi/detail.jinja:15 +#: sas/views.py:94 trombi/templates/trombi/detail.jinja:15 msgid "Add user" msgstr "Ajouter une personne" -#: sith/settings.py:246 sith/settings.py:452 +#: sith/settings.py:251 sith/settings.py:457 msgid "English" msgstr "Anglais" -#: sith/settings.py:246 sith/settings.py:451 +#: sith/settings.py:251 sith/settings.py:456 msgid "French" msgstr "Français" -#: sith/settings.py:371 +#: sith/settings.py:376 msgid "TC" msgstr "TC" -#: sith/settings.py:372 +#: sith/settings.py:377 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:373 +#: sith/settings.py:378 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:374 +#: sith/settings.py:379 msgid "INFO" msgstr "INFO" -#: sith/settings.py:375 +#: sith/settings.py:380 msgid "GI" msgstr "GI" -#: sith/settings.py:376 sith/settings.py:462 +#: sith/settings.py:381 sith/settings.py:467 msgid "E" msgstr "E" -#: sith/settings.py:377 +#: sith/settings.py:382 msgid "EE" msgstr "EE" -#: sith/settings.py:378 +#: sith/settings.py:383 msgid "GESC" msgstr "GESC" -#: sith/settings.py:379 +#: sith/settings.py:384 msgid "GMC" msgstr "GMC" -#: sith/settings.py:380 +#: sith/settings.py:385 msgid "MC" msgstr "MC" -#: sith/settings.py:381 +#: sith/settings.py:386 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:382 +#: sith/settings.py:387 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:383 +#: sith/settings.py:388 msgid "N/A" msgstr "N/A" -#: sith/settings.py:387 sith/settings.py:394 sith/settings.py:413 +#: sith/settings.py:392 sith/settings.py:399 sith/settings.py:418 msgid "Check" msgstr "Chèque" -#: sith/settings.py:388 sith/settings.py:396 sith/settings.py:414 +#: sith/settings.py:393 sith/settings.py:401 sith/settings.py:419 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:389 +#: sith/settings.py:394 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:402 +#: sith/settings.py:407 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:403 +#: sith/settings.py:408 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:404 +#: sith/settings.py:409 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:432 +#: sith/settings.py:437 msgid "Free" msgstr "Libre" -#: sith/settings.py:433 +#: sith/settings.py:438 msgid "CS" msgstr "CS" -#: sith/settings.py:434 +#: sith/settings.py:439 msgid "TM" msgstr "TM" -#: sith/settings.py:435 +#: sith/settings.py:440 msgid "OM" msgstr "OM" -#: sith/settings.py:436 +#: sith/settings.py:441 msgid "QC" msgstr "QC" -#: sith/settings.py:437 +#: sith/settings.py:442 msgid "EC" msgstr "EC" -#: sith/settings.py:438 +#: sith/settings.py:443 msgid "RN" msgstr "RN" -#: sith/settings.py:439 +#: sith/settings.py:444 msgid "ST" msgstr "ST" -#: sith/settings.py:440 +#: sith/settings.py:445 msgid "EXT" msgstr "EXT" -#: sith/settings.py:445 +#: sith/settings.py:450 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:446 +#: sith/settings.py:451 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:447 +#: sith/settings.py:452 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:453 +#: sith/settings.py:458 msgid "German" msgstr "Allemand" -#: sith/settings.py:454 +#: sith/settings.py:459 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:458 +#: sith/settings.py:463 msgid "A" msgstr "A" -#: sith/settings.py:459 +#: sith/settings.py:464 msgid "B" msgstr "B" -#: sith/settings.py:460 +#: sith/settings.py:465 msgid "C" msgstr "C" -#: sith/settings.py:461 +#: sith/settings.py:466 msgid "D" msgstr "D" -#: sith/settings.py:463 +#: sith/settings.py:468 msgid "FX" msgstr "FX" -#: sith/settings.py:464 +#: sith/settings.py:469 msgid "F" msgstr "F" -#: sith/settings.py:465 +#: sith/settings.py:470 msgid "Abs" msgstr "Abs" -#: sith/settings.py:469 +#: sith/settings.py:474 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:470 +#: sith/settings.py:475 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:507 +#: sith/settings.py:512 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:508 +#: sith/settings.py:513 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:510 +#: sith/settings.py:515 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:514 +#: sith/settings.py:519 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:515 +#: sith/settings.py:520 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:516 +#: sith/settings.py:521 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:517 +#: sith/settings.py:522 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:518 +#: sith/settings.py:523 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:519 +#: sith/settings.py:524 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:520 +#: sith/settings.py:525 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:521 +#: sith/settings.py:526 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:523 +#: sith/settings.py:528 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:527 +#: sith/settings.py:532 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:528 +#: sith/settings.py:533 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:529 +#: sith/settings.py:534 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:531 +#: sith/settings.py:536 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:535 +#: sith/settings.py:540 msgid "One day" msgstr "Un jour" -#: sith/settings.py:536 +#: sith/settings.py:541 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:539 +#: sith/settings.py:544 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:544 +#: sith/settings.py:549 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:549 +#: sith/settings.py:554 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:554 +#: sith/settings.py:559 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:559 +#: sith/settings.py:564 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:565 +#: sith/settings.py:570 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:585 +#: sith/settings.py:590 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:586 +#: sith/settings.py:591 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:587 +#: sith/settings.py:592 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:588 +#: sith/settings.py:593 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:589 +#: sith/settings.py:594 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:590 +#: sith/settings.py:595 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:591 +#: sith/settings.py:596 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:592 +#: sith/settings.py:597 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:593 +#: sith/settings.py:598 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:630 +#: sith/settings.py:635 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:631 +#: sith/settings.py:636 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:634 +#: sith/settings.py:639 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:636 +#: sith/settings.py:641 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:637 +#: sith/settings.py:642 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:638 +#: sith/settings.py:643 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:639 +#: sith/settings.py:644 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:640 +#: sith/settings.py:645 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:641 +#: sith/settings.py:646 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:642 +#: sith/settings.py:647 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:654 +#: sith/settings.py:659 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:655 +#: sith/settings.py:660 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:656 +#: sith/settings.py:661 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:657 +#: sith/settings.py:662 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:658 +#: sith/settings.py:663 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:666 +#: sith/settings.py:671 msgid "AE tee-shirt" msgstr "Tee-shirt AE" -#: stock/models.py:64 +#: stock/models.py:63 msgid "unit quantity" msgstr "quantité unitaire" -#: stock/models.py:64 +#: stock/models.py:63 msgid "number of element in one box" msgstr "nombre d'éléments par boîte" -#: stock/models.py:67 +#: stock/models.py:66 msgid "effective quantity" msgstr "quantité effective" -#: stock/models.py:67 +#: stock/models.py:66 msgid "number of box" msgstr "nombre de boîtes" -#: stock/models.py:70 +#: stock/models.py:69 msgid "minimal quantity" msgstr "quantité minimale" -#: stock/models.py:73 +#: stock/models.py:72 msgid "" "if the effective quantity is less than the minimal, item is added to the " "shopping list" @@ -5831,27 +5847,27 @@ msgstr "" "si la quantité effective est en dessous du minima, l'item est ajouté àla " "liste de courses" -#: stock/models.py:105 +#: stock/models.py:104 msgid "todo" msgstr "à faire" -#: stock/models.py:126 +#: stock/models.py:125 msgid "shopping lists" msgstr "listes de courses" -#: stock/models.py:142 +#: stock/models.py:141 msgid "quantity to buy" msgstr "quantité à acheter" -#: stock/models.py:144 +#: stock/models.py:143 msgid "quantity to buy during the next shopping session" msgstr "quantité à acheter pendant les prochaines courses" -#: stock/models.py:147 +#: stock/models.py:146 msgid "quantity bought" msgstr "quantité achetée" -#: stock/models.py:149 +#: stock/models.py:148 msgid "quantity bought during the last shopping session" msgstr "quantité achetée pendant les dernières courses" @@ -5972,15 +5988,15 @@ msgstr "Mettre à jour les quantités de %(s)s après les courses" msgid "Update stock quantities" msgstr "Mettre à jour les quantités en stock" -#: stock/views.py:242 +#: stock/views.py:241 msgid "Shopping list name" msgstr "Nom de la liste de courses" -#: stock/views.py:252 +#: stock/views.py:251 msgid " left" msgstr " restant" -#: stock/views.py:258 +#: stock/views.py:257 msgid "" "Add here, items to buy that are not reference as a stock item (example : " "sponge, knife, mugs ...)" @@ -5988,11 +6004,11 @@ msgstr "" "Ajouter ici les éléments non référencé comme élément de stock (example : " "éponge, couteau, mugs ...)" -#: stock/views.py:442 +#: stock/views.py:441 msgid " asked" msgstr " demandé" -#: stock/views.py:534 +#: stock/views.py:527 #, python-format msgid "%(effective_quantity)s left" msgstr "%(effective_quantity)s restant" @@ -6021,14 +6037,10 @@ msgstr "fin de la cotisation" msgid "location" msgstr "lieu" -#: subscription/models.py:83 +#: subscription/models.py:106 msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/models.py:88 -msgid "Subscription error" -msgstr "Erreur de cotisation" - #: subscription/templates/subscription/stats.jinja:25 msgid "Total subscriptions" msgstr "Cotisations totales" @@ -6042,20 +6054,20 @@ msgid "Eboutic is reserved to specific users. In doubt, don't use it." msgstr "" "Eboutic est réservé à des cas particuliers. Dans le doute, ne l'utilisez pas." -#: subscription/views.py:94 +#: subscription/views.py:93 msgid "A user with that email address already exists" msgstr "Un utilisateur avec cette adresse email existe déjà" -#: subscription/views.py:117 +#: subscription/views.py:116 msgid "You must either choose an existing user or create a new one properly" msgstr "" "Vous devez soit choisir un utilisateur existant, soit en créer un proprement" -#: trombi/models.py:60 +#: trombi/models.py:55 msgid "subscription deadline" msgstr "fin des inscriptions" -#: trombi/models.py:63 +#: trombi/models.py:58 msgid "" "Before this date, users are allowed to subscribe to this Trombi. After this " "date, users subscribed will be allowed to comment on each other." @@ -6064,46 +6076,46 @@ msgstr "" "Après cette date, les utilisateurs inscrits peuvent se soumettre des " "commentaires entre eux." -#: trombi/models.py:69 +#: trombi/models.py:64 msgid "comments deadline" msgstr "fin des commentaires" -#: trombi/models.py:72 +#: trombi/models.py:67 msgid "After this date, users won't be able to make comments anymore." msgstr "" "Après cette date, les utilisateurs ne peuvent plus faire de commentaires." -#: trombi/models.py:76 +#: trombi/models.py:71 msgid "maximum characters" msgstr "nombre de caractères max" -#: trombi/models.py:78 +#: trombi/models.py:73 msgid "Maximum number of characters allowed in a comment." msgstr "Nombre maximum de caractères autorisés dans un commentaire." -#: trombi/models.py:81 +#: trombi/models.py:76 msgid "show users profiles to each other" msgstr "montrer les profils aux inscrits" -#: trombi/models.py:95 +#: trombi/models.py:93 msgid "" "Closing the subscriptions after the comments is definitively not a good idea." msgstr "" "Fermer les inscriptions après les commentaires est vraiment une idée pourrie." -#: trombi/models.py:121 +#: trombi/models.py:116 msgid "trombi user" msgstr "utilisateur trombi" -#: trombi/models.py:127 +#: trombi/models.py:122 msgid "trombi" msgstr "trombi" -#: trombi/models.py:135 +#: trombi/models.py:130 msgid "profile pict" msgstr "photo de profil" -#: trombi/models.py:139 +#: trombi/models.py:134 msgid "" "The profile picture you want in the trombi (warning: this picture may be " "published)" @@ -6111,11 +6123,11 @@ msgstr "" "La photo de profil que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:144 +#: trombi/models.py:139 msgid "scrub pict" msgstr "photo de blouse" -#: trombi/models.py:148 +#: trombi/models.py:143 msgid "" "The scrub picture you want in the trombi (warning: this picture may be " "published)" @@ -6123,19 +6135,19 @@ msgstr "" "La photo de blouse que vous souhaitez voir dans le Trombi (attention: cette " "photo risque d'être publiée)" -#: trombi/models.py:193 +#: trombi/models.py:188 msgid "target" msgstr "cible" -#: trombi/models.py:198 +#: trombi/models.py:193 msgid "is the comment moderated" msgstr "le commentaire est modéré" -#: trombi/models.py:219 +#: trombi/models.py:217 msgid "start" msgstr "début" -#: trombi/models.py:220 +#: trombi/models.py:218 msgid "end" msgstr "fin" @@ -6282,11 +6294,11 @@ msgstr "Admin Trombi" msgid "Explain why you rejected the comment" msgstr "Expliquez pourquoi vous refusez le commentaire" -#: trombi/views.py:253 +#: trombi/views.py:251 msgid "Rejected comment" msgstr "Commentaire rejeté" -#: trombi/views.py:255 +#: trombi/views.py:253 #, python-format msgid "" "Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the " @@ -6303,16 +6315,16 @@ msgstr "" "\n" "%(content)s" -#: trombi/views.py:287 +#: trombi/views.py:285 #, python-format msgid "%(name)s (deadline: %(date)s)" msgstr "%(name)s (date limite: %(date)s)" -#: trombi/views.py:297 +#: trombi/views.py:295 msgid "Select trombi" msgstr "Choisir un trombi" -#: trombi/views.py:299 +#: trombi/views.py:297 msgid "" "This allows you to subscribe to a Trombi. Be aware that you can subscribe " "only once, so don't play with that, or you will expose yourself to the " @@ -6322,19 +6334,19 @@ msgstr "" "pouvez vous inscrire qu'à un seul Trombi, donc ne jouez pas avec cet option " "ou vous encourerez la colère des admins!" -#: trombi/views.py:372 +#: trombi/views.py:370 msgid "Personal email (not UTBM)" msgstr "Email personnel (pas UTBM)" -#: trombi/views.py:373 +#: trombi/views.py:371 msgid "Phone" msgstr "Téléphone" -#: trombi/views.py:374 +#: trombi/views.py:372 msgid "Native town" msgstr "Ville d'origine" -#: trombi/views.py:491 +#: trombi/views.py:482 msgid "" "You can not yet write comment, you must wait for the subscription deadline " "to be passed." @@ -6342,15 +6354,35 @@ msgstr "" "Vous ne pouvez pas encore écrire de commentaires, vous devez attendre la fin " "des inscriptions" -#: trombi/views.py:498 +#: trombi/views.py:489 msgid "You can not write comment anymore, the deadline is already passed." msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." -#: trombi/views.py:511 +#: trombi/views.py:502 #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" +#~ msgid "past member" +#~ msgstr "Anciens membres" + +#, python-format +#~ msgid "Welcome %(user_name)s!" +#~ msgstr "Bienvenue, %(user_name)s!" + +#~ msgid "" +#~ "You successfully registered and you will soon receive a confirmation mail." +#~ msgstr "" +#~ "Vous vous êtes correctement enregistré, et vous devriez recevoir " +#~ "rapidement un email de confirmation." + +#, python-format +#~ msgid "Your username is %(username)s." +#~ msgstr "Votre nom d'utilisateur est %(username)s." + +#~ msgid "Subscription error" +#~ msgstr "Erreur de cotisation" + #~ msgid "Folder: " #~ msgstr "Dossier : " From d6b27f2f218b2d3d9b9530db6bdd8b74e9b74391 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 10 Jul 2024 19:30:01 +0200 Subject: [PATCH 3/4] Make honeypot errors less suspicious --- core/middleware.py | 5 +++++ core/tests.py | 10 +++++++--- sith/settings.py | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/middleware.py b/core/middleware.py index 5f5c945b..fe4087c3 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -21,6 +21,7 @@ from django.contrib.auth import get_user from django.contrib.auth.middleware import ( AuthenticationMiddleware as DjangoAuthenticationMiddleware, ) +from django.http import HttpResponse from django.utils.functional import SimpleLazyObject module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) @@ -70,3 +71,7 @@ class SignalRequestMiddleware: def __call__(self, request): _threadlocal.request = request return self.get_response(request) + + +def custom_honeypot_error(request, context): + return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/core/tests.py b/core/tests.py index a5e885bf..5be0b2e6 100644 --- a/core/tests.py +++ b/core/tests.py @@ -80,13 +80,15 @@ class TestUserRegistration: assert response.status_code == 200 error_html = f'
    • {expected_error}
    ' assertInHTML(error_html, str(response.content.decode())) + assert not User.objects.filter(email=payload["email"]).exists() - def test_register_honeypot_fail(self, client, valid_payload): + def test_register_honeypot_fail(self, client: Client, valid_payload): payload = valid_payload | { settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "random" } response = client.post(reverse("core:register"), payload) - assert response.status_code == 400 + assert response.status_code == 200 + assert not User.objects.filter(email=payload["email"]).exists() def test_register_user_form_fail_already_exists( self, client: Client, valid_payload @@ -152,7 +154,8 @@ class TestUserLogin: settings.HONEYPOT_FIELD_NAME: settings.HONEYPOT_VALUE + "incorrect", }, ) - assert response.status_code == 400 + assert response.status_code == 200 + assert response.wsgi_request.user.is_anonymous def test_login_success(self, client, user): """ @@ -167,6 +170,7 @@ class TestUserLogin: }, ) assertRedirects(response, reverse("core:index")) + assert response.wsgi_request.user == user @pytest.mark.parametrize( diff --git a/sith/settings.py b/sith/settings.py index ab311a85..f668d73a 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -41,6 +41,7 @@ import os import sys import sentry_sdk +from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from sentry_sdk.integrations.django import DjangoIntegration @@ -58,6 +59,12 @@ SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" HONEYPOT_FIELD_NAME = "body2" HONEYPOT_VALUE = "content" +# Make honeypot errors less suspicious +# Since the app is not loaded yet, we wrap the import_string function in a lambda call to lazy load it +HONEYPOT_RESPONDER = lambda request, context: import_string( + "core.middleware.custom_honeypot_error" +)(request, context) + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules From 0fb61938ce16f86a768ae4bcbeb908fd83e91511 Mon Sep 17 00:00:00 2001 From: Sli Date: Thu, 11 Jul 2024 10:49:08 +0200 Subject: [PATCH 4/4] Reorganize honeypot settings --- core/middleware.py | 5 ----- sith/honeypot.py | 12 ++++++++++++ sith/settings.py | 18 +++++++----------- 3 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 sith/honeypot.py diff --git a/core/middleware.py b/core/middleware.py index fe4087c3..5f5c945b 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -21,7 +21,6 @@ from django.contrib.auth import get_user from django.contrib.auth.middleware import ( AuthenticationMiddleware as DjangoAuthenticationMiddleware, ) -from django.http import HttpResponse from django.utils.functional import SimpleLazyObject module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) @@ -71,7 +70,3 @@ class SignalRequestMiddleware: def __call__(self, request): _threadlocal.request = request return self.get_response(request) - - -def custom_honeypot_error(request, context): - return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/sith/honeypot.py b/sith/honeypot.py new file mode 100644 index 00000000..3659c0ea --- /dev/null +++ b/sith/honeypot.py @@ -0,0 +1,12 @@ +import logging +from typing import Any + +from django.http import HttpResponse +from django.test.client import WSGIRequest + + +def custom_honeypot_error( + request: WSGIRequest, context: dict[str, Any] +) -> HttpResponse: + logging.warning(f"HoneyPot blocked user with ip {request.META.get('REMOTE_ADDR')}") + return HttpResponse("Upon reading this, the http client was enlightened.") diff --git a/sith/settings.py b/sith/settings.py index f668d73a..e37ab2eb 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -41,10 +41,11 @@ import os import sys import sentry_sdk -from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from sentry_sdk.integrations.django import DjangoIntegration +from .honeypot import custom_honeypot_error + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.environ["HTTPS"] = "off" @@ -55,16 +56,6 @@ os.environ["HTTPS"] = "off" # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" -# Those values are to be changed in production to be more effective -HONEYPOT_FIELD_NAME = "body2" -HONEYPOT_VALUE = "content" - -# Make honeypot errors less suspicious -# Since the app is not loaded yet, we wrap the import_string function in a lambda call to lazy load it -HONEYPOT_RESPONDER = lambda request, context: import_string( - "core.middleware.custom_honeypot_error" -)(request, context) - # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False TESTING = "pytest" in sys.modules @@ -293,6 +284,11 @@ LOGIN_REDIRECT_URL = "/" DEFAULT_FROM_EMAIL = "bibou@git.an" SITH_COM_EMAIL = "bibou_com@git.an" REST_FRAMEWORK["UNAUTHENTICATED_USER"] = "core.models.AnonymousUser" +# Those values are to be changed in production to be more effective +HONEYPOT_FIELD_NAME = "body2" +HONEYPOT_VALUE = "content" +HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious + # Email EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"