From 653a059725ffbc0213a6ed5036978dad0dd6ed57 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 16:58:20 +0000 Subject: [PATCH 01/83] Add optional fields to select password during user creation --- static/js/toggle_password_fields.js | 24 +++++++++++ users/forms.py | 64 ++++++++++++++++++++++++++++- users/views.py | 40 +++++++++++++++--- 3 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 static/js/toggle_password_fields.js diff --git a/static/js/toggle_password_fields.js b/static/js/toggle_password_fields.js new file mode 100644 index 00000000..3c1f436f --- /dev/null +++ b/static/js/toggle_password_fields.js @@ -0,0 +1,24 @@ +/** This makes an checkbox toggle the appeareance of the + * password and password confirmations fields. + */ +function toggle_show_password_chkbox() { + var password1 = document.getElementById('id_Adherent-password1'); + var password2 = document.getElementById('id_Adherent-password2'); + + if (show_password_chkbox.checked) { + password1.parentElement.style.display = 'none'; + password2.parentElement.style.display = 'none'; + password1.required = false; + password2.required = false; + } else { + password1.parentElement.style.display = 'block'; + password2.parentElement.style.display = 'block'; + password1.required = true; + password2.required = true; + } +} + +var show_password_chkbox = document.getElementById('id_Adherent-init_password_by_mail'); +show_password_chkbox.onclick = toggle_show_password_chkbox; +toggle_show_password_chkbox(); + diff --git a/users/forms.py b/users/forms.py index cef1e43a..c5200fc2 100644 --- a/users/forms.py +++ b/users/forms.py @@ -380,7 +380,28 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): class AdherentCreationForm(AdherentForm): """Formulaire de création d'un user. AdherentForm auquel on ajoute une checkbox afin d'éviter les - doublons d'utilisateurs""" + doublons d'utilisateurs et, optionnellement, + un champ mot de passe""" + # Champ pour choisir si un lien est envoyé par mail pour le mot de passe + init_password_by_mail = forms.BooleanField(required=False, initial=True) + init_password_by_mail.label = _("Send password reset link by email.") + + # Champs pour initialiser le mot de passe + # Validators are handled manually since theses fields aren't always required + password1 = forms.CharField( + required=False, + label=_("Password"), + widget=forms.PasswordInput, + # validators=[MinLengthValidator(8)], + max_length=255, + ) + password2 = forms.CharField( + required=False, + label=_("Password confirmation"), + widget=forms.PasswordInput, + # validators=[MinLengthValidator(8)], + max_length=255, + ) # Champ permettant d'éviter au maxium les doublons d'utilisateurs former_user_check_info = _( @@ -422,6 +443,47 @@ class AdherentCreationForm(AdherentForm): ) ) + def clean_password1(self): + """Ignore ce champs si la case init_password_by_mail est décochée""" + send_email = self.cleaned_data.get("init_password_by_mail") + if send_email: + return None + + password1 = self.cleaned_data.get("password1") + if len(password1) < 8: + raise forms.ValidationError(_("Password must contain at least 8 characters.")) + + return password1 + + def clean_password2(self): + """Verifie que password1 et 2 sont identiques (si nécessaire)""" + send_email = self.cleaned_data.get("init_password_by_mail") + if send_email: + return None + + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError(_("The passwords don't match.")) + + return password2 + + def save(self, commit=True): + """Set the user's password, if entered + Returns the user and a bool indicating whether + an email to init the password should be sent""" + # Save the provided password in hashed format + user = super(AdherentForm, self).save(commit=False) + + send_email = self.cleaned_data.get("init_password_by_mail") + if not send_email: + user.set_password(self.cleaned_data["password1"]) + + user.should_send_password_reset_email = send_email + user.save() + return user + class AdherentEditForm(AdherentForm): """Formulaire d'édition d'un user. diff --git a/users/views.py b/users/views.py index 3fb4d472..f14cb295 100644 --- a/users/views.py +++ b/users/views.py @@ -119,15 +119,42 @@ def new_user(request): user = AdherentCreationForm(request.POST or None, user=request.user) GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up") GTU = GeneralOption.get_cached_value("GTU") + if user.is_valid(): user = user.save() - user.reset_passwd_mail(request) - messages.success( - request, - _("The user %s was created, an email to set the password was sent.") - % user.pseudo, - ) + + # Use "is False" so that if None, the email is sent + if user.should_send_password_reset_email is False: + messages.success( + request, + _("The user %s was created.") + % user.pseudo, + ) + else: + user.reset_passwd_mail(request) + messages.success( + request, + _("The user %s was created, an email to set the password was sent.") + % user.pseudo, + ) + return redirect(reverse("users:profil", kwargs={"userid": str(user.id)})) + + # Anonymous users are allowed to create new accounts + # but they should be treated differently + params = { + "userform": user, + "GTU_sum_up": GTU_sum_up, + "GTU": GTU, + "showCGU": True, + "action_name": _("Commit"), + } + + if request.user.is_anonymous: + params["load_js_file"] = "/static/js/toggle_password_fields.js" + + return form(params, "users/user.html", request) + """ return form( { "userform": user, @@ -139,6 +166,7 @@ def new_user(request): "users/user.html", request, ) + """ @login_required From 8c827cc84516ae152a9c7940b8072e5431c9fc82 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 17:16:33 +0000 Subject: [PATCH 02/83] Add option to enable the password field during account creation --- ...allow_set_password_during_user_creation.py | 20 +++++++++ preferences/models.py | 9 ++++ .../preferences/display_preferences.html | 4 ++ users/forms.py | 42 ++++++++++--------- users/views.py | 30 ++++--------- 5 files changed, 64 insertions(+), 41 deletions(-) create mode 100644 preferences/migrations/0068_optionaluser_allow_set_password_during_user_creation.py diff --git a/preferences/migrations/0068_optionaluser_allow_set_password_during_user_creation.py b/preferences/migrations/0068_optionaluser_allow_set_password_during_user_creation.py new file mode 100644 index 00000000..63d9e4c9 --- /dev/null +++ b/preferences/migrations/0068_optionaluser_allow_set_password_during_user_creation.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-16 17:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0067_auto_20191120_0159'), + ] + + operations = [ + migrations.AddField( + model_name='optionaluser', + name='allow_set_password_during_user_creation', + field=models.BooleanField(default=False, help_text='If True, users have the choice to receive an email containing a link to reset their password during creation, or to directly set their password in the page. If False, an email is always sent.'), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index b8189384..8570e79b 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -117,6 +117,15 @@ class OptionalUser(AclMixin, PreferencesModel): " If False, only when a valid registration has been paid." ), ) + allow_set_password_during_user_creation = models.BooleanField( + default=False, + help_text=_( + "If True, users have the choice to receive an email containing" + " a link to reset their password during creation, or to directly" + " set their password in the page." + " If False, an email is always sent." + ), + ) allow_archived_connexion = models.BooleanField( default=False, help_text=_("If True, archived users are allowed to connect.") ) diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 8e00962e..89858ddc 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -125,6 +125,10 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "All users are active by default" %} {{ useroptions.all_users_active|tick }} + {% trans "Allow directly entering a password during account creation" %} + {{ useroptions.allow_set_password_during_user_creation|tick }} + + {% trans "Allow archived users to log in" %} {{ useroptions.allow_archived_connexion|tick }} diff --git a/users/forms.py b/users/forms.py index c5200fc2..7b133c4b 100644 --- a/users/forms.py +++ b/users/forms.py @@ -382,26 +382,27 @@ class AdherentCreationForm(AdherentForm): AdherentForm auquel on ajoute une checkbox afin d'éviter les doublons d'utilisateurs et, optionnellement, un champ mot de passe""" - # Champ pour choisir si un lien est envoyé par mail pour le mot de passe - init_password_by_mail = forms.BooleanField(required=False, initial=True) - init_password_by_mail.label = _("Send password reset link by email.") + if OptionalUser.get_cached_value("allow_set_password_during_user_creation"): + # Champ pour choisir si un lien est envoyé par mail pour le mot de passe + init_password_by_mail = forms.BooleanField(required=False, initial=True) + init_password_by_mail.label = _("Send password reset link by email.") - # Champs pour initialiser le mot de passe - # Validators are handled manually since theses fields aren't always required - password1 = forms.CharField( - required=False, - label=_("Password"), - widget=forms.PasswordInput, - # validators=[MinLengthValidator(8)], - max_length=255, - ) - password2 = forms.CharField( - required=False, - label=_("Password confirmation"), - widget=forms.PasswordInput, - # validators=[MinLengthValidator(8)], - max_length=255, - ) + # Champs pour initialiser le mot de passe + # Validators are handled manually since theses fields aren't always required + password1 = forms.CharField( + required=False, + label=_("Password"), + widget=forms.PasswordInput, + #validators=[MinLengthValidator(8)], + max_length=255, + ) + password2 = forms.CharField( + required=False, + label=_("Password confirmation"), + widget=forms.PasswordInput, + #validators=[MinLengthValidator(8)], + max_length=255, + ) # Champ permettant d'éviter au maxium les doublons d'utilisateurs former_user_check_info = _( @@ -476,7 +477,8 @@ class AdherentCreationForm(AdherentForm): # Save the provided password in hashed format user = super(AdherentForm, self).save(commit=False) - send_email = self.cleaned_data.get("init_password_by_mail") + is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") + send_email = not is_set_password_allowed or self.cleaned_data.get("init_password_by_mail") if not send_email: user.set_password(self.cleaned_data["password1"]) diff --git a/users/views.py b/users/views.py index f14cb295..3f41a990 100644 --- a/users/views.py +++ b/users/views.py @@ -119,12 +119,13 @@ def new_user(request): user = AdherentCreationForm(request.POST or None, user=request.user) GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up") GTU = GeneralOption.get_cached_value("GTU") + is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") if user.is_valid(): user = user.save() # Use "is False" so that if None, the email is sent - if user.should_send_password_reset_email is False: + if is_set_password_allowed and user.should_send_password_reset_email is False: messages.success( request, _("The user %s was created.") @@ -143,30 +144,17 @@ def new_user(request): # Anonymous users are allowed to create new accounts # but they should be treated differently params = { - "userform": user, - "GTU_sum_up": GTU_sum_up, - "GTU": GTU, - "showCGU": True, - "action_name": _("Commit"), - } + "userform": user, + "GTU_sum_up": GTU_sum_up, + "GTU": GTU, + "showCGU": True, + "action_name": _("Commit"), + } - if request.user.is_anonymous: + if is_set_password_allowed: params["load_js_file"] = "/static/js/toggle_password_fields.js" return form(params, "users/user.html", request) - """ - return form( - { - "userform": user, - "GTU_sum_up": GTU_sum_up, - "GTU": GTU, - "showCGU": True, - "action_name": _("Commit"), - }, - "users/user.html", - request, - ) - """ @login_required From dbe8223efccc30b5c870b9c72909aa5beac90304 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 17:39:28 +0000 Subject: [PATCH 03/83] Add translations for initial password related strings --- preferences/forms.py | 1 + preferences/locale/fr/LC_MESSAGES/django.po | 22 ++++++++++++++++- users/locale/fr/LC_MESSAGES/django.po | 27 ++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/preferences/forms.py b/preferences/forms.py index 9ea9fca5..2591b73b 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -68,6 +68,7 @@ class EditOptionalUserForm(ModelForm): self.fields["all_can_create_adherent"].label = _("All can create a member") self.fields["self_adhesion"].label = _("Self registration") self.fields["shell_default"].label = _("Default shell") + self.fields["allow_set_password_during_user_creation"].label = _("Allow directly setting a password during account creation") class EditOptionalMachineForm(ModelForm): diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index 0047c101..deb77b22 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -56,6 +56,11 @@ msgstr "Tous peuvent créer un adhérent" msgid "Self registration" msgstr "Autoinscription" +#: preferences/forms.py:70 +#: preferences/templates/preferences/display_preferences.html:120 +msgid "Allow directly setting a password during account creation" +msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" + #: preferences/forms.py:70 msgid "Default shell" msgstr "Interface en ligne de commande par défaut" @@ -292,6 +297,18 @@ msgstr "" "Si True, tous les nouveaux utilisations créés et connectés sont actifs. Si " "False, seulement quand une inscription validée a été payée." +#: preferences/models.py:116 +msgid "" +"If True, users have the choice to receive an email containing" +" a link to reset their password during creation, or to directly" +" set their password in the page." +" If False, an email is always sent." +msgstr "" +"Si True, les utilisateurs ont le choix de recevoir un email avec" +" un lien pour changer leur mot de passe lors de la création de leur compte, " +" ou alors de choisir leur mot de passe immédiatement." +" Si False, un email est toujours envoyé." + #: preferences/models.py:121 msgid "If True, archived users are allowed to connect." msgstr "Si True, les utilisateurs archivés sont autorisés à se connecter." @@ -1049,7 +1066,10 @@ msgstr "%(delete_notyetactive)s jours" msgid "All users are active by default" msgstr "Tous les utilisateurs sont actifs par défault" -#: preferences/templates/preferences/display_preferences.html:128 +msgid "Allow directly entering a password during account creation" +msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" + +#: preferences/templates/preferences/display_preferences.html:130 msgid "Allow archived users to log in" msgstr "Autoriser les utilisateurs archivés à se connecter" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 7d630850..4d8794b6 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -157,7 +157,23 @@ msgstr "Vous ne pouvez pas utiliser une adresse {}." msgid "A valid telephone number is required." msgstr "Un numéro de téléphone valide est requis." -#: users/forms.py:387 +#: users/forms.py:404 +msgid "" +"If this options is set, you will receive a link to set" +" your initial password by email. If you do not have" +" any means of accessing your emails, you can disable" +" this option to set your password immediatly." +msgstr "" +"Si cette option est activée, vous recevrez un lien pour choisir" +" votre mot de passe par email. Si vous n'avez pas accès" +" à vos mails, vous pouvez désactiver cette option" +" pour immédiatement entrer votre mot de passe." + +#: users/forms.py:442 +msgid "Send password reset link by email." +msgstr "Envoyer le lien de modification du mot de passe par mail." + +#: users/forms.py:435 msgid "" "If you already have an account, please use it. If your lost access to it, " "please consider using the forgotten password button on the login page or " @@ -188,6 +204,10 @@ msgstr "Laissez vide si vous n'avez pas de clé GPG." msgid "Default shell" msgstr "Interface en ligne de commande par défaut" +#: users/forms.py:166 users/forms.py:481 +msgid "Password must contain at least 8 characters." +msgstr "Le mot de passe doit contenir au moins 8 caractères." + #: users/forms.py:463 users/templates/users/aff_clubs.html:36 #: users/templates/users/aff_serviceusers.html:32 msgid "Name" @@ -1242,6 +1262,11 @@ msgstr "Connecté avec l'appareil :" msgid "MAC address %(mac)s" msgstr "Adresse MAC %(mac)s" +#: users/views.py:131 +#, python-format +msgid "The user %s was created." +msgstr "L'utilisateur %s a été créé." + #: users/views.py:127 #, python-format msgid "The user %s was created, an email to set the password was sent." From e57ec2ccd1b39c1a2c713446c35f8be684a699ec Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 17:59:53 +0000 Subject: [PATCH 04/83] Add help text for password checkbox in user creation --- users/forms.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/users/forms.py b/users/forms.py index 7b133c4b..3cac1d16 100644 --- a/users/forms.py +++ b/users/forms.py @@ -384,7 +384,18 @@ class AdherentCreationForm(AdherentForm): un champ mot de passe""" if OptionalUser.get_cached_value("allow_set_password_during_user_creation"): # Champ pour choisir si un lien est envoyé par mail pour le mot de passe - init_password_by_mail = forms.BooleanField(required=False, initial=True) + init_password_by_mail_info = _( + "If this options is set, you will receive a link to set" + " your initial password by email. If you do not have" + " any means of accessing your emails, you can disable" + " this option to set your password immediatly." + ) + + init_password_by_mail = forms.BooleanField( + help_text=init_password_by_mail_info, + required=False, + initial=True + ) init_password_by_mail.label = _("Send password reset link by email.") # Champs pour initialiser le mot de passe From 8728bc69f503ff4eaa17916aaaa48fa15bde3596 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 22:06:14 +0200 Subject: [PATCH 05/83] Create EMAIL_NOT_YET_CONFIRMED state --- freeradius_utils/auth.py | 2 +- logs/views.py | 10 +++++ re2o/utils.py | 2 +- users/forms.py | 14 +++++++ users/management/commands/archive.py | 1 + users/models.py | 40 ++++++++++++++++++- .../users/email_confirmation_request | 33 +++++++++++++++ users/views.py | 35 ++++++++++++++++ 8 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 users/templates/users/email_confirmation_request diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index 496f2f3f..daebf95d 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -469,7 +469,7 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): RadiusOption.get_attributes("non_member_attributes", attributes_kwargs), ) for user in room_user: - if user.is_ban() or user.state != User.STATE_ACTIVE: + if user.is_ban() or user.state not in [User.STATE_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: return ( sw_name, room, diff --git a/logs/views.py b/logs/views.py index 7c509134..99ecf37b 100644 --- a/logs/views.py +++ b/logs/views.py @@ -260,6 +260,16 @@ def stats_general(request): ), Club.objects.filter(state=Club.STATE_NOT_YET_ACTIVE).count(), ], + "email_not_confirmed_users": [ + _("Email not yet confirmed users"), + User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED).count(), + ( + Adherent.objects.filter( + state=Adherent.STATE_EMAIL_NOT_YET_CONFIRMED + ).count() + ), + Club.objects.filter(state=Club.STATE_EMAIL_NOT_YET_CONFIRMED).count(), + ], "adherent_users": [ _("Contributing members"), _all_adherent.count(), diff --git a/re2o/utils.py b/re2o/utils.py index f4abc57c..61c45c0c 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -116,7 +116,7 @@ def all_has_access(search_time=None, including_asso=True): if search_time is None: search_time = timezone.now() filter_user = ( - Q(state=User.STATE_ACTIVE) + (Q(state=User.STATE_ACTIVE) | Q(state=User.STATE_EMAIL_NOT_YET_CONFIRMED)) & ~Q( ban__in=Ban.objects.filter( Q(date_start__lt=search_time) & Q(date_end__gt=search_time) diff --git a/users/forms.py b/users/forms.py index 3cac1d16..bc88a1f4 100644 --- a/users/forms.py +++ b/users/forms.py @@ -117,6 +117,20 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): user.save() +class ConfirmMailForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): + """Formulaire de confirmation de l'email de l'utilisateur""" + class Meta: + model = User + fields = [] + + def save(self, commit=True): + """Confirmation de l'email""" + user = super(ConfirmMailForm, self).save(commit=False) + user.confirm_mail() + user.set_active() + user.save() + + class UserCreationForm(FormRevMixin, forms.ModelForm): """A form for creating new users. Includes all the required fields, plus a repeated password. diff --git a/users/management/commands/archive.py b/users/management/commands/archive.py index 1e4601a0..d730cfcd 100644 --- a/users/management/commands/archive.py +++ b/users/management/commands/archive.py @@ -77,6 +77,7 @@ class Command(BaseCommand): .exclude(id__in=all_has_access(search_time=date)) .exclude(state=User.STATE_NOT_YET_ACTIVE) .exclude(state=User.STATE_FULL_ARCHIVE) + .exclude(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) ) if show: diff --git a/users/models.py b/users/models.py index 75a38aa9..f93048c2 100755 --- a/users/models.py +++ b/users/models.py @@ -176,12 +176,14 @@ class User( STATE_ARCHIVE = 2 STATE_NOT_YET_ACTIVE = 3 STATE_FULL_ARCHIVE = 4 + STATE_EMAIL_NOT_YET_CONFIRMED = 5 STATES = ( (0, _("Active")), (1, _("Disabled")), (2, _("Archived")), (3, _("Not yet active")), (4, _("Fully archived")), + (5, _("Waiting for email confirmation")), ) surname = models.CharField(max_length=255) @@ -326,6 +328,7 @@ class User( return ( self.state == self.STATE_ACTIVE or self.state == self.STATE_NOT_YET_ACTIVE + or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or ( allow_archived and self.state in (self.STATE_ARCHIVE, self.STATE_FULL_ARCHIVE) @@ -480,7 +483,7 @@ class User( def has_access(self): """ Renvoie si un utilisateur a accès à internet """ return ( - self.state == User.STATE_ACTIVE + self.state in [User.STATE_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED] and not self.is_ban() and (self.is_connected() or self.is_whitelisted()) ) or self == AssoOption.get_cached_value("utilisateur_asso") @@ -665,6 +668,7 @@ class User( Si l'instance n'existe pas, on crée le ldapuser correspondant""" if sys.version_info[0] >= 3 and ( self.state == self.STATE_ACTIVE + or self.state == STATE_EMAIL_NOT_YET_CONFIRMED or self.state == self.STATE_ARCHIVE or self.state == self.STATE_DISABLED ): @@ -783,6 +787,34 @@ class User( ) return + def confirm_email_address_mail(self, request): + """Prend en argument un request, envoie un mail pour + confirmer l'adresse""" + req = Request() + req.type = Request.EMAIL + req.user = self + req.save() + template = loader.get_template("users/email_confirmation_request") + context = { + "name": req.user.get_full_name(), + "asso": AssoOption.get_cached_value("name"), + "asso_mail": AssoOption.get_cached_value("contact"), + "site_name": GeneralOption.get_cached_value("site_name"), + "url": request.build_absolute_uri( + reverse("users:process", kwargs={"token": req.token}) + ), + "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), + } + send_mail( + "Confirmation de l'email de %(name)s / Email confirmation for " + "%(name)s" % {"name": AssoOption.get_cached_value("name")}, + template.render(context), + GeneralOption.get_cached_value("email_from"), + [req.user.email], + fail_silently=False, + ) + return + def autoregister_machine(self, mac_address, nas_type): """ Fonction appellée par freeradius. Enregistre la mac pour une machine inconnue sur le compte de l'user""" @@ -845,6 +877,12 @@ class User( self.pwd_ntlm = hashNT(password) return + def confirm_mail(self): + """Marque l'email de l'utilisateur comme confirmé""" + # Let the "set_active" method handle + self.state = self.STATE_NOT_YET_ACTIVE + self.set_active() + @cached_property def email_address(self): if ( diff --git a/users/templates/users/email_confirmation_request b/users/templates/users/email_confirmation_request new file mode 100644 index 00000000..b3385e02 --- /dev/null +++ b/users/templates/users/email_confirmation_request @@ -0,0 +1,33 @@ +Bonjour {{ name }}, + +Vous trouverez ci-dessous une URL permettant de confirmer votre +adresse mail pour votre compte {{ site_name }}. Celui-ci vous permet de gérer l'ensemble +de vos équipements, votre compte, vos factures, et tous les services proposés sur le réseau. + + {{ url }} + +Contactez les administrateurs si vous n'êtes pas à l'origine de cette requête. + +Ce lien expirera dans {{ expire_in }} heures. + +Respectueusement, + +L'équipe de {{ asso }} (contact : {{ asso_mail }}). + +--- + +Hello {{ name }}, + +You will find below an URL allowing you to confirm the email address of your account +on {{ site_name }}. It enables you to manage your devices, your account, your invoices, and all +the services offered on the network. + + {{ url }} + +Contact the administrators if you didn't request this. + +This link will expire in {{ expire_in }} hours. + +Regards, + +The {{ asso }} team (contact: {{ asso_mail }}). diff --git a/users/views.py b/users/views.py index 3f41a990..4f4a62d3 100644 --- a/users/views.py +++ b/users/views.py @@ -105,6 +105,7 @@ from .forms import ( ClubForm, MassArchiveForm, PassForm, + ConfirmMailForm, ResetPasswordForm, ClubAdminandMembersForm, GroupForm, @@ -126,6 +127,7 @@ def new_user(request): # Use "is False" so that if None, the email is sent if is_set_password_allowed and user.should_send_password_reset_email is False: + user.confirm_email_address_mail(request) messages.success( request, _("The user %s was created.") @@ -737,6 +739,7 @@ def mass_archive(request): .exclude(id__in=all_has_access(search_time=date)) .exclude(state=User.STATE_NOT_YET_ACTIVE) .exclude(state=User.STATE_FULL_ARCHIVE) + .exclude(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) ) if not full_archive: to_archive_list = to_archive_list.exclude(state=User.STATE_ARCHIVE) @@ -1020,6 +1023,38 @@ def process_passwd(request, req): ) +def confirm_email(request, token): + """Lien pour la confirmation de l'email""" + valid_reqs = Request.objects.filter(expires_at__gt=timezone.now()) + req = get_object_or_404(valid_reqs, token=token) + + if req.type == Request.EMAIL: + return process_email(request, req) + else: + messages.error(request, _("Error: please contact an admin.")) + redirect(reverse("index")) + + +def process_email(request, req): + """Process la confirmation de mail, renvoie le formulaire + de validation""" + user = req.user + u_form = ConfirmMailForm(request.POST or None, instance=user, user=request.user) + if u_form.is_valid(): + with transaction.atomic(), reversion.create_revision(): + u_form.save() + reversion.set_comment("Email confirmation") + req.delete() + messages.success(request, _("The email was confirmed.")) + return redirect(reverse("index")) + + return form( + {"userform": u_form, "action_name": _("Confirm the email")}, + "users/user.html", + request, + ) + + @login_required def initial_register(request): switch_ip = request.GET.get("switch_ip", None) From 91edc7ed311f782b273954b609253c4e7dadbd3b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 22:22:54 +0200 Subject: [PATCH 06/83] Create disable_emailnotyetconfirmed.py --- .../commands/disable_emailnotyetconfirmed.py | 42 +++++++++++++++++++ users/models.py | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 users/management/commands/disable_emailnotyetconfirmed.py diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py new file mode 100644 index 00000000..7c01f3fb --- /dev/null +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -0,0 +1,42 @@ +# Copyright © 2017 Gabriel Détraz +# Copyright © 2017 Lara Kermarec +# Copyright © 2017 Augustin Lemesle +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +from django.core.management.base import BaseCommand, CommandError + +from users.models import User +from cotisations.models import Facture +from preferences.models import OptionalUser +from datetime import timedelta + +from django.utils import timezone + + +class Command(BaseCommand): + help = "Delete non members users (not yet active)." + + def handle(self, *args, **options): + """First deleting invalid invoices, and then deleting the users""" + days = OptionalUser.get_cached_value("delete_notyetactive") + users_to_disable = ( + User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) + .filter(registered__lte=timezone.now() - timedelta(days=days)) + .distinct() + ) + print("Disabling " + str(users_to_disable.count()) + " users.") + + users_to_disable.delete() diff --git a/users/models.py b/users/models.py index f93048c2..12795a67 100755 --- a/users/models.py +++ b/users/models.py @@ -668,7 +668,7 @@ class User( Si l'instance n'existe pas, on crée le ldapuser correspondant""" if sys.version_info[0] >= 3 and ( self.state == self.STATE_ACTIVE - or self.state == STATE_EMAIL_NOT_YET_CONFIRMED + or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or self.state == self.STATE_ARCHIVE or self.state == self.STATE_DISABLED ): From d4380f866f6ef5344ba6bef3e9c861edf64113b1 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 16 Apr 2020 23:14:19 +0200 Subject: [PATCH 07/83] Add option to select number of days before disabling users --- preferences/models.py | 6 ++++++ .../templates/preferences/display_preferences.html | 10 ++++++---- .../commands/disable_emailnotyetconfirmed.py | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/preferences/models.py b/preferences/models.py index 8570e79b..e470303d 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -107,6 +107,12 @@ class OptionalUser(AclMixin, PreferencesModel): "Not yet active users will be deleted after this number of days." ), ) + disable_emailnotyetconfirmed = models.IntegerField( + default=2, + help_text=_( + "Users with an email address not yet confirmed will be disabled after this number of days." + ), + ) self_adhesion = models.BooleanField( default=False, help_text=_("A new user can create their account on Re2o.") ) diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 89858ddc..6d7c656a 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -125,13 +125,15 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "All users are active by default" %} {{ useroptions.all_users_active|tick }} - {% trans "Allow directly entering a password during account creation" %} - {{ useroptions.allow_set_password_during_user_creation|tick }} - - {% trans "Allow archived users to log in" %} {{ useroptions.allow_archived_connexion|tick }} + + {% trans "Allow directly entering a password during account creation" %} + {{ useroptions.allow_set_password_during_user_creation|tick }} + {% trans "Disable email not yet confirmed users after" %} + {% blocktrans with disable_emailnotyetconfirmed=useroptions.disable_emailnotyetconfirmed %}{{ disable_emailnotyetconfirmed }} days{% endblocktrans %} +

{% trans "Users general permissions" %}

diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 7c01f3fb..404b5004 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -27,16 +27,17 @@ from django.utils import timezone class Command(BaseCommand): - help = "Delete non members users (not yet active)." + help = "Disable users who haven't confirmed their email." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("delete_notyetactive") + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_disable = ( User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) .filter(registered__lte=timezone.now() - timedelta(days=days)) .distinct() ) print("Disabling " + str(users_to_disable.count()) + " users.") - - users_to_disable.delete() + + for user in users_to_disable: + self.state = User.STATE_DISABLED From b91efa3d292b62e208f80e642f4910e785ba526e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 00:24:35 +0200 Subject: [PATCH 08/83] Start implementing user-facing confirmation email mechanics --- users/forms.py | 22 +++++++++++++ .../commands/disable_emailnotyetconfirmed.py | 3 +- users/models.py | 6 +++- users/templates/users/profil.html | 31 ++++++++++++++++++- users/urls.py | 1 + users/views.py | 14 +++++++++ 6 files changed, 74 insertions(+), 3 deletions(-) diff --git a/users/forms.py b/users/forms.py index bc88a1f4..5669b155 100644 --- a/users/forms.py +++ b/users/forms.py @@ -299,6 +299,11 @@ class ResetPasswordForm(forms.Form): email = forms.EmailField(max_length=255) +class ResendConfirmationEmailForm(forms.Form): + """Formulaire de renvoie du mail de confirmation""" + pass + + class MassArchiveForm(forms.Form): """Formulaire d'archivage des users inactif. Prend en argument du formulaire la date de depart avant laquelle archiver les @@ -344,6 +349,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields["room"].label = _("Room") self.fields["room"].empty_label = _("No room") self.fields["school"].empty_label = _("Select a school") + self.initial["email"] = kwargs["instance"].email class Meta: model = Adherent @@ -390,6 +396,22 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): remove_user_room(room) return + def save(self, commit=True): + """On met à jour l'état de l'utilisateur en fonction de son mail""" + user = super(AdherentForm, self).save(commit=False) + + if user.email != self.initial["email"]: + # Send a confirmation email + if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: + user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED + user.confirm_email_address_mail() + + # Always keep the oldest change date + if user.email_change_date is None: + user.email_change_date = timezone.now() + + return user + class AdherentCreationForm(AdherentForm): """Formulaire de création d'un user. diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 404b5004..b2678c19 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -34,7 +34,8 @@ class Command(BaseCommand): days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_disable = ( User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) - .filter(registered__lte=timezone.now() - timedelta(days=days)) + .exclude(email_change_date__is_null=True) + .filter(email_change_date__lte=timezone.now() - timedelta(days=days)) .distinct() ) print("Disabling " + str(users_to_disable.count()) + " users.") diff --git a/users/models.py b/users/models.py index 12795a67..8671fba0 100755 --- a/users/models.py +++ b/users/models.py @@ -226,6 +226,7 @@ class User( shortcuts_enabled = models.BooleanField( verbose_name=_("enable shortcuts on Re2o website"), default=True ) + email_change_date = None USERNAME_FIELD = "pseudo" REQUIRED_FIELDS = ["surname", "email"] @@ -879,7 +880,10 @@ class User( def confirm_mail(self): """Marque l'email de l'utilisateur comme confirmé""" - # Let the "set_active" method handle + # Reset the email change date + self.email_change_date = None + + # Let the "set_active" method handle the rest self.state = self.STATE_NOT_YET_ACTIVE self.set_active() diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 0bd25f75..cb8358a8 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -38,7 +38,36 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}

{% endif %} +
+ {% if users.state == Users.STATE_NOT_YET_ACTIVE %} +

{% blocktrans with name=users.name surname=users.surname %}Welcome {{ name }} {{ surname }}{% endblocktrans %}

+ {% else %} +

{% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}

+ {% endif %} +
+ + + +
+ {% if users.state == Users.STATE_NOT_YET_ACTIVE %} +
+
+ {% blocktrans %}Please confirm your email address{% endblocktrans %} +
+ + {% blocktrans %}Resend the email{% endblocktrans %} + +
+
+ {% elif users.state == Users.STATE_DISABLED %} +
+
+ {% blocktrans %}Your account has been disabled{% endblocktrans %} +
+
+ {% endif %} +
{% if users.is_ban%} @@ -181,7 +210,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Email address" %}
-
{{ users.email }}
+
{{ users.email }}{% if users.email_change_date is not None %}{% trans "Pending confirmation..." %}{% endif %}
diff --git a/users/urls.py b/users/urls.py index 1cd303e6..8ab5253d 100644 --- a/users/urls.py +++ b/users/urls.py @@ -42,6 +42,7 @@ urlpatterns = [ url(r"^state/(?P[0-9]+)$", views.state, name="state"), url(r"^groups/(?P[0-9]+)$", views.groups, name="groups"), url(r"^password/(?P[0-9]+)$", views.password, name="password"), + url(r"^confirm_email/(?P[0-9]+)$", views.resend_confirmation_email, name="resend-confirmation-email"), url( r"^del_group/(?P[0-9]+)/(?P[0-9]+)$", views.del_group, diff --git a/users/views.py b/users/views.py index 4f4a62d3..266c0717 100644 --- a/users/views.py +++ b/users/views.py @@ -107,6 +107,7 @@ from .forms import ( PassForm, ConfirmMailForm, ResetPasswordForm, + ResendConfirmationEmailForm, ClubAdminandMembersForm, GroupForm, InitialRegisterForm, @@ -1023,6 +1024,19 @@ def process_passwd(request, req): ) +def resend_confirmation_email(request): + """ Renvoie du mail de confirmation """ + userform = ResendConfirmationEmailForm(request.POST or None) + if userform.is_valid(): + request.user.confirm_email_address_mail() + messages.success(request, _("An email to confirm your address was sent.")) + return redirect(reverse("index")) + + return form( + {"userform": userform, "action_name": _("Send")}, "users/user.html", request + ) + + def confirm_email(request, token): """Lien pour la confirmation de l'email""" valid_reqs = Request.objects.filter(expires_at__gt=timezone.now()) From 5e2e6094742afa5da1d8fc4d7c8a6aba5213e1de Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 00:16:56 +0000 Subject: [PATCH 09/83] Fix handling of confirmation email for front facing elements --- users/forms.py | 13 ++++++---- users/models.py | 3 ++- users/templates/users/profil.html | 40 +++++++++++------------------ users/views.py | 42 ++++++++++++++++++------------- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/users/forms.py b/users/forms.py index 5669b155..c9f97508 100644 --- a/users/forms.py +++ b/users/forms.py @@ -368,6 +368,8 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): label=_("Force the move?"), initial=False, required=False ) + should_send_confirmation_email = False + def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get( "email" @@ -398,18 +400,19 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def save(self, commit=True): """On met à jour l'état de l'utilisateur en fonction de son mail""" - user = super(AdherentForm, self).save(commit=False) + user = super(AdherentForm, self).save(commit=commit) if user.email != self.initial["email"]: # Send a confirmation email if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED - user.confirm_email_address_mail() + self.should_send_confirmation_email = True - # Always keep the oldest change date - if user.email_change_date is None: - user.email_change_date = timezone.now() + # Always keep the oldest change date + if user.email_change_date is None: + user.email_change_date = timezone.now() + user.save() return user diff --git a/users/models.py b/users/models.py index 8671fba0..48c91be9 100755 --- a/users/models.py +++ b/users/models.py @@ -226,7 +226,7 @@ class User( shortcuts_enabled = models.BooleanField( verbose_name=_("enable shortcuts on Re2o website"), default=True ) - email_change_date = None + email_change_date = models.DateTimeField(default=None, null=True) USERNAME_FIELD = "pseudo" REQUIRED_FIELDS = ["surname", "email"] @@ -795,6 +795,7 @@ class User( req.type = Request.EMAIL req.user = self req.save() + template = loader.get_template("users/email_confirmation_request") context = { "name": req.user.get_full_name(), diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index cb8358a8..2cbfe9ce 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -39,35 +39,23 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %}
-
- {% if users.state == Users.STATE_NOT_YET_ACTIVE %} -

{% blocktrans with name=users.name surname=users.surname %}Welcome {{ name }} {{ surname }}{% endblocktrans %}

- {% else %} -

{% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}

- {% endif %} + +{% if users.state == users.STATE_EMAIL_NOT_YET_CONFIRMED %} +
+ {% blocktrans %}Please confirm your email address.{% endblocktrans %} +
+ + {% blocktrans %}Didn't receive the email?{% endblocktrans %} +
- +{% elif users.state == users.STATE_DISABLED %} +
+ {% blocktrans %}Your account has been disabled{% endblocktrans %} +
+{% endif %}
- {% if users.state == Users.STATE_NOT_YET_ACTIVE %} -
-
- {% blocktrans %}Please confirm your email address{% endblocktrans %} -
- - {% blocktrans %}Resend the email{% endblocktrans %} - -
-
- {% elif users.state == Users.STATE_DISABLED %} -
-
- {% blocktrans %}Your account has been disabled{% endblocktrans %} -
-
- {% endif %} -
{% if users.is_ban%} @@ -210,7 +198,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Email address" %}
-
{{ users.email }}{% if users.email_change_date is not None %}{% trans "Pending confirmation..." %}{% endif %}
+
{{ users.email }}{% if users.email_change_date is not None %}
{% trans "Pending confirmation..." %}{% endif %}
diff --git a/users/views.py b/users/views.py index 266c0717..16acd748 100644 --- a/users/views.py +++ b/users/views.py @@ -223,8 +223,13 @@ def edit_info(request, user, userid): ) if user_form.is_valid(): if user_form.changed_data: - user_form.save() + user = user_form.save() messages.success(request, _("The user was edited.")) + + if user_form.should_send_confirmation_email: + user.confirm_email_address_mail(request) + messages.warning(request, _("Sent a new confirmation email")) + return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( {"userform": user_form, "action_name": _("Edit")}, @@ -994,12 +999,15 @@ def reset_password(request): def process(request, token): - """Process, lien pour la reinitialisation du mot de passe""" + """Process, lien pour la reinitialisation du mot de passe + et la confirmation de l'email""" valid_reqs = Request.objects.filter(expires_at__gt=timezone.now()) req = get_object_or_404(valid_reqs, token=token) if req.type == Request.PASSWD: return process_passwd(request, req) + elif req.type == Request.EMAIL: + return process_email(request, req) else: messages.error(request, _("Error: please contact an admin.")) redirect(reverse("index")) @@ -1024,31 +1032,31 @@ def process_passwd(request, req): ) -def resend_confirmation_email(request): +def resend_confirmation_email(request, userid): """ Renvoie du mail de confirmation """ userform = ResendConfirmationEmailForm(request.POST or None) if userform.is_valid(): - request.user.confirm_email_address_mail() + try: + user = User.objects.get( + id=userid, + state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], + ) + except User.DoesNotExist: + messages.error(request, _("The user doesn't exist.")) + return form( + {"userform": userform, "action_name": _("Reset")}, + "users/user.html", + request, + ) + user.confirm_email_address_mail(request) messages.success(request, _("An email to confirm your address was sent.")) - return redirect(reverse("index")) + return redirect(reverse("users:profil", kwargs={"userid": userid})) return form( {"userform": userform, "action_name": _("Send")}, "users/user.html", request ) -def confirm_email(request, token): - """Lien pour la confirmation de l'email""" - valid_reqs = Request.objects.filter(expires_at__gt=timezone.now()) - req = get_object_or_404(valid_reqs, token=token) - - if req.type == Request.EMAIL: - return process_email(request, req) - else: - messages.error(request, _("Error: please contact an admin.")) - redirect(reverse("index")) - - def process_email(request, req): """Process la confirmation de mail, renvoie le formulaire de validation""" From f171eeb607f85f47209883ae64b033dff15a835e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 00:17:18 +0000 Subject: [PATCH 10/83] Add missing migrations --- ...tionaluser_disable_emailnotyetconfirmed.py | 21 +++++++++++++++++++ users/migrations/0085_auto_20200417_0031.py | 20 ++++++++++++++++++ .../migrations/0086_user_email_change_date.py | 20 ++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py create mode 100644 users/migrations/0085_auto_20200417_0031.py create mode 100644 users/migrations/0086_user_email_change_date.py diff --git a/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py new file mode 100644 index 00000000..3cc12081 --- /dev/null +++ b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-17 00:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0068_optionaluser_allow_set_password_during_user_creation'), + ] + + operations = [ + migrations.AddField( + model_name='optionaluser', + name='disable_emailnotyetconfirmed', + field=models.IntegerField(default=2, help_text='Users with an email address not yet confirmed will be disabled after this number of days.') + ), + ] + diff --git a/users/migrations/0085_auto_20200417_0031.py b/users/migrations/0085_auto_20200417_0031.py new file mode 100644 index 00000000..3a2e4241 --- /dev/null +++ b/users/migrations/0085_auto_20200417_0031.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-16 22:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0084_auto_20191120_0159'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='state', + field=models.IntegerField(choices=[(0, 'Active'), (1, 'Disabled'), (2, 'Archived'), (3, 'Not yet active'), (4, 'Fully archived'), (5, 'Waiting for email confirmation')], default=3), + ), + ] diff --git a/users/migrations/0086_user_email_change_date.py b/users/migrations/0086_user_email_change_date.py new file mode 100644 index 00000000..b6f7075c --- /dev/null +++ b/users/migrations/0086_user_email_change_date.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-17 00:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0085_auto_20200417_0031'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='email_change_date', + field=models.DateTimeField(default=None, null=True), + ), + ] From c76d5c71023e168e5c5dbff2e49d13b14480b083 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:22:29 +0200 Subject: [PATCH 11/83] Improve template for resending a confirmation email --- users/forms.py | 5 --- users/models.py | 2 +- .../users/resend_confirmation_email.html | 44 +++++++++++++++++++ users/views.py | 29 ++++++------ 4 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 users/templates/users/resend_confirmation_email.html diff --git a/users/forms.py b/users/forms.py index c9f97508..79302182 100644 --- a/users/forms.py +++ b/users/forms.py @@ -299,11 +299,6 @@ class ResetPasswordForm(forms.Form): email = forms.EmailField(max_length=255) -class ResendConfirmationEmailForm(forms.Form): - """Formulaire de renvoie du mail de confirmation""" - pass - - class MassArchiveForm(forms.Form): """Formulaire d'archivage des users inactif. Prend en argument du formulaire la date de depart avant laquelle archiver les diff --git a/users/models.py b/users/models.py index 48c91be9..0ca94a37 100755 --- a/users/models.py +++ b/users/models.py @@ -339,7 +339,7 @@ class User( def set_active(self): """Enable this user if he subscribed successfully one time before Reenable it if it was archived - Do nothing if disabed""" + Do nothing if disabled or waiting for email confirmation""" if self.state == self.STATE_NOT_YET_ACTIVE: if self.facture_set.filter(valid=True).filter( Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion") diff --git a/users/templates/users/resend_confirmation_email.html b/users/templates/users/resend_confirmation_email.html new file mode 100644 index 00000000..2c086ccd --- /dev/null +++ b/users/templates/users/resend_confirmation_email.html @@ -0,0 +1,44 @@ +{% extends 'users/sidebar.html' %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Lara Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 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., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} +{% load i18n %} + +{% block title %}{% trans "Confirmation email" %}{% endblock %} + +{% block content %} + +
+ {% csrf_token %} +

{% blocktrans %}Re-send confirmation email{% endblocktrans %}

+

{% blocktrans %}The confirmation email will be sent to {{ email }}.{% endblocktrans %}

+ {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %} +
+
+
+
+{% endblock %} + diff --git a/users/views.py b/users/views.py index 16acd748..5c23932e 100644 --- a/users/views.py +++ b/users/views.py @@ -107,7 +107,6 @@ from .forms import ( PassForm, ConfirmMailForm, ResetPasswordForm, - ResendConfirmationEmailForm, ClubAdminandMembersForm, GroupForm, InitialRegisterForm, @@ -228,7 +227,7 @@ def edit_info(request, user, userid): if user_form.should_send_confirmation_email: user.confirm_email_address_mail(request) - messages.warning(request, _("Sent a new confirmation email")) + messages.success(request, _("Sent a new confirmation email")) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( @@ -1034,26 +1033,24 @@ def process_passwd(request, req): def resend_confirmation_email(request, userid): """ Renvoie du mail de confirmation """ - userform = ResendConfirmationEmailForm(request.POST or None) + try: + user = User.objects.get( + id=userid, + state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], + ) + except User.DoesNotExist: + messages.error(request, _("The user doesn't exist.")) + return redirect(reverse("users:profil", kwargs={"userid": userid})) + if userform.is_valid(): - try: - user = User.objects.get( - id=userid, - state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], - ) - except User.DoesNotExist: - messages.error(request, _("The user doesn't exist.")) - return form( - {"userform": userform, "action_name": _("Reset")}, - "users/user.html", - request, - ) user.confirm_email_address_mail(request) messages.success(request, _("An email to confirm your address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": userid})) return form( - {"userform": userform, "action_name": _("Send")}, "users/user.html", request + {"email": user.email}, + "users/resend_confirmation_email.html", + request, ) From fca81fe906c906f49ec982928bb54a7b5f4a4b96 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:30:17 +0200 Subject: [PATCH 12/83] Fix template shown when confirming send an email --- users/views.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/users/views.py b/users/views.py index 5c23932e..29bdb500 100644 --- a/users/views.py +++ b/users/views.py @@ -1032,25 +1032,22 @@ def process_passwd(request, req): def resend_confirmation_email(request, userid): - """ Renvoie du mail de confirmation """ - try: - user = User.objects.get( - id=userid, - state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], - ) - except User.DoesNotExist: - messages.error(request, _("The user doesn't exist.")) - return redirect(reverse("users:profil", kwargs={"userid": userid})) + """ Renvoi du mail de confirmation """ + if request.method == "POST": + try: + user = User.objects.get( + id=userid, + state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], + ) + user.confirm_email_address_mail(request) + messages.success(request, _("An email to confirm your address was sent.")) + except User.DoesNotExist: + messages.error(request, _("The user doesn't exist.")) - if userform.is_valid(): - user.confirm_email_address_mail(request) - messages.success(request, _("An email to confirm your address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": userid})) return form( - {"email": user.email}, - "users/resend_confirmation_email.html", - request, + {"email": user.email}, "users/resend_confirmation_email.html", request ) From 4322540077ffdac6fcf543a31d269fe9e41a4db6 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:34:54 +0200 Subject: [PATCH 13/83] Fix user referenced before assignment --- users/views.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/users/views.py b/users/views.py index 29bdb500..4ed046f3 100644 --- a/users/views.py +++ b/users/views.py @@ -1033,17 +1033,17 @@ def process_passwd(request, req): def resend_confirmation_email(request, userid): """ Renvoi du mail de confirmation """ - if request.method == "POST": - try: - user = User.objects.get( - id=userid, - state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], - ) - user.confirm_email_address_mail(request) - messages.success(request, _("An email to confirm your address was sent.")) - except User.DoesNotExist: - messages.error(request, _("The user doesn't exist.")) + try: + user = User.objects.get( + id=userid, + state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], + ) + except User.DoesNotExist: + messages.error(request, _("The user doesn't exist.")) + if request.method == "POST": + user.confirm_email_address_mail(request) + messages.success(request, _("An email to confirm your address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": userid})) return form( From 608f77b0134742448bcade3ae277f5f3cb043546 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:38:05 +0200 Subject: [PATCH 14/83] Improve template of email confirmation view --- users/templates/users/resend_confirmation_email.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/templates/users/resend_confirmation_email.html b/users/templates/users/resend_confirmation_email.html index 2c086ccd..e0f782e0 100644 --- a/users/templates/users/resend_confirmation_email.html +++ b/users/templates/users/resend_confirmation_email.html @@ -32,8 +32,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %} -

{% blocktrans %}Re-send confirmation email{% endblocktrans %}

-

{% blocktrans %}The confirmation email will be sent to {{ email }}.{% endblocktrans %}

+

{% blocktrans %}Re-send confirmation email?{% endblocktrans %}

+

{% blocktrans %}The confirmation email will be sent to{% endblocktrans %} {{ email }}.

{% trans "Confirm" as tr_confirm %} {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
From c41c723b0cb8b048fc3b167a6d47323e6b31cb11 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:25:42 +0200 Subject: [PATCH 15/83] Replace ConfirmMailForm with an html template --- users/forms.py | 14 -------- users/templates/users/confirm_email.html | 44 ++++++++++++++++++++++++ users/views.py | 20 ++++++----- 3 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 users/templates/users/confirm_email.html diff --git a/users/forms.py b/users/forms.py index 79302182..f3ea52de 100644 --- a/users/forms.py +++ b/users/forms.py @@ -117,20 +117,6 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): user.save() -class ConfirmMailForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): - """Formulaire de confirmation de l'email de l'utilisateur""" - class Meta: - model = User - fields = [] - - def save(self, commit=True): - """Confirmation de l'email""" - user = super(ConfirmMailForm, self).save(commit=False) - user.confirm_mail() - user.set_active() - user.save() - - class UserCreationForm(FormRevMixin, forms.ModelForm): """A form for creating new users. Includes all the required fields, plus a repeated password. diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html new file mode 100644 index 00000000..61ae250e --- /dev/null +++ b/users/templates/users/confirm_email.html @@ -0,0 +1,44 @@ +{% extends 'users/sidebar.html' %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Lara Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 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., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} +{% load i18n %} + +{% block title %}{% trans "Confirmation email" %}{% endblock %} + +{% block content %} + +
+ {% csrf_token %} +

{% blocktrans %}Confirmation email{% endblocktrans %}

+

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for user {{ firstname }} {{ lastname }}.{% endblocktrans %}.

+ {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %} +
+
+
+
+{% endblock %} + diff --git a/users/views.py b/users/views.py index 4ed046f3..5b8b8e47 100644 --- a/users/views.py +++ b/users/views.py @@ -105,7 +105,6 @@ from .forms import ( ClubForm, MassArchiveForm, PassForm, - ConfirmMailForm, ResetPasswordForm, ClubAdminandMembersForm, GroupForm, @@ -1047,7 +1046,9 @@ def resend_confirmation_email(request, userid): return redirect(reverse("users:profil", kwargs={"userid": userid})) return form( - {"email": user.email}, "users/resend_confirmation_email.html", request + {"email": user.email}, + "users/resend_confirmation_email.html", + request ) @@ -1055,19 +1056,20 @@ def process_email(request, req): """Process la confirmation de mail, renvoie le formulaire de validation""" user = req.user - u_form = ConfirmMailForm(request.POST or None, instance=user, user=request.user) - if u_form.is_valid(): + if request.method == "POST": with transaction.atomic(), reversion.create_revision(): - u_form.save() + user.confirm_mail() + user.save() reversion.set_comment("Email confirmation") + req.delete() - messages.success(request, _("The email was confirmed.")) + messages.success(request, _("The %(email)s address was confirmed." % user.email)) return redirect(reverse("index")) return form( - {"userform": u_form, "action_name": _("Confirm the email")}, - "users/user.html", - request, + {"email": user.email, "firstname": user.firstname, "lastname": user.surname}, + "users/confirm_email.html", + request ) From dbefd3a0d7bf4fb62ce857db82123d7627dda05a Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 10:28:04 +0000 Subject: [PATCH 16/83] Fix confirm email template --- users/templates/users/confirm_email.html | 2 +- users/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html index 61ae250e..c90af524 100644 --- a/users/templates/users/confirm_email.html +++ b/users/templates/users/confirm_email.html @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %}

{% blocktrans %}Confirmation email{% endblocktrans %}

-

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for user {{ firstname }} {{ lastname }}.{% endblocktrans %}.

+

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for user {{ firstname }} {{ lastname }}.{% endblocktrans %}

{% trans "Confirm" as tr_confirm %} {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
diff --git a/users/views.py b/users/views.py index 5b8b8e47..42dc48e2 100644 --- a/users/views.py +++ b/users/views.py @@ -1067,7 +1067,7 @@ def process_email(request, req): return redirect(reverse("index")) return form( - {"email": user.email, "firstname": user.firstname, "lastname": user.surname}, + {"email": user.email, "firstname": user.name, "lastname": user.surname}, "users/confirm_email.html", request ) From 781c459db3e074d178cbe1d08b2b8a64aa598053 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 10:30:44 +0000 Subject: [PATCH 17/83] Fix string formatting error during email confirmation --- users/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/views.py b/users/views.py index 42dc48e2..233ddaee 100644 --- a/users/views.py +++ b/users/views.py @@ -1063,7 +1063,7 @@ def process_email(request, req): reversion.set_comment("Email confirmation") req.delete() - messages.success(request, _("The %(email)s address was confirmed." % user.email)) + messages.success(request, _("The %s address was confirmed." % user.email)) return redirect(reverse("index")) return form( From 32a7c801336226705522bcf774ef4385dabc6d5b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:41:33 +0200 Subject: [PATCH 18/83] Handle manually switching user state to/from STATE_EMAIL_NOT_YET_CONFIRMED --- users/models.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 0ca94a37..467056e7 100755 --- a/users/models.py +++ b/users/models.py @@ -638,7 +638,7 @@ class User( self.ldap_sync() def state_sync(self): - """Archive, or unarchive, if the user was not active/or archived before""" + """Handle archiving/unarchiving, and manually confirming a user's email address""" if ( self.__original_state != self.STATE_ACTIVE and self.state == self.STATE_ACTIVE @@ -654,6 +654,16 @@ class User( and self.state == self.STATE_FULL_ARCHIVE ): self.full_archive() + elif ( + self.__original_state == self.STATE_EMAIL_NOT_YET_CONFIRMED + and self.state not in [self.STATE_EMAIL_NOT_YET_CONFIRMED, self.STATE_DISABLED] + ): + self.email_change_date = None + elif ( + self.__original_state != self.STATE_EMAIL_NOT_YET_CONFIRMED + and self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED + ): + self.email_change_date = timezone.now() def ldap_sync( self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False From bd153d53b258e136f5c1469487760d2a1eeb5c34 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:50:22 +0200 Subject: [PATCH 19/83] Automatically consider email valid when user is set to STATE_ACTIVE --- users/models.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/users/models.py b/users/models.py index 467056e7..7f2481cb 100755 --- a/users/models.py +++ b/users/models.py @@ -654,16 +654,15 @@ class User( and self.state == self.STATE_FULL_ARCHIVE ): self.full_archive() - elif ( - self.__original_state == self.STATE_EMAIL_NOT_YET_CONFIRMED - and self.state not in [self.STATE_EMAIL_NOT_YET_CONFIRMED, self.STATE_DISABLED] - ): - self.email_change_date = None elif ( self.__original_state != self.STATE_EMAIL_NOT_YET_CONFIRMED and self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED ): self.email_change_date = timezone.now() + elif ( + self.state == self.STATE_ACTIVE + ): + self.email_change_date = None def ldap_sync( self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False From 97ce17792b0679640edb89ee5c764d61f4e6a2e8 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:53:03 +0200 Subject: [PATCH 20/83] Fix overlapping conditions in User.state_sync --- users/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/users/models.py b/users/models.py index 7f2481cb..0f04aeaf 100755 --- a/users/models.py +++ b/users/models.py @@ -643,6 +643,7 @@ class User( self.__original_state != self.STATE_ACTIVE and self.state == self.STATE_ACTIVE ): + self.email_change_date = None self.unarchive() elif ( self.__original_state != self.STATE_ARCHIVE @@ -659,10 +660,6 @@ class User( and self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED ): self.email_change_date = timezone.now() - elif ( - self.state == self.STATE_ACTIVE - ): - self.email_change_date = None def ldap_sync( self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False From b171ac64bb3282dbd30c00d7ce02624fe1ddb812 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 13:01:10 +0200 Subject: [PATCH 21/83] Move user email_change_date update on manual state change to seperate method --- users/forms.py | 1 + users/models.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/users/forms.py b/users/forms.py index f3ea52de..be248214 100644 --- a/users/forms.py +++ b/users/forms.py @@ -675,6 +675,7 @@ class StateForm(FormRevMixin, ModelForm): if self.cleaned_data["state"]: user.state = self.cleaned_data.get("state") user.state_sync() + user.email_change_date_sync() user.save() diff --git a/users/models.py b/users/models.py index 0f04aeaf..12f88c56 100755 --- a/users/models.py +++ b/users/models.py @@ -638,12 +638,11 @@ class User( self.ldap_sync() def state_sync(self): - """Handle archiving/unarchiving, and manually confirming a user's email address""" + """Archive, or unarchive, if the user was not active/or archived before""" if ( self.__original_state != self.STATE_ACTIVE and self.state == self.STATE_ACTIVE ): - self.email_change_date = None self.unarchive() elif ( self.__original_state != self.STATE_ARCHIVE @@ -655,11 +654,21 @@ class User( and self.state == self.STATE_FULL_ARCHIVE ): self.full_archive() + + def email_change_date_sync(self): + """Update user's email_change_date based on state update""" + if ( + self.__original_state != self.STATE_ACTIVE + and self.state == self.STATE_ACTIVE + ): + self.email_change_date = None + self.save() elif ( self.__original_state != self.STATE_EMAIL_NOT_YET_CONFIRMED and self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED ): self.email_change_date = timezone.now() + self.save() def ldap_sync( self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False From 67b03827dfae879ce22ddd1ef9c390869a2fdbc8 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:13:56 +0000 Subject: [PATCH 22/83] Always sync email_change_date on manual state change --- users/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/users/forms.py b/users/forms.py index be248214..de229fcd 100644 --- a/users/forms.py +++ b/users/forms.py @@ -675,7 +675,8 @@ class StateForm(FormRevMixin, ModelForm): if self.cleaned_data["state"]: user.state = self.cleaned_data.get("state") user.state_sync() - user.email_change_date_sync() + + user.email_change_date_sync() user.save() From 04969abed3a103b0ac8315143a3943da469eec5d Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 13:17:18 +0200 Subject: [PATCH 23/83] Require user_edit permission to resend confirmation email --- users/views.py | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/users/views.py b/users/views.py index 233ddaee..b93bf7f3 100644 --- a/users/views.py +++ b/users/views.py @@ -1030,6 +1030,28 @@ def process_passwd(request, req): ) +def process_email(request, req): + """Process la confirmation de mail, renvoie le formulaire + de validation""" + user = req.user + if request.method == "POST": + with transaction.atomic(), reversion.create_revision(): + user.confirm_mail() + user.save() + reversion.set_comment("Email confirmation") + + req.delete() + messages.success(request, _("The %s address was confirmed." % user.email)) + return redirect(reverse("index")) + + return form( + {"email": user.email, "firstname": user.name, "lastname": user.surname}, + "users/confirm_email.html", + request + ) + + +@can_edit(User) def resend_confirmation_email(request, userid): """ Renvoi du mail de confirmation """ try: @@ -1052,27 +1074,6 @@ def resend_confirmation_email(request, userid): ) -def process_email(request, req): - """Process la confirmation de mail, renvoie le formulaire - de validation""" - user = req.user - if request.method == "POST": - with transaction.atomic(), reversion.create_revision(): - user.confirm_mail() - user.save() - reversion.set_comment("Email confirmation") - - req.delete() - messages.success(request, _("The %s address was confirmed." % user.email)) - return redirect(reverse("index")) - - return form( - {"email": user.email, "firstname": user.name, "lastname": user.surname}, - "users/confirm_email.html", - request - ) - - @login_required def initial_register(request): switch_ip = request.GET.get("switch_ip", None) From a91866e74169eddfc0293726a984827b231d27eb Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 11:39:11 +0000 Subject: [PATCH 24/83] Require login on confirmation email resend --- users/forms.py | 8 ++++++-- users/views.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/users/forms.py b/users/forms.py index de229fcd..dd9ddaa7 100644 --- a/users/forms.py +++ b/users/forms.py @@ -330,7 +330,11 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields["room"].label = _("Room") self.fields["room"].empty_label = _("No room") self.fields["school"].empty_label = _("Select a school") - self.initial["email"] = kwargs["instance"].email + + if not kwargs["user"].is_anonymous(): + self.initial["email"] = kwargs["user"].email + else: + self.initial["email"] = None class Meta: model = Adherent @@ -383,7 +387,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """On met à jour l'état de l'utilisateur en fonction de son mail""" user = super(AdherentForm, self).save(commit=commit) - if user.email != self.initial["email"]: + if self.initial["email"] is not None and user.email != self.initial["email"]: # Send a confirmation email if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED diff --git a/users/views.py b/users/views.py index b93bf7f3..cd135546 100644 --- a/users/views.py +++ b/users/views.py @@ -1051,8 +1051,9 @@ def process_email(request, req): ) +@login_required @can_edit(User) -def resend_confirmation_email(request, userid): +def resend_confirmation_email(request, logged_user, userid): """ Renvoi du mail de confirmation """ try: user = User.objects.get( From a88a2e4848f1a36c4d9d4aa1eacf8b8012130c1c Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 13:43:04 +0200 Subject: [PATCH 25/83] Delete disabled users who never created an invoice --- users/management/commands/clean_notyetactive.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index 994abfc2..d6c9a701 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -17,6 +17,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from django.core.management.base import BaseCommand, CommandError +from django.db.models import Q from users.models import User from cotisations.models import Facture @@ -27,13 +28,13 @@ from django.utils import timezone class Command(BaseCommand): - help = "Delete non members users (not yet active)." + help = "Delete non members users (not yet active or disabled too long ago without an invoice)." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" days = OptionalUser.get_cached_value("delete_notyetactive") users_to_delete = ( - User.objects.filter(state=User.STATE_NOT_YET_ACTIVE) + User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_DISABLED)) .filter(registered__lte=timezone.now() - timedelta(days=days)) .exclude(facture__valid=True) .distinct() From 1d21364515f6523ef4ce06d850104008498af79d Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 14:08:54 +0200 Subject: [PATCH 26/83] Show warning with delay before account suspension for users --- users/management/commands/clean_notyetactive.py | 2 +- users/models.py | 9 +++++++++ users/templates/users/email_confirmation_request | 6 ++++++ users/templates/users/profil.html | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index d6c9a701..d1857096 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -32,7 +32,7 @@ class Command(BaseCommand): def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("delete_notyetactive") + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_delete = ( User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_DISABLED)) .filter(registered__lte=timezone.now() - timedelta(days=days)) diff --git a/users/models.py b/users/models.py index 12f88c56..1e0db421 100755 --- a/users/models.py +++ b/users/models.py @@ -62,6 +62,7 @@ from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.db import transaction from django.utils import timezone +from datetime import timedelta from django.contrib.auth.models import ( AbstractBaseUser, BaseUserManager, @@ -803,6 +804,13 @@ class User( ) return + def confirm_email_before_date(self): + if self.email_change_date is None or self.state != self.STATE_EMAIL_NOT_YET_CONFIRMED: + return None + + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") + return str(self.email_change_date + timedelta(days=days)) + def confirm_email_address_mail(self, request): """Prend en argument un request, envoie un mail pour confirmer l'adresse""" @@ -821,6 +829,7 @@ class User( reverse("users:process", kwargs={"token": req.token}) ), "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), + "confirm_before": self.confirm_email_before_date(), } send_mail( "Confirmation de l'email de %(name)s / Email confirmation for " diff --git a/users/templates/users/email_confirmation_request b/users/templates/users/email_confirmation_request index b3385e02..65e20c13 100644 --- a/users/templates/users/email_confirmation_request +++ b/users/templates/users/email_confirmation_request @@ -9,6 +9,9 @@ de vos équipements, votre compte, vos factures, et tous les services proposés Contactez les administrateurs si vous n'êtes pas à l'origine de cette requête. Ce lien expirera dans {{ expire_in }} heures. +S'il a expiré, vous pouvez renvoyer un mail de confirmation depuis votre compte {{ site_name }}. + +Attention : Si vous ne confirmez pas votre email avant le {{ confirm_before }}, votre compte sera suspendu. Respectueusement, @@ -27,6 +30,9 @@ the services offered on the network. Contact the administrators if you didn't request this. This link will expire in {{ expire_in }} hours. +If it has expired, you can send a new confirmation email from your account on {{ site_name }}. + +Warning: If you do not confirm your email before {{ confirm_before }}, your account will be suspended. Regards, diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 2cbfe9ce..84871045 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -42,7 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if users.state == users.STATE_EMAIL_NOT_YET_CONFIRMED %}
- {% blocktrans %}Please confirm your email address.{% endblocktrans %} + {% blocktrans %}Please confirm your email address before {{ users.confirm_email_before_date }}, or your account will be suspended.{% endblocktrans %}
{% blocktrans %}Didn't receive the email?{% endblocktrans %} From d00ecd098ebfcd664739609d5a5b5445fe540566 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 14:37:52 +0200 Subject: [PATCH 27/83] Show users in state STATE_EMAIL_NOT_YET_CONFIRMED in search --- search/forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/search/forms.py b/search/forms.py index 9f2ff82a..741a15e8 100644 --- a/search/forms.py +++ b/search/forms.py @@ -35,6 +35,7 @@ CHOICES_USER = ( ("2", _("Archived")), ("3", _("Not yet active")), ("4", _("Fully archived")), + ("5", _("Waiting for email confirmation")), ) CHOICES_AFF = ( From 148fa3ec98a6bc75bf4fd2f43a70e9854058d6bd Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:53:51 +0000 Subject: [PATCH 28/83] Fix wrong email showing up when editing user --- users/forms.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/users/forms.py b/users/forms.py index dd9ddaa7..24657fc2 100644 --- a/users/forms.py +++ b/users/forms.py @@ -330,11 +330,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields["room"].label = _("Room") self.fields["room"].empty_label = _("No room") self.fields["school"].empty_label = _("Select a school") - - if not kwargs["user"].is_anonymous(): - self.initial["email"] = kwargs["user"].email - else: - self.initial["email"] = None + self.is_anon = kwargs["user"].is_anonymous() class Meta: model = Adherent @@ -387,7 +383,7 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """On met à jour l'état de l'utilisateur en fonction de son mail""" user = super(AdherentForm, self).save(commit=commit) - if self.initial["email"] is not None and user.email != self.initial["email"]: + if not self.is_anon and self.initial["email"] and user.email != self.initial["email"]: # Send a confirmation email if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED From 289f6caa88712a9ccf411db2e058ad18b2cf6b6f Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 12:54:28 +0000 Subject: [PATCH 29/83] Improve various templates related to email confirmation --- users/models.py | 5 +++-- users/templates/users/confirm_email.html | 2 +- users/templates/users/email_confirmation_request | 4 ++-- users/templates/users/profil.html | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/users/models.py b/users/models.py index 1e0db421..6b570337 100755 --- a/users/models.py +++ b/users/models.py @@ -809,7 +809,7 @@ class User( return None days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") - return str(self.email_change_date + timedelta(days=days)) + return self.email_change_date + timedelta(days=days) def confirm_email_address_mail(self, request): """Prend en argument un request, envoie un mail pour @@ -829,7 +829,8 @@ class User( reverse("users:process", kwargs={"token": req.token}) ), "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), - "confirm_before": self.confirm_email_before_date(), + "confirm_before_fr": self.confirm_email_before_date().strftime("%d/%m/%Y"), + "confirm_before_en": self.confirm_email_before_date().strftime("%Y-%m-%d"), } send_mail( "Confirmation de l'email de %(name)s / Email confirmation for " diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html index c90af524..6d562377 100644 --- a/users/templates/users/confirm_email.html +++ b/users/templates/users/confirm_email.html @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %}

{% blocktrans %}Confirmation email{% endblocktrans %}

-

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for user {{ firstname }} {{ lastname }}.{% endblocktrans %}

+

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for {{ firstname }} {{ lastname }}.{% endblocktrans %}

{% trans "Confirm" as tr_confirm %} {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
diff --git a/users/templates/users/email_confirmation_request b/users/templates/users/email_confirmation_request index 65e20c13..8c5fe03d 100644 --- a/users/templates/users/email_confirmation_request +++ b/users/templates/users/email_confirmation_request @@ -11,7 +11,7 @@ Contactez les administrateurs si vous n'êtes pas à l'origine de cette requête Ce lien expirera dans {{ expire_in }} heures. S'il a expiré, vous pouvez renvoyer un mail de confirmation depuis votre compte {{ site_name }}. -Attention : Si vous ne confirmez pas votre email avant le {{ confirm_before }}, votre compte sera suspendu. +/!\ Attention : Si vous ne confirmez pas votre email avant le {{ confirm_before_fr }}, votre compte sera suspendu. Respectueusement, @@ -32,7 +32,7 @@ Contact the administrators if you didn't request this. This link will expire in {{ expire_in }} hours. If it has expired, you can send a new confirmation email from your account on {{ site_name }}. -Warning: If you do not confirm your email before {{ confirm_before }}, your account will be suspended. +/!\ Warning: If you do not confirm your email before {{ confirm_before_en }}, your account will be suspended. Regards, diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 84871045..1e52d61a 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -42,7 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if users.state == users.STATE_EMAIL_NOT_YET_CONFIRMED %}
- {% blocktrans %}Please confirm your email address before {{ users.confirm_email_before_date }}, or your account will be suspended.{% endblocktrans %} + {% blocktrans with confirm_before_date=users.confirm_email_before_date|date:"DATE_FORMAT" %}Please confirm your email address before {{ confirm_before_date }}, or your account will be suspended.{% endblocktrans %}
{% blocktrans %}Didn't receive the email?{% endblocktrans %} From 8c99f54145636da303195da4f0ba442a4e560e70 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 13:14:32 +0000 Subject: [PATCH 30/83] Fix disable_emailnotyetconfirmed task --- users/management/commands/disable_emailnotyetconfirmed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index b2678c19..e6de32f3 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -34,11 +34,11 @@ class Command(BaseCommand): days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_disable = ( User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) - .exclude(email_change_date__is_null=True) .filter(email_change_date__lte=timezone.now() - timedelta(days=days)) .distinct() ) print("Disabling " + str(users_to_disable.count()) + " users.") for user in users_to_disable: - self.state = User.STATE_DISABLED + user.state = User.STATE_DISABLED + user.save() From d01fe8c16340f8f1cc0ac8dc6a3e3bf58c55efb4 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 15:16:26 +0200 Subject: [PATCH 31/83] Notify users of suspension when they failed to confirm their email --- .../commands/disable_emailnotyetconfirmed.py | 1 + users/models.py | 18 +++++++++++++++++ users/templates/users/email_disable_notif | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 users/templates/users/email_disable_notif diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index e6de32f3..89dd3e86 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -41,4 +41,5 @@ class Command(BaseCommand): for user in users_to_disable: user.state = User.STATE_DISABLED + user.notif_disable() user.save() diff --git a/users/models.py b/users/models.py index 6b570337..e64db5d1 100755 --- a/users/models.py +++ b/users/models.py @@ -895,6 +895,24 @@ class User( ) return + def notif_disable(self): + """Envoi un mail de notification informant que l'adresse mail n'a pas été confirmée""" + template = loader.get_template("users/email_disable_notif") + context = { + "name": self.get_full_name(), + "asso_name": AssoOption.get_cached_value("name"), + "asso_email": AssoOption.get_cached_value("contact"), + "site_name": GeneralOption.get_cached_value("site_name"), + } + send_mail( + "Suspension automatique / Automatic suspension", + template.render(context), + GeneralOption.get_cached_value("email_from"), + [self.email], + fail_silently=False, + ) + return + def set_password(self, password): """ A utiliser de préférence, set le password en hash courrant et dans la version ntlm""" diff --git a/users/templates/users/email_disable_notif b/users/templates/users/email_disable_notif new file mode 100644 index 00000000..5b2a90e0 --- /dev/null +++ b/users/templates/users/email_disable_notif @@ -0,0 +1,20 @@ +Bonjour {{ name }}, + +Votre connexion a été automatiquement suspendue car votre adresse mail n'a pas été confirmée. Vous pouvez renvoyer un mail de confirmation sur votre compte {{ site_name }} pour réactiver votre connexion. + +Pour de plus amples renseignements, contactez {{ asso_name }} à l'adresse {{ asso_mail }}. + +Respectueusement, +L'équipe de {{ asso_name }}. + +--- + +Hello {{ name }}, + +Your connection has automatically been suspended because you have not confirmed your email address. You can ask for a new confirmation email to be sent on your profil at {{ site_name }} to enable your connection. + + +For more information, contactez {{ asso_name }} at {{ asso_mail }}. + +Regards, +The {{ asso_name }} team. From 4fc24fcf4735779df28b3e0dbd255ff8b743d858 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 15:22:53 +0200 Subject: [PATCH 32/83] Use get_full_name to generate confirm_email.html --- users/templates/users/confirm_email.html | 2 +- users/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html index 6d562377..60bcf11d 100644 --- a/users/templates/users/confirm_email.html +++ b/users/templates/users/confirm_email.html @@ -33,7 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %}

{% blocktrans %}Confirmation email{% endblocktrans %}

-

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for {{ firstname }} {{ lastname }}.{% endblocktrans %}

+

{% blocktrans %}Confirm the email{% endblocktrans %} {{ email }} {% blocktrans %}for {{ name }}.{% endblocktrans %}

{% trans "Confirm" as tr_confirm %} {% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
diff --git a/users/views.py b/users/views.py index cd135546..19d7d97f 100644 --- a/users/views.py +++ b/users/views.py @@ -1045,7 +1045,7 @@ def process_email(request, req): return redirect(reverse("index")) return form( - {"email": user.email, "firstname": user.name, "lastname": user.surname}, + {"email": user.email, "name": user.get_full_name()}, "users/confirm_email.html", request ) From 52e1a77044f8ffc78ec06b4e71fb25a2532da7b5 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 16:03:37 +0200 Subject: [PATCH 33/83] Add missing translations --- logs/locale/fr/LC_MESSAGES/django.po | 4 + logs/views.py | 2 +- preferences/forms.py | 1 + preferences/locale/fr/LC_MESSAGES/django.po | 19 ++++- .../preferences/display_preferences.html | 2 +- search/locale/fr/LC_MESSAGES/django.po | 4 + users/forms.py | 3 + users/locale/fr/LC_MESSAGES/django.po | 73 ++++++++++++++++++- users/templates/users/profil.html | 2 +- users/views.py | 4 +- 10 files changed, 103 insertions(+), 11 deletions(-) diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index f04f6bef..92dbb89b 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -244,6 +244,10 @@ msgid "Not yet active users" msgstr "Utilisateurs pas encore actifs" #: logs/views.py:264 +msgid "Waiting for email confirmation users" +msgstr "Utilisateurs en attente de confirmation d'email" + +#: logs/views.py:273 msgid "Contributing members" msgstr "Adhérents cotisants" diff --git a/logs/views.py b/logs/views.py index 99ecf37b..615fb69b 100644 --- a/logs/views.py +++ b/logs/views.py @@ -261,7 +261,7 @@ def stats_general(request): Club.objects.filter(state=Club.STATE_NOT_YET_ACTIVE).count(), ], "email_not_confirmed_users": [ - _("Email not yet confirmed users"), + _("Waiting for email confirmation users"), User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED).count(), ( Adherent.objects.filter( diff --git a/preferences/forms.py b/preferences/forms.py index 2591b73b..b296c0d4 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -66,6 +66,7 @@ class EditOptionalUserForm(ModelForm): self.fields["gpg_fingerprint"].label = _("GPG fingerprint") self.fields["all_can_create_club"].label = _("All can create a club") self.fields["all_can_create_adherent"].label = _("All can create a member") + self.fields["disable_emailnotyetconfirmed"].label = _("Delay before disabling accounts without a verified email") self.fields["self_adhesion"].label = _("Self registration") self.fields["shell_default"].label = _("Default shell") self.fields["allow_set_password_during_user_creation"].label = _("Allow directly setting a password during account creation") diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index deb77b22..e7ac2e66 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -286,6 +286,14 @@ msgstr "" "jours." #: preferences/models.py:111 +msgid "" +"Users with an email address not yet confirmed will be disabled after" +" this number of days." +msgstr "" +"Les utilisateurs n'ayant pas confirmé leur addresse mail seront" +" désactivés après ce nombre de jours" + +#: preferences/models.py:114 msgid "A new user can create their account on Re2o." msgstr "Un nouvel utilisateur peut créer son compte sur Re2o." @@ -1066,14 +1074,19 @@ msgstr "%(delete_notyetactive)s jours" msgid "All users are active by default" msgstr "Tous les utilisateurs sont actifs par défault" -msgid "Allow directly entering a password during account creation" -msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" - #: preferences/templates/preferences/display_preferences.html:130 msgid "Allow archived users to log in" msgstr "Autoriser les utilisateurs archivés à se connecter" #: preferences/templates/preferences/display_preferences.html:133 +msgid "Allow directly entering a password during account creation" +msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" + +#: preferences/templates/preferences/display_preferences.html:136 +msgid "Delay before disabling accounts without a verified email" +msgstr "Délai avant la désactivation des comptes sans adresse mail confirmé" + +#: preferences/templates/preferences/display_preferences.html:136 msgid "Users general permissions" msgstr "Permissions générales des utilisateurs" diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 6d7c656a..8eb75918 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -131,7 +131,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Allow directly entering a password during account creation" %} {{ useroptions.allow_set_password_during_user_creation|tick }} - {% trans "Disable email not yet confirmed users after" %} + {% trans "Delay before disabling accounts without a verified email" %} {% blocktrans with disable_emailnotyetconfirmed=useroptions.disable_emailnotyetconfirmed %}{{ disable_emailnotyetconfirmed }} days{% endblocktrans %} diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index ca0507ae..6da1a477 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -50,6 +50,10 @@ msgstr "Pas encore adhéré" msgid "Fully archived" msgstr "Complètement archivés" +#: search/forms.py:38 +msgid "Waiting for email confirmation" +msgstr "En attente de confirmation d'email" + #: search/forms.py:41 msgid "Users" msgstr "Utilisateurs" diff --git a/users/forms.py b/users/forms.py index 24657fc2..da833203 100644 --- a/users/forms.py +++ b/users/forms.py @@ -409,6 +409,9 @@ class AdherentCreationForm(AdherentForm): " your initial password by email. If you do not have" " any means of accessing your emails, you can disable" " this option to set your password immediatly." + " You will still receive an email to confirm your address." + " Failure to confirm your address will result in an" + " automatic suspension of your account until you do." ) init_password_by_mail = forms.BooleanField( diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 4d8794b6..1d6ec63b 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -163,11 +163,17 @@ msgid "" " your initial password by email. If you do not have" " any means of accessing your emails, you can disable" " this option to set your password immediatly." +" You will still receive an email to confirm your address." +" Failure to confirm your address will result in an" +" automatic suspension of your account until you do." msgstr "" "Si cette option est activée, vous recevrez un lien pour choisir" " votre mot de passe par email. Si vous n'avez pas accès" " à vos mails, vous pouvez désactiver cette option" " pour immédiatement entrer votre mot de passe." +" Vous recevrez tous de même un email de confirmation." +" Si vous ne confirmez pas votre adresse dans les délais" +" impartis, votre connexion sera automatiquement suspendue." #: users/forms.py:442 msgid "Send password reset link by email." @@ -961,6 +967,21 @@ msgstr "" "débrancher et rebrancher votre câble Ethernet pour bénéficier d'une " "connexion filaire." +#: users/templates/users/confirm_email.html:35 +#, python-format +msgid "Confirmation email" +msgstr "Email de confirmation" + +#: users/templates/users/confirm_email.html:36 +#, python-format +msgid "Confirm the email" +msgstr "Confirmer l'email" + +#: users/templates/users/confirm_email.html:36 +#, python-format +msgid "for %(name)s." +msgstr "pour %(name)s." + #: users/templates/users/profil.html:36 #, python-format msgid "Welcome %(name)s %(surname)s" @@ -971,6 +992,25 @@ msgstr "Bienvenue %(name)s %(surname)s" msgid "Profile of %(name)s %(surname)s" msgstr "Profil de %(name)s %(surname)s" +#: users/templates/users/profil.html:43 +#, python-format +msgid "" +"Please confirm your email address before %(confirm_before_date)s," +" or your account will be suspended." +msgstr "" +"Veuillez confirmer votre adresse mail avant le %(confirm_before_date)s," +" sous peine de suspension de votre compte." + +#: users/templates/users/profil.html:48 +#, python-format +msgid "Didn't receive the email?" +msgstr "Vous n'avez pas reçu le mail ?" + +#: users/templates/users/profil.html:53 +#, python-format +msgid "Your account has been suspended." +msgstr "Votre compte a été suspendu." + #: users/templates/users/profil.html:46 msgid "Your account has been banned." msgstr "Votre compte a été banni." @@ -1054,6 +1094,10 @@ msgstr "Envoi de mails désactivé" msgid "Connected" msgstr "Connecté" +#: users/templates/users/profil.html:201 +msgid "Pending confirmation..." +msgstr "En attente de confirmation..." + #: users/templates/users/profil.html:193 msgid "Pending connection..." msgstr "Connexion en attente..." @@ -1203,6 +1247,16 @@ msgstr "" msgid "Add an email address" msgstr "Ajouter une adresse mail" +#: users/templates/users/resend_confirmation_email.html:35 +#, python-format +msgid "Re-send confirmation email?" +msgstr "Renvoyer l'email de confirmation ?" + +#: users/templates/users/resend_confirmation_email.html:36 +#, python-format +msgid "The confirmation email will be sent to" +msgstr "L'email de confirmation sera envoyé à" + #: users/templates/users/sidebar.html:33 msgid "Create a club or organisation" msgstr "Créer un club ou une association" @@ -1264,10 +1318,11 @@ msgstr "Adresse MAC %(mac)s" #: users/views.py:131 #, python-format -msgid "The user %s was created." -msgstr "L'utilisateur %s a été créé." +msgid "The user %s was created, a confirmation email was sent." +msgstr "" +"L'utilisateur %s a été créé, un mail de confirmation a été envoyé." -#: users/views.py:127 +#: users/views.py:130 #, python-format msgid "The user %s was created, an email to set the password was sent." msgstr "" @@ -1296,6 +1351,10 @@ msgstr "Le club a été modifié." msgid "The user was edited." msgstr "L'utilisateur a été modifié." +#: users/views.py:229 +msgid "Sent a new confirmation email." +msgstr "Un nouveau mail de confirmation a été envoyé." + #: users/views.py:225 msgid "The state was edited." msgstr "L'état a été modifié." @@ -1477,6 +1536,14 @@ msgstr "" "Enregistrement réussi ! Veuillez débrancher et rebrancher votre câble " "Ethernet pour avoir accès à Internet." +#: users/views.py:1044 +msgid "The %s address was confirmed." +msgstr "L'adresse mail %s a été confirmée." + +#: users/views.py:1068 +msgid "An email to confirm your address was sent." +msgstr "Un mail pour confirmer votre adresse a été envoyé." + #: users/views.py:1072 users/views.py:1096 users/views.py:1111 msgid "The mailing list doesn't exist." msgstr "La liste de diffusion n'existe pas." diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 1e52d61a..1b1db462 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -50,7 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% elif users.state == users.STATE_DISABLED %}
- {% blocktrans %}Your account has been disabled{% endblocktrans %} + {% blocktrans %}Your account has been suspended.{% endblocktrans %}
{% endif %} diff --git a/users/views.py b/users/views.py index 19d7d97f..36ddefbd 100644 --- a/users/views.py +++ b/users/views.py @@ -129,7 +129,7 @@ def new_user(request): user.confirm_email_address_mail(request) messages.success( request, - _("The user %s was created.") + _("The user %s was created, a confirmation email was sent.") % user.pseudo, ) else: @@ -226,7 +226,7 @@ def edit_info(request, user, userid): if user_form.should_send_confirmation_email: user.confirm_email_address_mail(request) - messages.success(request, _("Sent a new confirmation email")) + messages.success(request, _("Sent a new confirmation email.")) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( From f8dfb072ebf475c33176df29d40bd54a19d9df4e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 16:32:37 +0200 Subject: [PATCH 34/83] Allow users in the STATE_EMAIL_NOT_YET_CONFIRMED to reset their password --- users/forms.py | 1 + users/views.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/users/forms.py b/users/forms.py index da833203..8135ed30 100644 --- a/users/forms.py +++ b/users/forms.py @@ -113,6 +113,7 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): """Changement du mot de passe""" user = super(PassForm, self).save(commit=False) user.set_password(self.cleaned_data.get("passwd1")) + user.state = User.STATE_NOT_YET_ACTIVE user.set_active() user.save() diff --git a/users/views.py b/users/views.py index 36ddefbd..0a4d05f5 100644 --- a/users/views.py +++ b/users/views.py @@ -979,7 +979,7 @@ def reset_password(request): user = User.objects.get( pseudo=userform.cleaned_data["pseudo"], email=userform.cleaned_data["email"], - state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE], + state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED], ) except User.DoesNotExist: messages.error(request, _("The user doesn't exist.")) From 98cbf79b01cf9f307bea4995f1b4abb200110c58 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 16:34:34 +0200 Subject: [PATCH 35/83] Add missing case for STATE_EMAIL_NOT_YET_CONFIRMED state in API --- api/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/views.py b/api/views.py index 4077eeeb..58730754 100644 --- a/api/views.py +++ b/api/views.py @@ -513,6 +513,7 @@ class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.User.objects.exclude( Q(state=users.User.STATE_DISABLED) | Q(state=users.User.STATE_NOT_YET_ACTIVE) + | Q(state=users.STATE_EMAIL_NOT_YET_CONFIRMED) | Q(state=users.User.STATE_FULL_ARCHIVE) ) serializer_class = serializers.BasicUserSerializer From d88a2b7b8ed79c5eb96836dbee0b67a8bd314c64 Mon Sep 17 00:00:00 2001 From: chirac Date: Fri, 17 Apr 2020 16:40:09 +0200 Subject: [PATCH 36/83] Update resend_confirmation_email.html --- users/templates/users/resend_confirmation_email.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/users/templates/users/resend_confirmation_email.html b/users/templates/users/resend_confirmation_email.html index e0f782e0..ab226374 100644 --- a/users/templates/users/resend_confirmation_email.html +++ b/users/templates/users/resend_confirmation_email.html @@ -4,9 +4,7 @@ Re2o est un logiciel d'administration développé initiallement au rezometz. Il se veut agnostique au réseau considéré, de manière à être installable en quelques clics. -Copyright © 2017 Gabriel Détraz -Copyright © 2017 Lara Kermarec -Copyright © 2017 Augustin Lemesle +Copyright © 2020 Jean-Romain Garnier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From accd946edf6a0f2c5fb574b7c3a07ee328ca513f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 17 Apr 2020 16:48:27 +0200 Subject: [PATCH 37/83] Update headers --- users/forms.py | 8 +++++--- users/management/commands/disable_emailnotyetconfirmed.py | 4 +--- users/models.py | 8 +++++--- users/templates/users/confirm_email.html | 8 +++++--- users/templates/users/profil.html | 8 +++++--- users/urls.py | 8 +++++--- users/views.py | 8 +++++--- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/users/forms.py b/users/forms.py index 8135ed30..ad5c3708 100644 --- a/users/forms.py +++ b/users/forms.py @@ -3,9 +3,11 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2017-2020 Gabriel Détraz +# Copyright © 2017-2020 Lara Kermarec +# Copyright © 2017-2020 Augustin Lemesle +# Copyright © 2017-2020 Hugo Levy--Falk +# Copyright © 2017-2020 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 89dd3e86..955234e5 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -1,6 +1,4 @@ -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2017-2020 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/users/models.py b/users/models.py index e64db5d1..397dc7f3 100755 --- a/users/models.py +++ b/users/models.py @@ -3,9 +3,11 @@ # Il se veut agnostique au réseau considéré, de manière à être installable # en quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2017-2020 Gabriel Détraz +# Copyright © 2017-2020 Lara Kermarec +# Copyright © 2017-2020 Augustin Lemesle +# Copyright © 2017-2020 Hugo Levy--Falk +# Copyright © 2017-2020 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/users/templates/users/confirm_email.html b/users/templates/users/confirm_email.html index 60bcf11d..b00d3e88 100644 --- a/users/templates/users/confirm_email.html +++ b/users/templates/users/confirm_email.html @@ -4,9 +4,11 @@ Re2o est un logiciel d'administration développé initiallement au rezometz. Il se veut agnostique au réseau considéré, de manière à être installable en quelques clics. -Copyright © 2017 Gabriel Détraz -Copyright © 2017 Lara Kermarec -Copyright © 2017 Augustin Lemesle +Copyright © 2017-2020 Gabriel Détraz +Copyright © 2017-2020 Lara Kermarec +Copyright © 2017-2020 Augustin Lemesle +Copyright © 2017-2020 Hugo Levy--Falk +Copyright © 2017-2020 Jean-Romain Garnier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 1b1db462..ed613537 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -4,9 +4,11 @@ Re2o est un logiciel d'administration développé initiallement au rezometz. Il se veut agnostique au réseau considéré, de manière à être installable en quelques clics. -Copyright © 2017 Gabriel Détraz -Copyright © 2017 Lara Kermarec -Copyright © 2017 Augustin Lemesle +Copyright © 2017-2020 Gabriel Détraz +Copyright © 2017-2020 Lara Kermarec +Copyright © 2017-2020 Augustin Lemesle +Copyright © 2017-2020 Hugo Levy--Falk +Copyright © 2017-2020 Jean-Romain Garnier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/users/urls.py b/users/urls.py index 8ab5253d..a1579a29 100644 --- a/users/urls.py +++ b/users/urls.py @@ -3,9 +3,11 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2017-2020 Gabriel Détraz +# Copyright © 2017-2020 Lara Kermarec +# Copyright © 2017-2020 Augustin Lemesle +# Copyright © 2017-2020 Hugo Levy--Falk +# Copyright © 2017-2020 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/users/views.py b/users/views.py index 0a4d05f5..fb5f8d73 100644 --- a/users/views.py +++ b/users/views.py @@ -3,9 +3,11 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2017-2020 Gabriel Détraz +# Copyright © 2017-2020 Lara Kermarec +# Copyright © 2017-2020 Augustin Lemesle +# Copyright © 2017-2020 Hugo Levy--Falk +# Copyright © 2017-2020 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 2aef2ae3fae59ef041542be533d2641dc7d6fbd2 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 16:51:58 +0200 Subject: [PATCH 38/83] Create STATE_SUSPENDED --- api/views.py | 3 ++- logs/locale/fr/LC_MESSAGES/django.po | 4 ++++ logs/views.py | 10 ++++++++++ preferences/forms.py | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 8 ++++---- ...9_optionaluser_disable_emailnotyetconfirmed.py | 4 ++-- preferences/models.py | 4 ++-- .../preferences/display_preferences.html | 4 ++-- search/forms.py | 1 + search/locale/fr/LC_MESSAGES/django.po | 4 ++++ users/forms.py | 8 ++++++-- users/management/commands/clean_notyetactive.py | 6 +++--- .../commands/disable_emailnotyetconfirmed.py | 14 +++++++------- users/models.py | 15 +++++++++------ ...email_disable_notif => email_suspension_notif} | 0 users/templates/users/profil.html | 2 +- users/views.py | 2 +- 17 files changed, 59 insertions(+), 32 deletions(-) rename users/templates/users/{email_disable_notif => email_suspension_notif} (100%) diff --git a/api/views.py b/api/views.py index 58730754..5dae2bba 100644 --- a/api/views.py +++ b/api/views.py @@ -513,8 +513,9 @@ class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.User.objects.exclude( Q(state=users.User.STATE_DISABLED) | Q(state=users.User.STATE_NOT_YET_ACTIVE) - | Q(state=users.STATE_EMAIL_NOT_YET_CONFIRMED) + | Q(state=users.User.STATE_EMAIL_NOT_YET_CONFIRMED) | Q(state=users.User.STATE_FULL_ARCHIVE) + | Q(state=users.User.STATE_SUSPENDED) ) serializer_class = serializers.BasicUserSerializer diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 92dbb89b..6268b05b 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -247,6 +247,10 @@ msgstr "Utilisateurs pas encore actifs" msgid "Waiting for email confirmation users" msgstr "Utilisateurs en attente de confirmation d'email" +#: logs/views.py:273 +msgid "Suspended users" +msgstr "Utilisateurs suspendus" + #: logs/views.py:273 msgid "Contributing members" msgstr "Adhérents cotisants" diff --git a/logs/views.py b/logs/views.py index 615fb69b..69ec5635 100644 --- a/logs/views.py +++ b/logs/views.py @@ -270,6 +270,16 @@ def stats_general(request): ), Club.objects.filter(state=Club.STATE_EMAIL_NOT_YET_CONFIRMED).count(), ], + "suspended_users": [ + _("Suspended users"), + User.objects.filter(state=User.STATE_SUSPENDED).count(), + ( + Adherent.objects.filter( + state=Adherent.STATE_SUSPENDED + ).count() + ), + Club.objects.filter(state=Club.STATE_SUSPENDED).count(), + ], "adherent_users": [ _("Contributing members"), _all_adherent.count(), diff --git a/preferences/forms.py b/preferences/forms.py index b296c0d4..1fdefa6a 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -66,7 +66,7 @@ class EditOptionalUserForm(ModelForm): self.fields["gpg_fingerprint"].label = _("GPG fingerprint") self.fields["all_can_create_club"].label = _("All can create a club") self.fields["all_can_create_adherent"].label = _("All can create a member") - self.fields["disable_emailnotyetconfirmed"].label = _("Delay before disabling accounts without a verified email") + self.fields["suspend_emailnotyetconfirmed"].label = _("Delay before suspending accounts without a verified email") self.fields["self_adhesion"].label = _("Self registration") self.fields["shell_default"].label = _("Default shell") self.fields["allow_set_password_during_user_creation"].label = _("Allow directly setting a password during account creation") diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index e7ac2e66..caa5719c 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -287,11 +287,11 @@ msgstr "" #: preferences/models.py:111 msgid "" -"Users with an email address not yet confirmed will be disabled after" +"Users with an email address not yet confirmed will be suspended after" " this number of days." msgstr "" "Les utilisateurs n'ayant pas confirmé leur addresse mail seront" -" désactivés après ce nombre de jours" +" suspendus après ce nombre de jours" #: preferences/models.py:114 msgid "A new user can create their account on Re2o." @@ -1083,8 +1083,8 @@ msgid "Allow directly entering a password during account creation" msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" #: preferences/templates/preferences/display_preferences.html:136 -msgid "Delay before disabling accounts without a verified email" -msgstr "Délai avant la désactivation des comptes sans adresse mail confirmé" +msgid "Delay before suspending accounts without a verified email" +msgstr "Délai avant la suspension des comptes sans adresse mail confirmé" #: preferences/templates/preferences/display_preferences.html:136 msgid "Users general permissions" diff --git a/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py index 3cc12081..137211ff 100644 --- a/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py +++ b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py @@ -14,8 +14,8 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='optionaluser', - name='disable_emailnotyetconfirmed', - field=models.IntegerField(default=2, help_text='Users with an email address not yet confirmed will be disabled after this number of days.') + name='suspend_emailnotyetconfirmed', + field=models.IntegerField(default=2, help_text='Users with an email address not yet confirmed will be suspended after this number of days.') ), ] diff --git a/preferences/models.py b/preferences/models.py index e470303d..360f2e51 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -107,10 +107,10 @@ class OptionalUser(AclMixin, PreferencesModel): "Not yet active users will be deleted after this number of days." ), ) - disable_emailnotyetconfirmed = models.IntegerField( + suspend_emailnotyetconfirmed = models.IntegerField( default=2, help_text=_( - "Users with an email address not yet confirmed will be disabled after this number of days." + "Users with an email address not yet confirmed will be suspended after this number of days." ), ) self_adhesion = models.BooleanField( diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 8eb75918..6ccb1b2f 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -131,8 +131,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Allow directly entering a password during account creation" %} {{ useroptions.allow_set_password_during_user_creation|tick }} - {% trans "Delay before disabling accounts without a verified email" %} - {% blocktrans with disable_emailnotyetconfirmed=useroptions.disable_emailnotyetconfirmed %}{{ disable_emailnotyetconfirmed }} days{% endblocktrans %} + {% trans "Delay before suspending accounts without a verified email" %} + {% blocktrans with suspend_emailnotyetconfirmed=useroptions.suspend_emailnotyetconfirmed %}{{ suspend_emailnotyetconfirmed }} days{% endblocktrans %} diff --git a/search/forms.py b/search/forms.py index 741a15e8..a49b92e7 100644 --- a/search/forms.py +++ b/search/forms.py @@ -36,6 +36,7 @@ CHOICES_USER = ( ("3", _("Not yet active")), ("4", _("Fully archived")), ("5", _("Waiting for email confirmation")), + ("6", _("Suspended")), ) CHOICES_AFF = ( diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index 6da1a477..4c34f29d 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -54,6 +54,10 @@ msgstr "Complètement archivés" msgid "Waiting for email confirmation" msgstr "En attente de confirmation d'email" +#: search/forms.py:39 +msgid "Suspended" +msgstr "Suspendu" + #: search/forms.py:41 msgid "Users" msgstr "Utilisateurs" diff --git a/users/forms.py b/users/forms.py index ad5c3708..52f4232b 100644 --- a/users/forms.py +++ b/users/forms.py @@ -388,10 +388,14 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): if not self.is_anon and self.initial["email"] and user.email != self.initial["email"]: # Send a confirmation email - if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: - user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED + # Don't do this for archived or disabled users + if user.state not in [User.STATE_ARCHIVE, User.STATE_FULL_ARCHIVE, User.STATE_DISABLED]: self.should_send_confirmation_email = True + # Suspend users stay suspended + if user.state == User.STATE_SUSPENDED: + user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED + # Always keep the oldest change date if user.email_change_date is None: user.email_change_date = timezone.now() diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index d1857096..5ca68827 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -28,13 +28,13 @@ from django.utils import timezone class Command(BaseCommand): - help = "Delete non members users (not yet active or disabled too long ago without an invoice)." + help = "Delete non members users (not yet active or suspended too long ago without an invoice)." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") + days = OptionalUser.get_cached_value("delete_notyetactive") users_to_delete = ( - User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_DISABLED)) + User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_SUSPENDED)) .filter(registered__lte=timezone.now() - timedelta(days=days)) .exclude(facture__valid=True) .distinct() diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 955234e5..e0cd6d5e 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -25,19 +25,19 @@ from django.utils import timezone class Command(BaseCommand): - help = "Disable users who haven't confirmed their email." + help = "Suspend users who haven't confirmed their email." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") - users_to_disable = ( + days = OptionalUser.get_cached_value("suspend_emailnotyetconfirmed") + users_to_suspend = ( User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) .filter(email_change_date__lte=timezone.now() - timedelta(days=days)) .distinct() ) - print("Disabling " + str(users_to_disable.count()) + " users.") + print("Suspending " + str(users_to_suspend.count()) + " users.") - for user in users_to_disable: - user.state = User.STATE_DISABLED - user.notif_disable() + for user in users_to_suspend: + user.state = User.STATE_SUSPENDED + user.notif_suspension() user.save() diff --git a/users/models.py b/users/models.py index 397dc7f3..36884115 100755 --- a/users/models.py +++ b/users/models.py @@ -174,12 +174,13 @@ class User( Champs principaux : name, surnname, pseudo, email, room, password Herite du django BaseUser et du système d'auth django""" - STATE_ACTIVE = 0 - STATE_DISABLED = 1 + STATE_ACTIVE = 0 # Can login and has Internet (if invoice is valid) + STATE_DISABLED = 1 # Cannot login to Re2o and doesn't have Internet STATE_ARCHIVE = 2 STATE_NOT_YET_ACTIVE = 3 STATE_FULL_ARCHIVE = 4 STATE_EMAIL_NOT_YET_CONFIRMED = 5 + STATE_SUSPENDED = 6 # Can login to Re2o but doesn't have Internet STATES = ( (0, _("Active")), (1, _("Disabled")), @@ -187,6 +188,7 @@ class User( (3, _("Not yet active")), (4, _("Fully archived")), (5, _("Waiting for email confirmation")), + (5, _("Suspended")), ) surname = models.CharField(max_length=255) @@ -395,7 +397,7 @@ class User( @cached_property def get_shadow_expire(self): """Return the shadow_expire value for the user""" - if self.state == self.STATE_DISABLED: + if self.state == self.STATE_DISABLED or self.STATE_SUSPENDED: return str(0) else: return None @@ -690,6 +692,7 @@ class User( or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or self.state == self.STATE_ARCHIVE or self.state == self.STATE_DISABLED + or self.state == self.STATE_SUSPENDED ): self.refresh_from_db() try: @@ -810,7 +813,7 @@ class User( if self.email_change_date is None or self.state != self.STATE_EMAIL_NOT_YET_CONFIRMED: return None - days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") + days = OptionalUser.get_cached_value("suspend_emailnotyetconfirmed") return self.email_change_date + timedelta(days=days) def confirm_email_address_mail(self, request): @@ -897,9 +900,9 @@ class User( ) return - def notif_disable(self): + def notif_suspension(self): """Envoi un mail de notification informant que l'adresse mail n'a pas été confirmée""" - template = loader.get_template("users/email_disable_notif") + template = loader.get_template("users/email_suspension_notif") context = { "name": self.get_full_name(), "asso_name": AssoOption.get_cached_value("name"), diff --git a/users/templates/users/email_disable_notif b/users/templates/users/email_suspension_notif similarity index 100% rename from users/templates/users/email_disable_notif rename to users/templates/users/email_suspension_notif diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index ed613537..e6878927 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -50,7 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans %}Didn't receive the email?{% endblocktrans %}
-{% elif users.state == users.STATE_DISABLED %} +{% elif users.state == users.STATE_SUSPENDED %}
{% blocktrans %}Your account has been suspended.{% endblocktrans %}
diff --git a/users/views.py b/users/views.py index fb5f8d73..0f7fee24 100644 --- a/users/views.py +++ b/users/views.py @@ -981,7 +981,7 @@ def reset_password(request): user = User.objects.get( pseudo=userform.cleaned_data["pseudo"], email=userform.cleaned_data["email"], - state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED], + state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED, User.STATE_SUSPENDED], ) except User.DoesNotExist: messages.error(request, _("The user doesn't exist.")) From 91c51c50df1d947cb451a4a037cb2b651a973ba0 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 16:57:29 +0200 Subject: [PATCH 39/83] Allow suspended users to login --- users/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 36884115..09877ae1 100755 --- a/users/models.py +++ b/users/models.py @@ -335,6 +335,7 @@ class User( self.state == self.STATE_ACTIVE or self.state == self.STATE_NOT_YET_ACTIVE or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED + or self.state == self.STATE_SUSPENDED or ( allow_archived and self.state in (self.STATE_ARCHIVE, self.STATE_FULL_ARCHIVE) @@ -344,7 +345,7 @@ class User( def set_active(self): """Enable this user if he subscribed successfully one time before Reenable it if it was archived - Do nothing if disabled or waiting for email confirmation""" + Do nothing if suspended, disabled or waiting for email confirmation""" if self.state == self.STATE_NOT_YET_ACTIVE: if self.facture_set.filter(valid=True).filter( Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion") From be7ffbcb904fde7d5eade37b38ff419070bbf142 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 17:03:51 +0200 Subject: [PATCH 40/83] Revert "Allow suspended users to login" This reverts commit 91c51c50df1d947cb451a4a037cb2b651a973ba0. --- users/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/users/models.py b/users/models.py index 09877ae1..36884115 100755 --- a/users/models.py +++ b/users/models.py @@ -335,7 +335,6 @@ class User( self.state == self.STATE_ACTIVE or self.state == self.STATE_NOT_YET_ACTIVE or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED - or self.state == self.STATE_SUSPENDED or ( allow_archived and self.state in (self.STATE_ARCHIVE, self.STATE_FULL_ARCHIVE) @@ -345,7 +344,7 @@ class User( def set_active(self): """Enable this user if he subscribed successfully one time before Reenable it if it was archived - Do nothing if suspended, disabled or waiting for email confirmation""" + Do nothing if disabled or waiting for email confirmation""" if self.state == self.STATE_NOT_YET_ACTIVE: if self.facture_set.filter(valid=True).filter( Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion") From 60186071dba3fb294d45768d814bceeb6edc9c5d Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 17:03:54 +0200 Subject: [PATCH 41/83] Revert "Create STATE_SUSPENDED" This reverts commit 2aef2ae3fae59ef041542be533d2641dc7d6fbd2. --- api/views.py | 3 +-- logs/locale/fr/LC_MESSAGES/django.po | 4 ---- logs/views.py | 10 ---------- preferences/forms.py | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 8 ++++---- ...9_optionaluser_disable_emailnotyetconfirmed.py | 4 ++-- preferences/models.py | 4 ++-- .../preferences/display_preferences.html | 4 ++-- search/forms.py | 1 - search/locale/fr/LC_MESSAGES/django.po | 4 ---- users/forms.py | 8 ++------ users/management/commands/clean_notyetactive.py | 6 +++--- .../commands/disable_emailnotyetconfirmed.py | 14 +++++++------- users/models.py | 15 ++++++--------- ...email_suspension_notif => email_disable_notif} | 0 users/templates/users/profil.html | 2 +- users/views.py | 2 +- 17 files changed, 32 insertions(+), 59 deletions(-) rename users/templates/users/{email_suspension_notif => email_disable_notif} (100%) diff --git a/api/views.py b/api/views.py index 5dae2bba..58730754 100644 --- a/api/views.py +++ b/api/views.py @@ -513,9 +513,8 @@ class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.User.objects.exclude( Q(state=users.User.STATE_DISABLED) | Q(state=users.User.STATE_NOT_YET_ACTIVE) - | Q(state=users.User.STATE_EMAIL_NOT_YET_CONFIRMED) + | Q(state=users.STATE_EMAIL_NOT_YET_CONFIRMED) | Q(state=users.User.STATE_FULL_ARCHIVE) - | Q(state=users.User.STATE_SUSPENDED) ) serializer_class = serializers.BasicUserSerializer diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 6268b05b..92dbb89b 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -247,10 +247,6 @@ msgstr "Utilisateurs pas encore actifs" msgid "Waiting for email confirmation users" msgstr "Utilisateurs en attente de confirmation d'email" -#: logs/views.py:273 -msgid "Suspended users" -msgstr "Utilisateurs suspendus" - #: logs/views.py:273 msgid "Contributing members" msgstr "Adhérents cotisants" diff --git a/logs/views.py b/logs/views.py index 69ec5635..615fb69b 100644 --- a/logs/views.py +++ b/logs/views.py @@ -270,16 +270,6 @@ def stats_general(request): ), Club.objects.filter(state=Club.STATE_EMAIL_NOT_YET_CONFIRMED).count(), ], - "suspended_users": [ - _("Suspended users"), - User.objects.filter(state=User.STATE_SUSPENDED).count(), - ( - Adherent.objects.filter( - state=Adherent.STATE_SUSPENDED - ).count() - ), - Club.objects.filter(state=Club.STATE_SUSPENDED).count(), - ], "adherent_users": [ _("Contributing members"), _all_adherent.count(), diff --git a/preferences/forms.py b/preferences/forms.py index 1fdefa6a..b296c0d4 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -66,7 +66,7 @@ class EditOptionalUserForm(ModelForm): self.fields["gpg_fingerprint"].label = _("GPG fingerprint") self.fields["all_can_create_club"].label = _("All can create a club") self.fields["all_can_create_adherent"].label = _("All can create a member") - self.fields["suspend_emailnotyetconfirmed"].label = _("Delay before suspending accounts without a verified email") + self.fields["disable_emailnotyetconfirmed"].label = _("Delay before disabling accounts without a verified email") self.fields["self_adhesion"].label = _("Self registration") self.fields["shell_default"].label = _("Default shell") self.fields["allow_set_password_during_user_creation"].label = _("Allow directly setting a password during account creation") diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index caa5719c..e7ac2e66 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -287,11 +287,11 @@ msgstr "" #: preferences/models.py:111 msgid "" -"Users with an email address not yet confirmed will be suspended after" +"Users with an email address not yet confirmed will be disabled after" " this number of days." msgstr "" "Les utilisateurs n'ayant pas confirmé leur addresse mail seront" -" suspendus après ce nombre de jours" +" désactivés après ce nombre de jours" #: preferences/models.py:114 msgid "A new user can create their account on Re2o." @@ -1083,8 +1083,8 @@ msgid "Allow directly entering a password during account creation" msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" #: preferences/templates/preferences/display_preferences.html:136 -msgid "Delay before suspending accounts without a verified email" -msgstr "Délai avant la suspension des comptes sans adresse mail confirmé" +msgid "Delay before disabling accounts without a verified email" +msgstr "Délai avant la désactivation des comptes sans adresse mail confirmé" #: preferences/templates/preferences/display_preferences.html:136 msgid "Users general permissions" diff --git a/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py index 137211ff..3cc12081 100644 --- a/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py +++ b/preferences/migrations/0069_optionaluser_disable_emailnotyetconfirmed.py @@ -14,8 +14,8 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='optionaluser', - name='suspend_emailnotyetconfirmed', - field=models.IntegerField(default=2, help_text='Users with an email address not yet confirmed will be suspended after this number of days.') + name='disable_emailnotyetconfirmed', + field=models.IntegerField(default=2, help_text='Users with an email address not yet confirmed will be disabled after this number of days.') ), ] diff --git a/preferences/models.py b/preferences/models.py index 360f2e51..e470303d 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -107,10 +107,10 @@ class OptionalUser(AclMixin, PreferencesModel): "Not yet active users will be deleted after this number of days." ), ) - suspend_emailnotyetconfirmed = models.IntegerField( + disable_emailnotyetconfirmed = models.IntegerField( default=2, help_text=_( - "Users with an email address not yet confirmed will be suspended after this number of days." + "Users with an email address not yet confirmed will be disabled after this number of days." ), ) self_adhesion = models.BooleanField( diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 6ccb1b2f..8eb75918 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -131,8 +131,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Allow directly entering a password during account creation" %} {{ useroptions.allow_set_password_during_user_creation|tick }} - {% trans "Delay before suspending accounts without a verified email" %} - {% blocktrans with suspend_emailnotyetconfirmed=useroptions.suspend_emailnotyetconfirmed %}{{ suspend_emailnotyetconfirmed }} days{% endblocktrans %} + {% trans "Delay before disabling accounts without a verified email" %} + {% blocktrans with disable_emailnotyetconfirmed=useroptions.disable_emailnotyetconfirmed %}{{ disable_emailnotyetconfirmed }} days{% endblocktrans %} diff --git a/search/forms.py b/search/forms.py index a49b92e7..741a15e8 100644 --- a/search/forms.py +++ b/search/forms.py @@ -36,7 +36,6 @@ CHOICES_USER = ( ("3", _("Not yet active")), ("4", _("Fully archived")), ("5", _("Waiting for email confirmation")), - ("6", _("Suspended")), ) CHOICES_AFF = ( diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index 4c34f29d..6da1a477 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -54,10 +54,6 @@ msgstr "Complètement archivés" msgid "Waiting for email confirmation" msgstr "En attente de confirmation d'email" -#: search/forms.py:39 -msgid "Suspended" -msgstr "Suspendu" - #: search/forms.py:41 msgid "Users" msgstr "Utilisateurs" diff --git a/users/forms.py b/users/forms.py index 52f4232b..ad5c3708 100644 --- a/users/forms.py +++ b/users/forms.py @@ -388,14 +388,10 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): if not self.is_anon and self.initial["email"] and user.email != self.initial["email"]: # Send a confirmation email - # Don't do this for archived or disabled users - if user.state not in [User.STATE_ARCHIVE, User.STATE_FULL_ARCHIVE, User.STATE_DISABLED]: + if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: + user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED self.should_send_confirmation_email = True - # Suspend users stay suspended - if user.state == User.STATE_SUSPENDED: - user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED - # Always keep the oldest change date if user.email_change_date is None: user.email_change_date = timezone.now() diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index 5ca68827..d1857096 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -28,13 +28,13 @@ from django.utils import timezone class Command(BaseCommand): - help = "Delete non members users (not yet active or suspended too long ago without an invoice)." + help = "Delete non members users (not yet active or disabled too long ago without an invoice)." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("delete_notyetactive") + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_delete = ( - User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_SUSPENDED)) + User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_DISABLED)) .filter(registered__lte=timezone.now() - timedelta(days=days)) .exclude(facture__valid=True) .distinct() diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index e0cd6d5e..955234e5 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -25,19 +25,19 @@ from django.utils import timezone class Command(BaseCommand): - help = "Suspend users who haven't confirmed their email." + help = "Disable users who haven't confirmed their email." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" - days = OptionalUser.get_cached_value("suspend_emailnotyetconfirmed") - users_to_suspend = ( + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") + users_to_disable = ( User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) .filter(email_change_date__lte=timezone.now() - timedelta(days=days)) .distinct() ) - print("Suspending " + str(users_to_suspend.count()) + " users.") + print("Disabling " + str(users_to_disable.count()) + " users.") - for user in users_to_suspend: - user.state = User.STATE_SUSPENDED - user.notif_suspension() + for user in users_to_disable: + user.state = User.STATE_DISABLED + user.notif_disable() user.save() diff --git a/users/models.py b/users/models.py index 36884115..397dc7f3 100755 --- a/users/models.py +++ b/users/models.py @@ -174,13 +174,12 @@ class User( Champs principaux : name, surnname, pseudo, email, room, password Herite du django BaseUser et du système d'auth django""" - STATE_ACTIVE = 0 # Can login and has Internet (if invoice is valid) - STATE_DISABLED = 1 # Cannot login to Re2o and doesn't have Internet + STATE_ACTIVE = 0 + STATE_DISABLED = 1 STATE_ARCHIVE = 2 STATE_NOT_YET_ACTIVE = 3 STATE_FULL_ARCHIVE = 4 STATE_EMAIL_NOT_YET_CONFIRMED = 5 - STATE_SUSPENDED = 6 # Can login to Re2o but doesn't have Internet STATES = ( (0, _("Active")), (1, _("Disabled")), @@ -188,7 +187,6 @@ class User( (3, _("Not yet active")), (4, _("Fully archived")), (5, _("Waiting for email confirmation")), - (5, _("Suspended")), ) surname = models.CharField(max_length=255) @@ -397,7 +395,7 @@ class User( @cached_property def get_shadow_expire(self): """Return the shadow_expire value for the user""" - if self.state == self.STATE_DISABLED or self.STATE_SUSPENDED: + if self.state == self.STATE_DISABLED: return str(0) else: return None @@ -692,7 +690,6 @@ class User( or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or self.state == self.STATE_ARCHIVE or self.state == self.STATE_DISABLED - or self.state == self.STATE_SUSPENDED ): self.refresh_from_db() try: @@ -813,7 +810,7 @@ class User( if self.email_change_date is None or self.state != self.STATE_EMAIL_NOT_YET_CONFIRMED: return None - days = OptionalUser.get_cached_value("suspend_emailnotyetconfirmed") + days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") return self.email_change_date + timedelta(days=days) def confirm_email_address_mail(self, request): @@ -900,9 +897,9 @@ class User( ) return - def notif_suspension(self): + def notif_disable(self): """Envoi un mail de notification informant que l'adresse mail n'a pas été confirmée""" - template = loader.get_template("users/email_suspension_notif") + template = loader.get_template("users/email_disable_notif") context = { "name": self.get_full_name(), "asso_name": AssoOption.get_cached_value("name"), diff --git a/users/templates/users/email_suspension_notif b/users/templates/users/email_disable_notif similarity index 100% rename from users/templates/users/email_suspension_notif rename to users/templates/users/email_disable_notif diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index e6878927..ed613537 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -50,7 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans %}Didn't receive the email?{% endblocktrans %}
-{% elif users.state == users.STATE_SUSPENDED %} +{% elif users.state == users.STATE_DISABLED %}
{% blocktrans %}Your account has been suspended.{% endblocktrans %}
diff --git a/users/views.py b/users/views.py index 0f7fee24..fb5f8d73 100644 --- a/users/views.py +++ b/users/views.py @@ -981,7 +981,7 @@ def reset_password(request): user = User.objects.get( pseudo=userform.cleaned_data["pseudo"], email=userform.cleaned_data["email"], - state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED, User.STATE_SUSPENDED], + state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED], ) except User.DoesNotExist: messages.error(request, _("The user doesn't exist.")) From 43eadaa7120fc83d2e25433a81f7ba93095dc788 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 17:35:24 +0200 Subject: [PATCH 42/83] Replace STATE_EMAIL_NOT_YET_CONFIRMED with an email_state --- api/views.py | 1 - freeradius_utils/auth.py | 17 ++++++++- logs/views.py | 10 ------ re2o/utils.py | 3 +- users/forms.py | 5 ++- users/locale/fr/LC_MESSAGES/django.po | 4 +-- users/management/commands/archive.py | 1 - .../commands/disable_emailnotyetconfirmed.py | 4 +-- users/migrations/0085_auto_20200417_0031.py | 12 +++++-- users/migrations/0085_user_email_state.py | 20 +++++++++++ .../migrations/0086_user_email_change_date.py | 2 +- users/models.py | 36 ++++++++----------- users/templates/users/profil.html | 6 ++-- users/views.py | 5 ++- 14 files changed, 73 insertions(+), 53 deletions(-) create mode 100644 users/migrations/0085_user_email_state.py diff --git a/api/views.py b/api/views.py index 58730754..4077eeeb 100644 --- a/api/views.py +++ b/api/views.py @@ -513,7 +513,6 @@ class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): queryset = users.User.objects.exclude( Q(state=users.User.STATE_DISABLED) | Q(state=users.User.STATE_NOT_YET_ACTIVE) - | Q(state=users.STATE_EMAIL_NOT_YET_CONFIRMED) | Q(state=users.User.STATE_FULL_ARCHIVE) ) serializer_class = serializers.BasicUserSerializer diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index daebf95d..966663b1 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -469,7 +469,7 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): RadiusOption.get_attributes("non_member_attributes", attributes_kwargs), ) for user in room_user: - if user.is_ban() or user.state not in [User.STATE_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: + if user.is_ban() or user.state != User.STATE_ACTIVE: return ( sw_name, room, @@ -480,6 +480,21 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): RadiusOption.get_cached_value("banned") != RadiusOption.REJECT, RadiusOption.get_attributes("banned_attributes", attributes_kwargs), ) + elif user.email_state == User.EMAIL_STATE_UNVERIFIED: + return ( + sw_name, + room, + u"Utilisateur suspendu (mail non confirme)", + getattr( + RadiusOption.get_cached_value("non_member_vlan"), + "vlan_id", + None, + ), + RadiusOption.get_cached_value("non_member") != RadiusOption.REJECT, + RadiusOption.get_attributes( + "non_member_attributes", attributes_kwargs + ), + ) elif not (user.is_connected() or user.is_whitelisted()): return ( sw_name, diff --git a/logs/views.py b/logs/views.py index 615fb69b..7c509134 100644 --- a/logs/views.py +++ b/logs/views.py @@ -260,16 +260,6 @@ def stats_general(request): ), Club.objects.filter(state=Club.STATE_NOT_YET_ACTIVE).count(), ], - "email_not_confirmed_users": [ - _("Waiting for email confirmation users"), - User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED).count(), - ( - Adherent.objects.filter( - state=Adherent.STATE_EMAIL_NOT_YET_CONFIRMED - ).count() - ), - Club.objects.filter(state=Club.STATE_EMAIL_NOT_YET_CONFIRMED).count(), - ], "adherent_users": [ _("Contributing members"), _all_adherent.count(), diff --git a/re2o/utils.py b/re2o/utils.py index 61c45c0c..348e5c55 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -116,7 +116,8 @@ def all_has_access(search_time=None, including_asso=True): if search_time is None: search_time = timezone.now() filter_user = ( - (Q(state=User.STATE_ACTIVE) | Q(state=User.STATE_EMAIL_NOT_YET_CONFIRMED)) + Q(state=User.STATE_ACTIVE) + & ~Q(email_state=User.EMAIL_STATE_UNVERIFIED) & ~Q( ban__in=Ban.objects.filter( Q(date_start__lt=search_time) & Q(date_end__gt=search_time) diff --git a/users/forms.py b/users/forms.py index ad5c3708..94002eeb 100644 --- a/users/forms.py +++ b/users/forms.py @@ -388,8 +388,8 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): if not self.is_anon and self.initial["email"] and user.email != self.initial["email"]: # Send a confirmation email - if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED]: - user.state = User.STATE_EMAIL_NOT_YET_CONFIRMED + if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE]: + user.email_state = User.EMAIL_STATE_PENDING self.should_send_confirmation_email = True # Always keep the oldest change date @@ -682,7 +682,6 @@ class StateForm(FormRevMixin, ModelForm): user.state = self.cleaned_data.get("state") user.state_sync() - user.email_change_date_sync() user.save() diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 1d6ec63b..922c2dbe 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -1008,8 +1008,8 @@ msgstr "Vous n'avez pas reçu le mail ?" #: users/templates/users/profil.html:53 #, python-format -msgid "Your account has been suspended." -msgstr "Votre compte a été suspendu." +msgid "Your account has been suspended, please confirm your email address." +msgstr "Votre compte a été suspendu, veuillez confirmer votre adresse mail." #: users/templates/users/profil.html:46 msgid "Your account has been banned." diff --git a/users/management/commands/archive.py b/users/management/commands/archive.py index d730cfcd..1e4601a0 100644 --- a/users/management/commands/archive.py +++ b/users/management/commands/archive.py @@ -77,7 +77,6 @@ class Command(BaseCommand): .exclude(id__in=all_has_access(search_time=date)) .exclude(state=User.STATE_NOT_YET_ACTIVE) .exclude(state=User.STATE_FULL_ARCHIVE) - .exclude(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) ) if show: diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 955234e5..85b12699 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -31,13 +31,13 @@ class Command(BaseCommand): """First deleting invalid invoices, and then deleting the users""" days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_disable = ( - User.objects.filter(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) + User.objects.filter(email_state=User.EMAIL_STATE_PENDING) .filter(email_change_date__lte=timezone.now() - timedelta(days=days)) .distinct() ) print("Disabling " + str(users_to_disable.count()) + " users.") for user in users_to_disable: - user.state = User.STATE_DISABLED + user.email_state = User.EMAIL_STATE_UNVERIFIED user.notif_disable() user.save() diff --git a/users/migrations/0085_auto_20200417_0031.py b/users/migrations/0085_auto_20200417_0031.py index 3a2e4241..802ea721 100644 --- a/users/migrations/0085_auto_20200417_0031.py +++ b/users/migrations/0085_auto_20200417_0031.py @@ -11,10 +11,16 @@ class Migration(migrations.Migration): ('users', '0084_auto_20191120_0159'), ] + def flag_verified(apps, schema_editor): + db_alias = schema_editor.connection.alias + users = apps.get_model("users", "User") + users.objects.using(db_alias).all().update(email_state=0) + operations = [ - migrations.AlterField( + migrations.AddField( model_name='user', - name='state', - field=models.IntegerField(choices=[(0, 'Active'), (1, 'Disabled'), (2, 'Archived'), (3, 'Not yet active'), (4, 'Fully archived'), (5, 'Waiting for email confirmation')], default=3), + name='email_state', + field=models.IntegerField(choices=[(0, 'Verified'), (1, 'Unverified'), (2, 'Waiting for email confirmation')], default=2), ), + migrations.RunPython(flag_verified), ] diff --git a/users/migrations/0085_user_email_state.py b/users/migrations/0085_user_email_state.py new file mode 100644 index 00000000..9dfff9af --- /dev/null +++ b/users/migrations/0085_user_email_state.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-16 22:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0084_auto_20191120_0159'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='email_state', + field=models.IntegerField(choices=[(0, 'Verified'), (1, 'Unverified'), (2, 'Waiting for email confirmation')], default=2), + ), + ] diff --git a/users/migrations/0086_user_email_change_date.py b/users/migrations/0086_user_email_change_date.py index b6f7075c..e8d9ea9d 100644 --- a/users/migrations/0086_user_email_change_date.py +++ b/users/migrations/0086_user_email_change_date.py @@ -8,7 +8,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('users', '0085_auto_20200417_0031'), + ('users', '0085_user_email_state'), ] operations = [ diff --git a/users/models.py b/users/models.py index 397dc7f3..b658e9ef 100755 --- a/users/models.py +++ b/users/models.py @@ -179,14 +179,21 @@ class User( STATE_ARCHIVE = 2 STATE_NOT_YET_ACTIVE = 3 STATE_FULL_ARCHIVE = 4 - STATE_EMAIL_NOT_YET_CONFIRMED = 5 STATES = ( (0, _("Active")), (1, _("Disabled")), (2, _("Archived")), (3, _("Not yet active")), (4, _("Fully archived")), - (5, _("Waiting for email confirmation")), + ) + + EMAIL_STATE_VERIFIED = 0 + EMAIL_STATE_UNVERIFIED = 1 + EMAIL_STATE_PENDING = 2 + EMAIL_STATES = ( + (0, _("Verified")), + (1, _("Unverified")), + (2, _("Waiting for email confirmation")), ) surname = models.CharField(max_length=255) @@ -222,6 +229,7 @@ class User( ) pwd_ntlm = models.CharField(max_length=255) state = models.IntegerField(choices=STATES, default=STATE_NOT_YET_ACTIVE) + email_state = models.IntegerField(choices=EMAIL_STATES, default=EMAIL_STATE_PENDING) registered = models.DateTimeField(auto_now_add=True) telephone = models.CharField(max_length=15, blank=True, null=True) uid_number = models.PositiveIntegerField(default=get_fresh_user_uid, unique=True) @@ -332,7 +340,6 @@ class User( return ( self.state == self.STATE_ACTIVE or self.state == self.STATE_NOT_YET_ACTIVE - or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or ( allow_archived and self.state in (self.STATE_ARCHIVE, self.STATE_FULL_ARCHIVE) @@ -395,7 +402,7 @@ class User( @cached_property def get_shadow_expire(self): """Return the shadow_expire value for the user""" - if self.state == self.STATE_DISABLED: + if self.state == self.STATE_DISABLED or self.email_state == self.EMAIL_STATE_UNVERIFIED: return str(0) else: return None @@ -487,7 +494,8 @@ class User( def has_access(self): """ Renvoie si un utilisateur a accès à internet """ return ( - self.state in [User.STATE_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED] + self.state == User.STATE_ACTIVE + and self.email_state != User.EMAIL_STATE_UNVERIFIED and not self.is_ban() and (self.is_connected() or self.is_whitelisted()) ) or self == AssoOption.get_cached_value("utilisateur_asso") @@ -658,21 +666,6 @@ class User( ): self.full_archive() - def email_change_date_sync(self): - """Update user's email_change_date based on state update""" - if ( - self.__original_state != self.STATE_ACTIVE - and self.state == self.STATE_ACTIVE - ): - self.email_change_date = None - self.save() - elif ( - self.__original_state != self.STATE_EMAIL_NOT_YET_CONFIRMED - and self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED - ): - self.email_change_date = timezone.now() - self.save() - def ldap_sync( self, base=True, access_refresh=True, mac_refresh=True, group_refresh=False ): @@ -687,7 +680,6 @@ class User( Si l'instance n'existe pas, on crée le ldapuser correspondant""" if sys.version_info[0] >= 3 and ( self.state == self.STATE_ACTIVE - or self.state == self.STATE_EMAIL_NOT_YET_CONFIRMED or self.state == self.STATE_ARCHIVE or self.state == self.STATE_DISABLED ): @@ -807,7 +799,7 @@ class User( return def confirm_email_before_date(self): - if self.email_change_date is None or self.state != self.STATE_EMAIL_NOT_YET_CONFIRMED: + if self.email_change_date is None or self.email_state == self.EMAIL_STATE_VERIFIED: return None days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index ed613537..c5347dcb 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -42,7 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
-{% if users.state == users.STATE_EMAIL_NOT_YET_CONFIRMED %} +{% if users.email_state == users.EMAIL_STATE_PENDING %}
{% blocktrans with confirm_before_date=users.confirm_email_before_date|date:"DATE_FORMAT" %}Please confirm your email address before {{ confirm_before_date }}, or your account will be suspended.{% endblocktrans %}
@@ -50,9 +50,9 @@ with this program; if not, write to the Free Software Foundation, Inc., {% blocktrans %}Didn't receive the email?{% endblocktrans %}
-{% elif users.state == users.STATE_DISABLED %} +{% elif users.email_state == users.EMAIL_STATE_UNVERIFIED %}
- {% blocktrans %}Your account has been suspended.{% endblocktrans %} + {% blocktrans %}Your account has been suspended, please confirm your email address.{% endblocktrans %}
{% endif %} diff --git a/users/views.py b/users/views.py index fb5f8d73..db41c7d1 100644 --- a/users/views.py +++ b/users/views.py @@ -745,7 +745,6 @@ def mass_archive(request): .exclude(id__in=all_has_access(search_time=date)) .exclude(state=User.STATE_NOT_YET_ACTIVE) .exclude(state=User.STATE_FULL_ARCHIVE) - .exclude(state=User.STATE_EMAIL_NOT_YET_CONFIRMED) ) if not full_archive: to_archive_list = to_archive_list.exclude(state=User.STATE_ARCHIVE) @@ -981,7 +980,7 @@ def reset_password(request): user = User.objects.get( pseudo=userform.cleaned_data["pseudo"], email=userform.cleaned_data["email"], - state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE, User.STATE_EMAIL_NOT_YET_CONFIRMED], + state__in=[User.STATE_ACTIVE, User.STATE_NOT_YET_ACTIVE], ) except User.DoesNotExist: messages.error(request, _("The user doesn't exist.")) @@ -1060,7 +1059,7 @@ def resend_confirmation_email(request, logged_user, userid): try: user = User.objects.get( id=userid, - state__in=[User.STATE_EMAIL_NOT_YET_CONFIRMED], + email_state__in=[User.EMAIL_STATE_PENDING, User.EMAIL_STATE_UNVERIFIED], ) except User.DoesNotExist: messages.error(request, _("The user doesn't exist.")) From 1484b6ec1e9a6e8921cd7c0879fc2995ecdae771 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 17:37:47 +0200 Subject: [PATCH 43/83] Delete old migration --- users/migrations/0085_auto_20200417_0031.py | 26 --------------------- users/migrations/0085_user_email_state.py | 6 +++++ 2 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 users/migrations/0085_auto_20200417_0031.py diff --git a/users/migrations/0085_auto_20200417_0031.py b/users/migrations/0085_auto_20200417_0031.py deleted file mode 100644 index 802ea721..00000000 --- a/users/migrations/0085_auto_20200417_0031.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.28 on 2020-04-16 22:31 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0084_auto_20191120_0159'), - ] - - def flag_verified(apps, schema_editor): - db_alias = schema_editor.connection.alias - users = apps.get_model("users", "User") - users.objects.using(db_alias).all().update(email_state=0) - - operations = [ - migrations.AddField( - model_name='user', - name='email_state', - field=models.IntegerField(choices=[(0, 'Verified'), (1, 'Unverified'), (2, 'Waiting for email confirmation')], default=2), - ), - migrations.RunPython(flag_verified), - ] diff --git a/users/migrations/0085_user_email_state.py b/users/migrations/0085_user_email_state.py index 9dfff9af..802ea721 100644 --- a/users/migrations/0085_user_email_state.py +++ b/users/migrations/0085_user_email_state.py @@ -11,10 +11,16 @@ class Migration(migrations.Migration): ('users', '0084_auto_20191120_0159'), ] + def flag_verified(apps, schema_editor): + db_alias = schema_editor.connection.alias + users = apps.get_model("users", "User") + users.objects.using(db_alias).all().update(email_state=0) + operations = [ migrations.AddField( model_name='user', name='email_state', field=models.IntegerField(choices=[(0, 'Verified'), (1, 'Unverified'), (2, 'Waiting for email confirmation')], default=2), ), + migrations.RunPython(flag_verified), ] From 31c2255a3b4c7329a306589fa92a4a8f07bf433a Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 17 Apr 2020 17:42:23 +0200 Subject: [PATCH 44/83] Allow revert migrations --- users/migrations/0085_user_email_state.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/users/migrations/0085_user_email_state.py b/users/migrations/0085_user_email_state.py index 802ea721..92535565 100644 --- a/users/migrations/0085_user_email_state.py +++ b/users/migrations/0085_user_email_state.py @@ -16,11 +16,14 @@ class Migration(migrations.Migration): users = apps.get_model("users", "User") users.objects.using(db_alias).all().update(email_state=0) + def undo_flag_verified(apps, schema_editor): + return + operations = [ migrations.AddField( model_name='user', name='email_state', field=models.IntegerField(choices=[(0, 'Verified'), (1, 'Unverified'), (2, 'Waiting for email confirmation')], default=2), ), - migrations.RunPython(flag_verified), + migrations.RunPython(flag_verified, undo_flag_verified), ] From 3c5e5382182f13adb001951ed543fca48b802cc1 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:01:35 +0200 Subject: [PATCH 45/83] Fix error preventing migration --- users/forms.py | 71 +++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/users/forms.py b/users/forms.py index 94002eeb..a29c1fbe 100644 --- a/users/forms.py +++ b/users/forms.py @@ -405,41 +405,40 @@ class AdherentCreationForm(AdherentForm): AdherentForm auquel on ajoute une checkbox afin d'éviter les doublons d'utilisateurs et, optionnellement, un champ mot de passe""" - if OptionalUser.get_cached_value("allow_set_password_during_user_creation"): - # Champ pour choisir si un lien est envoyé par mail pour le mot de passe - init_password_by_mail_info = _( - "If this options is set, you will receive a link to set" - " your initial password by email. If you do not have" - " any means of accessing your emails, you can disable" - " this option to set your password immediatly." - " You will still receive an email to confirm your address." - " Failure to confirm your address will result in an" - " automatic suspension of your account until you do." - ) + # Champ pour choisir si un lien est envoyé par mail pour le mot de passe + init_password_by_mail_info = _( + "If this options is set, you will receive a link to set" + " your initial password by email. If you do not have" + " any means of accessing your emails, you can disable" + " this option to set your password immediatly." + " You will still receive an email to confirm your address." + " Failure to confirm your address will result in an" + " automatic suspension of your account until you do." + ) - init_password_by_mail = forms.BooleanField( - help_text=init_password_by_mail_info, - required=False, - initial=True - ) - init_password_by_mail.label = _("Send password reset link by email.") + init_password_by_mail = forms.BooleanField( + help_text=init_password_by_mail_info, + required=False, + initial=True + ) + init_password_by_mail.label = _("Send password reset link by email.") - # Champs pour initialiser le mot de passe - # Validators are handled manually since theses fields aren't always required - password1 = forms.CharField( - required=False, - label=_("Password"), - widget=forms.PasswordInput, - #validators=[MinLengthValidator(8)], - max_length=255, - ) - password2 = forms.CharField( - required=False, - label=_("Password confirmation"), - widget=forms.PasswordInput, - #validators=[MinLengthValidator(8)], - max_length=255, - ) + # Champs pour initialiser le mot de passe + # Validators are handled manually since theses fields aren't always required + password1 = forms.CharField( + required=False, + label=_("Password"), + widget=forms.PasswordInput, + #validators=[MinLengthValidator(8)], + max_length=255, + ) + password2 = forms.CharField( + required=False, + label=_("Password confirmation"), + widget=forms.PasswordInput, + #validators=[MinLengthValidator(8)], + max_length=255, + ) # Champ permettant d'éviter au maxium les doublons d'utilisateurs former_user_check_info = _( @@ -481,6 +480,12 @@ class AdherentCreationForm(AdherentForm): ) ) + # Remove password fields if option is disabled + if not OptionalUser.get_cached_value("allow_set_password_during_user_creation"): + self.fields.pop("init_password_by_mail") + self.fields.pop("password1") + self.fields.pop("password2") + def clean_password1(self): """Ignore ce champs si la case init_password_by_mail est décochée""" send_email = self.cleaned_data.get("init_password_by_mail") From 1e994cc1cca64fa782998f841ec97f5681645236 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:16:21 +0200 Subject: [PATCH 46/83] Automatically validate superuer's email address --- users/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/users/models.py b/users/models.py index b658e9ef..f6b9387e 100755 --- a/users/models.py +++ b/users/models.py @@ -147,6 +147,8 @@ class UserManager(BaseUserManager): ) user.set_password(password) + user.email_change_date = None + user.email_state = User.EMAIL_STATE_VERIFIED if su: user.is_superuser = True user.save(using=self._db) From 91c031f28727baa9c0162e0f2cc32b81fff391b5 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:16:53 +0200 Subject: [PATCH 47/83] Make check of pending email confirmation cleaner in profile.html --- users/templates/users/profil.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index c5347dcb..82feb8e6 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -200,7 +200,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Email address" %}
-
{{ users.email }}{% if users.email_change_date is not None %}
{% trans "Pending confirmation..." %}{% endif %}
+
{{ users.email }}{% if users.email_state != users.EMAIL_STATE_VERIFIED %}
{% trans "Pending confirmation..." %}{% endif %}
From 30667eb259ea69d92908dc78f65ae8f0aaf53a79 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:29:22 +0200 Subject: [PATCH 48/83] Set email_change_date on user creation --- users/forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/users/forms.py b/users/forms.py index a29c1fbe..d7c3c15b 100644 --- a/users/forms.py +++ b/users/forms.py @@ -518,6 +518,7 @@ class AdherentCreationForm(AdherentForm): an email to init the password should be sent""" # Save the provided password in hashed format user = super(AdherentForm, self).save(commit=False) + user.email_change_date = timezone.now() is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") send_email = not is_set_password_allowed or self.cleaned_data.get("init_password_by_mail") From a940f4f0787136a4e0c252ac23e2ae3e0e43877e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:32:38 +0200 Subject: [PATCH 49/83] Fix marking email as verified --- users/models.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/users/models.py b/users/models.py index f6b9387e..9c43561f 100755 --- a/users/models.py +++ b/users/models.py @@ -920,12 +920,9 @@ class User( def confirm_mail(self): """Marque l'email de l'utilisateur comme confirmé""" - # Reset the email change date + # Reset the email change date and update the email status self.email_change_date = None - - # Let the "set_active" method handle the rest - self.state = self.STATE_NOT_YET_ACTIVE - self.set_active() + self.email_state = self.EMAIL_STATE_VERIFIED @cached_property def email_address(self): From 00b6e471e46da36558455f92c1c904a7640ac32e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:46:18 +0200 Subject: [PATCH 50/83] Remove obsolete user state option from search --- search/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/search/forms.py b/search/forms.py index 741a15e8..9f2ff82a 100644 --- a/search/forms.py +++ b/search/forms.py @@ -35,7 +35,6 @@ CHOICES_USER = ( ("2", _("Archived")), ("3", _("Not yet active")), ("4", _("Fully archived")), - ("5", _("Waiting for email confirmation")), ) CHOICES_AFF = ( From 5feef8ae17a6f3c2acab3da922835acae77d990b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:53:49 +0200 Subject: [PATCH 51/83] Also update email state in EmailSettingsForm --- users/forms.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/users/forms.py b/users/forms.py index d7c3c15b..6fda0847 100644 --- a/users/forms.py +++ b/users/forms.py @@ -875,6 +875,22 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): ) ) + def save(self, commit=True): + """Update email state if email was changed""" + user = super(EmailSettingsForm, self).save(commit=commit) + + if self.initial["email"] and user.email != self.initial["email"]: + # Send a confirmation email + if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE]: + user.email_state = User.EMAIL_STATE_PENDING + self.should_send_confirmation_email = True + + # Always keep the oldest change date + if user.email_change_date is None: + user.email_change_date = timezone.now() + + user.save() + class Meta: model = User fields = ["email", "local_email_enabled", "local_email_redirect"] From 2532f8a1427f22dc337966f7eefa44cdeda3df54 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 18:59:21 +0200 Subject: [PATCH 52/83] Send confirmation email if necessary after editing EmailSettingsForm --- users/forms.py | 3 +++ users/views.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/users/forms.py b/users/forms.py index 6fda0847..0e40870e 100644 --- a/users/forms.py +++ b/users/forms.py @@ -857,7 +857,10 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(EmailSettingsForm, self).__init__(*args, prefix=prefix, **kwargs) + + self.should_send_confirmation_email = False self.fields["email"].label = _("Main email address") + if "local_email_redirect" in self.fields: self.fields["local_email_redirect"].label = _("Redirect local emails") if "local_email_enabled" in self.fields: diff --git a/users/views.py b/users/views.py index db41c7d1..027896ed 100644 --- a/users/views.py +++ b/users/views.py @@ -546,6 +546,12 @@ def edit_email_settings(request, user_instance, **_kwargs): if email_settings.changed_data: email_settings.save() messages.success(request, _("The email settings were edited.")) + + # Send confirmation email if necessary + if email_settings.should_send_confirmation_email is True: + user_instance.confirm_email_address_mail(request) + messages.success(request, _("An email to confirm your address was sent.")) + return redirect( reverse("users:profil", kwargs={"userid": str(user_instance.id)}) ) From 2a4bd6bd79530e641dcbd00232466e41c68bb043 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 17:04:00 +0000 Subject: [PATCH 53/83] Move should_send_confirmation_email in EmailSettingsForm for consistency --- users/forms.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/users/forms.py b/users/forms.py index 0e40870e..68492937 100644 --- a/users/forms.py +++ b/users/forms.py @@ -853,14 +853,12 @@ class EMailAddressForm(FormRevMixin, ModelForm): class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """Edit email-related settings""" + should_send_confirmation_email = False def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(EmailSettingsForm, self).__init__(*args, prefix=prefix, **kwargs) - - self.should_send_confirmation_email = False self.fields["email"].label = _("Main email address") - if "local_email_redirect" in self.fields: self.fields["local_email_redirect"].label = _("Redirect local emails") if "local_email_enabled" in self.fields: From bef496b3e242cd79478c0057c17e443a53f64e93 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 17 Apr 2020 19:18:11 +0200 Subject: [PATCH 54/83] =?UTF-8?q?Remove=20state=5Fsync=20:=20est=20appell?= =?UTF-8?q?=C3=A9=20en=20post-save=20de=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/forms.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/users/forms.py b/users/forms.py index 68492937..441d645c 100644 --- a/users/forms.py +++ b/users/forms.py @@ -682,14 +682,6 @@ class StateForm(FormRevMixin, ModelForm): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(StateForm, self).__init__(*args, prefix=prefix, **kwargs) - def save(self, commit=True): - user = super(StateForm, self).save(commit=False) - if self.cleaned_data["state"]: - user.state = self.cleaned_data.get("state") - user.state_sync() - - user.save() - class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm): """ Gestion des groupes d'un user""" From e323972b96f5ea4893485c3a7dea249b9f303420 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 19:11:44 +0200 Subject: [PATCH 55/83] Reset clean_notyetactive command to its correct state --- users/management/commands/clean_notyetactive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index d1857096..14f7c826 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -28,13 +28,13 @@ from django.utils import timezone class Command(BaseCommand): - help = "Delete non members users (not yet active or disabled too long ago without an invoice)." + help = "Delete non members users (not yet active)." def handle(self, *args, **options): """First deleting invalid invoices, and then deleting the users""" days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") users_to_delete = ( - User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE) | Q(state=User.STATE_DISABLED)) + User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE)) .filter(registered__lte=timezone.now() - timedelta(days=days)) .exclude(facture__valid=True) .distinct() From ede81adaf79ed99d156e1a586d0cdfd25d6ce2c4 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 19:11:59 +0200 Subject: [PATCH 56/83] Remove unused translation --- logs/locale/fr/LC_MESSAGES/django.po | 4 ---- 1 file changed, 4 deletions(-) diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 92dbb89b..66bc7234 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -243,10 +243,6 @@ msgstr "Utilisateurs complètement archivés" msgid "Not yet active users" msgstr "Utilisateurs pas encore actifs" -#: logs/views.py:264 -msgid "Waiting for email confirmation users" -msgstr "Utilisateurs en attente de confirmation d'email" - #: logs/views.py:273 msgid "Contributing members" msgstr "Adhérents cotisants" From 55cde725126c728a819d7938f1e919de16819b53 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 19:59:25 +0200 Subject: [PATCH 57/83] Confirm email after resetting password --- users/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/users/views.py b/users/views.py index 027896ed..a2a62316 100644 --- a/users/views.py +++ b/users/views.py @@ -1025,8 +1025,10 @@ def process_passwd(request, req): u_form = PassForm(request.POST or None, instance=user, user=request.user) if u_form.is_valid(): with transaction.atomic(), reversion.create_revision(): + user.confirm_mail() u_form.save() reversion.set_comment("Password reset") + req.delete() messages.success(request, _("The password was changed.")) return redirect(reverse("index")) From f790dca202e88875ced688a8a26d8c5a0a9818a1 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:23:28 +0200 Subject: [PATCH 58/83] Move some code away from the forms --- users/forms.py | 19 ------------------- users/models.py | 21 +++++++++++++++++++++ users/views.py | 24 ++++++++++-------------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/users/forms.py b/users/forms.py index 441d645c..effb9623 100644 --- a/users/forms.py +++ b/users/forms.py @@ -333,7 +333,6 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): self.fields["room"].label = _("Room") self.fields["room"].empty_label = _("No room") self.fields["school"].empty_label = _("Select a school") - self.is_anon = kwargs["user"].is_anonymous() class Meta: model = Adherent @@ -382,23 +381,6 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): remove_user_room(room) return - def save(self, commit=True): - """On met à jour l'état de l'utilisateur en fonction de son mail""" - user = super(AdherentForm, self).save(commit=commit) - - if not self.is_anon and self.initial["email"] and user.email != self.initial["email"]: - # Send a confirmation email - if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE]: - user.email_state = User.EMAIL_STATE_PENDING - self.should_send_confirmation_email = True - - # Always keep the oldest change date - if user.email_change_date is None: - user.email_change_date = timezone.now() - - user.save() - return user - class AdherentCreationForm(AdherentForm): """Formulaire de création d'un user. @@ -518,7 +500,6 @@ class AdherentCreationForm(AdherentForm): an email to init the password should be sent""" # Save the provided password in hashed format user = super(AdherentForm, self).save(commit=False) - user.email_change_date = timezone.now() is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") send_email = not is_set_password_allowed or self.cleaned_data.get("init_password_by_mail") diff --git a/users/models.py b/users/models.py index 9c43561f..b9162f5b 100755 --- a/users/models.py +++ b/users/models.py @@ -800,6 +800,26 @@ class User( ) return + def send_confirm_email_if_necessary(self, request): + """Update the user's email state + Returns whether an email was sent""" + # Only update the state if the email changed + if self.__original_email == self.email: + return False + + # Archived users shouldn't get an email + if self.state not in [self.STATE_ACTIVE, self.STATE_DISABLED, self.STATE_NOT_YET_ACTIVE]: + return False + + # Always keep the oldest change date + if self.email_change_date is None: + self.email_change_date = timezone.now() + + self.email_state = self.EMAIL_STATE_PENDING + self.confirm_email_address_mail(request) + + return True + def confirm_email_before_date(self): if self.email_change_date is None or self.email_state == self.EMAIL_STATE_VERIFIED: return None @@ -810,6 +830,7 @@ class User( def confirm_email_address_mail(self, request): """Prend en argument un request, envoie un mail pour confirmer l'adresse""" + # Create the request and send the email req = Request() req.type = Request.EMAIL req.user = self diff --git a/users/views.py b/users/views.py index a2a62316..2366e417 100644 --- a/users/views.py +++ b/users/views.py @@ -124,11 +124,9 @@ def new_user(request): is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") if user.is_valid(): - user = user.save() - # Use "is False" so that if None, the email is sent - if is_set_password_allowed and user.should_send_password_reset_email is False: - user.confirm_email_address_mail(request) + if is_set_password_allowed and user.should_send_password_reset_email: + user.send_confirm_email_if_necessary(request) messages.success( request, _("The user %s was created, a confirmation email was sent.") @@ -142,6 +140,7 @@ def new_user(request): % user.pseudo, ) + user = user.save() return redirect(reverse("users:profil", kwargs={"userid": str(user.id)})) # Anonymous users are allowed to create new accounts @@ -223,13 +222,12 @@ def edit_info(request, user, userid): ) if user_form.is_valid(): if user_form.changed_data: + if user.send_confirm_email_if_necessary(request): + messages.success(request, _("Sent a new confirmation email.")) + user = user_form.save() messages.success(request, _("The user was edited.")) - if user_form.should_send_confirmation_email: - user.confirm_email_address_mail(request) - messages.success(request, _("Sent a new confirmation email.")) - return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( {"userform": user_form, "action_name": _("Edit")}, @@ -544,14 +542,12 @@ def edit_email_settings(request, user_instance, **_kwargs): ) if email_settings.is_valid(): if email_settings.changed_data: + if user_instance.send_confirm_email_if_necessary(request): + messages.success(request, _("An email to confirm your address was sent.")) + email_settings.save() messages.success(request, _("The email settings were edited.")) - # Send confirmation email if necessary - if email_settings.should_send_confirmation_email is True: - user_instance.confirm_email_address_mail(request) - messages.success(request, _("An email to confirm your address was sent.")) - return redirect( reverse("users:profil", kwargs={"userid": str(user_instance.id)}) ) @@ -1073,7 +1069,7 @@ def resend_confirmation_email(request, logged_user, userid): messages.error(request, _("The user doesn't exist.")) if request.method == "POST": - user.confirm_email_address_mail(request) + user.send_confirm_email_if_necessary(request) messages.success(request, _("An email to confirm your address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": userid})) From 1ee4d917212059a8be7bf52e26b2bd1e07078ef9 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:41:02 +0200 Subject: [PATCH 59/83] Make AdherentCreationForm code clearer --- users/forms.py | 6 +++--- users/views.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/users/forms.py b/users/forms.py index effb9623..dea3c317 100644 --- a/users/forms.py +++ b/users/forms.py @@ -502,11 +502,11 @@ class AdherentCreationForm(AdherentForm): user = super(AdherentForm, self).save(commit=False) is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") - send_email = not is_set_password_allowed or self.cleaned_data.get("init_password_by_mail") - if not send_email: + set_passwd = is_set_password_allowed and not self.cleaned_data.get("init_password_by_mail") + if set_passwd: user.set_password(self.cleaned_data["password1"]) - user.should_send_password_reset_email = send_email + user.did_set_initial_passwd = set_passwd user.save() return user diff --git a/users/views.py b/users/views.py index 2366e417..06f65dce 100644 --- a/users/views.py +++ b/users/views.py @@ -124,8 +124,7 @@ def new_user(request): is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") if user.is_valid(): - # Use "is False" so that if None, the email is sent - if is_set_password_allowed and user.should_send_password_reset_email: + if user.did_set_initial_passwd: user.send_confirm_email_if_necessary(request) messages.success( request, From e169b5175971f7eaaf407b765148d1e49c272d6b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:50:26 +0200 Subject: [PATCH 60/83] Always send confirmation email, except for fully-archived users --- users/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/users/models.py b/users/models.py index b9162f5b..d1d8c86a 100755 --- a/users/models.py +++ b/users/models.py @@ -807,15 +807,16 @@ class User( if self.__original_email == self.email: return False - # Archived users shouldn't get an email - if self.state not in [self.STATE_ACTIVE, self.STATE_DISABLED, self.STATE_NOT_YET_ACTIVE]: + self.email_state = self.EMAIL_STATE_PENDING + + # Fully archived users shouldn't get an email + if self.state != self.STATE_FULL_ARCHIVE: return False # Always keep the oldest change date if self.email_change_date is None: self.email_change_date = timezone.now() - self.email_state = self.EMAIL_STATE_PENDING self.confirm_email_address_mail(request) return True From be5226fcdd0e4b294b3f1ac2006afd87f75cb005 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:50:47 +0200 Subject: [PATCH 61/83] Add missing __original_email init --- users/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/users/models.py b/users/models.py index d1d8c86a..c9730167 100755 --- a/users/models.py +++ b/users/models.py @@ -1291,6 +1291,7 @@ class User( "room": self.can_change_room, } self.__original_state = self.state + self.__original_email = self.email def clean(self, *args, **kwargs): """Check if this pseudo is already used by any mailalias. From 10243e1e72e75d77d693f1c6200a4cead284552a Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:53:10 +0200 Subject: [PATCH 62/83] Save before checking if confirmation email should be sent --- users/views.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/users/views.py b/users/views.py index 06f65dce..4b539b9d 100644 --- a/users/views.py +++ b/users/views.py @@ -124,6 +124,8 @@ def new_user(request): is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") if user.is_valid(): + user = user.save() + if user.did_set_initial_passwd: user.send_confirm_email_if_necessary(request) messages.success( @@ -139,7 +141,6 @@ def new_user(request): % user.pseudo, ) - user = user.save() return redirect(reverse("users:profil", kwargs={"userid": str(user.id)})) # Anonymous users are allowed to create new accounts @@ -221,12 +222,12 @@ def edit_info(request, user, userid): ) if user_form.is_valid(): if user_form.changed_data: - if user.send_confirm_email_if_necessary(request): - messages.success(request, _("Sent a new confirmation email.")) - user = user_form.save() messages.success(request, _("The user was edited.")) + if user.send_confirm_email_if_necessary(request): + messages.success(request, _("Sent a new confirmation email.")) + return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( {"userform": user_form, "action_name": _("Edit")}, @@ -541,12 +542,12 @@ def edit_email_settings(request, user_instance, **_kwargs): ) if email_settings.is_valid(): if email_settings.changed_data: - if user_instance.send_confirm_email_if_necessary(request): - messages.success(request, _("An email to confirm your address was sent.")) - email_settings.save() messages.success(request, _("The email settings were edited.")) + if user_instance.send_confirm_email_if_necessary(request): + messages.success(request, _("An email to confirm your address was sent.")) + return redirect( reverse("users:profil", kwargs={"userid": str(user_instance.id)}) ) From 00b62e1fdc2c9756941202f63f19b78dc6389269 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 19:11:24 +0000 Subject: [PATCH 63/83] Correctly send a confirmation email --- users/models.py | 4 ++-- users/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/users/models.py b/users/models.py index c9730167..2c71c466 100755 --- a/users/models.py +++ b/users/models.py @@ -810,15 +810,15 @@ class User( self.email_state = self.EMAIL_STATE_PENDING # Fully archived users shouldn't get an email - if self.state != self.STATE_FULL_ARCHIVE: + if self.state == self.STATE_FULL_ARCHIVE: return False # Always keep the oldest change date if self.email_change_date is None: self.email_change_date = timezone.now() + self.save() self.confirm_email_address_mail(request) - return True def confirm_email_before_date(self): diff --git a/users/views.py b/users/views.py index 4b539b9d..551fd350 100644 --- a/users/views.py +++ b/users/views.py @@ -1069,7 +1069,7 @@ def resend_confirmation_email(request, logged_user, userid): messages.error(request, _("The user doesn't exist.")) if request.method == "POST": - user.send_confirm_email_if_necessary(request) + user.confirm_email_address_mail(request) messages.success(request, _("An email to confirm your address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": userid})) From e6680e0717d9d350605cdf09b8d58539d48cad37 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 17 Apr 2020 21:15:18 +0200 Subject: [PATCH 64/83] Remove code mort --- users/forms.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/users/forms.py b/users/forms.py index dea3c317..25e6c1b4 100644 --- a/users/forms.py +++ b/users/forms.py @@ -351,8 +351,6 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): label=_("Force the move?"), initial=False, required=False ) - should_send_confirmation_email = False - def clean_email(self): if not OptionalUser.objects.first().local_email_domain in self.cleaned_data.get( "email" @@ -826,7 +824,6 @@ class EMailAddressForm(FormRevMixin, ModelForm): class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """Edit email-related settings""" - should_send_confirmation_email = False def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) @@ -849,22 +846,6 @@ class EmailSettingsForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): ) ) - def save(self, commit=True): - """Update email state if email was changed""" - user = super(EmailSettingsForm, self).save(commit=commit) - - if self.initial["email"] and user.email != self.initial["email"]: - # Send a confirmation email - if user.state in [User.STATE_ACTIVE, User.STATE_DISABLED, User.STATE_NOT_YET_ACTIVE]: - user.email_state = User.EMAIL_STATE_PENDING - self.should_send_confirmation_email = True - - # Always keep the oldest change date - if user.email_change_date is None: - user.email_change_date = timezone.now() - - user.save() - class Meta: model = User fields = ["email", "local_email_enabled", "local_email_redirect"] From f7259ae69b198f2f3d2ef12d6deb9eb72ced4ae1 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 21:18:10 +0200 Subject: [PATCH 65/83] Improve check did_set_initial_passwd in new_user --- users/forms.py | 1 - users/views.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/users/forms.py b/users/forms.py index 25e6c1b4..687c9e11 100644 --- a/users/forms.py +++ b/users/forms.py @@ -504,7 +504,6 @@ class AdherentCreationForm(AdherentForm): if set_passwd: user.set_password(self.cleaned_data["password1"]) - user.did_set_initial_passwd = set_passwd user.save() return user diff --git a/users/views.py b/users/views.py index 551fd350..78c0332e 100644 --- a/users/views.py +++ b/users/views.py @@ -126,7 +126,7 @@ def new_user(request): if user.is_valid(): user = user.save() - if user.did_set_initial_passwd: + if user.pwd_ntlm: user.send_confirm_email_if_necessary(request) messages.success( request, From 7c0e694f6127989be6805f1a84ddbc6ccbcd4997 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Fri, 17 Apr 2020 22:08:40 +0200 Subject: [PATCH 66/83] Test sur le mot primaire au lieu du mot de pass radius --- users/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/views.py b/users/views.py index 78c0332e..77e40701 100644 --- a/users/views.py +++ b/users/views.py @@ -126,7 +126,7 @@ def new_user(request): if user.is_valid(): user = user.save() - if user.pwd_ntlm: + if user.password: user.send_confirm_email_if_necessary(request) messages.success( request, From cb99843c2b42fa072ddd0ce1ea86796ed93741c3 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 20:12:12 +0000 Subject: [PATCH 67/83] Ensure confirmation email tokens are deleted if no longer valid --- users/migrations/0087_request_email.py | 20 ++++++++++++++++++++ users/models.py | 6 ++++++ 2 files changed, 26 insertions(+) create mode 100644 users/migrations/0087_request_email.py diff --git a/users/migrations/0087_request_email.py b/users/migrations/0087_request_email.py new file mode 100644 index 00000000..3cb8d792 --- /dev/null +++ b/users/migrations/0087_request_email.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-17 20:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0086_user_email_change_date'), + ] + + operations = [ + migrations.AddField( + model_name='request', + name='email', + field=models.EmailField(blank=True, max_length=254, null=True), + ), + ] diff --git a/users/models.py b/users/models.py index 2c71c466..8431b0b3 100755 --- a/users/models.py +++ b/users/models.py @@ -831,10 +831,15 @@ class User( def confirm_email_address_mail(self, request): """Prend en argument un request, envoie un mail pour confirmer l'adresse""" + # Delete all older requests for this user, that aren't for this email + filter = Q(user=self) & Q(type=Request.EMAIL) & ~Q(email=self.email) + Request.objects.filter(filter).delete() + # Create the request and send the email req = Request() req.type = Request.EMAIL req.user = self + req.email = self.email req.save() template = loader.get_template("users/email_confirmation_request") @@ -1873,6 +1878,7 @@ class Request(models.Model): type = models.CharField(max_length=2, choices=TYPE_CHOICES) token = models.CharField(max_length=32) user = models.ForeignKey("User", on_delete=models.CASCADE) + email = models.EmailField(blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True, editable=False) expires_at = models.DateTimeField() From 01a52f88d3613be4f071c9a34830b1b2cefaf3c8 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 22:20:45 +0200 Subject: [PATCH 68/83] Fix error in profil of users with an unverified email --- users/locale/fr/LC_MESSAGES/django.po | 4 ++++ users/templates/users/profil.html | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 922c2dbe..e19e19c6 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -1028,6 +1028,10 @@ msgstr "Pas de connexion" msgid "Pay for a connection" msgstr "Payer une connexion" +#: users/templates/users/profil.html:81 +msgid "Resend the email" +msgstr "Renvoyer le mail" + #: users/templates/users/profil.html:60 msgid "Ask someone with the appropriate rights to pay for a connection." msgstr "" diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 82feb8e6..4fec3c18 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -70,14 +70,21 @@ with this program; if not, write to the Free Software Foundation, Inc., {% elif not users.has_access %}
{% trans "No connection" %}
+
- {% can_create Facture %} - - {% trans "Pay for a connection" %} - - {% acl_else %} - {% trans "Ask someone with the appropriate rights to pay for a connection." %} - {% acl_end %} + {% if users.email_state == users.EMAIL_STATE_UNVERIFIED %} + + {% trans "Resend the email" %} + + {% else %} + {% can_create Facture %} + + {% trans "Pay for a connection" %} + + {% acl_else %} + {% trans "Ask someone with the appropriate rights to pay for a connection." %} + {% acl_end %} + {% endif %}
{% else %} From 1eeab86c84c79c7fc00a2bf0acc948ac63b779d7 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 22:49:50 +0200 Subject: [PATCH 69/83] Call user.confirm_mail in UserManager._create_user --- users/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/users/models.py b/users/models.py index 8431b0b3..f6596fa9 100755 --- a/users/models.py +++ b/users/models.py @@ -147,8 +147,7 @@ class UserManager(BaseUserManager): ) user.set_password(password) - user.email_change_date = None - user.email_state = User.EMAIL_STATE_VERIFIED + user.confirm_mail() if su: user.is_superuser = True user.save(using=self._db) From 7eca43a0c52d3f24725700471c1347a82952f123 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 22:54:27 +0200 Subject: [PATCH 70/83] Don't set User.email_change_date to None --- users/models.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/users/models.py b/users/models.py index f6596fa9..11db2a8e 100755 --- a/users/models.py +++ b/users/models.py @@ -812,8 +812,8 @@ class User( if self.state == self.STATE_FULL_ARCHIVE: return False - # Always keep the oldest change date - if self.email_change_date is None: + # Don't allow users without a confirmed email to postpone their due date + if self.state == self.STATE_ACTIVE or not self.email_change_date: self.email_change_date = timezone.now() self.save() @@ -821,7 +821,7 @@ class User( return True def confirm_email_before_date(self): - if self.email_change_date is None or self.email_state == self.EMAIL_STATE_VERIFIED: + if self.email_state == self.EMAIL_STATE_VERIFIED: return None days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed") @@ -946,8 +946,6 @@ class User( def confirm_mail(self): """Marque l'email de l'utilisateur comme confirmé""" - # Reset the email change date and update the email status - self.email_change_date = None self.email_state = self.EMAIL_STATE_VERIFIED @cached_property From 1e2d8d44d1e16ee767e70425a875dd8b832f87bf Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 22:57:41 +0200 Subject: [PATCH 71/83] Fix wrong state check in send_confirm_email_if_necessary --- users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 11db2a8e..4345050e 100755 --- a/users/models.py +++ b/users/models.py @@ -813,7 +813,7 @@ class User( return False # Don't allow users without a confirmed email to postpone their due date - if self.state == self.STATE_ACTIVE or not self.email_change_date: + if self.email_state == self.EMAIL_STATE_VERIFIED or not self.email_change_date: self.email_change_date = timezone.now() self.save() From 6620d14fc53f18b767ff2b7654608f517053cc4e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 23:07:40 +0200 Subject: [PATCH 72/83] Make send_confirm_email_if_necessary clearer --- users/models.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/users/models.py b/users/models.py index 4345050e..1baac3a8 100755 --- a/users/models.py +++ b/users/models.py @@ -800,23 +800,31 @@ class User( return def send_confirm_email_if_necessary(self, request): - """Update the user's email state + """Update the user's email state: + * If the user changed email, it needs to be confirmed + * If they're not fully archived, send a confirmation email + Returns whether an email was sent""" # Only update the state if the email changed if self.__original_email == self.email: return False - self.email_state = self.EMAIL_STATE_PENDING + # If the user was previously in the PENDING or UNVERIFIED state, + # we can't update email_change_date otherwise it would push back + # their due date + # However, if the user is in the VERIFIED state, we reset the date + if self.email_state == self.EMAIL_STATE_VERIFIED: + self.email_change_date = timezone.now() - # Fully archived users shouldn't get an email + # Remember that the user needs to confirm their email address again + self.email_state = self.EMAIL_STATE_PENDING + self.save() + + # Fully archived users shouldn't get an email, so stop here if self.state == self.STATE_FULL_ARCHIVE: return False - # Don't allow users without a confirmed email to postpone their due date - if self.email_state == self.EMAIL_STATE_VERIFIED or not self.email_change_date: - self.email_change_date = timezone.now() - - self.save() + # Send the email self.confirm_email_address_mail(request) return True From 93dcc79cad8033c4648dd9feb0e1bf793affbae9 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 21:13:16 +0000 Subject: [PATCH 73/83] Provide default value for email_change_date and don't allow it to be null --- users/migrations/0088_auto_20200417_2312.py | 23 +++++++++++++++++++++ users/models.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 users/migrations/0088_auto_20200417_2312.py diff --git a/users/migrations/0088_auto_20200417_2312.py b/users/migrations/0088_auto_20200417_2312.py new file mode 100644 index 00000000..e9f82f7c --- /dev/null +++ b/users/migrations/0088_auto_20200417_2312.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-17 21:12 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0087_request_email'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email_change_date', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2020, 4, 17, 21, 12, 19, 739799, tzinfo=utc)), + preserve_default=False, + ), + ] diff --git a/users/models.py b/users/models.py index 1baac3a8..613aa9ef 100755 --- a/users/models.py +++ b/users/models.py @@ -238,7 +238,7 @@ class User( shortcuts_enabled = models.BooleanField( verbose_name=_("enable shortcuts on Re2o website"), default=True ) - email_change_date = models.DateTimeField(default=None, null=True) + email_change_date = models.DateTimeField(auto_now_add=True) USERNAME_FIELD = "pseudo" REQUIRED_FIELDS = ["surname", "email"] From c2f180896f8ae939459728f7a61dc12d88a01e7b Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 18 Apr 2020 00:12:22 +0200 Subject: [PATCH 74/83] Allow admin to modify email state --- users/forms.py | 4 ++-- users/locale/fr/LC_MESSAGES/django.po | 4 ++-- users/models.py | 10 ++++++++++ users/views.py | 6 ++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/users/forms.py b/users/forms.py index 687c9e11..158965d6 100644 --- a/users/forms.py +++ b/users/forms.py @@ -650,11 +650,11 @@ class EditServiceUserForm(ServiceUserForm): class StateForm(FormRevMixin, ModelForm): - """ Changement de l'état d'un user""" + """Change state of an user, and if its main email is verified or not""" class Meta: model = User - fields = ["state"] + fields = ["state" ,"email_state"] def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index e19e19c6..77e5a4f9 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -1360,8 +1360,8 @@ msgid "Sent a new confirmation email." msgstr "Un nouveau mail de confirmation a été envoyé." #: users/views.py:225 -msgid "The state was edited." -msgstr "L'état a été modifié." +msgid "The states were edited." +msgstr "Les états ont été modifié." #: users/views.py:242 msgid "The groups were edited." diff --git a/users/models.py b/users/models.py index 613aa9ef..56f411b1 100755 --- a/users/models.py +++ b/users/models.py @@ -828,6 +828,16 @@ class User( self.confirm_email_address_mail(request) return True + def trigger_email_changed_state(self, request): + """Trigger an email, and changed values after email_state been manually updated""" + if self.email_state == self.EMAIL_STATE_VERIFIED: + return False + + self.email_change_date = timezone.now() + + self.confirm_email_address_mail(request) + return True + def confirm_email_before_date(self): if self.email_state == self.EMAIL_STATE_VERIFIED: return None diff --git a/users/views.py b/users/views.py index 77e40701..3d5b0205 100644 --- a/users/views.py +++ b/users/views.py @@ -243,8 +243,10 @@ def state(request, user, userid): state_form = StateForm(request.POST or None, instance=user) if state_form.is_valid(): if state_form.changed_data: - state_form.save() - messages.success(request, _("The state was edited.")) + user_instance = state_form.save() + messages.success(request, _("The states were edited.")) + if user_instance.trigger_email_changed_state(request): + messages.success(request, _("An email to confirm the address was sent.")) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( {"userform": state_form, "action_name": _("Edit")}, From 9173f158a84281a9ef1ced64162c00471319877b Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 18 Apr 2020 00:16:27 +0200 Subject: [PATCH 75/83] Don't forget to save --- users/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/users/models.py b/users/models.py index 56f411b1..689b0bf0 100755 --- a/users/models.py +++ b/users/models.py @@ -834,6 +834,7 @@ class User( return False self.email_change_date = timezone.now() + self.save() self.confirm_email_address_mail(request) return True From 91b418e64fe6844e8a8902c46137c1c446a0e5ac Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 18 Apr 2020 00:29:50 +0200 Subject: [PATCH 76/83] Add missing translations --- search/locale/fr/LC_MESSAGES/django.po | 16 ++++++++++++---- users/forms.py | 4 +++- users/locale/fr/LC_MESSAGES/django.po | 12 ++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index 6da1a477..c5d18323 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -50,10 +50,6 @@ msgstr "Pas encore adhéré" msgid "Fully archived" msgstr "Complètement archivés" -#: search/forms.py:38 -msgid "Waiting for email confirmation" -msgstr "En attente de confirmation d'email" - #: search/forms.py:41 msgid "Users" msgstr "Utilisateurs" @@ -117,6 +113,18 @@ msgstr "Date de début" msgid "End date" msgstr "Date de fin" +#: search/models.py:195 +msgid "Verified" +msgstr "Confirmé" + +#: search/models.py:196 +msgid "Unverified" +msgstr "Non-confirmé" + +#: search/models.py:197 +msgid "Waiting for email confirmation" +msgstr "En attente de confirmation d'email" + #: search/templates/search/index.html:29 msgid "Search results" msgstr "Résultats de la recherche" diff --git a/users/forms.py b/users/forms.py index 158965d6..8e60b698 100644 --- a/users/forms.py +++ b/users/forms.py @@ -654,11 +654,13 @@ class StateForm(FormRevMixin, ModelForm): class Meta: model = User - fields = ["state" ,"email_state"] + fields = ["state", "email_state"] def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(StateForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields["state"].label = _("State") + self.fields["email_state"].label = _("Email state") class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm): diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 77e5a4f9..c53cc94f 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -223,6 +223,14 @@ msgstr "Nom" msgid "Use a mailing list" msgstr "Utiliser une liste de diffusion" +#: users/forms.py:662 +msgid "State" +msgstr "État" + +#: users/forms.py:663 +msgid "Email state" +msgstr "État du mail" + #: users/forms.py:601 users/templates/users/aff_listright.html:38 msgid "Superuser" msgstr "Superutilisateur" @@ -1367,6 +1375,10 @@ msgstr "Les états ont été modifié." msgid "The groups were edited." msgstr "Les groupes ont été modifiés." +#: users/views.py:249 +msgid "An email to confirm the address was sent." +msgstr "Un mail pour confirmer l'adresse a été envoyé." + #: users/views.py:261 users/views.py:998 msgid "The password was changed." msgstr "Le mot de passe a été changé." From cb4ee419fc22812903d350b9acff2d58f29c3ebd Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 18 Apr 2020 00:33:18 +0200 Subject: [PATCH 77/83] Remove duplicate translation --- users/locale/fr/LC_MESSAGES/django.po | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index c53cc94f..818c973a 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -223,10 +223,6 @@ msgstr "Nom" msgid "Use a mailing list" msgstr "Utiliser une liste de diffusion" -#: users/forms.py:662 -msgid "State" -msgstr "État" - #: users/forms.py:663 msgid "Email state" msgstr "État du mail" @@ -1146,7 +1142,7 @@ msgstr "Bannissement" msgid "Not banned" msgstr "Non banni" -#: users/templates/users/profil.html:251 +#: users/templates/users/profil.html:251 users/forms.py:662 msgid "State" msgstr "État" From 45637c8e267502da3f7552c3571030638437d1cc Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 18 Apr 2020 00:39:06 +0200 Subject: [PATCH 78/83] Make email translations more consistent --- preferences/locale/fr/LC_MESSAGES/django.po | 2 +- search/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 4 ++-- users/models.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index e7ac2e66..8341c1b9 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -315,7 +315,7 @@ msgstr "" "Si True, les utilisateurs ont le choix de recevoir un email avec" " un lien pour changer leur mot de passe lors de la création de leur compte, " " ou alors de choisir leur mot de passe immédiatement." -" Si False, un email est toujours envoyé." +" Si False, un mail est toujours envoyé." #: preferences/models.py:121 msgid "If True, archived users are allowed to connect." diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index c5d18323..e03da256 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -123,7 +123,7 @@ msgstr "Non-confirmé" #: search/models.py:197 msgid "Waiting for email confirmation" -msgstr "En attente de confirmation d'email" +msgstr "En attente de confirmation du mail" #: search/templates/search/index.html:29 msgid "Search results" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 818c973a..e2a8f3ce 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -979,7 +979,7 @@ msgstr "Email de confirmation" #: users/templates/users/confirm_email.html:36 #, python-format msgid "Confirm the email" -msgstr "Confirmer l'email" +msgstr "Confirmer le mail" #: users/templates/users/confirm_email.html:36 #, python-format @@ -1263,7 +1263,7 @@ msgstr "Renvoyer l'email de confirmation ?" #: users/templates/users/resend_confirmation_email.html:36 #, python-format msgid "The confirmation email will be sent to" -msgstr "L'email de confirmation sera envoyé à" +msgstr "Le mail de confirmation sera envoyé à" #: users/templates/users/sidebar.html:33 msgid "Create a club or organisation" diff --git a/users/models.py b/users/models.py index 689b0bf0..e956a26a 100755 --- a/users/models.py +++ b/users/models.py @@ -874,7 +874,7 @@ class User( "confirm_before_en": self.confirm_email_before_date().strftime("%Y-%m-%d"), } send_mail( - "Confirmation de l'email de %(name)s / Email confirmation for " + "Confirmation du mail de %(name)s / Email confirmation for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, template.render(context), GeneralOption.get_cached_value("email_from"), From 227511504effed5611b4a0b9d21fa55a6e867877 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 22:52:30 +0000 Subject: [PATCH 79/83] Fix translations --- api/locale/fr/LC_MESSAGES/django.po | 2 +- cotisations/locale/fr/LC_MESSAGES/django.po | 228 +++---- logs/locale/fr/LC_MESSAGES/django.po | 4 +- machines/locale/fr/LC_MESSAGES/django.po | 46 +- multi_op/locale/fr/LC_MESSAGES/django.po | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 549 +++++++-------- re2o/locale/fr/LC_MESSAGES/django.po | 2 +- search/locale/fr/LC_MESSAGES/django.po | 68 +- templates/locale/fr/LC_MESSAGES/django.po | 10 +- tickets/locale/fr/LC_MESSAGES/django.po | 2 +- topologie/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 719 ++++++++++---------- users/models.py | 4 +- 13 files changed, 826 insertions(+), 812 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index aba3d217..ccc73c63 100644 --- a/api/locale/fr/LC_MESSAGES/django.po +++ b/api/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-19 23:43+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2019-01-07 01:37+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/cotisations/locale/fr/LC_MESSAGES/django.po b/cotisations/locale/fr/LC_MESSAGES/django.po index 1d68e1fe..344c4269 100644 --- a/cotisations/locale/fr/LC_MESSAGES/django.po +++ b/cotisations/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language: fr_FR\n" @@ -37,7 +37,7 @@ msgstr "Vous n'avez pas le droit de voir cette application." msgid "Select a payment method" msgstr "Sélectionnez un moyen de paiement" -#: cotisations/forms.py:73 cotisations/models.py:676 +#: cotisations/forms.py:73 cotisations/models.py:682 msgid "Member" msgstr "Adhérent" @@ -154,8 +154,8 @@ msgstr "Peut voir un objet facture" msgid "Can edit all the previous invoices" msgstr "Peut modifier toutes les factures précédentes" -#: cotisations/models.py:145 cotisations/models.py:431 cotisations/views.py:378 -#: cotisations/views.py:573 +#: cotisations/models.py:145 cotisations/models.py:456 cotisations/views.py:376 +#: cotisations/views.py:571 msgid "invoice" msgstr "facture" @@ -217,115 +217,115 @@ msgstr "Il n'y a pas de moyens de paiement que vous puissiez utiliser." msgid "There are no articles that you can buy." msgstr "Il n'y a pas d'articles que vous puissiez acheter." -#: cotisations/models.py:347 +#: cotisations/models.py:372 msgid "Can view a custom invoice object" msgstr "Peut voir un objet facture personnalisée" -#: cotisations/models.py:349 +#: cotisations/models.py:374 msgid "recipient" msgstr "destinataire" -#: cotisations/models.py:350 +#: cotisations/models.py:375 msgid "payment type" msgstr "type de paiement" -#: cotisations/models.py:351 +#: cotisations/models.py:376 msgid "address" msgstr "adresse" -#: cotisations/models.py:352 +#: cotisations/models.py:377 msgid "paid" msgstr "payé" -#: cotisations/models.py:353 +#: cotisations/models.py:378 msgid "remark" msgstr "remarque" -#: cotisations/models.py:358 +#: cotisations/models.py:383 msgid "Can view a cost estimate object" msgstr "Peut voir un objet devis" -#: cotisations/models.py:361 +#: cotisations/models.py:386 msgid "period of validity" msgstr "période de validité" -#: cotisations/models.py:396 +#: cotisations/models.py:421 msgid "You don't have the right to delete a cost estimate." msgstr "Vous n'avez pas le droit de supprimer un devis." -#: cotisations/models.py:402 +#: cotisations/models.py:427 msgid "The cost estimate has an invoice and can't be deleted." msgstr "Le devis a une facture et ne peut pas être supprimé." -#: cotisations/models.py:424 cotisations/models.py:682 -#: cotisations/models.py:940 +#: cotisations/models.py:449 cotisations/models.py:688 +#: cotisations/models.py:946 msgid "Connection" msgstr "Connexion" -#: cotisations/models.py:425 cotisations/models.py:683 -#: cotisations/models.py:941 +#: cotisations/models.py:450 cotisations/models.py:689 +#: cotisations/models.py:947 msgid "Membership" msgstr "Adhésion" -#: cotisations/models.py:426 cotisations/models.py:678 -#: cotisations/models.py:684 cotisations/models.py:942 +#: cotisations/models.py:451 cotisations/models.py:684 +#: cotisations/models.py:690 cotisations/models.py:948 msgid "Both of them" msgstr "Les deux" -#: cotisations/models.py:435 +#: cotisations/models.py:460 msgid "amount" msgstr "montant" -#: cotisations/models.py:438 +#: cotisations/models.py:463 msgid "article" msgstr "article" -#: cotisations/models.py:441 +#: cotisations/models.py:466 msgid "price" msgstr "prix" -#: cotisations/models.py:444 cotisations/models.py:696 +#: cotisations/models.py:469 cotisations/models.py:702 msgid "duration (in months)" msgstr "durée (en mois)" -#: cotisations/models.py:450 cotisations/models.py:702 +#: cotisations/models.py:475 cotisations/models.py:708 msgid "duration (in days, will be added to duration in months)" msgstr "durée (en jours, sera ajoutée à la durée en mois)" -#: cotisations/models.py:458 cotisations/models.py:716 -#: cotisations/models.py:953 +#: cotisations/models.py:483 cotisations/models.py:722 +#: cotisations/models.py:959 msgid "subscription type" msgstr "type de cotisation" -#: cotisations/models.py:463 +#: cotisations/models.py:488 msgid "Can view a purchase object" msgstr "Peut voir un objet achat" -#: cotisations/models.py:464 +#: cotisations/models.py:489 msgid "Can edit all the previous purchases" msgstr "Peut modifier tous les achats précédents" -#: cotisations/models.py:466 cotisations/models.py:947 +#: cotisations/models.py:491 cotisations/models.py:953 msgid "purchase" msgstr "achat" -#: cotisations/models.py:467 +#: cotisations/models.py:492 msgid "purchases" msgstr "achats" -#: cotisations/models.py:539 cotisations/models.py:736 +#: cotisations/models.py:545 cotisations/models.py:742 msgid "Duration must be specified for a subscription." msgstr "La durée doit être renseignée pour une cotisation." -#: cotisations/models.py:550 +#: cotisations/models.py:556 msgid "You don't have the right to edit a purchase." msgstr "Vous n'avez pas le droit de modifier un achat." -#: cotisations/models.py:556 +#: cotisations/models.py:562 msgid "You don't have the right to edit this user's purchases." msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur." -#: cotisations/models.py:565 +#: cotisations/models.py:571 msgid "" "You don't have the right to edit a purchase already controlled or " "invalidated." @@ -333,15 +333,15 @@ msgstr "" "Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou " "invalidé." -#: cotisations/models.py:580 +#: cotisations/models.py:586 msgid "You don't have the right to delete a purchase." msgstr "Vous n'avez pas le droit de supprimer un achat." -#: cotisations/models.py:586 +#: cotisations/models.py:592 msgid "You don't have the right to delete this user's purchases." msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur." -#: cotisations/models.py:593 +#: cotisations/models.py:599 msgid "" "You don't have the right to delete a purchase already controlled or " "invalidated." @@ -349,134 +349,134 @@ msgstr "" "Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou " "invalidé." -#: cotisations/models.py:609 +#: cotisations/models.py:615 msgid "You don't have the right to view someone else's purchase history." msgstr "" "Vous n'avez pas le droit de voir l'historique des achats d'un autre " "utilisateur." -#: cotisations/models.py:677 +#: cotisations/models.py:683 msgid "Club" msgstr "Club" -#: cotisations/models.py:687 +#: cotisations/models.py:693 msgid "designation" msgstr "désignation" -#: cotisations/models.py:690 +#: cotisations/models.py:696 msgid "unit price" msgstr "prix unitaire" -#: cotisations/models.py:708 +#: cotisations/models.py:714 msgid "type of users concerned" msgstr "type d'utilisateurs concernés" -#: cotisations/models.py:719 cotisations/models.py:820 +#: cotisations/models.py:725 cotisations/models.py:826 msgid "is available for every user" msgstr "est disponible pour chaque utilisateur" -#: cotisations/models.py:726 +#: cotisations/models.py:732 msgid "Can view an article object" msgstr "Peut voir un objet article" -#: cotisations/models.py:727 +#: cotisations/models.py:733 msgid "Can buy every article" msgstr "Peut acheter chaque article" -#: cotisations/models.py:734 +#: cotisations/models.py:740 msgid "Solde is a reserved article name." msgstr "Solde est un nom d'article réservé." -#: cotisations/models.py:759 +#: cotisations/models.py:765 msgid "You can't buy this article." msgstr "Vous ne pouvez pas acheter cet article." -#: cotisations/models.py:800 +#: cotisations/models.py:806 msgid "Can view a bank object" msgstr "Peut voir un objet banque" -#: cotisations/models.py:801 +#: cotisations/models.py:807 msgid "bank" msgstr "banque" -#: cotisations/models.py:802 +#: cotisations/models.py:808 msgid "banks" msgstr "banques" -#: cotisations/models.py:818 +#: cotisations/models.py:824 msgid "method" msgstr "moyen" -#: cotisations/models.py:825 +#: cotisations/models.py:831 msgid "is user balance" msgstr "est solde utilisateur" -#: cotisations/models.py:826 +#: cotisations/models.py:832 msgid "There should be only one balance payment method." msgstr "Il ne devrait y avoir qu'un moyen de paiement solde." -#: cotisations/models.py:832 +#: cotisations/models.py:838 msgid "Can view a payment method object" msgstr "Peut voir un objet moyen de paiement" -#: cotisations/models.py:833 +#: cotisations/models.py:839 msgid "Can use every payment method" msgstr "Peut utiliser chaque moyen de paiement" -#: cotisations/models.py:835 +#: cotisations/models.py:841 msgid "payment method" msgstr "moyen de paiement" -#: cotisations/models.py:836 +#: cotisations/models.py:842 msgid "payment methods" msgstr "moyens de paiement" -#: cotisations/models.py:875 cotisations/payment_methods/comnpay/views.py:62 +#: cotisations/models.py:881 cotisations/payment_methods/comnpay/views.py:62 #, python-format msgid "The subscription of %(member_name)s was extended to %(end_date)s." msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s." -#: cotisations/models.py:885 +#: cotisations/models.py:891 msgid "The invoice was created." msgstr "La facture a été créée." -#: cotisations/models.py:905 +#: cotisations/models.py:911 msgid "You can't use this payment method." msgstr "Vous ne pouvez pas utiliser ce moyen de paiement." -#: cotisations/models.py:924 +#: cotisations/models.py:930 msgid "No custom payment methods." msgstr "Pas de moyens de paiement personnalisés." -#: cotisations/models.py:955 +#: cotisations/models.py:961 msgid "start date" msgstr "date de début" -#: cotisations/models.py:956 +#: cotisations/models.py:962 msgid "end date" msgstr "date de fin" -#: cotisations/models.py:960 +#: cotisations/models.py:966 msgid "Can view a subscription object" msgstr "Peut voir un objet cotisation" -#: cotisations/models.py:961 +#: cotisations/models.py:967 msgid "Can edit the previous subscriptions" msgstr "Peut modifier les cotisations précédentes" -#: cotisations/models.py:963 +#: cotisations/models.py:969 msgid "subscription" msgstr "cotisation" -#: cotisations/models.py:964 +#: cotisations/models.py:970 msgid "subscriptions" msgstr "cotisations" -#: cotisations/models.py:970 +#: cotisations/models.py:976 msgid "You don't have the right to edit a subscription." msgstr "Vous n'avez pas le droit de modifier une cotisation." -#: cotisations/models.py:979 +#: cotisations/models.py:985 msgid "" "You don't have the right to edit a subscription already controlled or " "invalidated." @@ -484,11 +484,11 @@ msgstr "" "Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée " "ou invalidée." -#: cotisations/models.py:991 +#: cotisations/models.py:997 msgid "You don't have the right to delete a subscription." msgstr "Vous n'avez pas le droit de supprimer une cotisation." -#: cotisations/models.py:998 +#: cotisations/models.py:1004 msgid "" "You don't have the right to delete a subscription already controlled or " "invalidated." @@ -496,7 +496,7 @@ msgstr "" "Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée " "ou invalidée." -#: cotisations/models.py:1014 +#: cotisations/models.py:1020 msgid "You don't have the right to view someone else's subscription history." msgstr "" "Vous n'avez pas le droit de voir l'historique des cotisations d'un autre " @@ -784,7 +784,7 @@ msgstr "Contrôlé" #: cotisations/templates/cotisations/control.html:107 #: cotisations/templates/cotisations/delete.html:38 #: cotisations/templates/cotisations/edit_facture.html:64 -#: cotisations/views.py:168 cotisations/views.py:222 cotisations/views.py:278 +#: cotisations/views.py:170 cotisations/views.py:222 cotisations/views.py:276 msgid "Confirm" msgstr "Confirmer" @@ -921,7 +921,7 @@ msgstr "Rechargement de solde" msgid "Pay %(amount)s €" msgstr "Payer %(amount)s €" -#: cotisations/templates/cotisations/payment.html:42 cotisations/views.py:1028 +#: cotisations/templates/cotisations/payment.html:42 cotisations/views.py:1026 msgid "Pay" msgstr "Payer" @@ -933,11 +933,11 @@ msgstr "Créer une facture" msgid "Control the invoices" msgstr "Contrôler les factures" -#: cotisations/views.py:155 +#: cotisations/views.py:157 msgid "You need to choose at least one article." msgstr "Vous devez choisir au moins un article." -#: cotisations/views.py:169 +#: cotisations/views.py:171 msgid "New invoice" msgstr "Nouvelle facture" @@ -949,104 +949,104 @@ msgstr "Le devis a été créé." msgid "New cost estimate" msgstr "Nouveau devis" -#: cotisations/views.py:272 +#: cotisations/views.py:270 msgid "The custom invoice was created." msgstr "La facture personnalisée a été créée." -#: cotisations/views.py:282 +#: cotisations/views.py:280 msgid "New custom invoice" msgstr "Nouvelle facture personnalisée" -#: cotisations/views.py:357 cotisations/views.py:438 +#: cotisations/views.py:355 cotisations/views.py:436 msgid "The invoice was edited." msgstr "La facture a été modifiée." -#: cotisations/views.py:375 cotisations/views.py:570 +#: cotisations/views.py:373 cotisations/views.py:568 msgid "The invoice was deleted." msgstr "La facture a été supprimée." -#: cotisations/views.py:398 +#: cotisations/views.py:396 msgid "The cost estimate was edited." msgstr "Le devis a été modifié." -#: cotisations/views.py:405 +#: cotisations/views.py:403 msgid "Edit cost estimate" msgstr "Modifier le devis" -#: cotisations/views.py:419 +#: cotisations/views.py:417 msgid "An invoice was successfully created from your cost estimate." msgstr "Une facture a bien été créée à partir de votre devis." -#: cotisations/views.py:445 +#: cotisations/views.py:443 msgid "Edit custom invoice" msgstr "Modifier la facture personnalisée" -#: cotisations/views.py:507 +#: cotisations/views.py:505 msgid "The cost estimate was deleted." msgstr "Le devis a été supprimé." -#: cotisations/views.py:510 +#: cotisations/views.py:508 msgid "cost estimate" msgstr "devis" -#: cotisations/views.py:594 +#: cotisations/views.py:592 msgid "The article was created." msgstr "L'article a été créé." -#: cotisations/views.py:599 cotisations/views.py:673 cotisations/views.py:767 +#: cotisations/views.py:597 cotisations/views.py:671 cotisations/views.py:765 msgid "Add" msgstr "Ajouter" -#: cotisations/views.py:600 +#: cotisations/views.py:598 msgid "New article" msgstr "Nouvel article" -#: cotisations/views.py:617 +#: cotisations/views.py:615 msgid "The article was edited." msgstr "L'article a été modifié." -#: cotisations/views.py:622 cotisations/views.py:705 cotisations/views.py:791 +#: cotisations/views.py:620 cotisations/views.py:703 cotisations/views.py:789 msgid "Edit" msgstr "Modifier" -#: cotisations/views.py:623 +#: cotisations/views.py:621 msgid "Edit article" msgstr "Modifier l'article" -#: cotisations/views.py:640 +#: cotisations/views.py:638 msgid "The articles were deleted." msgstr "Les articles ont été supprimés." -#: cotisations/views.py:645 cotisations/views.py:744 cotisations/views.py:829 +#: cotisations/views.py:643 cotisations/views.py:742 cotisations/views.py:827 msgid "Delete" msgstr "Supprimer" -#: cotisations/views.py:646 +#: cotisations/views.py:644 msgid "Delete article" msgstr "Supprimer l'article" -#: cotisations/views.py:667 +#: cotisations/views.py:665 msgid "The payment method was created." msgstr "Le moyen de paiment a été créé." -#: cotisations/views.py:674 +#: cotisations/views.py:672 msgid "New payment method" msgstr "Nouveau moyen de paiement" -#: cotisations/views.py:699 +#: cotisations/views.py:697 msgid "The payment method was edited." msgstr "Le moyen de paiment a été modifié." -#: cotisations/views.py:706 +#: cotisations/views.py:704 msgid "Edit payment method" msgstr "Modifier le moyen de paiement" -#: cotisations/views.py:728 +#: cotisations/views.py:726 #, python-format msgid "The payment method %(method_name)s was deleted." msgstr "Le moyen de paiement %(method_name)s a été supprimé." -#: cotisations/views.py:735 +#: cotisations/views.py:733 #, python-format msgid "" "The payment method %(method_name)s can't be deleted because there are " @@ -1055,32 +1055,32 @@ msgstr "" "Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a " "des factures qui l'utilisent." -#: cotisations/views.py:745 +#: cotisations/views.py:743 msgid "Delete payment method" msgstr "Supprimer le moyen de paiement" -#: cotisations/views.py:762 +#: cotisations/views.py:760 msgid "The bank was created." msgstr "La banque a été créée." -#: cotisations/views.py:768 +#: cotisations/views.py:766 msgid "New bank" msgstr "Nouvelle banque" -#: cotisations/views.py:786 +#: cotisations/views.py:784 msgid "The bank was edited." msgstr "La banque a été modifiée." -#: cotisations/views.py:792 +#: cotisations/views.py:790 msgid "Edit bank" msgstr "Modifier la banque" -#: cotisations/views.py:814 +#: cotisations/views.py:812 #, python-format msgid "The bank %(bank_name)s was deleted." msgstr "La banque %(bank_name)s a été supprimée." -#: cotisations/views.py:820 +#: cotisations/views.py:818 #, python-format msgid "" "The bank %(bank_name)s can't be deleted because there are invoices using it." @@ -1088,22 +1088,22 @@ msgstr "" "La banque %(bank_name)s ne peut pas être supprimée car il y a des factures " "qui l'utilisent." -#: cotisations/views.py:830 +#: cotisations/views.py:828 msgid "Delete bank" msgstr "Supprimer la banque" -#: cotisations/views.py:864 +#: cotisations/views.py:862 msgid "Your changes have been properly taken into account." msgstr "Vos modifications ont correctement été prises en compte." -#: cotisations/views.py:996 +#: cotisations/views.py:994 msgid "You are not allowed to credit your balance." msgstr "Vous n'êtes pas autorisé à créditer votre solde." -#: cotisations/views.py:1027 +#: cotisations/views.py:1025 msgid "Refill your balance" msgstr "Recharger votre solde" -#: cotisations/views.py:1046 +#: cotisations/views.py:1044 msgid "Could not find a voucher for that invoice." msgstr "Impossible de trouver un reçu pour cette facture." diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 66bc7234..6a2bd167 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-19 23:43+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -243,7 +243,7 @@ msgstr "Utilisateurs complètement archivés" msgid "Not yet active users" msgstr "Utilisateurs pas encore actifs" -#: logs/views.py:273 +#: logs/views.py:264 msgid "Contributing members" msgstr "Adhérents cotisants" diff --git a/machines/locale/fr/LC_MESSAGES/django.po b/machines/locale/fr/LC_MESSAGES/django.po index 049c01ad..ff54149b 100644 --- a/machines/locale/fr/LC_MESSAGES/django.po +++ b/machines/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-23 16:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -55,91 +55,91 @@ msgstr "Sélectionnez un type de machine" msgid "Automatic IPv4 assignment" msgstr "Assignation automatique IPv4" -#: machines/forms.py:177 +#: machines/forms.py:172 msgid "Current aliases" msgstr "Alias actuels" -#: machines/forms.py:199 +#: machines/forms.py:194 msgid "Machine type to add" msgstr "Type de machine à ajouter" -#: machines/forms.py:200 +#: machines/forms.py:195 msgid "Related IP type" msgstr "Type d'IP relié" -#: machines/forms.py:208 +#: machines/forms.py:203 msgid "Current machine types" msgstr "Types de machines actuels" -#: machines/forms.py:232 +#: machines/forms.py:227 msgid "IP type to add" msgstr "Type d'IP à ajouter" -#: machines/forms.py:260 +#: machines/forms.py:255 msgid "Current IP types" msgstr "Types d'IP actuels" -#: machines/forms.py:283 +#: machines/forms.py:278 msgid "Extension to add" msgstr "Extension à ajouter" -#: machines/forms.py:284 machines/templates/machines/aff_extension.html:37 +#: machines/forms.py:279 machines/templates/machines/aff_extension.html:37 msgid "A record origin" msgstr "Enregistrement A origin" -#: machines/forms.py:285 machines/templates/machines/aff_extension.html:39 +#: machines/forms.py:280 machines/templates/machines/aff_extension.html:39 msgid "AAAA record origin" msgstr "Enregistrement AAAA origin" -#: machines/forms.py:286 +#: machines/forms.py:281 msgid "SOA record to use" msgstr "Enregistrement SOA à utiliser" -#: machines/forms.py:287 +#: machines/forms.py:282 msgid "Sign with DNSSEC" msgstr "Signer avec DNSSEC" -#: machines/forms.py:295 +#: machines/forms.py:290 msgid "Current extensions" msgstr "Extensions actuelles" -#: machines/forms.py:337 +#: machines/forms.py:332 msgid "Current SOA records" msgstr "Enregistrements SOA actuels" -#: machines/forms.py:370 +#: machines/forms.py:365 msgid "Current MX records" msgstr "Enregistrements MX actuels" -#: machines/forms.py:405 +#: machines/forms.py:400 msgid "Current NS records" msgstr "Enregistrements NS actuels" -#: machines/forms.py:435 +#: machines/forms.py:430 msgid "Current TXT records" msgstr "Enregistrements TXT actuels" -#: machines/forms.py:465 +#: machines/forms.py:460 msgid "Current DNAME records" msgstr "Enregistrements DNAME actuels" -#: machines/forms.py:495 +#: machines/forms.py:490 msgid "Current SRV records" msgstr "Enregistrements SRV actuels" -#: machines/forms.py:526 +#: machines/forms.py:521 msgid "Current NAS devices" msgstr "Dispositifs NAS actuels" -#: machines/forms.py:559 +#: machines/forms.py:554 msgid "Current roles" msgstr "Rôles actuels" -#: machines/forms.py:601 +#: machines/forms.py:596 msgid "Current services" msgstr "Services actuels" -#: machines/forms.py:643 +#: machines/forms.py:638 msgid "Current VLANs" msgstr "VLANs actuels" diff --git a/multi_op/locale/fr/LC_MESSAGES/django.po b/multi_op/locale/fr/LC_MESSAGES/django.po index 4faf9166..4457f37a 100644 --- a/multi_op/locale/fr/LC_MESSAGES/django.po +++ b/multi_op/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2019-11-16 00:22+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index 8341c1b9..d48f427e 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-24 15:54+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -35,7 +35,7 @@ msgid "You don't have the right to view this application." msgstr "Vous n'avez pas le droit de voir cette application." #: preferences/forms.py:65 -#: preferences/templates/preferences/display_preferences.html:144 +#: preferences/templates/preferences/display_preferences.html:150 msgid "Telephone number required" msgstr "Numéro de téléphone requis" @@ -52,201 +52,207 @@ msgid "All can create a member" msgstr "Tous peuvent créer un adhérent" #: preferences/forms.py:69 +#: preferences/templates/preferences/display_preferences.html:134 +msgid "Delay before disabling accounts without a verified email" +msgstr "Délai avant la désactivation des comptes sans adresse mail confirmé" + +#: preferences/forms.py:70 #: preferences/templates/preferences/display_preferences.html:120 msgid "Self registration" msgstr "Autoinscription" -#: preferences/forms.py:70 -#: preferences/templates/preferences/display_preferences.html:120 -msgid "Allow directly setting a password during account creation" -msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" - -#: preferences/forms.py:70 +#: preferences/forms.py:71 msgid "Default shell" msgstr "Interface en ligne de commande par défaut" -#: preferences/forms.py:84 +#: preferences/forms.py:72 +msgid "Allow directly setting a password during account creation" +msgstr "" +"Permettre le choix d'un mot de passe directement lors de la création du " +"compte" + +#: preferences/forms.py:86 msgid "Possibility to set a password per machine" msgstr "Possibilité de mettre un mot de passe par machine" -#: preferences/forms.py:87 -#: preferences/templates/preferences/display_preferences.html:174 +#: preferences/forms.py:89 +#: preferences/templates/preferences/display_preferences.html:180 msgid "Maximum number of interfaces allowed for a standard user" msgstr "Nombre maximum d'interfaces autorisé pour un utilisateur standard" -#: preferences/forms.py:90 -#: preferences/templates/preferences/display_preferences.html:178 +#: preferences/forms.py:92 +#: preferences/templates/preferences/display_preferences.html:184 msgid "Maximum number of DNS aliases allowed for a standard user" msgstr "Nombre maximum d'alias DNS autorisé pour un utilisateur standard" -#: preferences/forms.py:92 +#: preferences/forms.py:94 msgid "IPv6 mode" msgstr "Mode IPv6" -#: preferences/forms.py:93 +#: preferences/forms.py:95 msgid "Can create a machine" msgstr "Peut créer une machine" -#: preferences/forms.py:136 +#: preferences/forms.py:138 msgid "General message in French" msgstr "Message général en français" -#: preferences/forms.py:137 +#: preferences/forms.py:139 msgid "General message in English" msgstr "Message général en anglais" -#: preferences/forms.py:139 +#: preferences/forms.py:141 #: preferences/templates/preferences/display_preferences.html:58 msgid "Number of results displayed when searching" msgstr "Nombre de résultats affichés lors de la recherche" -#: preferences/forms.py:142 +#: preferences/forms.py:144 msgid "Number of items per page, standard size (e.g. users)" msgstr "Nombre d'éléments par page, taille standard (ex : utilisateurs)" -#: preferences/forms.py:145 +#: preferences/forms.py:147 msgid "Number of items per page, large size (e.g. machines)" msgstr "Nombre d'éléments par page, taille importante (ex : machines)" -#: preferences/forms.py:148 +#: preferences/forms.py:150 #: preferences/templates/preferences/display_preferences.html:66 msgid "Time before expiration of the reset password link (in hours)" msgstr "" "Temps avant expiration du lien de réinitialisation de mot de passe (en " "heures)" -#: preferences/forms.py:150 +#: preferences/forms.py:152 #: preferences/templates/preferences/display_preferences.html:52 msgid "Website name" msgstr "Nom du site" -#: preferences/forms.py:151 +#: preferences/forms.py:153 #: preferences/templates/preferences/display_preferences.html:54 msgid "Email address for automatic emailing" msgstr "Adresse mail pour les mails automatiques" -#: preferences/forms.py:152 +#: preferences/forms.py:154 #: preferences/templates/preferences/display_preferences.html:76 msgid "Summary of the General Terms of Use" msgstr "Résumé des Conditions Générales d'Utilisation" -#: preferences/forms.py:153 +#: preferences/forms.py:155 #: preferences/templates/preferences/display_preferences.html:78 msgid "General Terms of Use" msgstr "Conditions Générales d'Utilisation" -#: preferences/forms.py:166 +#: preferences/forms.py:168 msgid "Organisation name" msgstr "Nom de l'association" -#: preferences/forms.py:167 -#: preferences/templates/preferences/display_preferences.html:307 +#: preferences/forms.py:169 +#: preferences/templates/preferences/display_preferences.html:313 msgid "SIRET number" msgstr "Numéro SIRET" -#: preferences/forms.py:168 +#: preferences/forms.py:170 msgid "Address (line 1)" msgstr "Adresse (ligne 1)" -#: preferences/forms.py:169 +#: preferences/forms.py:171 msgid "Address (line 2)" msgstr "Adresse (ligne 2)" -#: preferences/forms.py:170 -#: preferences/templates/preferences/display_preferences.html:315 +#: preferences/forms.py:172 +#: preferences/templates/preferences/display_preferences.html:321 msgid "Contact email address" msgstr "Adresse mail de contact" -#: preferences/forms.py:171 -#: preferences/templates/preferences/display_preferences.html:319 +#: preferences/forms.py:173 +#: preferences/templates/preferences/display_preferences.html:325 msgid "Telephone number" msgstr "Numéro de téléphone" -#: preferences/forms.py:172 -#: preferences/templates/preferences/display_preferences.html:321 +#: preferences/forms.py:174 +#: preferences/templates/preferences/display_preferences.html:327 msgid "Usual name" msgstr "Nom d'usage" -#: preferences/forms.py:174 +#: preferences/forms.py:176 msgid "Account used for editing from /admin" msgstr "Compte utilisé pour les modifications depuis /admin" -#: preferences/forms.py:176 preferences/forms.py:321 +#: preferences/forms.py:178 preferences/forms.py:323 #: preferences/templates/preferences/aff_service.html:33 msgid "Description" msgstr "Description" -#: preferences/forms.py:190 +#: preferences/forms.py:192 msgid "Message for the French welcome email" msgstr "Message pour le mail de bienvenue en français" -#: preferences/forms.py:193 +#: preferences/forms.py:195 msgid "Message for the English welcome email" msgstr "Message pour le mail de bienvenue en anglais" -#: preferences/forms.py:207 +#: preferences/forms.py:209 msgid "Facebook URL" msgstr "URL du compte Facebook" -#: preferences/forms.py:208 +#: preferences/forms.py:210 msgid "Twitter URL" msgstr "URL du compte Twitter" -#: preferences/forms.py:209 -#: preferences/templates/preferences/display_preferences.html:482 +#: preferences/forms.py:211 +#: preferences/templates/preferences/display_preferences.html:488 msgid "Twitter account name" msgstr "Nom du compte Twitter" -#: preferences/forms.py:227 +#: preferences/forms.py:229 msgid "You chose to set vlan but did not set any VLAN." msgstr "" "Vous avez choisi de paramétrer vlan mais vous n'avez indiqué aucun VLAN." -#: preferences/forms.py:228 +#: preferences/forms.py:230 msgid "Please, choose a VLAN." msgstr "Veuillez choisir un VLAN." -#: preferences/forms.py:259 +#: preferences/forms.py:261 msgid "There is already a mandate taking place at the specified start date." msgstr "Il y a déjà un mandat ayant cours à la date de début renseignée." -#: preferences/forms.py:273 +#: preferences/forms.py:275 msgid "There is already a mandate taking place at the specified end date." msgstr "Il y a déjà un madant ayant cours à la date de fin renseignée." -#: preferences/forms.py:287 +#: preferences/forms.py:289 msgid "The specified dates overlap with an existing mandate." msgstr "Les dates renseignées se superposent avec un mandat existant." -#: preferences/forms.py:319 +#: preferences/forms.py:321 #: preferences/templates/preferences/aff_service.html:31 -#: preferences/templates/preferences/display_preferences.html:305 +#: preferences/templates/preferences/display_preferences.html:311 msgid "Name" msgstr "Nom" -#: preferences/forms.py:320 +#: preferences/forms.py:322 #: preferences/templates/preferences/aff_service.html:32 msgid "URL" msgstr "URL" -#: preferences/forms.py:322 +#: preferences/forms.py:324 #: preferences/templates/preferences/aff_service.html:34 msgid "Image" msgstr "Image" -#: preferences/forms.py:330 +#: preferences/forms.py:332 msgid "Current services" msgstr "Services actuels" -#: preferences/forms.py:419 +#: preferences/forms.py:421 msgid "Current email addresses" msgstr "Adresses mail actuelles" -#: preferences/forms.py:454 +#: preferences/forms.py:456 msgid "Current document templates" msgstr "Modèles de document actuels" -#: preferences/forms.py:484 +#: preferences/forms.py:486 msgid "Current attributes" msgstr "Attributs actuels" @@ -285,19 +291,19 @@ msgstr "" "Les utilisateurs n'ayant jamais adhéré seront supprimés après ce nombre de " "jours." -#: preferences/models.py:111 +#: preferences/models.py:113 msgid "" -"Users with an email address not yet confirmed will be disabled after" -" this number of days." +"Users with an email address not yet confirmed will be disabled after this " +"number of days." msgstr "" -"Les utilisateurs n'ayant pas confirmé leur addresse mail seront" -" désactivés après ce nombre de jours" +"Les utilisateurs n'ayant pas confirmé leur addresse mail seront désactivés " +"après ce nombre de jours" -#: preferences/models.py:114 +#: preferences/models.py:117 msgid "A new user can create their account on Re2o." msgstr "Un nouvel utilisateur peut créer son compte sur Re2o." -#: preferences/models.py:116 +#: preferences/models.py:122 msgid "" "If True, all new created and connected users are active. If False, only when " "a valid registration has been paid." @@ -305,184 +311,183 @@ msgstr "" "Si True, tous les nouveaux utilisations créés et connectés sont actifs. Si " "False, seulement quand une inscription validée a été payée." -#: preferences/models.py:116 +#: preferences/models.py:129 msgid "" -"If True, users have the choice to receive an email containing" -" a link to reset their password during creation, or to directly" -" set their password in the page." -" If False, an email is always sent." +"If True, users have the choice to receive an email containing a link to " +"reset their password during creation, or to directly set their password in " +"the page. If False, an email is always sent." msgstr "" -"Si True, les utilisateurs ont le choix de recevoir un email avec" -" un lien pour changer leur mot de passe lors de la création de leur compte, " -" ou alors de choisir leur mot de passe immédiatement." -" Si False, un mail est toujours envoyé." +"Si True, les utilisateurs ont le choix de recevoir un email avec un lien " +"pour changer leur mot de passe lors de la création de leur compte, ou alors " +"de choisir leur mot de passe immédiatement. Si False, un mail est toujours " +"envoyé." -#: preferences/models.py:121 +#: preferences/models.py:136 msgid "If True, archived users are allowed to connect." msgstr "Si True, les utilisateurs archivés sont autorisés à se connecter." -#: preferences/models.py:125 +#: preferences/models.py:140 msgid "Can view the user preferences" msgstr "Peut voir les préférences d'utilisateur" -#: preferences/models.py:126 +#: preferences/models.py:141 msgid "user preferences" msgstr "Préférences d'utilisateur" -#: preferences/models.py:133 +#: preferences/models.py:148 msgid "Email domain must begin with @." msgstr "Un domaine mail doit commencer par @." -#: preferences/models.py:151 +#: preferences/models.py:166 msgid "Automatic configuration by RA" msgstr "Configuration automatique par RA" -#: preferences/models.py:152 +#: preferences/models.py:167 msgid "IP addresses assignment by DHCPv6" msgstr "Attribution d'adresses IP par DHCPv6" -#: preferences/models.py:153 +#: preferences/models.py:168 msgid "Disabled" msgstr "Désactivé" -#: preferences/models.py:162 +#: preferences/models.py:177 msgid "default Time To Live (TTL) for CNAME, A and AAAA records" msgstr "" "Temps de vie (TTL) par défault pour des enregistrements CNAME, A et AAAA" -#: preferences/models.py:172 +#: preferences/models.py:187 msgid "Can view the machine preferences" msgstr "Peut voir les préférences de machine" -#: preferences/models.py:173 +#: preferences/models.py:188 msgid "machine preferences" msgstr "Préférences de machine" -#: preferences/models.py:193 preferences/models.py:651 +#: preferences/models.py:208 preferences/models.py:666 msgid "On the IP range's VLAN of the machine" msgstr "Sur le VLAN de la plage d'IP de la machine" -#: preferences/models.py:194 preferences/models.py:652 +#: preferences/models.py:209 preferences/models.py:667 msgid "Preset in \"VLAN for machines accepted by RADIUS\"" msgstr "Prédéfinie dans « VLAN pour les machines acceptées par RADIUS »" -#: preferences/models.py:200 +#: preferences/models.py:215 msgid "Web management, activated in case of automatic provision." msgstr "Gestion web, activée en cas de provision automatique." -#: preferences/models.py:205 +#: preferences/models.py:220 msgid "" "SSL web management, make sure that a certificate is installed on the switch." msgstr "" "Gestion web SSL, vérifiez qu'un certificat est installé sur le commutateur " "réseau." -#: preferences/models.py:211 +#: preferences/models.py:226 msgid "REST management, activated in case of automatic provision." msgstr "Gestion REST, activée en cas de provision automatique." -#: preferences/models.py:218 +#: preferences/models.py:233 msgid "IP range for the management of switches." msgstr "Plage d'IP pour la gestion des commutateurs réseau." -#: preferences/models.py:224 +#: preferences/models.py:239 msgid "Provision of configuration mode for switches." msgstr "Mode de provision de configuration pour les commutateurs réseau." -#: preferences/models.py:227 +#: preferences/models.py:242 msgid "SFTP login for switches." msgstr "Identifiant SFTP pour les commutateurs réseau." -#: preferences/models.py:230 +#: preferences/models.py:245 msgid "SFTP password." msgstr "Mot de passe SFTP." -#: preferences/models.py:334 +#: preferences/models.py:349 msgid "Can view the topology preferences" msgstr "Peut voir les préférences de topologie" -#: preferences/models.py:335 +#: preferences/models.py:350 msgid "topology preferences" msgstr "préférences de topologie" -#: preferences/models.py:348 +#: preferences/models.py:363 msgid "RADIUS key." msgstr "Clé RADIUS." -#: preferences/models.py:350 +#: preferences/models.py:365 msgid "Comment for this key." msgstr "Commentaire pour cette clé." -#: preferences/models.py:353 +#: preferences/models.py:368 msgid "Default key for switches." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:357 +#: preferences/models.py:372 msgid "Can view a RADIUS key object" msgstr "Peut voir un objet clé RADIUS" -#: preferences/models.py:358 preferences/views.py:331 +#: preferences/models.py:373 preferences/views.py:331 msgid "RADIUS key" msgstr "Clé RADIUS" -#: preferences/models.py:359 -#: preferences/templates/preferences/display_preferences.html:201 +#: preferences/models.py:374 +#: preferences/templates/preferences/display_preferences.html:207 msgid "RADIUS keys" msgstr "clés RADIUS" -#: preferences/models.py:366 +#: preferences/models.py:381 msgid "Default RADIUS key for switches already exists." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:369 +#: preferences/models.py:384 msgid "RADIUS key " msgstr "clé RADIUS " -#: preferences/models.py:375 +#: preferences/models.py:390 msgid "Switch login." msgstr "Identifiant du commutateur réseau." -#: preferences/models.py:376 +#: preferences/models.py:391 msgid "Password." msgstr "Mot de passe." -#: preferences/models.py:378 +#: preferences/models.py:393 msgid "Default credentials for switches." msgstr "Identifiants par défaut pour les commutateurs réseau." -#: preferences/models.py:385 +#: preferences/models.py:400 msgid "Can view a switch management credentials object" msgstr "Peut voir un objet identifiants de gestion de commutateur réseau" -#: preferences/models.py:388 preferences/views.py:394 +#: preferences/models.py:403 preferences/views.py:394 msgid "switch management credentials" msgstr "identifiants de gestion de commutateur réseau" -#: preferences/models.py:391 +#: preferences/models.py:406 msgid "Switch login " msgstr "Identifiant du commutateur réseau " -#: preferences/models.py:403 +#: preferences/models.py:418 msgid "Delay between the email and the membership's end." msgstr "Délai entre le mail et la fin d'adhésion." -#: preferences/models.py:409 +#: preferences/models.py:424 msgid "Message displayed specifically for this reminder." msgstr "Message affiché spécifiquement pour ce rappel." -#: preferences/models.py:413 +#: preferences/models.py:428 msgid "Can view a reminder object" msgstr "Peut voir un objet rappel" -#: preferences/models.py:414 preferences/views.py:276 +#: preferences/models.py:429 preferences/views.py:276 msgid "reminder" msgstr "rappel" -#: preferences/models.py:415 +#: preferences/models.py:430 msgid "reminders" msgstr "rappels" -#: preferences/models.py:436 +#: preferences/models.py:451 msgid "" "General message displayed on the French version of the website (e.g. in case " "of maintenance)." @@ -490,7 +495,7 @@ msgstr "" "Message général affiché sur la version française du site (ex : en cas de " "maintenance)." -#: preferences/models.py:444 +#: preferences/models.py:459 msgid "" "General message displayed on the English version of the website (e.g. in " "case of maintenance)." @@ -498,75 +503,75 @@ msgstr "" "Message général affiché sur la version anglaise du site (ex : en cas de " "maintenance)." -#: preferences/models.py:459 +#: preferences/models.py:474 msgid "Can view the general preferences" msgstr "Peut voir les préférences générales" -#: preferences/models.py:460 +#: preferences/models.py:475 msgid "general preferences" msgstr "préférences générales" -#: preferences/models.py:480 +#: preferences/models.py:495 msgid "Can view the service preferences" msgstr "Peut voir les préférences de service" -#: preferences/models.py:481 preferences/views.py:227 +#: preferences/models.py:496 preferences/views.py:227 msgid "service" msgstr "service" -#: preferences/models.py:482 +#: preferences/models.py:497 msgid "services" msgstr "services" -#: preferences/models.py:492 +#: preferences/models.py:507 msgid "Contact email address." msgstr "Adresse mail de contact." -#: preferences/models.py:498 +#: preferences/models.py:513 msgid "Description of the associated email address." msgstr "Description de l'adresse mail associée." -#: preferences/models.py:508 +#: preferences/models.py:523 msgid "Can view a contact email address object" msgstr "Peut voir un objet adresse mail de contact" -#: preferences/models.py:510 +#: preferences/models.py:525 msgid "contact email address" msgstr "adresse mail de contact" -#: preferences/models.py:511 +#: preferences/models.py:526 msgid "contact email addresses" msgstr "adresses mail de contact" -#: preferences/models.py:519 preferences/views.py:634 +#: preferences/models.py:534 preferences/views.py:634 msgid "mandate" msgstr "mandat" -#: preferences/models.py:520 +#: preferences/models.py:535 msgid "mandates" msgstr "mandats" -#: preferences/models.py:521 +#: preferences/models.py:536 msgid "Can view a mandate object" msgstr "Peut voir un objet mandat" -#: preferences/models.py:528 +#: preferences/models.py:543 msgid "president of the association" msgstr "président de l'association" -#: preferences/models.py:529 +#: preferences/models.py:544 msgid "Displayed on subscription vouchers." msgstr "Affiché sur les reçus de cotisation." -#: preferences/models.py:531 +#: preferences/models.py:546 msgid "start date" msgstr "date de début" -#: preferences/models.py:532 +#: preferences/models.py:547 msgid "end date" msgstr "date de fin" -#: preferences/models.py:545 +#: preferences/models.py:560 msgid "" "No mandates have been created. Please go to the preferences page to create " "one." @@ -574,140 +579,140 @@ msgstr "" "Aucun mandat n'a été créé. Veuillez vous rendre sur la page de préférences " "pour en créer un." -#: preferences/models.py:560 +#: preferences/models.py:575 msgid "Networking organisation school Something" msgstr "Association de réseau de l'école Machin" -#: preferences/models.py:563 +#: preferences/models.py:578 msgid "Threadneedle Street" msgstr "1 rue de la Vrillière" -#: preferences/models.py:564 +#: preferences/models.py:579 msgid "London EC2R 8AH" msgstr "75001 Paris" -#: preferences/models.py:567 +#: preferences/models.py:582 msgid "Organisation" msgstr "Association" -#: preferences/models.py:574 +#: preferences/models.py:589 msgid "Can view the organisation preferences" msgstr "Peut voir les préférences d'association" -#: preferences/models.py:575 +#: preferences/models.py:590 msgid "organisation preferences" msgstr "préférences d'association" -#: preferences/models.py:593 +#: preferences/models.py:608 msgid "Can view the homepage preferences" msgstr "Peut voir les préférences de page d'accueil" -#: preferences/models.py:594 +#: preferences/models.py:609 msgid "homepage preferences" msgstr "Préférences de page d'accueil" -#: preferences/models.py:608 +#: preferences/models.py:623 msgid "Welcome email in French." msgstr "Mail de bienvenue en français." -#: preferences/models.py:611 +#: preferences/models.py:626 msgid "Welcome email in English." msgstr "Mail de bienvenue en anglais." -#: preferences/models.py:616 +#: preferences/models.py:631 msgid "Can view the email message preferences" msgstr "Peut voir les préférences de message pour les mails" -#: preferences/models.py:618 +#: preferences/models.py:633 msgid "email message preferences" msgstr "préférences de messages pour les mails" -#: preferences/models.py:623 +#: preferences/models.py:638 msgid "RADIUS attribute" msgstr "attribut RADIUS" -#: preferences/models.py:624 +#: preferences/models.py:639 msgid "RADIUS attributes" msgstr "attributs RADIUS" -#: preferences/models.py:628 preferences/views.py:587 +#: preferences/models.py:643 preferences/views.py:587 msgid "attribute" msgstr "attribut" -#: preferences/models.py:629 +#: preferences/models.py:644 msgid "See https://freeradius.org/rfc/attributes.html." msgstr "Voir https://freeradius.org/rfc/attributes.html." -#: preferences/models.py:631 +#: preferences/models.py:646 msgid "value" msgstr "valeur" -#: preferences/models.py:633 +#: preferences/models.py:648 msgid "comment" msgstr "commentaire" -#: preferences/models.py:634 +#: preferences/models.py:649 msgid "Use this field to document this attribute." msgstr "Utilisez ce champ pour documenter cet attribut." -#: preferences/models.py:645 +#: preferences/models.py:660 msgid "RADIUS policy" msgstr "politique de RADIUS" -#: preferences/models.py:646 -#: preferences/templates/preferences/display_preferences.html:279 +#: preferences/models.py:661 +#: preferences/templates/preferences/display_preferences.html:285 msgid "RADIUS policies" msgstr "politiques de RADIUS" -#: preferences/models.py:657 +#: preferences/models.py:672 msgid "Reject the machine" msgstr "Rejeter la machine" -#: preferences/models.py:658 +#: preferences/models.py:673 msgid "Place the machine on the VLAN" msgstr "Placer la machine sur le VLAN" -#: preferences/models.py:667 +#: preferences/models.py:682 msgid "policy for unknown machines" msgstr "politique pour les machines inconnues" -#: preferences/models.py:675 +#: preferences/models.py:690 msgid "unknown machines VLAN" msgstr "VLAN pour les machines inconnues" -#: preferences/models.py:676 +#: preferences/models.py:691 msgid "VLAN for unknown machines if not rejected." msgstr "VLAN pour les machines inconnues si non rejeté." -#: preferences/models.py:682 +#: preferences/models.py:697 msgid "unknown machines attributes" msgstr "attributs pour les machines inconnues" -#: preferences/models.py:683 +#: preferences/models.py:698 msgid "Answer attributes for unknown machines." msgstr "Attributs de réponse pour les machines inconnues." -#: preferences/models.py:689 +#: preferences/models.py:704 msgid "policy for unknown ports" msgstr "politique pour les ports inconnus" -#: preferences/models.py:697 +#: preferences/models.py:712 msgid "unknown ports VLAN" msgstr "VLAN pour les ports inconnus" -#: preferences/models.py:698 +#: preferences/models.py:713 msgid "VLAN for unknown ports if not rejected." msgstr "VLAN pour les ports inconnus si non rejeté." -#: preferences/models.py:704 +#: preferences/models.py:719 msgid "unknown ports attributes" msgstr "attributs pour les ports inconnus" -#: preferences/models.py:705 +#: preferences/models.py:720 msgid "Answer attributes for unknown ports." msgstr "Attributs de réponse pour les ports inconnus." -#: preferences/models.py:712 +#: preferences/models.py:727 msgid "" "Policy for machines connecting from unregistered rooms (relevant on ports " "with STRICT RADIUS mode)" @@ -715,87 +720,87 @@ msgstr "" "Politique pour les machines se connectant depuis des chambre non " "enregistrées (pertinent pour les ports avec le mode de RADIUS STRICT)" -#: preferences/models.py:722 +#: preferences/models.py:737 msgid "unknown rooms VLAN" msgstr "VLAN pour les chambres inconnues" -#: preferences/models.py:723 +#: preferences/models.py:738 msgid "VLAN for unknown rooms if not rejected." msgstr "VLAN pour les chambres inconnues si non rejeté." -#: preferences/models.py:729 +#: preferences/models.py:744 msgid "unknown rooms attributes" msgstr "attributs pour les chambres inconnues" -#: preferences/models.py:730 +#: preferences/models.py:745 msgid "Answer attributes for unknown rooms." msgstr "Attributs de réponse pour les chambres inconnues." -#: preferences/models.py:736 +#: preferences/models.py:751 msgid "policy for non members" msgstr "politique pour les non adhérents" -#: preferences/models.py:744 +#: preferences/models.py:759 msgid "non members VLAN" msgstr "VLAN pour les non adhérents" -#: preferences/models.py:745 +#: preferences/models.py:760 msgid "VLAN for non members if not rejected." msgstr "VLAN pour les non adhérents si non rejeté." -#: preferences/models.py:751 +#: preferences/models.py:766 msgid "non members attributes" msgstr "attributs pour les non adhérents" -#: preferences/models.py:752 +#: preferences/models.py:767 msgid "Answer attributes for non members." msgstr "Attributs de réponse pour les non adhérents." -#: preferences/models.py:758 +#: preferences/models.py:773 msgid "policy for banned users" msgstr "politique pour les utilisateurs bannis" -#: preferences/models.py:766 +#: preferences/models.py:781 msgid "banned users VLAN" msgstr "VLAN pour les utilisateurs bannis" -#: preferences/models.py:767 +#: preferences/models.py:782 msgid "VLAN for banned users if not rejected." msgstr "VLAN pour les utilisateurs bannis si non rejeté." -#: preferences/models.py:773 +#: preferences/models.py:788 msgid "banned users attributes" msgstr "attributs pour les utilisateurs bannis" -#: preferences/models.py:774 +#: preferences/models.py:789 msgid "Answer attributes for banned users." msgstr "Attributs de réponse pour les utilisateurs bannis." -#: preferences/models.py:787 +#: preferences/models.py:802 msgid "accepted users attributes" msgstr "attributs pour les utilisateurs acceptés" -#: preferences/models.py:788 +#: preferences/models.py:803 msgid "Answer attributes for accepted users." msgstr "Attributs de réponse pour les utilisateurs acceptés." -#: preferences/models.py:815 +#: preferences/models.py:830 msgid "subscription preferences" msgstr "préférences de cotisation" -#: preferences/models.py:819 +#: preferences/models.py:834 msgid "template for invoices" msgstr "modèle pour les factures" -#: preferences/models.py:826 +#: preferences/models.py:841 msgid "template for subscription vouchers" msgstr "modèle pour les reçus de cotisation" -#: preferences/models.py:832 +#: preferences/models.py:847 msgid "send voucher by email when the invoice is controlled" msgstr "envoyer le reçu par mail quand la facture est contrôlée" -#: preferences/models.py:834 +#: preferences/models.py:849 msgid "" "Be careful, if no mandate is defined on the preferences page, errors will be " "triggered when generating vouchers." @@ -803,19 +808,19 @@ msgstr "" "Faites attention, si aucun mandat n'est défini sur la page de préférences, " "des erreurs seront déclenchées en générant des reçus." -#: preferences/models.py:846 +#: preferences/models.py:861 msgid "template" msgstr "modèle" -#: preferences/models.py:847 +#: preferences/models.py:862 msgid "name" msgstr "nom" -#: preferences/models.py:850 +#: preferences/models.py:865 msgid "document template" msgstr "modèle de document" -#: preferences/models.py:851 +#: preferences/models.py:866 msgid "document templates" msgstr "modèles de document" @@ -828,7 +833,7 @@ msgid "File" msgstr "Fichier" #: preferences/templates/preferences/aff_mailcontact.html:31 -#: preferences/templates/preferences/display_preferences.html:311 +#: preferences/templates/preferences/display_preferences.html:317 msgid "Address" msgstr "Adresse" @@ -1008,12 +1013,12 @@ msgstr "Préférences générales" #: preferences/templates/preferences/display_preferences.html:46 #: preferences/templates/preferences/display_preferences.html:108 -#: preferences/templates/preferences/display_preferences.html:167 -#: preferences/templates/preferences/display_preferences.html:220 -#: preferences/templates/preferences/display_preferences.html:282 -#: preferences/templates/preferences/display_preferences.html:300 -#: preferences/templates/preferences/display_preferences.html:397 -#: preferences/templates/preferences/display_preferences.html:475 +#: preferences/templates/preferences/display_preferences.html:173 +#: preferences/templates/preferences/display_preferences.html:226 +#: preferences/templates/preferences/display_preferences.html:288 +#: preferences/templates/preferences/display_preferences.html:306 +#: preferences/templates/preferences/display_preferences.html:403 +#: preferences/templates/preferences/display_preferences.html:481 #: preferences/templates/preferences/edit_preferences.html:46 #: preferences/views.py:212 preferences/views.py:261 preferences/views.py:307 #: preferences/views.py:367 preferences/views.py:431 preferences/views.py:496 @@ -1074,244 +1079,248 @@ msgstr "%(delete_notyetactive)s jours" msgid "All users are active by default" msgstr "Tous les utilisateurs sont actifs par défault" -#: preferences/templates/preferences/display_preferences.html:130 +#: preferences/templates/preferences/display_preferences.html:128 msgid "Allow archived users to log in" msgstr "Autoriser les utilisateurs archivés à se connecter" -#: preferences/templates/preferences/display_preferences.html:133 +#: preferences/templates/preferences/display_preferences.html:132 msgid "Allow directly entering a password during account creation" -msgstr "Permettre le choix d'un mot de passe directement lors de la création du compte" +msgstr "" +"Permettre le choix d'un mot de passe directement lors de la création du " +"compte" -#: preferences/templates/preferences/display_preferences.html:136 -msgid "Delay before disabling accounts without a verified email" -msgstr "Délai avant la désactivation des comptes sans adresse mail confirmé" +#: preferences/templates/preferences/display_preferences.html:135 +#, fuzzy, python-format +#| msgid "%(delete_notyetactive)s days" +msgid "%(disable_emailnotyetconfirmed)s days" +msgstr "%(delete_notyetactive)s jours" -#: preferences/templates/preferences/display_preferences.html:136 +#: preferences/templates/preferences/display_preferences.html:139 msgid "Users general permissions" msgstr "Permissions générales des utilisateurs" -#: preferences/templates/preferences/display_preferences.html:136 +#: preferences/templates/preferences/display_preferences.html:142 msgid "Default shell for users" msgstr "Interface en ligne de commande par défaut pour les utilisateurs" -#: preferences/templates/preferences/display_preferences.html:138 +#: preferences/templates/preferences/display_preferences.html:144 msgid "Users can edit their shell" msgstr "Les utilisateurs peuvent modifier leur interface en ligne de commande" -#: preferences/templates/preferences/display_preferences.html:142 +#: preferences/templates/preferences/display_preferences.html:148 msgid "Users can edit their room" msgstr "Les utilisateurs peuvent modifier leur chambre" -#: preferences/templates/preferences/display_preferences.html:148 +#: preferences/templates/preferences/display_preferences.html:154 msgid "GPG fingerprint field" msgstr "Champ empreinte GPG" -#: preferences/templates/preferences/display_preferences.html:159 +#: preferences/templates/preferences/display_preferences.html:165 msgid "Machine preferences" msgstr "Préférences de machine" -#: preferences/templates/preferences/display_preferences.html:172 +#: preferences/templates/preferences/display_preferences.html:178 msgid "Password per machine" msgstr "Mot de passe par machine" -#: preferences/templates/preferences/display_preferences.html:180 +#: preferences/templates/preferences/display_preferences.html:186 msgid "Default Time To Live (TTL) for CNAME, A and AAAA records." msgstr "" "Temps de vie (TTL) par défault pour des enregistrements CNAME, A et AAAA." -#: preferences/templates/preferences/display_preferences.html:184 +#: preferences/templates/preferences/display_preferences.html:190 msgid "IPv6 support" msgstr "Support de l'IPv6" -#: preferences/templates/preferences/display_preferences.html:186 +#: preferences/templates/preferences/display_preferences.html:192 msgid "Creation of machines" msgstr "Création de machines" -#: preferences/templates/preferences/display_preferences.html:196 +#: preferences/templates/preferences/display_preferences.html:202 msgid "Topology preferences" msgstr "Préférences de topologie" -#: preferences/templates/preferences/display_preferences.html:203 +#: preferences/templates/preferences/display_preferences.html:209 msgid "Add a RADIUS key" msgstr "Ajouter une clé RADIUS" -#: preferences/templates/preferences/display_preferences.html:213 +#: preferences/templates/preferences/display_preferences.html:219 msgid "Configuration of switches" msgstr "Configuration de commutateurs réseau" -#: preferences/templates/preferences/display_preferences.html:226 +#: preferences/templates/preferences/display_preferences.html:232 msgid "Web management, activated in case of automatic provision" msgstr "Gestion web, activée en cas de provision automatique" -#: preferences/templates/preferences/display_preferences.html:228 +#: preferences/templates/preferences/display_preferences.html:234 msgid "REST management, activated in case of automatic provision" msgstr "Gestion REST, activée en cas de provision automatique" -#: preferences/templates/preferences/display_preferences.html:235 +#: preferences/templates/preferences/display_preferences.html:241 msgid "Provision of configuration for switches" msgstr "Provision de configuration pour les commutateurs réseau" -#: preferences/templates/preferences/display_preferences.html:238 +#: preferences/templates/preferences/display_preferences.html:244 msgid "Switches with automatic provision" msgstr "Commutateurs réseau avec provision automatique" -#: preferences/templates/preferences/display_preferences.html:239 -#: preferences/templates/preferences/display_preferences.html:243 -#: preferences/templates/preferences/display_preferences.html:247 -#: preferences/templates/preferences/display_preferences.html:255 -#: preferences/templates/preferences/display_preferences.html:259 -#: preferences/templates/preferences/display_preferences.html:269 +#: preferences/templates/preferences/display_preferences.html:245 +#: preferences/templates/preferences/display_preferences.html:249 +#: preferences/templates/preferences/display_preferences.html:253 +#: preferences/templates/preferences/display_preferences.html:261 +#: preferences/templates/preferences/display_preferences.html:265 +#: preferences/templates/preferences/display_preferences.html:275 msgid "OK" msgstr "OK" -#: preferences/templates/preferences/display_preferences.html:239 -#: preferences/templates/preferences/display_preferences.html:243 -#: preferences/templates/preferences/display_preferences.html:247 -#: preferences/templates/preferences/display_preferences.html:269 +#: preferences/templates/preferences/display_preferences.html:245 +#: preferences/templates/preferences/display_preferences.html:249 +#: preferences/templates/preferences/display_preferences.html:253 +#: preferences/templates/preferences/display_preferences.html:275 msgid "Missing" msgstr "Manquant" -#: preferences/templates/preferences/display_preferences.html:242 +#: preferences/templates/preferences/display_preferences.html:248 msgid "IP range for the management of switches" msgstr "Plage d'IP pour la gestion des commutateurs réseau" -#: preferences/templates/preferences/display_preferences.html:246 +#: preferences/templates/preferences/display_preferences.html:252 msgid "Server for the configuration of switches" msgstr "Serveur pour la configuration des commutateurs réseau" -#: preferences/templates/preferences/display_preferences.html:250 +#: preferences/templates/preferences/display_preferences.html:256 msgid "Provision of configuration mode for switches" msgstr "Mode de provision de configuration pour les commutateurs réseau" -#: preferences/templates/preferences/display_preferences.html:254 +#: preferences/templates/preferences/display_preferences.html:260 msgid "TFTP mode" msgstr "Mode TFTP" -#: preferences/templates/preferences/display_preferences.html:258 +#: preferences/templates/preferences/display_preferences.html:264 msgid "SFTP mode" msgstr "Mode SFTP" -#: preferences/templates/preferences/display_preferences.html:259 +#: preferences/templates/preferences/display_preferences.html:265 msgid "Missing credentials" msgstr "Identifiants manquants" -#: preferences/templates/preferences/display_preferences.html:263 +#: preferences/templates/preferences/display_preferences.html:269 msgid "Switch management credentials" msgstr "Identifiants de gestion de commutateur réseau" -#: preferences/templates/preferences/display_preferences.html:265 +#: preferences/templates/preferences/display_preferences.html:271 msgid "Add switch management credentials" msgstr "Ajouter des identifiants de gestion de commutateur réseau" -#: preferences/templates/preferences/display_preferences.html:276 +#: preferences/templates/preferences/display_preferences.html:282 msgid "RADIUS preferences" msgstr "Préférences RADIUS" -#: preferences/templates/preferences/display_preferences.html:285 +#: preferences/templates/preferences/display_preferences.html:291 msgid "Current RADIUS attributes" msgstr "Attributs RADIUS actuels" -#: preferences/templates/preferences/display_preferences.html:286 +#: preferences/templates/preferences/display_preferences.html:292 msgid "Add an attribute" msgstr "Ajouter un attribut" -#: preferences/templates/preferences/display_preferences.html:294 +#: preferences/templates/preferences/display_preferences.html:300 msgid "Information about the organisation" msgstr "Informations sur l'association" -#: preferences/templates/preferences/display_preferences.html:325 +#: preferences/templates/preferences/display_preferences.html:331 msgid "User object of the organisation" msgstr "Objet utilisateur de l'association" -#: preferences/templates/preferences/display_preferences.html:327 +#: preferences/templates/preferences/display_preferences.html:333 msgid "Description of the organisation" msgstr "Description de l'association" -#: preferences/templates/preferences/display_preferences.html:331 +#: preferences/templates/preferences/display_preferences.html:337 msgid "Mandates" msgstr "Mandats" -#: preferences/templates/preferences/display_preferences.html:334 +#: preferences/templates/preferences/display_preferences.html:340 msgid "Add a mandate" msgstr "Ajouter un mandat" -#: preferences/templates/preferences/display_preferences.html:343 +#: preferences/templates/preferences/display_preferences.html:349 msgid "Document templates" msgstr "Modèles de document" -#: preferences/templates/preferences/display_preferences.html:349 +#: preferences/templates/preferences/display_preferences.html:355 msgid "Add a document template" msgstr "Ajouter un modèle de document" -#: preferences/templates/preferences/display_preferences.html:353 +#: preferences/templates/preferences/display_preferences.html:359 msgid "Delete one or several document templates" msgstr " Supprimer un ou plusieurs modèles de document" -#: preferences/templates/preferences/display_preferences.html:362 +#: preferences/templates/preferences/display_preferences.html:368 msgid "Subscription preferences" msgstr "Préférences de cotisation" -#: preferences/templates/preferences/display_preferences.html:371 +#: preferences/templates/preferences/display_preferences.html:377 msgid "Send voucher by email" msgstr "Envoyer le reçu par mail" -#: preferences/templates/preferences/display_preferences.html:375 +#: preferences/templates/preferences/display_preferences.html:381 msgid "Invoices' template" msgstr "Modèle des factures" -#: preferences/templates/preferences/display_preferences.html:379 +#: preferences/templates/preferences/display_preferences.html:385 msgid "Vouchers' template" msgstr "Modèle des reçus" -#: preferences/templates/preferences/display_preferences.html:390 +#: preferences/templates/preferences/display_preferences.html:396 msgid "Message for emails" msgstr "Message pour les mails" -#: preferences/templates/preferences/display_preferences.html:403 +#: preferences/templates/preferences/display_preferences.html:409 msgid "Welcome email (in French)" msgstr "Mail de bienvenue (en français)" -#: preferences/templates/preferences/display_preferences.html:407 +#: preferences/templates/preferences/display_preferences.html:413 msgid "Welcome email (in English)" msgstr "Mail de bienvenue (en anglais)" -#: preferences/templates/preferences/display_preferences.html:417 +#: preferences/templates/preferences/display_preferences.html:423 msgid "Preferences for the membership's end email" msgstr "Préférences pour le mail de fin d'adhésion" -#: preferences/templates/preferences/display_preferences.html:423 +#: preferences/templates/preferences/display_preferences.html:429 msgid "Add a reminder" msgstr "Ajouter un rappel" -#: preferences/templates/preferences/display_preferences.html:434 +#: preferences/templates/preferences/display_preferences.html:440 msgid "List of services and homepage preferences" msgstr "Liste des services et préférences de page d'accueil" -#: preferences/templates/preferences/display_preferences.html:440 +#: preferences/templates/preferences/display_preferences.html:446 msgid "Add a service" msgstr "Ajouter un service" -#: preferences/templates/preferences/display_preferences.html:451 +#: preferences/templates/preferences/display_preferences.html:457 msgid "List of contact email addresses" msgstr "Liste des adresses mail de contact" -#: preferences/templates/preferences/display_preferences.html:457 +#: preferences/templates/preferences/display_preferences.html:463 msgid "Add an address" msgstr "Ajouter une adresse" -#: preferences/templates/preferences/display_preferences.html:459 +#: preferences/templates/preferences/display_preferences.html:465 msgid "Delete one or several addresses" msgstr "Supprimer une ou plusieurs adresses" -#: preferences/templates/preferences/display_preferences.html:468 +#: preferences/templates/preferences/display_preferences.html:474 msgid "Social networks" msgstr "Réseaux sociaux" -#: preferences/templates/preferences/display_preferences.html:480 +#: preferences/templates/preferences/display_preferences.html:486 msgid "Twitter account URL" msgstr "URL du compte Twitter" -#: preferences/templates/preferences/display_preferences.html:486 +#: preferences/templates/preferences/display_preferences.html:492 msgid "Facebook account URL" msgstr "URL du compte Facebook" diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index 6f5dbd0b..05954f8f 100644 --- a/re2o/locale/fr/LC_MESSAGES/django.po +++ b/re2o/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index e03da256..45226988 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-19 23:43+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-24 20:10+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -82,94 +82,81 @@ msgstr "Ports" msgid "Switches" msgstr "Commutateurs réseau" -#: search/forms.py:62 search/forms.py:77 search/templates/search/search.html:29 +#: search/forms.py:62 search/forms.py:78 search/templates/search/search.html:29 #: search/templates/search/search.html:48 msgid "Search" msgstr "Rechercher" -#: search/forms.py:65 search/forms.py:80 +#: search/forms.py:65 search/forms.py:81 msgid "" -"Use « » and «,» to specify distinct words, «\"query\"» for" -" an exact search, «\\» to escape a character and «+» to" -" combine keywords." +"Use « » and «,» to specify distinct words, «\"query\"» for an exact search, " +"«\\» to escape a character and «+» to combine keywords." msgstr "" -"Utilisez « » et «,» pour spécifier différents mots, «\"recherche\"» pour" -" une recherche exacte, «\\» pour échapper un caractère et «+» pour" -" combiner des mots clés." +"Utilisez « » et «,» pour spécifier différents mots, «\"recherche\"» pour une " +"recherche exacte, «\\» pour échapper un caractère et «+» pour combiner des " +"mots clés." -#: search/forms.py:88 +#: search/forms.py:90 msgid "Users filter" msgstr "Filtre utilisateurs" -#: search/forms.py:95 +#: search/forms.py:97 msgid "Display filter" msgstr "Filtre affichage" -#: search/forms.py:101 +#: search/forms.py:103 msgid "Start date" msgstr "Date de début" -#: search/forms.py:102 +#: search/forms.py:104 msgid "End date" msgstr "Date de fin" -#: search/models.py:195 -msgid "Verified" -msgstr "Confirmé" - -#: search/models.py:196 -msgid "Unverified" -msgstr "Non-confirmé" - -#: search/models.py:197 -msgid "Waiting for email confirmation" -msgstr "En attente de confirmation du mail" - #: search/templates/search/index.html:29 msgid "Search results" msgstr "Résultats de la recherche" -#: search/templates/search/index.html:33 +#: search/templates/search/index.html:34 msgid "Results among users:" msgstr "Résultats parmi les utilisateurs :" -#: search/templates/search/index.html:37 +#: search/templates/search/index.html:44 msgid "Results among clubs:" msgstr "Résultats parmi les clubs :" -#: search/templates/search/index.html:41 +#: search/templates/search/index.html:54 msgid "Results among machines:" msgstr "Résultats parmi les machines :" -#: search/templates/search/index.html:45 +#: search/templates/search/index.html:64 msgid "Results among invoices:" msgstr "Résultats parmi les factures :" -#: search/templates/search/index.html:49 +#: search/templates/search/index.html:74 msgid "Results among whitelists:" msgstr "Résultats parmi les accès à titre gracieux :" -#: search/templates/search/index.html:53 +#: search/templates/search/index.html:84 msgid "Results among bans:" msgstr "Résultats parmi les bannissements :" -#: search/templates/search/index.html:57 +#: search/templates/search/index.html:94 msgid "Results among rooms:" msgstr "Résultats parmi les chambres :" -#: search/templates/search/index.html:61 +#: search/templates/search/index.html:104 msgid "Results among ports:" msgstr "Résultats parmi les ports :" -#: search/templates/search/index.html:65 +#: search/templates/search/index.html:114 msgid "Results among switches:" msgstr "Résultats parmi les commutateurs réseau :" -#: search/templates/search/index.html:69 +#: search/templates/search/index.html:123 msgid "No result" msgstr "Pas de résultat" -#: search/templates/search/index.html:71 +#: search/templates/search/index.html:125 #, python-format msgid "Only the first %(max_result)s results are displayed in each category." msgstr "" @@ -183,3 +170,12 @@ msgstr "Recherche simple" #: search/templates/search/sidebar.html:35 msgid "Advanced search" msgstr "Recherche avancée" + +#~ msgid "Verified" +#~ msgstr "Confirmé" + +#~ msgid "Unverified" +#~ msgstr "Non-confirmé" + +#~ msgid "Waiting for email confirmation" +#~ msgstr "En attente de confirmation du mail" diff --git a/templates/locale/fr/LC_MESSAGES/django.po b/templates/locale/fr/LC_MESSAGES/django.po index 80131b1d..6aa9bf63 100644 --- a/templates/locale/fr/LC_MESSAGES/django.po +++ b/templates/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-19 23:43+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -339,19 +339,19 @@ msgstr "Si vous n'avez aucune idée de ce que vous avez fait :" msgid "Go back to a safe page" msgstr "Retourner à une page sécurisée" -#: templates/pagination.html:34 +#: templates/pagination.html:35 msgid "First" msgstr "Première page" -#: templates/pagination.html:40 +#: templates/pagination.html:41 msgid "Previous" msgstr "Précédent" -#: templates/pagination.html:60 +#: templates/pagination.html:61 msgid "Next" msgstr "Suivant" -#: templates/pagination.html:66 +#: templates/pagination.html:67 msgid "Last" msgstr "Dernière page" diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 1ccfe87f..b562c90c 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/topologie/locale/fr/LC_MESSAGES/django.po b/topologie/locale/fr/LC_MESSAGES/django.po index ef0afb98..4f4d8217 100644 --- a/topologie/locale/fr/LC_MESSAGES/django.po +++ b/topologie/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-20 01:24+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-25 14:53+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index e2a8f3ce..6d22504c 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-11-19 23:43+0100\n" +"POT-Creation-Date: 2020-04-18 00:48+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -35,151 +35,150 @@ msgstr "" msgid "You don't have the right to view this application." msgstr "Vous n'avez pas le droit de voir cette application." -#: users/forms.py:78 +#: users/forms.py:80 msgid "Current password" msgstr "Mot de passe actuel" -#: users/forms.py:81 users/forms.py:528 users/forms.py:556 +#: users/forms.py:83 users/forms.py:613 users/forms.py:641 msgid "New password" msgstr "Nouveau mot de passe" -#: users/forms.py:87 +#: users/forms.py:89 msgid "New password confirmation" msgstr "Confirmation du nouveau mot de passe" -#: users/forms.py:103 +#: users/forms.py:105 msgid "The new passwords don't match." msgstr "Les nouveaux mots de passe ne correspondent pas." -#: users/forms.py:109 +#: users/forms.py:111 msgid "The current password is incorrect." msgstr "Le mot de passe actuel est incorrect." -#: users/forms.py:129 users/forms.py:186 users/models.py:1760 +#: users/forms.py:132 users/forms.py:189 users/forms.py:410 +#: users/models.py:1893 msgid "Password" msgstr "Mot de passe" -#: users/forms.py:135 users/forms.py:189 +#: users/forms.py:138 users/forms.py:192 users/forms.py:417 msgid "Password confirmation" msgstr "Confirmation du mot de passe" -#: users/forms.py:140 users/forms.py:229 +#: users/forms.py:143 users/forms.py:232 msgid "Is admin" msgstr "Est admin" -#: users/forms.py:153 +#: users/forms.py:156 msgid "You can't use an internal address as your external address." msgstr "" "Vous ne pouvez pas utiliser une adresse interne pour votre adresse externe." -#: users/forms.py:166 users/forms.py:209 +#: users/forms.py:169 users/forms.py:212 users/forms.py:491 msgid "The passwords don't match." msgstr "Les mots de passe ne correspondent pas." -#: users/forms.py:238 +#: users/forms.py:241 #, python-format msgid "User is admin: %s" msgstr "L'utilisateur est admin : %s" -#: users/forms.py:284 users/templates/users/aff_clubs.html:38 +#: users/forms.py:287 users/templates/users/aff_clubs.html:38 #: users/templates/users/aff_listright.html:63 #: users/templates/users/aff_listright.html:168 #: users/templates/users/aff_users.html:39 -#: users/templates/users/profil.html:178 users/templates/users/profil.html:342 -#: users/templates/users/profil.html:361 +#: users/templates/users/profil.html:204 users/templates/users/profil.html:368 +#: users/templates/users/profil.html:387 msgid "Username" msgstr "Pseudo" -#: users/forms.py:296 +#: users/forms.py:299 msgid "Fully archive users? WARNING: CRITICAL OPERATION IF TRUE" msgstr "" "Complètement archiver les utilisateurs ? ATTENTION: OPÉRATION CRITIQUE SI OUI" -#: users/forms.py:309 +#: users/forms.py:312 msgid "Impossible to archive users whose end access date is in the future." msgstr "" "Impossible d'archiver des utilisateurs dont la date de fin de connexion est " "dans le futur." -#: users/forms.py:324 users/templates/users/profil.html:167 -#: users/templates/users/profil.html:341 users/templates/users/profil.html:360 +#: users/forms.py:327 users/templates/users/aff_users.html:35 +#: users/templates/users/profil.html:193 users/templates/users/profil.html:367 +#: users/templates/users/profil.html:386 msgid "First name" msgstr "Prénom" -#: users/forms.py:325 users/templates/users/aff_users.html:37 -#: users/templates/users/profil.html:173 users/templates/users/profil.html:340 -#: users/templates/users/profil.html:359 +#: users/forms.py:328 users/templates/users/aff_users.html:37 +#: users/templates/users/profil.html:199 users/templates/users/profil.html:366 +#: users/templates/users/profil.html:385 msgid "Surname" msgstr "Nom" -#: users/forms.py:326 users/forms.py:466 users/models.py:1760 +#: users/forms.py:329 users/forms.py:551 users/models.py:1893 #: users/templates/users/aff_emailaddress.html:36 -#: users/templates/users/profil.html:183 +#: users/templates/users/profil.html:209 msgid "Email address" msgstr "Adresse mail" -#: users/forms.py:327 users/forms.py:464 users/forms.py:614 +#: users/forms.py:330 users/forms.py:549 users/forms.py:694 #: users/templates/users/aff_schools.html:37 -#: users/templates/users/profil.html:204 +#: users/templates/users/profil.html:230 msgid "School" msgstr "Établissement" -#: users/forms.py:328 users/forms.py:465 +#: users/forms.py:331 users/forms.py:550 #: users/templates/users/aff_serviceusers.html:34 -#: users/templates/users/profil.html:209 +#: users/templates/users/profil.html:235 msgid "Comment" msgstr "Commentaire" -#: users/forms.py:330 users/forms.py:468 +#: users/forms.py:333 users/forms.py:553 #: users/templates/users/aff_clubs.html:40 #: users/templates/users/aff_users.html:41 -#: users/templates/users/profil.html:188 +#: users/templates/users/profil.html:214 msgid "Room" msgstr "Chambre" -#: users/forms.py:331 users/forms.py:469 +#: users/forms.py:334 users/forms.py:554 msgid "No room" msgstr "Pas de chambre" -#: users/forms.py:332 users/forms.py:470 +#: users/forms.py:335 users/forms.py:555 msgid "Select a school" msgstr "Sélectionnez un établissement" -#: users/forms.py:348 +#: users/forms.py:351 msgid "Force the move?" msgstr "Forcer le déménagement ?" -#: users/forms.py:358 users/forms.py:765 +#: users/forms.py:361 users/forms.py:845 msgid "You can't use a {} address." msgstr "Vous ne pouvez pas utiliser une adresse {}." -#: users/forms.py:368 users/forms.py:493 +#: users/forms.py:371 users/forms.py:578 msgid "A valid telephone number is required." msgstr "Un numéro de téléphone valide est requis." -#: users/forms.py:404 +#: users/forms.py:390 msgid "" -"If this options is set, you will receive a link to set" -" your initial password by email. If you do not have" -" any means of accessing your emails, you can disable" -" this option to set your password immediatly." -" You will still receive an email to confirm your address." -" Failure to confirm your address will result in an" -" automatic suspension of your account until you do." +"If this options is set, you will receive a link to set your initial password " +"by email. If you do not have any means of accessing your emails, you can " +"disable this option to set your password immediatly. You will still receive " +"an email to confirm your address. Failure to confirm your address will " +"result in an automatic suspension of your account until you do." msgstr "" -"Si cette option est activée, vous recevrez un lien pour choisir" -" votre mot de passe par email. Si vous n'avez pas accès" -" à vos mails, vous pouvez désactiver cette option" -" pour immédiatement entrer votre mot de passe." -" Vous recevrez tous de même un email de confirmation." -" Si vous ne confirmez pas votre adresse dans les délais" -" impartis, votre connexion sera automatiquement suspendue." +"Si cette option est activée, vous recevrez un lien pour choisir votre mot de " +"passe par email. Si vous n'avez pas accès à vos mails, vous pouvez " +"désactiver cette option pour immédiatement entrer votre mot de passe. Vous " +"recevrez tous de même un email de confirmation. Si vous ne confirmez pas " +"votre adresse dans les délais impartis, votre connexion sera automatiquement " +"suspendue." -#: users/forms.py:442 +#: users/forms.py:404 msgid "Send password reset link by email." msgstr "Envoyer le lien de modification du mot de passe par mail." -#: users/forms.py:435 +#: users/forms.py:425 msgid "" "If you already have an account, please use it. If your lost access to it, " "please consider using the forgotten password button on the login page or " @@ -190,239 +189,255 @@ msgstr "" "passe oublié est à votre disposition. Si vous avez oublié votre login, " "contactez le support." -#: users/forms.py:394 +#: users/forms.py:432 msgid "I certify that I have not had an account before." msgstr "Je certifie sur l'honneur ne pas déjà avoir de compte." -#: users/forms.py:419 +#: users/forms.py:457 msgid "I commit to accept the" msgstr "J'accepte les" -#: users/forms.py:421 +#: users/forms.py:459 msgid "General Terms of Use" msgstr "Conditions Générales d'Utilisation" -#: users/forms.py:433 -msgid "Leave empty if you don't have any GPG key." -msgstr "Laissez vide si vous n'avez pas de clé GPG." - -#: users/forms.py:436 -msgid "Default shell" -msgstr "Interface en ligne de commande par défaut" - -#: users/forms.py:166 users/forms.py:481 +#: users/forms.py:477 msgid "Password must contain at least 8 characters." msgstr "Le mot de passe doit contenir au moins 8 caractères." -#: users/forms.py:463 users/templates/users/aff_clubs.html:36 +#: users/forms.py:518 +msgid "Leave empty if you don't have any GPG key." +msgstr "Laissez vide si vous n'avez pas de clé GPG." + +#: users/forms.py:521 +msgid "Default shell" +msgstr "Interface en ligne de commande par défaut" + +#: users/forms.py:548 users/templates/users/aff_clubs.html:36 #: users/templates/users/aff_serviceusers.html:32 msgid "Name" msgstr "Nom" -#: users/forms.py:471 +#: users/forms.py:556 msgid "Use a mailing list" msgstr "Utiliser une liste de diffusion" +#: users/forms.py:662 users/templates/users/profil.html:277 +msgid "State" +msgstr "État" + #: users/forms.py:663 msgid "Email state" msgstr "État du mail" -#: users/forms.py:601 users/templates/users/aff_listright.html:38 +#: users/forms.py:681 users/templates/users/aff_listright.html:38 msgid "Superuser" msgstr "Superutilisateur" -#: users/forms.py:627 +#: users/forms.py:707 msgid "Shell name" msgstr "Nom de l'interface en ligne de commande" -#: users/forms.py:647 +#: users/forms.py:727 msgid "Name of the group of rights" msgstr "Nom du groupe de droits" -#: users/forms.py:659 +#: users/forms.py:739 msgid "GID. Warning: this field must not be edited after creation." msgstr "GID. Attention : ce champ ne doit pas être modifié après création." -#: users/forms.py:668 +#: users/forms.py:748 msgid "Current groups of rights" msgstr "Groupes de droits actuels" -#: users/forms.py:686 +#: users/forms.py:766 msgid "Current schools" msgstr "Établissements actuels" -#: users/forms.py:705 users/forms.py:720 users/templates/users/aff_bans.html:41 +#: users/forms.py:785 users/forms.py:800 users/templates/users/aff_bans.html:41 #: users/templates/users/aff_whitelists.html:41 msgid "End date" msgstr "Date de fin" -#: users/forms.py:735 +#: users/forms.py:815 msgid "Local part of the email address" msgstr "Partie locale de l'adresse mail" -#: users/forms.py:736 +#: users/forms.py:816 msgid "Can't contain @." msgstr "Ne peut pas contenir @." -#: users/forms.py:752 +#: users/forms.py:832 msgid "Main email address" msgstr "Adresse mail principale" -#: users/forms.py:754 +#: users/forms.py:834 msgid "Redirect local emails" msgstr "Rediriger les mails locaux" -#: users/forms.py:756 +#: users/forms.py:836 msgid "Use local emails" msgstr "Utiliser les mails locaux" -#: users/forms.py:808 +#: users/forms.py:888 msgid "This room is my room" msgstr "Il s'agit bien de ma chambre" -#: users/forms.py:813 +#: users/forms.py:893 msgid "This new connected device is mine" msgstr "Ce nouvel appareil connecté m'appartient" -#: users/models.py:105 +#: users/models.py:108 #, python-format msgid "The username \"%(label)s\" contains forbidden characters." msgstr "Le pseudo « %(label)s » contient des caractères interdits." -#: users/models.py:134 +#: users/models.py:137 msgid "Users must have an username." msgstr "Les utilisateurs doivent avoir un pseudo." -#: users/models.py:137 +#: users/models.py:140 msgid "Username should only contain [a-z0-9-]." msgstr "Le pseudo devrait seulement contenir [a-z0-9-]" -#: users/models.py:180 users/templates/users/aff_clubs.html:55 +#: users/models.py:184 users/templates/users/aff_clubs.html:55 #: users/templates/users/aff_users.html:57 -#: users/templates/users/profil.html:253 +#: users/templates/users/profil.html:279 msgid "Active" msgstr "Actif" -#: users/models.py:181 users/templates/users/aff_clubs.html:57 +#: users/models.py:185 users/templates/users/aff_clubs.html:57 #: users/templates/users/aff_users.html:59 -#: users/templates/users/profil.html:255 users/templates/users/profil.html:271 +#: users/templates/users/profil.html:281 users/templates/users/profil.html:297 msgid "Disabled" msgstr "Désactivé" -#: users/models.py:182 users/templates/users/profil.html:257 +#: users/models.py:186 users/templates/users/profil.html:283 msgid "Archived" msgstr "Archivé" -#: users/models.py:183 users/templates/users/profil.html:259 +#: users/models.py:187 users/templates/users/profil.html:285 msgid "Not yet active" msgstr "Pas encore adhéré" -#: users/models.py:184 users/templates/users/profil.html:261 +#: users/models.py:188 users/templates/users/profil.html:287 msgid "Fully archived" msgstr "Complètement archivé" -#: users/models.py:191 users/models.py:1416 +#: users/models.py:195 +msgid "Confirmed" +msgstr "Confirmé" + +#: users/models.py:196 +msgid "Not confirmed" +msgstr "Non confirmé" + +#: users/models.py:197 +msgid "Waiting for email confirmation" +msgstr "En attente de confirmation" + +#: users/models.py:204 users/models.py:1549 msgid "Must only contain letters, numerals or dashes." msgstr "Doit seulement contenir des lettres, chiffres ou tirets." -#: users/models.py:197 +#: users/models.py:210 msgid "External email address allowing us to contact you." msgstr "Adresse mail externe nous permettant de vous contacter." -#: users/models.py:202 +#: users/models.py:215 msgid "" "Enable redirection of the local email messages to the main email address." msgstr "" "Activer la redirection des mails locaux vers l'adresse mail principale." -#: users/models.py:207 +#: users/models.py:220 msgid "Enable the local email account." msgstr "Activer le compte mail local" -#: users/models.py:216 +#: users/models.py:229 msgid "Comment, school year." msgstr "Commentaire, promotion." -#: users/models.py:225 +#: users/models.py:239 msgid "enable shortcuts on Re2o website" msgstr "activer les raccourcis sur le site de Re2o" -#: users/models.py:235 +#: users/models.py:250 msgid "Can change the password of a user" msgstr "Peut changer le mot de passe d'un utilisateur" -#: users/models.py:236 +#: users/models.py:251 msgid "Can edit the state of a user" msgstr "Peut changer l'état d'un utilisateur" -#: users/models.py:237 +#: users/models.py:252 msgid "Can force the move" msgstr "Peut forcer le déménagement" -#: users/models.py:238 +#: users/models.py:253 msgid "Can edit the shell of a user" msgstr "Peut modifier l'interface en ligne de commande d'un utilisateur" -#: users/models.py:241 +#: users/models.py:256 msgid "Can edit the groups of rights of a user (critical permission)" msgstr "" "Peut modifier les groupes de droits d'un utilisateur (permission critique)" -#: users/models.py:243 +#: users/models.py:258 msgid "Can edit all users, including those with rights" msgstr "" "Peut modifier tous les utilisateurs, y compris ceux possédant des droits" -#: users/models.py:244 +#: users/models.py:259 msgid "Can view a user object" msgstr "Peut voir un objet utilisateur" -#: users/models.py:246 +#: users/models.py:261 msgid "user (member or club)" msgstr "utilisateur (adhérent ou club)" -#: users/models.py:247 +#: users/models.py:262 msgid "users (members or clubs)" msgstr "utilisateurs (adhérents ou clubs)" -#: users/models.py:265 users/models.py:293 +#: users/models.py:280 users/models.py:308 users/models.py:318 msgid "Unknown type." msgstr "Type inconnu." -#: users/models.py:289 users/templates/users/aff_listright.html:75 +#: users/models.py:314 users/templates/users/aff_listright.html:75 #: users/templates/users/aff_listright.html:180 msgid "Member" msgstr "Adhérent" -#: users/models.py:291 +#: users/models.py:316 msgid "Club" msgstr "Club" -#: users/models.py:781 +#: users/models.py:891 msgid "Maximum number of registered machines reached." msgstr "Nombre maximum de machines enregistrées atteint." -#: users/models.py:783 +#: users/models.py:893 msgid "Re2o doesn't know wich machine type to assign." msgstr "Re2o ne sait pas quel type de machine attribuer." -#: users/models.py:806 users/templates/users/user_autocapture.html:64 +#: users/models.py:916 users/templates/users/user_autocapture.html:64 msgid "OK" msgstr "OK" -#: users/models.py:878 +#: users/models.py:1010 msgid "This user is archived." msgstr "Cet utilisateur est archivé." -#: users/models.py:892 users/models.py:946 +#: users/models.py:1024 users/models.py:1078 msgid "You don't have the right to edit this club." msgstr "Vous n'avez pas le droit de modifier ce club." -#: users/models.py:904 +#: users/models.py:1036 msgid "User with critical rights, can't be edited." msgstr "Utilisateur avec des droits critiques, ne peut être modifié." -#: users/models.py:911 +#: users/models.py:1043 msgid "" "Impossible to edit the organisation's user without the \"change_all_users\" " "right." @@ -430,61 +445,61 @@ msgstr "" "Impossible de modifier l'utilisateur de l'association sans le droit « " "change_all_users »." -#: users/models.py:923 users/models.py:961 +#: users/models.py:1055 users/models.py:1093 msgid "You don't have the right to edit another user." msgstr "Vous n'avez pas le droit de modifier un autre utilisateur." -#: users/models.py:987 +#: users/models.py:1119 msgid "You don't have the right to change the room." msgstr "Vous n'avez pas le droit de changer la chambre." -#: users/models.py:1004 +#: users/models.py:1136 msgid "You don't have the right to change the state." msgstr "Vous n'avez pas le droit de changer l'état." -#: users/models.py:1024 +#: users/models.py:1156 msgid "You don't have the right to change the shell." msgstr "Vous n'avez pas le droit de changer l'interface en ligne de commande." -#: users/models.py:1041 users/models.py:1056 +#: users/models.py:1173 users/models.py:1188 msgid "Local email accounts must be enabled." msgstr "Les comptes mail locaux doivent être activés." -#: users/models.py:1071 +#: users/models.py:1203 msgid "You don't have the right to force the move." msgstr "Vous n'avez pas le droit de forcer le déménagement." -#: users/models.py:1086 +#: users/models.py:1218 msgid "You don't have the right to edit the user's groups of rights." msgstr "" "Vous n'avez pas le droit de modifier les groupes de droits d'un autre " "utilisateur." -#: users/models.py:1102 +#: users/models.py:1234 msgid "\"superuser\" right required to edit the superuser flag." msgstr "Droit « superuser » requis pour modifier le signalement superuser." -#: users/models.py:1127 +#: users/models.py:1259 msgid "You don't have the right to view this club." msgstr "Vous n'avez pas le droit de voir ce club." -#: users/models.py:1136 +#: users/models.py:1268 msgid "You don't have the right to view another user." msgstr "Vous n'avez pas le droit de voir un autre utilisateur." -#: users/models.py:1151 users/models.py:1354 +#: users/models.py:1283 users/models.py:1487 msgid "You don't have the right to view the list of users." msgstr "Vous n'avez pas le droit de voir la liste des utilisateurs." -#: users/models.py:1168 +#: users/models.py:1300 msgid "You don't have the right to delete this user." msgstr "Vous n'avez pas le droit de supprimer cet utilisateur." -#: users/models.py:1190 +#: users/models.py:1323 msgid "This username is already used." msgstr "Ce pseudo est déjà utilisé." -#: users/models.py:1198 +#: users/models.py:1331 msgid "" "There is neither a local email address nor an external email address for " "this user." @@ -492,7 +507,7 @@ msgstr "" "Il n'y a pas d'adresse mail locale ni d'adresse mail externe pour cet " "utilisateur." -#: users/models.py:1205 +#: users/models.py:1338 msgid "" "You can't redirect your local emails if no external email address has been " "set." @@ -500,195 +515,195 @@ msgstr "" "Vous ne pouvez pas rediriger vos mails locaux si aucune adresse mail externe " "n'a été définie." -#: users/models.py:1225 +#: users/models.py:1358 msgid "member" msgstr "adhérent" -#: users/models.py:1226 +#: users/models.py:1359 msgid "members" msgstr "adhérents" -#: users/models.py:1243 +#: users/models.py:1376 msgid "A GPG fingerprint must contain 40 hexadecimal characters." msgstr "Une empreinte GPG doit contenir 40 caractères hexadécimaux." -#: users/models.py:1267 +#: users/models.py:1400 msgid "Self registration is disabled." msgstr "L'auto inscription est désactivée." -#: users/models.py:1277 +#: users/models.py:1410 msgid "You don't have the right to create a user." msgstr "Vous n'avez pas le droit de créer un utilisateur." -#: users/models.py:1307 +#: users/models.py:1440 msgid "club" msgstr "club" -#: users/models.py:1308 +#: users/models.py:1441 msgid "clubs" msgstr "clubs" -#: users/models.py:1319 +#: users/models.py:1452 msgid "You must be authenticated." msgstr "Vous devez être authentifié." -#: users/models.py:1327 +#: users/models.py:1460 msgid "You don't have the right to create a club." msgstr "Vous n'avez pas le droit de créer un club." -#: users/models.py:1420 +#: users/models.py:1553 msgid "Comment." msgstr "Commentaire." -#: users/models.py:1426 +#: users/models.py:1559 msgid "Can view a service user object" msgstr "Peut voir un objet utilisateur service" -#: users/models.py:1427 users/views.py:332 +#: users/models.py:1560 users/views.py:356 msgid "service user" msgstr "utilisateur service" -#: users/models.py:1428 +#: users/models.py:1561 msgid "service users" msgstr "utilisateurs service" -#: users/models.py:1432 +#: users/models.py:1565 #, python-brace-format msgid "Service user <{name}>" msgstr "Utilisateur service <{name}>" -#: users/models.py:1499 +#: users/models.py:1632 msgid "Can view a school object" msgstr "Peut voir un objet établissement" -#: users/models.py:1500 +#: users/models.py:1633 msgid "school" msgstr "établissement" -#: users/models.py:1501 +#: users/models.py:1634 msgid "schools" msgstr "établissements" -#: users/models.py:1520 +#: users/models.py:1653 msgid "UNIX group names can only contain lower case letters." msgstr "" "Les noms de groupe UNIX peuvent seulement contenir des lettres minuscules." -#: users/models.py:1526 +#: users/models.py:1659 msgid "Description." msgstr "Description." -#: users/models.py:1529 +#: users/models.py:1662 msgid "Can view a group of rights object" msgstr "Peut voir un objet groupe de droits" -#: users/models.py:1530 +#: users/models.py:1663 msgid "group of rights" msgstr "groupe de droits" -#: users/models.py:1531 +#: users/models.py:1664 msgid "groups of rights" msgstr "groupes de droits" -#: users/models.py:1576 +#: users/models.py:1709 msgid "Can view a shell object" msgstr "Peut voir un objet interface en ligne de commande" -#: users/models.py:1577 users/views.py:643 +#: users/models.py:1710 users/views.py:671 msgid "shell" msgstr "interface en ligne de commande" -#: users/models.py:1578 +#: users/models.py:1711 msgid "shells" msgstr "interfaces en ligne de commande" -#: users/models.py:1596 +#: users/models.py:1729 msgid "HARD (no access)" msgstr "HARD (pas d'accès)" -#: users/models.py:1597 +#: users/models.py:1730 msgid "SOFT (local access only)" msgstr "SOFT (accès local uniquement)" -#: users/models.py:1598 +#: users/models.py:1731 msgid "RESTRICTED (speed limitation)" msgstr "RESTRICTED (limitation de vitesse)" -#: users/models.py:1608 +#: users/models.py:1741 msgid "Can view a ban object" msgstr "Peut voir un objet bannissement" -#: users/models.py:1609 users/views.py:383 +#: users/models.py:1742 users/views.py:407 msgid "ban" msgstr "bannissement" -#: users/models.py:1610 +#: users/models.py:1743 msgid "bans" msgstr "bannissements" -#: users/models.py:1645 +#: users/models.py:1778 msgid "You don't have the right to view other bans than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres bannissements que les vôtres." -#: users/models.py:1693 +#: users/models.py:1826 msgid "Can view a whitelist object" msgstr "Peut voir un objet accès gracieux" -#: users/models.py:1694 +#: users/models.py:1827 msgid "whitelist (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1695 +#: users/models.py:1828 msgid "whitelists (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1715 +#: users/models.py:1848 msgid "You don't have the right to view other whitelists than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres accès gracieux que les vôtres." -#: users/models.py:1912 +#: users/models.py:2046 msgid "User of the local email account." msgstr "Utilisateur du compte mail local." -#: users/models.py:1915 +#: users/models.py:2049 msgid "Local part of the email address." msgstr "Partie locale de l'adresse mail." -#: users/models.py:1920 +#: users/models.py:2054 msgid "Can view a local email account object" msgstr "Peut voir un objet compte mail local" -#: users/models.py:1922 +#: users/models.py:2056 msgid "local email account" msgstr "compte mail local" -#: users/models.py:1923 +#: users/models.py:2057 msgid "local email accounts" msgstr "comptes mail locaux" -#: users/models.py:1951 users/models.py:1986 users/models.py:2020 -#: users/models.py:2054 +#: users/models.py:2085 users/models.py:2120 users/models.py:2154 +#: users/models.py:2188 msgid "The local email accounts are not enabled." msgstr "Les comptes mail locaux ne sont pas activés." -#: users/models.py:1956 +#: users/models.py:2090 msgid "You don't have the right to add a local email account to another user." msgstr "" "Vous n'avez pas le droit d'ajouter un compte mail local à un autre " "utilisateur." -#: users/models.py:1966 +#: users/models.py:2100 msgid "You reached the limit of {} local email accounts." msgstr "Vous avez atteint la limite de {} comptes mail locaux." -#: users/models.py:1992 +#: users/models.py:2126 msgid "You don't have the right to view another user's local email account." msgstr "" "Vous n'avez pas le droit de voir le compte mail local d'un autre utilisateur." -#: users/models.py:2012 +#: users/models.py:2146 msgid "" "You can't delete a local email account whose local part is the same as the " "username." @@ -696,13 +711,13 @@ msgstr "" "Vous ne pouvez pas supprimer un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2026 +#: users/models.py:2160 msgid "You don't have the right to delete another user's local email account." msgstr "" "Vous n'avez pas le droit de supprimer le compte mail local d'un autre " "utilisateur." -#: users/models.py:2046 +#: users/models.py:2180 msgid "" "You can't edit a local email account whose local part is the same as the " "username." @@ -710,13 +725,13 @@ msgstr "" "Vous ne pouvez pas modifier un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2060 +#: users/models.py:2194 msgid "You don't have the right to edit another user's local email account." msgstr "" "Vous n'avez pas le droit de modifier le compte mail local d'un autre " "utilisateur." -#: users/models.py:2069 +#: users/models.py:2203 msgid "The local part must not contain @ or +." msgstr "La partie locale ne doit pas contenir @ ou +." @@ -742,18 +757,18 @@ msgstr "Fin de cotisation le" #: users/templates/users/aff_clubs.html:43 #: users/templates/users/aff_users.html:44 -#: users/templates/users/profil.html:266 +#: users/templates/users/profil.html:292 msgid "Internet access" msgstr "Accès Internet" #: users/templates/users/aff_clubs.html:44 -#: users/templates/users/aff_users.html:45 users/templates/users/profil.html:31 +#: users/templates/users/aff_users.html:45 users/templates/users/profil.html:33 msgid "Profile" msgstr "Profil" #: users/templates/users/aff_clubs.html:53 #: users/templates/users/aff_users.html:54 -#: users/templates/users/profil.html:228 +#: users/templates/users/profil.html:254 msgid "Not a member" msgstr "Non adhérent" @@ -851,10 +866,33 @@ msgid "Access group" msgstr "Group d'accès" #: users/templates/users/aff_shell.html:32 -#: users/templates/users/profil.html:307 +#: users/templates/users/profil.html:333 msgid "Shell" msgstr "Interface en ligne de commande" +#: users/templates/users/confirm_email.html:31 +#: users/templates/users/confirm_email.html:37 +#: users/templates/users/resend_confirmation_email.html:27 +msgid "Confirmation email" +msgstr "Email de confirmation" + +#: users/templates/users/confirm_email.html:38 +msgid "Confirm the email" +msgstr "Confirmer le mail" + +#: users/templates/users/confirm_email.html:38 +#, python-format +msgid "for %(name)s." +msgstr "pour %(name)s." + +#: users/templates/users/confirm_email.html:39 +#: users/templates/users/delete.html:36 +#: users/templates/users/mass_archive.html:36 +#: users/templates/users/resend_confirmation_email.html:35 users/views.py:628 +#: users/views.py:732 +msgid "Confirm" +msgstr "Confirmer" + #: users/templates/users/delete.html:29 msgid "Deletion of users" msgstr "Suppression d'utilisateurs" @@ -868,14 +906,8 @@ msgstr "" "Attention : voulez-vous vraiment supprimer cet objet %(objet_name)s " "( %(objet)s ) ?" -#: users/templates/users/delete.html:36 -#: users/templates/users/mass_archive.html:36 users/views.py:600 -#: users/views.py:704 -msgid "Confirm" -msgstr "Confirmer" - #: users/templates/users/index_ban.html:32 -#: users/templates/users/profil.html:438 users/templates/users/sidebar.html:58 +#: users/templates/users/profil.html:464 users/templates/users/sidebar.html:58 msgid "Bans" msgstr "Bannissements" @@ -946,7 +978,7 @@ msgid "Add a shell" msgstr "Ajouter une interface en ligne de commande" #: users/templates/users/index_whitelist.html:32 -#: users/templates/users/profil.html:463 users/templates/users/sidebar.html:64 +#: users/templates/users/profil.html:489 users/templates/users/sidebar.html:64 msgid "Whitelists" msgstr "Accès gracieux" @@ -971,275 +1003,254 @@ msgstr "" "débrancher et rebrancher votre câble Ethernet pour bénéficier d'une " "connexion filaire." -#: users/templates/users/confirm_email.html:35 -#, python-format -msgid "Confirmation email" -msgstr "Email de confirmation" - -#: users/templates/users/confirm_email.html:36 -#, python-format -msgid "Confirm the email" -msgstr "Confirmer le mail" - -#: users/templates/users/confirm_email.html:36 -#, python-format -msgid "for %(name)s." -msgstr "pour %(name)s." - -#: users/templates/users/profil.html:36 +#: users/templates/users/profil.html:38 #, python-format msgid "Welcome %(name)s %(surname)s" msgstr "Bienvenue %(name)s %(surname)s" -#: users/templates/users/profil.html:38 +#: users/templates/users/profil.html:40 #, python-format msgid "Profile of %(name)s %(surname)s" msgstr "Profil de %(name)s %(surname)s" -#: users/templates/users/profil.html:43 +#: users/templates/users/profil.html:47 #, python-format msgid "" -"Please confirm your email address before %(confirm_before_date)s," -" or your account will be suspended." +"Please confirm your email address before %(confirm_before_date)s, or your " +"account will be suspended." msgstr "" -"Veuillez confirmer votre adresse mail avant le %(confirm_before_date)s," -" sous peine de suspension de votre compte." +"Veuillez confirmer votre adresse mail avant le %(confirm_before_date)s, sous " +"peine de suspension de votre compte." -#: users/templates/users/profil.html:48 -#, python-format +#: users/templates/users/profil.html:50 msgid "Didn't receive the email?" msgstr "Vous n'avez pas reçu le mail ?" -#: users/templates/users/profil.html:53 -#, python-format +#: users/templates/users/profil.html:55 msgid "Your account has been suspended, please confirm your email address." msgstr "Votre compte a été suspendu, veuillez confirmer votre adresse mail." -#: users/templates/users/profil.html:46 +#: users/templates/users/profil.html:65 msgid "Your account has been banned." msgstr "Votre compte a été banni." -#: users/templates/users/profil.html:48 +#: users/templates/users/profil.html:67 #, python-format msgid "End of the ban: %(end_ban_date)s" msgstr "Fin du bannissement : %(end_ban_date)s" -#: users/templates/users/profil.html:53 +#: users/templates/users/profil.html:72 msgid "No connection" msgstr "Pas de connexion" -#: users/templates/users/profil.html:57 -msgid "Pay for a connection" -msgstr "Payer une connexion" - -#: users/templates/users/profil.html:81 +#: users/templates/users/profil.html:77 msgid "Resend the email" msgstr "Renvoyer le mail" -#: users/templates/users/profil.html:60 +#: users/templates/users/profil.html:82 +msgid "Pay for a connection" +msgstr "Payer une connexion" + +#: users/templates/users/profil.html:85 msgid "Ask someone with the appropriate rights to pay for a connection." msgstr "" "Demandez à quelqu'un ayant les droits appropriés de payer une connexion." -#: users/templates/users/profil.html:66 +#: users/templates/users/profil.html:92 #, python-format msgid "Connection (until %(end_connection_date)s )" msgstr "Connexion (jusqu'au %(end_connection_date)s)" -#: users/templates/users/profil.html:70 +#: users/templates/users/profil.html:96 msgid "Extend the connection period" msgstr "Étendre la durée de connexion" -#: users/templates/users/profil.html:86 +#: users/templates/users/profil.html:112 msgid "Refill the balance" msgstr "Recharger le solde" -#: users/templates/users/profil.html:97 users/templates/users/profil.html:382 +#: users/templates/users/profil.html:123 users/templates/users/profil.html:408 msgid "Machines" msgstr "Machines" -#: users/templates/users/profil.html:101 users/templates/users/profil.html:113 -#: users/templates/users/profil.html:390 +#: users/templates/users/profil.html:127 users/templates/users/profil.html:139 +#: users/templates/users/profil.html:416 msgid "Add a machine" msgstr "Ajouter une machine" -#: users/templates/users/profil.html:109 users/templates/users/profil.html:397 +#: users/templates/users/profil.html:135 users/templates/users/profil.html:423 msgid "No machine" msgstr "Pas de machine" -#: users/templates/users/profil.html:128 +#: users/templates/users/profil.html:154 msgid "Detailed information" msgstr "Informations détaillées" -#: users/templates/users/profil.html:135 users/views.py:184 users/views.py:211 -#: users/views.py:228 users/views.py:245 users/views.py:317 users/views.py:371 -#: users/views.py:425 users/views.py:490 users/views.py:533 users/views.py:569 -#: users/views.py:629 users/views.py:675 +#: users/templates/users/profil.html:161 users/views.py:202 users/views.py:233 +#: users/views.py:252 users/views.py:269 users/views.py:341 users/views.py:395 +#: users/views.py:449 users/views.py:514 users/views.py:561 users/views.py:597 +#: users/views.py:657 users/views.py:703 msgid "Edit" msgstr "Modifier" -#: users/templates/users/profil.html:139 users/views.py:264 users/views.py:1001 +#: users/templates/users/profil.html:165 users/views.py:288 users/views.py:1034 msgid "Change the password" msgstr "Changer le mot de passe" -#: users/templates/users/profil.html:144 +#: users/templates/users/profil.html:170 msgid "Change the state" msgstr "Changer l'état" -#: users/templates/users/profil.html:150 +#: users/templates/users/profil.html:176 msgid "Edit the groups" msgstr "Modifier les groupes" -#: users/templates/users/profil.html:160 +#: users/templates/users/profil.html:186 msgid "Mailing" msgstr "Envoi de mails" -#: users/templates/users/profil.html:164 +#: users/templates/users/profil.html:190 msgid "Mailing disabled" msgstr "Envoi de mails désactivé" -#: users/templates/users/profil.html:192 -msgid "Connected" -msgstr "Connecté" - -#: users/templates/users/profil.html:201 +#: users/templates/users/profil.html:210 msgid "Pending confirmation..." msgstr "En attente de confirmation..." -#: users/templates/users/profil.html:193 +#: users/templates/users/profil.html:218 +msgid "Connected" +msgstr "Connecté" + +#: users/templates/users/profil.html:219 msgid "Pending connection..." msgstr "Connexion en attente..." -#: users/templates/users/profil.html:199 +#: users/templates/users/profil.html:225 msgid "Telephone number" msgstr "Numéro de téléphone" -#: users/templates/users/profil.html:214 +#: users/templates/users/profil.html:240 msgid "Registration date" msgstr "Date d'inscription" -#: users/templates/users/profil.html:219 +#: users/templates/users/profil.html:245 msgid "Last login" msgstr "Dernière connexion" -#: users/templates/users/profil.html:224 +#: users/templates/users/profil.html:250 msgid "End of membership" msgstr "Fin d'adhésion" -#: users/templates/users/profil.html:233 +#: users/templates/users/profil.html:259 msgid "Whitelist" msgstr "Accès gracieux" -#: users/templates/users/profil.html:237 users/templates/users/profil.html:280 +#: users/templates/users/profil.html:263 users/templates/users/profil.html:306 msgid "None" msgstr "Aucun" -#: users/templates/users/profil.html:242 +#: users/templates/users/profil.html:268 msgid "Ban" msgstr "Bannissement" -#: users/templates/users/profil.html:246 +#: users/templates/users/profil.html:272 msgid "Not banned" msgstr "Non banni" -#: users/templates/users/profil.html:251 users/forms.py:662 -msgid "State" -msgstr "État" - -#: users/templates/users/profil.html:269 +#: users/templates/users/profil.html:295 #, python-format msgid "Active (until %(end_access)s)" msgstr "Actif (jusqu'au %(end_access)s)" -#: users/templates/users/profil.html:276 users/templates/users/sidebar.html:82 +#: users/templates/users/profil.html:302 users/templates/users/sidebar.html:82 msgid "Groups of rights" msgstr "Groupes de droits" -#: users/templates/users/profil.html:285 +#: users/templates/users/profil.html:311 msgid "Balance" msgstr "Solde" -#: users/templates/users/profil.html:292 +#: users/templates/users/profil.html:318 msgid "Refill" msgstr "Recharger" -#: users/templates/users/profil.html:300 +#: users/templates/users/profil.html:326 msgid "GPG fingerprint" msgstr "Empreinte GPG" -#: users/templates/users/profil.html:312 +#: users/templates/users/profil.html:338 msgid "Shortcuts enabled" msgstr "Raccourcis activés" -#: users/templates/users/profil.html:323 +#: users/templates/users/profil.html:349 msgid "Manage the club" msgstr "Gérer le club" -#: users/templates/users/profil.html:331 +#: users/templates/users/profil.html:357 msgid "Manage the admins and members" msgstr "Gérer les admins et les membres" -#: users/templates/users/profil.html:335 +#: users/templates/users/profil.html:361 msgid "Club admins" msgstr "Admins du clubs" -#: users/templates/users/profil.html:354 +#: users/templates/users/profil.html:380 msgid "Members" msgstr "Adhérents" -#: users/templates/users/profil.html:407 +#: users/templates/users/profil.html:433 msgid "Subscriptions" msgstr "Cotisations" -#: users/templates/users/profil.html:415 +#: users/templates/users/profil.html:441 msgid "Add a subscription" msgstr "Ajouter une cotisation" -#: users/templates/users/profil.html:420 +#: users/templates/users/profil.html:446 msgid "Edit the balance" msgstr "Modifier le solde" -#: users/templates/users/profil.html:429 +#: users/templates/users/profil.html:455 msgid "No invoice" msgstr "Pas de facture" -#: users/templates/users/profil.html:446 +#: users/templates/users/profil.html:472 msgid "Add a ban" msgstr "Ajouter un bannissement" -#: users/templates/users/profil.html:454 +#: users/templates/users/profil.html:480 msgid "No ban" msgstr "Pas de bannissement" -#: users/templates/users/profil.html:471 +#: users/templates/users/profil.html:497 msgid "Grant a whitelist" msgstr "Donner un accès gracieux" -#: users/templates/users/profil.html:479 +#: users/templates/users/profil.html:505 msgid "No whitelist" msgstr "Pas d'accès gracieux" -#: users/templates/users/profil.html:487 +#: users/templates/users/profil.html:513 msgid "Email settings" msgstr "Paramètres mail" -#: users/templates/users/profil.html:494 +#: users/templates/users/profil.html:520 msgid "Edit email settings" msgstr "Modifier les paramètres mail" -#: users/templates/users/profil.html:503 users/templates/users/profil.html:529 +#: users/templates/users/profil.html:529 users/templates/users/profil.html:555 msgid "Contact email address" msgstr "Adresse mail de contact" -#: users/templates/users/profil.html:507 +#: users/templates/users/profil.html:533 msgid "Enable the local email account" msgstr "Activer le compte mail local" -#: users/templates/users/profil.html:509 +#: users/templates/users/profil.html:535 msgid "Enable the local email redirection" msgstr "Activer la redirection mail locale" -#: users/templates/users/profil.html:513 +#: users/templates/users/profil.html:539 msgid "" "The contact email address is the email address to which we send emails to " "contact you. If you would like to use your external email address for that, " @@ -1251,17 +1262,15 @@ msgstr "" "externe pour cela, vous pouvez soit désactiver votre adresse mail locale " "soit activer la redirection des mails locaux." -#: users/templates/users/profil.html:518 +#: users/templates/users/profil.html:544 msgid "Add an email address" msgstr "Ajouter une adresse mail" -#: users/templates/users/resend_confirmation_email.html:35 -#, python-format +#: users/templates/users/resend_confirmation_email.html:33 msgid "Re-send confirmation email?" msgstr "Renvoyer l'email de confirmation ?" -#: users/templates/users/resend_confirmation_email.html:36 -#, python-format +#: users/templates/users/resend_confirmation_email.html:34 msgid "The confirmation email will be sent to" msgstr "Le mail de confirmation sera envoyé à" @@ -1324,38 +1333,37 @@ msgstr "Connecté avec l'appareil :" msgid "MAC address %(mac)s" msgstr "Adresse MAC %(mac)s" -#: users/views.py:131 +#: users/views.py:133 #, python-format msgid "The user %s was created, a confirmation email was sent." -msgstr "" -"L'utilisateur %s a été créé, un mail de confirmation a été envoyé." +msgstr "L'utilisateur %s a été créé, un mail de confirmation a été envoyé." -#: users/views.py:130 +#: users/views.py:140 #, python-format msgid "The user %s was created, an email to set the password was sent." msgstr "" "L'utilisateur %s a été créé, un mail pour initialiser le mot de passe a été " "envoyé." -#: users/views.py:137 +#: users/views.py:153 msgid "Commit" msgstr "Valider" -#: users/views.py:156 +#: users/views.py:174 #, python-format msgid "The club %s was created, an email to set the password was sent." msgstr "" "Le club %s a été créé, un mail pour initialiser le mot de passe a été envoyé." -#: users/views.py:161 +#: users/views.py:179 msgid "Create a club" msgstr "Créer un club" -#: users/views.py:176 +#: users/views.py:194 msgid "The club was edited." msgstr "Le club a été modifié." -#: users/views.py:208 +#: users/views.py:226 msgid "The user was edited." msgstr "L'utilisateur a été modifié." @@ -1363,118 +1371,122 @@ msgstr "L'utilisateur a été modifié." msgid "Sent a new confirmation email." msgstr "Un nouveau mail de confirmation a été envoyé." -#: users/views.py:225 +#: users/views.py:247 msgid "The states were edited." msgstr "Les états ont été modifié." -#: users/views.py:242 -msgid "The groups were edited." -msgstr "Les groupes ont été modifiés." - #: users/views.py:249 msgid "An email to confirm the address was sent." msgstr "Un mail pour confirmer l'adresse a été envoyé." -#: users/views.py:261 users/views.py:998 +#: users/views.py:266 +msgid "The groups were edited." +msgstr "Les groupes ont été modifiés." + +#: users/views.py:285 users/views.py:1031 msgid "The password was changed." msgstr "Le mot de passe a été changé." -#: users/views.py:276 +#: users/views.py:300 #, python-format msgid "%s was removed from the group." msgstr "%s a été retiré du groupe." -#: users/views.py:286 +#: users/views.py:310 #, python-format msgid "%s is no longer superuser." msgstr "%s n'est plus superutilisateur." -#: users/views.py:297 +#: users/views.py:321 msgid "The service user was created." msgstr "L'utilisateur service a été créé." -#: users/views.py:300 users/views.py:354 users/views.py:405 users/views.py:463 -#: users/views.py:551 users/views.py:614 users/views.py:657 +#: users/views.py:324 users/views.py:378 users/views.py:429 users/views.py:487 +#: users/views.py:579 users/views.py:642 users/views.py:685 msgid "Add" msgstr "Ajouter" -#: users/views.py:314 +#: users/views.py:338 msgid "The service user was edited." msgstr "L'utilisateur service a été modifié." -#: users/views.py:329 +#: users/views.py:353 msgid "The service user was deleted." msgstr "L'utilisateur service a été supprimé." -#: users/views.py:349 +#: users/views.py:373 msgid "The ban was added." msgstr "Le bannissement a été ajouté." -#: users/views.py:352 +#: users/views.py:376 msgid "Warning: this user already has an active ban." msgstr "Attention : cet utilisateur a déjà un bannissement actif." -#: users/views.py:368 +#: users/views.py:392 msgid "The ban was edited." msgstr "Le bannissement a été modifié." -#: users/views.py:381 +#: users/views.py:405 msgid "The ban was deleted." msgstr "Le bannissement a été supprimé." -#: users/views.py:398 +#: users/views.py:422 msgid "The whitelist was added." msgstr "L'accès gracieux a été ajouté." -#: users/views.py:402 +#: users/views.py:426 msgid "Warning: this user already has an active whitelist." msgstr "Attention : cet utilisateur a déjà un accès gracieux actif." -#: users/views.py:422 +#: users/views.py:446 msgid "The whitelist was edited." msgstr "L'accès gracieux a été ajouté." -#: users/views.py:437 +#: users/views.py:461 msgid "The whitelist was deleted." msgstr "L'accès gracieux a été supprimé." -#: users/views.py:442 +#: users/views.py:466 msgid "whitelist" msgstr "accès gracieux" -#: users/views.py:457 +#: users/views.py:481 msgid "The local email account was created." msgstr "Le compte mail local a été créé." -#: users/views.py:480 +#: users/views.py:504 msgid "The local email account was edited." msgstr "Le compte mail local a été modifié." -#: users/views.py:503 +#: users/views.py:527 msgid "The local email account was deleted." msgstr "Le compte mail local a été supprimé." -#: users/views.py:508 +#: users/views.py:532 msgid "email address" msgstr "adresse mail" -#: users/views.py:524 +#: users/views.py:548 msgid "The email settings were edited." msgstr "Les paramètres mail ont été modifiés." -#: users/views.py:548 +#: users/views.py:551 users/views.py:1075 +msgid "An email to confirm your address was sent." +msgstr "Un mail pour confirmer votre adresse a été envoyé." + +#: users/views.py:576 msgid "The school was added." msgstr "L'établissement a été ajouté." -#: users/views.py:566 +#: users/views.py:594 msgid "The school was edited." msgstr "L'établissement a été modifié." -#: users/views.py:588 +#: users/views.py:616 msgid "The school was deleted." msgstr "L'établissement a été supprimé." -#: users/views.py:593 +#: users/views.py:621 #, python-format msgid "" "The school %s is assigned to at least one user, impossible to delete it." @@ -1482,31 +1494,31 @@ msgstr "" "L'établissement %s est assigné à au moins un utilisateur, impossible de le " "supprimer." -#: users/views.py:611 +#: users/views.py:639 msgid "The shell was added." msgstr "L'interface en ligne de commande a été ajoutée." -#: users/views.py:626 +#: users/views.py:654 msgid "The shell was edited." msgstr "L'interface en ligne de commande a été modifiée." -#: users/views.py:641 +#: users/views.py:669 msgid "The shell was deleted." msgstr "L'interface en ligne de commande a été supprimée." -#: users/views.py:654 +#: users/views.py:682 msgid "The group of rights was added." msgstr "Le groupe de droits a été ajouté." -#: users/views.py:672 +#: users/views.py:700 msgid "The group of rights was edited." msgstr "Le groupe de droits a été modifié." -#: users/views.py:692 +#: users/views.py:720 msgid "The group of rights was deleted." msgstr "Le groupe de droits a été supprimé." -#: users/views.py:697 +#: users/views.py:725 #, python-format msgid "" "The group of rights %s is assigned to at least one user, impossible to " @@ -1515,32 +1527,37 @@ msgstr "" "Le groupe de droits %s est assigné à au moins un utilisateur, impossible de " "le supprimer." -#: users/views.py:733 +#: users/views.py:761 #, python-format msgid "%s users were archived." msgstr "%s utilisateurs ont été archivés." -#: users/views.py:962 +#: users/views.py:990 users/views.py:1071 msgid "The user doesn't exist." msgstr "L'utilisateur n'existe pas." -#: users/views.py:964 users/views.py:972 +#: users/views.py:992 users/views.py:1000 msgid "Reset" msgstr "Réinitialiser" -#: users/views.py:969 +#: users/views.py:997 msgid "An email to reset the password was sent." msgstr "Un mail pour réinitialiser le mot de passe a été envoyé." -#: users/views.py:984 +#: users/views.py:1015 msgid "Error: please contact an admin." msgstr "Erreur : veuillez contacter un admin." -#: users/views.py:1020 +#: users/views.py:1051 +#, python-format +msgid "The %s address was confirmed." +msgstr "L'adresse mail %s a été confirmée." + +#: users/views.py:1098 msgid "Incorrect URL, or already registered device." msgstr "URL incorrect, ou appareil déjà enregistré." -#: users/views.py:1032 +#: users/views.py:1110 msgid "" "Successful registration! Please disconnect and reconnect your Ethernet cable " "to get Internet access." @@ -1548,15 +1565,7 @@ msgstr "" "Enregistrement réussi ! Veuillez débrancher et rebrancher votre câble " "Ethernet pour avoir accès à Internet." -#: users/views.py:1044 -msgid "The %s address was confirmed." -msgstr "L'adresse mail %s a été confirmée." - -#: users/views.py:1068 -msgid "An email to confirm your address was sent." -msgstr "Un mail pour confirmer votre adresse a été envoyé." - -#: users/views.py:1072 users/views.py:1096 users/views.py:1111 +#: users/views.py:1150 users/views.py:1174 users/views.py:1189 msgid "The mailing list doesn't exist." msgstr "La liste de diffusion n'existe pas." diff --git a/users/models.py b/users/models.py index e956a26a..36558a74 100755 --- a/users/models.py +++ b/users/models.py @@ -192,8 +192,8 @@ class User( EMAIL_STATE_UNVERIFIED = 1 EMAIL_STATE_PENDING = 2 EMAIL_STATES = ( - (0, _("Verified")), - (1, _("Unverified")), + (0, _("Confirmed")), + (1, _("Not confirmed")), (2, _("Waiting for email confirmation")), ) From cb1784142f77bd24ef96a2df65b231b480fbff5b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 18 Apr 2020 01:03:41 +0200 Subject: [PATCH 80/83] Cleanup old Request objects in process_passwd and process_email --- users/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/users/views.py b/users/views.py index 3d5b0205..28fb107c 100644 --- a/users/views.py +++ b/users/views.py @@ -1027,7 +1027,8 @@ def process_passwd(request, req): u_form.save() reversion.set_comment("Password reset") - req.delete() + # Delete all remaining requests + Request.objects.filter(user=user, type=Request.PASSWD).delete() messages.success(request, _("The password was changed.")) return redirect(reverse("index")) return form( @@ -1047,7 +1048,8 @@ def process_email(request, req): user.save() reversion.set_comment("Email confirmation") - req.delete() + # Delete all remaining requests + Request.objects.filter(user=user, type=Request.EMAIL).delete() messages.success(request, _("The %s address was confirmed." % user.email)) return redirect(reverse("index")) From 309134286cc38c59237777424d3b1f242ba8d1d7 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 23:13:36 +0000 Subject: [PATCH 81/83] Add missing migration --- users/migrations/0089_auto_20200418_0112.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 users/migrations/0089_auto_20200418_0112.py diff --git a/users/migrations/0089_auto_20200418_0112.py b/users/migrations/0089_auto_20200418_0112.py new file mode 100644 index 00000000..f3711f0f --- /dev/null +++ b/users/migrations/0089_auto_20200418_0112.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-17 23:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0088_auto_20200417_2312'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email_state', + field=models.IntegerField(choices=[(0, 'Confirmed'), (1, 'Not confirmed'), (2, 'Waiting for email confirmation')], default=2), + ), + ] From e3393083189cb6eaaf60f694076e9db8f4c81881 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 18 Apr 2020 01:36:24 +0200 Subject: [PATCH 82/83] Add email state statistics --- logs/views.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/logs/views.py b/logs/views.py index 7c509134..5d67f0eb 100644 --- a/logs/views.py +++ b/logs/views.py @@ -284,6 +284,24 @@ def stats_general(request): _all_whitelisted.exclude(adherent__isnull=True).count(), _all_whitelisted.exclude(club__isnull=True).count(), ], + "email_state_verified_users": [ + _("Users with a verified email"), + User.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), + Adherent.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), + Club.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), + ], + "email_state_unverified_users": [ + _("Users with an unverified email"), + User.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), + Adherent.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), + Club.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), + ], + "email_state_pending_users": [ + _("Users pending email confirmation"), + User.objects.filter(email_state=User.EMAIL_STATE_PENDING).count(), + Adherent.objects.filter(email_state=User.EMAIL_STATE_PENDING).count(), + Club.objects.filter(email_state=User.EMAIL_STATE_PENDING).count(), + ], "actives_interfaces": [ _("Active interfaces (with access to the network)"), _all_active_interfaces_count.count(), From f3d8b70213b47c306bb6c2bdd2217fce7e3d8ffc Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 17 Apr 2020 23:40:01 +0000 Subject: [PATCH 83/83] Add translations for statistics --- api/locale/fr/LC_MESSAGES/django.po | 2 +- cotisations/locale/fr/LC_MESSAGES/django.po | 2 +- logs/locale/fr/LC_MESSAGES/django.po | 46 +++++++++++++-------- logs/views.py | 4 +- machines/locale/fr/LC_MESSAGES/django.po | 2 +- multi_op/locale/fr/LC_MESSAGES/django.po | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 2 +- re2o/locale/fr/LC_MESSAGES/django.po | 2 +- search/locale/fr/LC_MESSAGES/django.po | 2 +- templates/locale/fr/LC_MESSAGES/django.po | 2 +- tickets/locale/fr/LC_MESSAGES/django.po | 2 +- topologie/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 18 ++++---- 13 files changed, 50 insertions(+), 38 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index ccc73c63..2a43ffa3 100644 --- a/api/locale/fr/LC_MESSAGES/django.po +++ b/api/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2019-01-07 01:37+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/cotisations/locale/fr/LC_MESSAGES/django.po b/cotisations/locale/fr/LC_MESSAGES/django.po index 344c4269..bcd86e72 100644 --- a/cotisations/locale/fr/LC_MESSAGES/django.po +++ b/cotisations/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language: fr_FR\n" diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 6a2bd167..d7c38182 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -159,7 +159,7 @@ msgid "Statistics" msgstr "Statistiques" #: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32 -#: logs/views.py:400 +#: logs/views.py:418 msgid "Actions performed" msgstr "Actions effectuées" @@ -260,65 +260,77 @@ msgid "Users benefiting from a free connection" msgstr "Utilisateurs bénéficiant d'une connexion gratuite" #: logs/views.py:288 +msgid "Users with a confirmed email" +msgstr "Utilisateurs ayant un mail confirmé" + +#: logs/views.py:294 +msgid "Users with an unconfirmed email" +msgstr "Utilisateurs ayant un mail non confirmé" + +#: logs/views.py:300 +msgid "Users pending email confirmation" +msgstr "Utilisateurs en attente de confirmation du mail" + +#: logs/views.py:306 msgid "Active interfaces (with access to the network)" msgstr "Interfaces actives (ayant accès au réseau)" -#: logs/views.py:302 +#: logs/views.py:320 msgid "Active interfaces assigned IPv4" msgstr "Interfaces actives assignées IPv4" -#: logs/views.py:319 +#: logs/views.py:337 msgid "IP range" msgstr "Plage d'IP" -#: logs/views.py:320 +#: logs/views.py:338 msgid "VLAN" msgstr "VLAN" -#: logs/views.py:321 +#: logs/views.py:339 msgid "Total number of IP addresses" msgstr "Nombre total d'adresses IP" -#: logs/views.py:322 +#: logs/views.py:340 msgid "Number of assigned IP addresses" msgstr "Nombre d'adresses IP assignées" -#: logs/views.py:323 +#: logs/views.py:341 msgid "Number of IP address assigned to an activated machine" msgstr "Nombre d'adresses IP assignées à une machine activée" -#: logs/views.py:324 +#: logs/views.py:342 msgid "Number of unassigned IP addresses" msgstr "Nombre d'adresses IP non assignées" -#: logs/views.py:339 +#: logs/views.py:357 msgid "Users (members and clubs)" msgstr "Utilisateurs (adhérents et clubs)" -#: logs/views.py:385 +#: logs/views.py:403 msgid "Topology" msgstr "Topologie" -#: logs/views.py:401 +#: logs/views.py:419 msgid "Number of actions" msgstr "Nombre d'actions" -#: logs/views.py:426 +#: logs/views.py:444 msgid "rights" msgstr "droits" -#: logs/views.py:455 +#: logs/views.py:473 msgid "actions" msgstr "actions" -#: logs/views.py:486 +#: logs/views.py:504 msgid "No model found." msgstr "Aucun modèle trouvé." -#: logs/views.py:492 +#: logs/views.py:510 msgid "Nonexistent entry." msgstr "Entrée inexistante." -#: logs/views.py:499 +#: logs/views.py:517 msgid "You don't have the right to access this menu." msgstr "Vous n'avez pas le droit d'accéder à ce menu." diff --git a/logs/views.py b/logs/views.py index 5d67f0eb..78971d18 100644 --- a/logs/views.py +++ b/logs/views.py @@ -285,13 +285,13 @@ def stats_general(request): _all_whitelisted.exclude(club__isnull=True).count(), ], "email_state_verified_users": [ - _("Users with a verified email"), + _("Users with a confirmed email"), User.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), Adherent.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), Club.objects.filter(email_state=User.EMAIL_STATE_VERIFIED).count(), ], "email_state_unverified_users": [ - _("Users with an unverified email"), + _("Users with an unconfirmed email"), User.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), Adherent.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), Club.objects.filter(email_state=User.EMAIL_STATE_UNVERIFIED).count(), diff --git a/machines/locale/fr/LC_MESSAGES/django.po b/machines/locale/fr/LC_MESSAGES/django.po index ff54149b..94a700de 100644 --- a/machines/locale/fr/LC_MESSAGES/django.po +++ b/machines/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-23 16:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/multi_op/locale/fr/LC_MESSAGES/django.po b/multi_op/locale/fr/LC_MESSAGES/django.po index 4457f37a..201e577f 100644 --- a/multi_op/locale/fr/LC_MESSAGES/django.po +++ b/multi_op/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2019-11-16 00:22+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index d48f427e..4c43a549 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-24 15:54+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index 05954f8f..92760212 100644 --- a/re2o/locale/fr/LC_MESSAGES/django.po +++ b/re2o/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index 45226988..ce63a66e 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-24 20:10+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/templates/locale/fr/LC_MESSAGES/django.po b/templates/locale/fr/LC_MESSAGES/django.po index 6aa9bf63..5fb07ef3 100644 --- a/templates/locale/fr/LC_MESSAGES/django.po +++ b/templates/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index b562c90c..977d5325 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/topologie/locale/fr/LC_MESSAGES/django.po b/topologie/locale/fr/LC_MESSAGES/django.po index 4f4d8217..53df3972 100644 --- a/topologie/locale/fr/LC_MESSAGES/django.po +++ b/topologie/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-25 14:53+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 6d22504c..bf9d0f26 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-18 00:48+0200\n" +"POT-Creation-Date: 2020-04-18 01:38+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -1093,7 +1093,7 @@ msgstr "Informations détaillées" msgid "Edit" msgstr "Modifier" -#: users/templates/users/profil.html:165 users/views.py:288 users/views.py:1034 +#: users/templates/users/profil.html:165 users/views.py:288 users/views.py:1035 msgid "Change the password" msgstr "Changer le mot de passe" @@ -1383,7 +1383,7 @@ msgstr "Un mail pour confirmer l'adresse a été envoyé." msgid "The groups were edited." msgstr "Les groupes ont été modifiés." -#: users/views.py:285 users/views.py:1031 +#: users/views.py:285 users/views.py:1032 msgid "The password was changed." msgstr "Le mot de passe a été changé." @@ -1470,7 +1470,7 @@ msgstr "adresse mail" msgid "The email settings were edited." msgstr "Les paramètres mail ont été modifiés." -#: users/views.py:551 users/views.py:1075 +#: users/views.py:551 users/views.py:1077 msgid "An email to confirm your address was sent." msgstr "Un mail pour confirmer votre adresse a été envoyé." @@ -1532,7 +1532,7 @@ msgstr "" msgid "%s users were archived." msgstr "%s utilisateurs ont été archivés." -#: users/views.py:990 users/views.py:1071 +#: users/views.py:990 users/views.py:1073 msgid "The user doesn't exist." msgstr "L'utilisateur n'existe pas." @@ -1548,16 +1548,16 @@ msgstr "Un mail pour réinitialiser le mot de passe a été envoyé." msgid "Error: please contact an admin." msgstr "Erreur : veuillez contacter un admin." -#: users/views.py:1051 +#: users/views.py:1053 #, python-format msgid "The %s address was confirmed." msgstr "L'adresse mail %s a été confirmée." -#: users/views.py:1098 +#: users/views.py:1100 msgid "Incorrect URL, or already registered device." msgstr "URL incorrect, ou appareil déjà enregistré." -#: users/views.py:1110 +#: users/views.py:1112 msgid "" "Successful registration! Please disconnect and reconnect your Ethernet cable " "to get Internet access." @@ -1565,7 +1565,7 @@ msgstr "" "Enregistrement réussi ! Veuillez débrancher et rebrancher votre câble " "Ethernet pour avoir accès à Internet." -#: users/views.py:1150 users/views.py:1174 users/views.py:1189 +#: users/views.py:1152 users/views.py:1176 users/views.py:1191 msgid "The mailing list doesn't exist." msgstr "La liste de diffusion n'existe pas."