8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-25 22:22:26 +00:00

Merge branch 'choose_password_during_user_creation' into 'dev'

Allow users to choose password during account creation

See merge request re2o/re2o!496
This commit is contained in:
chirac 2020-04-18 01:46:22 +02:00
commit f8b6044938
37 changed files with 1689 additions and 776 deletions

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 23:43+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2019-01-07 01:37+0100\n" "PO-Revision-Date: 2019-01-07 01:37+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language: fr_FR\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" msgid "Select a payment method"
msgstr "Sélectionnez un moyen de paiement" 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" msgid "Member"
msgstr "Adhérent" msgstr "Adhérent"
@ -154,8 +154,8 @@ msgstr "Peut voir un objet facture"
msgid "Can edit all the previous invoices" msgid "Can edit all the previous invoices"
msgstr "Peut modifier toutes les factures précédentes" msgstr "Peut modifier toutes les factures précédentes"
#: cotisations/models.py:145 cotisations/models.py:431 cotisations/views.py:378 #: cotisations/models.py:145 cotisations/models.py:456 cotisations/views.py:376
#: cotisations/views.py:573 #: cotisations/views.py:571
msgid "invoice" msgid "invoice"
msgstr "facture" 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." msgid "There are no articles that you can buy."
msgstr "Il n'y a pas d'articles que vous puissiez acheter." 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" msgid "Can view a custom invoice object"
msgstr "Peut voir un objet facture personnalisée" msgstr "Peut voir un objet facture personnalisée"
#: cotisations/models.py:349 #: cotisations/models.py:374
msgid "recipient" msgid "recipient"
msgstr "destinataire" msgstr "destinataire"
#: cotisations/models.py:350 #: cotisations/models.py:375
msgid "payment type" msgid "payment type"
msgstr "type de paiement" msgstr "type de paiement"
#: cotisations/models.py:351 #: cotisations/models.py:376
msgid "address" msgid "address"
msgstr "adresse" msgstr "adresse"
#: cotisations/models.py:352 #: cotisations/models.py:377
msgid "paid" msgid "paid"
msgstr "payé" msgstr "payé"
#: cotisations/models.py:353 #: cotisations/models.py:378
msgid "remark" msgid "remark"
msgstr "remarque" msgstr "remarque"
#: cotisations/models.py:358 #: cotisations/models.py:383
msgid "Can view a cost estimate object" msgid "Can view a cost estimate object"
msgstr "Peut voir un objet devis" msgstr "Peut voir un objet devis"
#: cotisations/models.py:361 #: cotisations/models.py:386
msgid "period of validity" msgid "period of validity"
msgstr "période de validité" 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." msgid "You don't have the right to delete a cost estimate."
msgstr "Vous n'avez pas le droit de supprimer un devis." 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." msgid "The cost estimate has an invoice and can't be deleted."
msgstr "Le devis a une facture et ne peut pas être supprimé." msgstr "Le devis a une facture et ne peut pas être supprimé."
#: cotisations/models.py:424 cotisations/models.py:682 #: cotisations/models.py:449 cotisations/models.py:688
#: cotisations/models.py:940 #: cotisations/models.py:946
msgid "Connection" msgid "Connection"
msgstr "Connexion" msgstr "Connexion"
#: cotisations/models.py:425 cotisations/models.py:683 #: cotisations/models.py:450 cotisations/models.py:689
#: cotisations/models.py:941 #: cotisations/models.py:947
msgid "Membership" msgid "Membership"
msgstr "Adhésion" msgstr "Adhésion"
#: cotisations/models.py:426 cotisations/models.py:678 #: cotisations/models.py:451 cotisations/models.py:684
#: cotisations/models.py:684 cotisations/models.py:942 #: cotisations/models.py:690 cotisations/models.py:948
msgid "Both of them" msgid "Both of them"
msgstr "Les deux" msgstr "Les deux"
#: cotisations/models.py:435 #: cotisations/models.py:460
msgid "amount" msgid "amount"
msgstr "montant" msgstr "montant"
#: cotisations/models.py:438 #: cotisations/models.py:463
msgid "article" msgid "article"
msgstr "article" msgstr "article"
#: cotisations/models.py:441 #: cotisations/models.py:466
msgid "price" msgid "price"
msgstr "prix" msgstr "prix"
#: cotisations/models.py:444 cotisations/models.py:696 #: cotisations/models.py:469 cotisations/models.py:702
msgid "duration (in months)" msgid "duration (in months)"
msgstr "durée (en mois)" 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)" msgid "duration (in days, will be added to duration in months)"
msgstr "durée (en jours, sera ajoutée à la durée en mois)" msgstr "durée (en jours, sera ajoutée à la durée en mois)"
#: cotisations/models.py:458 cotisations/models.py:716 #: cotisations/models.py:483 cotisations/models.py:722
#: cotisations/models.py:953 #: cotisations/models.py:959
msgid "subscription type" msgid "subscription type"
msgstr "type de cotisation" msgstr "type de cotisation"
#: cotisations/models.py:463 #: cotisations/models.py:488
msgid "Can view a purchase object" msgid "Can view a purchase object"
msgstr "Peut voir un objet achat" msgstr "Peut voir un objet achat"
#: cotisations/models.py:464 #: cotisations/models.py:489
msgid "Can edit all the previous purchases" msgid "Can edit all the previous purchases"
msgstr "Peut modifier tous les achats précédents" 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" msgid "purchase"
msgstr "achat" msgstr "achat"
#: cotisations/models.py:467 #: cotisations/models.py:492
msgid "purchases" msgid "purchases"
msgstr "achats" 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." msgid "Duration must be specified for a subscription."
msgstr "La durée doit être renseignée pour une cotisation." 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." msgid "You don't have the right to edit a purchase."
msgstr "Vous n'avez pas le droit de modifier un achat." 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." 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." msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur."
#: cotisations/models.py:565 #: cotisations/models.py:571
msgid "" msgid ""
"You don't have the right to edit a purchase already controlled or " "You don't have the right to edit a purchase already controlled or "
"invalidated." "invalidated."
@ -333,15 +333,15 @@ msgstr ""
"Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou " "Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou "
"invalidé." "invalidé."
#: cotisations/models.py:580 #: cotisations/models.py:586
msgid "You don't have the right to delete a purchase." msgid "You don't have the right to delete a purchase."
msgstr "Vous n'avez pas le droit de supprimer un achat." 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." 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." msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur."
#: cotisations/models.py:593 #: cotisations/models.py:599
msgid "" msgid ""
"You don't have the right to delete a purchase already controlled or " "You don't have the right to delete a purchase already controlled or "
"invalidated." "invalidated."
@ -349,134 +349,134 @@ msgstr ""
"Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou " "Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou "
"invalidé." "invalidé."
#: cotisations/models.py:609 #: cotisations/models.py:615
msgid "You don't have the right to view someone else's purchase history." msgid "You don't have the right to view someone else's purchase history."
msgstr "" msgstr ""
"Vous n'avez pas le droit de voir l'historique des achats d'un autre " "Vous n'avez pas le droit de voir l'historique des achats d'un autre "
"utilisateur." "utilisateur."
#: cotisations/models.py:677 #: cotisations/models.py:683
msgid "Club" msgid "Club"
msgstr "Club" msgstr "Club"
#: cotisations/models.py:687 #: cotisations/models.py:693
msgid "designation" msgid "designation"
msgstr "désignation" msgstr "désignation"
#: cotisations/models.py:690 #: cotisations/models.py:696
msgid "unit price" msgid "unit price"
msgstr "prix unitaire" msgstr "prix unitaire"
#: cotisations/models.py:708 #: cotisations/models.py:714
msgid "type of users concerned" msgid "type of users concerned"
msgstr "type d'utilisateurs concernés" 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" msgid "is available for every user"
msgstr "est disponible pour chaque utilisateur" msgstr "est disponible pour chaque utilisateur"
#: cotisations/models.py:726 #: cotisations/models.py:732
msgid "Can view an article object" msgid "Can view an article object"
msgstr "Peut voir un objet article" msgstr "Peut voir un objet article"
#: cotisations/models.py:727 #: cotisations/models.py:733
msgid "Can buy every article" msgid "Can buy every article"
msgstr "Peut acheter chaque article" msgstr "Peut acheter chaque article"
#: cotisations/models.py:734 #: cotisations/models.py:740
msgid "Solde is a reserved article name." msgid "Solde is a reserved article name."
msgstr "Solde est un nom d'article réservé." msgstr "Solde est un nom d'article réservé."
#: cotisations/models.py:759 #: cotisations/models.py:765
msgid "You can't buy this article." msgid "You can't buy this article."
msgstr "Vous ne pouvez pas acheter cet article." msgstr "Vous ne pouvez pas acheter cet article."
#: cotisations/models.py:800 #: cotisations/models.py:806
msgid "Can view a bank object" msgid "Can view a bank object"
msgstr "Peut voir un objet banque" msgstr "Peut voir un objet banque"
#: cotisations/models.py:801 #: cotisations/models.py:807
msgid "bank" msgid "bank"
msgstr "banque" msgstr "banque"
#: cotisations/models.py:802 #: cotisations/models.py:808
msgid "banks" msgid "banks"
msgstr "banques" msgstr "banques"
#: cotisations/models.py:818 #: cotisations/models.py:824
msgid "method" msgid "method"
msgstr "moyen" msgstr "moyen"
#: cotisations/models.py:825 #: cotisations/models.py:831
msgid "is user balance" msgid "is user balance"
msgstr "est solde utilisateur" msgstr "est solde utilisateur"
#: cotisations/models.py:826 #: cotisations/models.py:832
msgid "There should be only one balance payment method." msgid "There should be only one balance payment method."
msgstr "Il ne devrait y avoir qu'un moyen de paiement solde." 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" msgid "Can view a payment method object"
msgstr "Peut voir un objet moyen de paiement" msgstr "Peut voir un objet moyen de paiement"
#: cotisations/models.py:833 #: cotisations/models.py:839
msgid "Can use every payment method" msgid "Can use every payment method"
msgstr "Peut utiliser chaque moyen de paiement" msgstr "Peut utiliser chaque moyen de paiement"
#: cotisations/models.py:835 #: cotisations/models.py:841
msgid "payment method" msgid "payment method"
msgstr "moyen de paiement" msgstr "moyen de paiement"
#: cotisations/models.py:836 #: cotisations/models.py:842
msgid "payment methods" msgid "payment methods"
msgstr "moyens de paiement" 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 #, python-format
msgid "The subscription of %(member_name)s was extended to %(end_date)s." 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." 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." msgid "The invoice was created."
msgstr "La facture a été créée." msgstr "La facture a été créée."
#: cotisations/models.py:905 #: cotisations/models.py:911
msgid "You can't use this payment method." msgid "You can't use this payment method."
msgstr "Vous ne pouvez pas utiliser ce moyen de paiement." msgstr "Vous ne pouvez pas utiliser ce moyen de paiement."
#: cotisations/models.py:924 #: cotisations/models.py:930
msgid "No custom payment methods." msgid "No custom payment methods."
msgstr "Pas de moyens de paiement personnalisés." msgstr "Pas de moyens de paiement personnalisés."
#: cotisations/models.py:955 #: cotisations/models.py:961
msgid "start date" msgid "start date"
msgstr "date de début" msgstr "date de début"
#: cotisations/models.py:956 #: cotisations/models.py:962
msgid "end date" msgid "end date"
msgstr "date de fin" msgstr "date de fin"
#: cotisations/models.py:960 #: cotisations/models.py:966
msgid "Can view a subscription object" msgid "Can view a subscription object"
msgstr "Peut voir un objet cotisation" msgstr "Peut voir un objet cotisation"
#: cotisations/models.py:961 #: cotisations/models.py:967
msgid "Can edit the previous subscriptions" msgid "Can edit the previous subscriptions"
msgstr "Peut modifier les cotisations précédentes" msgstr "Peut modifier les cotisations précédentes"
#: cotisations/models.py:963 #: cotisations/models.py:969
msgid "subscription" msgid "subscription"
msgstr "cotisation" msgstr "cotisation"
#: cotisations/models.py:964 #: cotisations/models.py:970
msgid "subscriptions" msgid "subscriptions"
msgstr "cotisations" msgstr "cotisations"
#: cotisations/models.py:970 #: cotisations/models.py:976
msgid "You don't have the right to edit a subscription." msgid "You don't have the right to edit a subscription."
msgstr "Vous n'avez pas le droit de modifier une cotisation." msgstr "Vous n'avez pas le droit de modifier une cotisation."
#: cotisations/models.py:979 #: cotisations/models.py:985
msgid "" msgid ""
"You don't have the right to edit a subscription already controlled or " "You don't have the right to edit a subscription already controlled or "
"invalidated." "invalidated."
@ -484,11 +484,11 @@ msgstr ""
"Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée " "Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée "
"ou invalidée." "ou invalidée."
#: cotisations/models.py:991 #: cotisations/models.py:997
msgid "You don't have the right to delete a subscription." msgid "You don't have the right to delete a subscription."
msgstr "Vous n'avez pas le droit de supprimer une cotisation." msgstr "Vous n'avez pas le droit de supprimer une cotisation."
#: cotisations/models.py:998 #: cotisations/models.py:1004
msgid "" msgid ""
"You don't have the right to delete a subscription already controlled or " "You don't have the right to delete a subscription already controlled or "
"invalidated." "invalidated."
@ -496,7 +496,7 @@ msgstr ""
"Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée " "Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée "
"ou invalidé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." msgid "You don't have the right to view someone else's subscription history."
msgstr "" msgstr ""
"Vous n'avez pas le droit de voir l'historique des cotisations d'un autre " "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/control.html:107
#: cotisations/templates/cotisations/delete.html:38 #: cotisations/templates/cotisations/delete.html:38
#: cotisations/templates/cotisations/edit_facture.html:64 #: 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" msgid "Confirm"
msgstr "Confirmer" msgstr "Confirmer"
@ -921,7 +921,7 @@ msgstr "Rechargement de solde"
msgid "Pay %(amount)s €" msgid "Pay %(amount)s €"
msgstr "Payer %(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" msgid "Pay"
msgstr "Payer" msgstr "Payer"
@ -933,11 +933,11 @@ msgstr "Créer une facture"
msgid "Control the invoices" msgid "Control the invoices"
msgstr "Contrôler les factures" msgstr "Contrôler les factures"
#: cotisations/views.py:155 #: cotisations/views.py:157
msgid "You need to choose at least one article." msgid "You need to choose at least one article."
msgstr "Vous devez choisir au moins un article." msgstr "Vous devez choisir au moins un article."
#: cotisations/views.py:169 #: cotisations/views.py:171
msgid "New invoice" msgid "New invoice"
msgstr "Nouvelle facture" msgstr "Nouvelle facture"
@ -949,104 +949,104 @@ msgstr "Le devis a été créé."
msgid "New cost estimate" msgid "New cost estimate"
msgstr "Nouveau devis" msgstr "Nouveau devis"
#: cotisations/views.py:272 #: cotisations/views.py:270
msgid "The custom invoice was created." msgid "The custom invoice was created."
msgstr "La facture personnalisée a été créée." msgstr "La facture personnalisée a été créée."
#: cotisations/views.py:282 #: cotisations/views.py:280
msgid "New custom invoice" msgid "New custom invoice"
msgstr "Nouvelle facture personnalisée" 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." msgid "The invoice was edited."
msgstr "La facture a été modifiée." 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." msgid "The invoice was deleted."
msgstr "La facture a été supprimée." msgstr "La facture a été supprimée."
#: cotisations/views.py:398 #: cotisations/views.py:396
msgid "The cost estimate was edited." msgid "The cost estimate was edited."
msgstr "Le devis a été modifié." msgstr "Le devis a été modifié."
#: cotisations/views.py:405 #: cotisations/views.py:403
msgid "Edit cost estimate" msgid "Edit cost estimate"
msgstr "Modifier le devis" msgstr "Modifier le devis"
#: cotisations/views.py:419 #: cotisations/views.py:417
msgid "An invoice was successfully created from your cost estimate." msgid "An invoice was successfully created from your cost estimate."
msgstr "Une facture a bien été créée à partir de votre devis." msgstr "Une facture a bien été créée à partir de votre devis."
#: cotisations/views.py:445 #: cotisations/views.py:443
msgid "Edit custom invoice" msgid "Edit custom invoice"
msgstr "Modifier la facture personnalisée" msgstr "Modifier la facture personnalisée"
#: cotisations/views.py:507 #: cotisations/views.py:505
msgid "The cost estimate was deleted." msgid "The cost estimate was deleted."
msgstr "Le devis a été supprimé." msgstr "Le devis a été supprimé."
#: cotisations/views.py:510 #: cotisations/views.py:508
msgid "cost estimate" msgid "cost estimate"
msgstr "devis" msgstr "devis"
#: cotisations/views.py:594 #: cotisations/views.py:592
msgid "The article was created." msgid "The article was created."
msgstr "L'article a été créé." 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" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
#: cotisations/views.py:600 #: cotisations/views.py:598
msgid "New article" msgid "New article"
msgstr "Nouvel article" msgstr "Nouvel article"
#: cotisations/views.py:617 #: cotisations/views.py:615
msgid "The article was edited." msgid "The article was edited."
msgstr "L'article a été modifié." 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" msgid "Edit"
msgstr "Modifier" msgstr "Modifier"
#: cotisations/views.py:623 #: cotisations/views.py:621
msgid "Edit article" msgid "Edit article"
msgstr "Modifier l'article" msgstr "Modifier l'article"
#: cotisations/views.py:640 #: cotisations/views.py:638
msgid "The articles were deleted." msgid "The articles were deleted."
msgstr "Les articles ont été supprimés." 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" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
#: cotisations/views.py:646 #: cotisations/views.py:644
msgid "Delete article" msgid "Delete article"
msgstr "Supprimer l'article" msgstr "Supprimer l'article"
#: cotisations/views.py:667 #: cotisations/views.py:665
msgid "The payment method was created." msgid "The payment method was created."
msgstr "Le moyen de paiment a été créé." msgstr "Le moyen de paiment a été créé."
#: cotisations/views.py:674 #: cotisations/views.py:672
msgid "New payment method" msgid "New payment method"
msgstr "Nouveau moyen de paiement" msgstr "Nouveau moyen de paiement"
#: cotisations/views.py:699 #: cotisations/views.py:697
msgid "The payment method was edited." msgid "The payment method was edited."
msgstr "Le moyen de paiment a été modifié." msgstr "Le moyen de paiment a été modifié."
#: cotisations/views.py:706 #: cotisations/views.py:704
msgid "Edit payment method" msgid "Edit payment method"
msgstr "Modifier le moyen de paiement" msgstr "Modifier le moyen de paiement"
#: cotisations/views.py:728 #: cotisations/views.py:726
#, python-format #, python-format
msgid "The payment method %(method_name)s was deleted." msgid "The payment method %(method_name)s was deleted."
msgstr "Le moyen de paiement %(method_name)s a été supprimé." msgstr "Le moyen de paiement %(method_name)s a été supprimé."
#: cotisations/views.py:735 #: cotisations/views.py:733
#, python-format #, python-format
msgid "" msgid ""
"The payment method %(method_name)s can't be deleted because there are " "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 " "Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a "
"des factures qui l'utilisent." "des factures qui l'utilisent."
#: cotisations/views.py:745 #: cotisations/views.py:743
msgid "Delete payment method" msgid "Delete payment method"
msgstr "Supprimer le moyen de paiement" msgstr "Supprimer le moyen de paiement"
#: cotisations/views.py:762 #: cotisations/views.py:760
msgid "The bank was created." msgid "The bank was created."
msgstr "La banque a été créée." msgstr "La banque a été créée."
#: cotisations/views.py:768 #: cotisations/views.py:766
msgid "New bank" msgid "New bank"
msgstr "Nouvelle banque" msgstr "Nouvelle banque"
#: cotisations/views.py:786 #: cotisations/views.py:784
msgid "The bank was edited." msgid "The bank was edited."
msgstr "La banque a été modifiée." msgstr "La banque a été modifiée."
#: cotisations/views.py:792 #: cotisations/views.py:790
msgid "Edit bank" msgid "Edit bank"
msgstr "Modifier la banque" msgstr "Modifier la banque"
#: cotisations/views.py:814 #: cotisations/views.py:812
#, python-format #, python-format
msgid "The bank %(bank_name)s was deleted." msgid "The bank %(bank_name)s was deleted."
msgstr "La banque %(bank_name)s a été supprimée." msgstr "La banque %(bank_name)s a été supprimée."
#: cotisations/views.py:820 #: cotisations/views.py:818
#, python-format #, python-format
msgid "" msgid ""
"The bank %(bank_name)s can't be deleted because there are invoices using it." "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 " "La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
"qui l'utilisent." "qui l'utilisent."
#: cotisations/views.py:830 #: cotisations/views.py:828
msgid "Delete bank" msgid "Delete bank"
msgstr "Supprimer la banque" msgstr "Supprimer la banque"
#: cotisations/views.py:864 #: cotisations/views.py:862
msgid "Your changes have been properly taken into account." msgid "Your changes have been properly taken into account."
msgstr "Vos modifications ont correctement été prises en compte." 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." msgid "You are not allowed to credit your balance."
msgstr "Vous n'êtes pas autorisé à créditer votre solde." msgstr "Vous n'êtes pas autorisé à créditer votre solde."
#: cotisations/views.py:1027 #: cotisations/views.py:1025
msgid "Refill your balance" msgid "Refill your balance"
msgstr "Recharger votre solde" msgstr "Recharger votre solde"
#: cotisations/views.py:1046 #: cotisations/views.py:1044
msgid "Could not find a voucher for that invoice." msgid "Could not find a voucher for that invoice."
msgstr "Impossible de trouver un reçu pour cette facture." msgstr "Impossible de trouver un reçu pour cette facture."

View file

@ -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_cached_value("banned") != RadiusOption.REJECT,
RadiusOption.get_attributes("banned_attributes", attributes_kwargs), 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()): elif not (user.is_connected() or user.is_whitelisted()):
return ( return (
sw_name, sw_name,

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 23:43+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-06-23 16:01+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"
@ -159,7 +159,7 @@ msgid "Statistics"
msgstr "Statistiques" msgstr "Statistiques"
#: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32 #: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32
#: logs/views.py:400 #: logs/views.py:418
msgid "Actions performed" msgid "Actions performed"
msgstr "Actions effectuées" msgstr "Actions effectuées"
@ -260,65 +260,77 @@ msgid "Users benefiting from a free connection"
msgstr "Utilisateurs bénéficiant d'une connexion gratuite" msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
#: logs/views.py:288 #: 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)" msgid "Active interfaces (with access to the network)"
msgstr "Interfaces actives (ayant accès au réseau)" msgstr "Interfaces actives (ayant accès au réseau)"
#: logs/views.py:302 #: logs/views.py:320
msgid "Active interfaces assigned IPv4" msgid "Active interfaces assigned IPv4"
msgstr "Interfaces actives assignées IPv4" msgstr "Interfaces actives assignées IPv4"
#: logs/views.py:319 #: logs/views.py:337
msgid "IP range" msgid "IP range"
msgstr "Plage d'IP" msgstr "Plage d'IP"
#: logs/views.py:320 #: logs/views.py:338
msgid "VLAN" msgid "VLAN"
msgstr "VLAN" msgstr "VLAN"
#: logs/views.py:321 #: logs/views.py:339
msgid "Total number of IP addresses" msgid "Total number of IP addresses"
msgstr "Nombre total d'adresses IP" msgstr "Nombre total d'adresses IP"
#: logs/views.py:322 #: logs/views.py:340
msgid "Number of assigned IP addresses" msgid "Number of assigned IP addresses"
msgstr "Nombre d'adresses IP assignées" 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" msgid "Number of IP address assigned to an activated machine"
msgstr "Nombre d'adresses IP assignées à une machine activée" 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" msgid "Number of unassigned IP addresses"
msgstr "Nombre d'adresses IP non assignées" msgstr "Nombre d'adresses IP non assignées"
#: logs/views.py:339 #: logs/views.py:357
msgid "Users (members and clubs)" msgid "Users (members and clubs)"
msgstr "Utilisateurs (adhérents et clubs)" msgstr "Utilisateurs (adhérents et clubs)"
#: logs/views.py:385 #: logs/views.py:403
msgid "Topology" msgid "Topology"
msgstr "Topologie" msgstr "Topologie"
#: logs/views.py:401 #: logs/views.py:419
msgid "Number of actions" msgid "Number of actions"
msgstr "Nombre d'actions" msgstr "Nombre d'actions"
#: logs/views.py:426 #: logs/views.py:444
msgid "rights" msgid "rights"
msgstr "droits" msgstr "droits"
#: logs/views.py:455 #: logs/views.py:473
msgid "actions" msgid "actions"
msgstr "actions" msgstr "actions"
#: logs/views.py:486 #: logs/views.py:504
msgid "No model found." msgid "No model found."
msgstr "Aucun modèle trouvé." msgstr "Aucun modèle trouvé."
#: logs/views.py:492 #: logs/views.py:510
msgid "Nonexistent entry." msgid "Nonexistent entry."
msgstr "Entrée inexistante." msgstr "Entrée inexistante."
#: logs/views.py:499 #: logs/views.py:517
msgid "You don't have the right to access this menu." msgid "You don't have the right to access this menu."
msgstr "Vous n'avez pas le droit d'accéder à ce menu." msgstr "Vous n'avez pas le droit d'accéder à ce menu."

View file

@ -284,6 +284,24 @@ def stats_general(request):
_all_whitelisted.exclude(adherent__isnull=True).count(), _all_whitelisted.exclude(adherent__isnull=True).count(),
_all_whitelisted.exclude(club__isnull=True).count(), _all_whitelisted.exclude(club__isnull=True).count(),
], ],
"email_state_verified_users": [
_("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 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(),
],
"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": [ "actives_interfaces": [
_("Active interfaces (with access to the network)"), _("Active interfaces (with access to the network)"),
_all_active_interfaces_count.count(), _all_active_interfaces_count.count(),

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-06-23 16:35+0200\n" "PO-Revision-Date: 2018-06-23 16:35+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"
@ -55,91 +55,91 @@ msgstr "Sélectionnez un type de machine"
msgid "Automatic IPv4 assignment" msgid "Automatic IPv4 assignment"
msgstr "Assignation automatique IPv4" msgstr "Assignation automatique IPv4"
#: machines/forms.py:177 #: machines/forms.py:172
msgid "Current aliases" msgid "Current aliases"
msgstr "Alias actuels" msgstr "Alias actuels"
#: machines/forms.py:199 #: machines/forms.py:194
msgid "Machine type to add" msgid "Machine type to add"
msgstr "Type de machine à ajouter" msgstr "Type de machine à ajouter"
#: machines/forms.py:200 #: machines/forms.py:195
msgid "Related IP type" msgid "Related IP type"
msgstr "Type d'IP relié" msgstr "Type d'IP relié"
#: machines/forms.py:208 #: machines/forms.py:203
msgid "Current machine types" msgid "Current machine types"
msgstr "Types de machines actuels" msgstr "Types de machines actuels"
#: machines/forms.py:232 #: machines/forms.py:227
msgid "IP type to add" msgid "IP type to add"
msgstr "Type d'IP à ajouter" msgstr "Type d'IP à ajouter"
#: machines/forms.py:260 #: machines/forms.py:255
msgid "Current IP types" msgid "Current IP types"
msgstr "Types d'IP actuels" msgstr "Types d'IP actuels"
#: machines/forms.py:283 #: machines/forms.py:278
msgid "Extension to add" msgid "Extension to add"
msgstr "Extension à ajouter" 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" msgid "A record origin"
msgstr "Enregistrement A 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" msgid "AAAA record origin"
msgstr "Enregistrement AAAA origin" msgstr "Enregistrement AAAA origin"
#: machines/forms.py:286 #: machines/forms.py:281
msgid "SOA record to use" msgid "SOA record to use"
msgstr "Enregistrement SOA à utiliser" msgstr "Enregistrement SOA à utiliser"
#: machines/forms.py:287 #: machines/forms.py:282
msgid "Sign with DNSSEC" msgid "Sign with DNSSEC"
msgstr "Signer avec DNSSEC" msgstr "Signer avec DNSSEC"
#: machines/forms.py:295 #: machines/forms.py:290
msgid "Current extensions" msgid "Current extensions"
msgstr "Extensions actuelles" msgstr "Extensions actuelles"
#: machines/forms.py:337 #: machines/forms.py:332
msgid "Current SOA records" msgid "Current SOA records"
msgstr "Enregistrements SOA actuels" msgstr "Enregistrements SOA actuels"
#: machines/forms.py:370 #: machines/forms.py:365
msgid "Current MX records" msgid "Current MX records"
msgstr "Enregistrements MX actuels" msgstr "Enregistrements MX actuels"
#: machines/forms.py:405 #: machines/forms.py:400
msgid "Current NS records" msgid "Current NS records"
msgstr "Enregistrements NS actuels" msgstr "Enregistrements NS actuels"
#: machines/forms.py:435 #: machines/forms.py:430
msgid "Current TXT records" msgid "Current TXT records"
msgstr "Enregistrements TXT actuels" msgstr "Enregistrements TXT actuels"
#: machines/forms.py:465 #: machines/forms.py:460
msgid "Current DNAME records" msgid "Current DNAME records"
msgstr "Enregistrements DNAME actuels" msgstr "Enregistrements DNAME actuels"
#: machines/forms.py:495 #: machines/forms.py:490
msgid "Current SRV records" msgid "Current SRV records"
msgstr "Enregistrements SRV actuels" msgstr "Enregistrements SRV actuels"
#: machines/forms.py:526 #: machines/forms.py:521
msgid "Current NAS devices" msgid "Current NAS devices"
msgstr "Dispositifs NAS actuels" msgstr "Dispositifs NAS actuels"
#: machines/forms.py:559 #: machines/forms.py:554
msgid "Current roles" msgid "Current roles"
msgstr "Rôles actuels" msgstr "Rôles actuels"
#: machines/forms.py:601 #: machines/forms.py:596
msgid "Current services" msgid "Current services"
msgstr "Services actuels" msgstr "Services actuels"
#: machines/forms.py:643 #: machines/forms.py:638
msgid "Current VLANs" msgid "Current VLANs"
msgstr "VLANs actuels" msgstr "VLANs actuels"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2019-11-16 00:22+0100\n" "PO-Revision-Date: 2019-11-16 00:22+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -66,8 +66,10 @@ class EditOptionalUserForm(ModelForm):
self.fields["gpg_fingerprint"].label = _("GPG fingerprint") self.fields["gpg_fingerprint"].label = _("GPG fingerprint")
self.fields["all_can_create_club"].label = _("All can create a club") 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["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["self_adhesion"].label = _("Self registration")
self.fields["shell_default"].label = _("Default shell") 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): class EditOptionalMachineForm(ModelForm):

File diff suppressed because it is too large Load diff

View file

@ -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.'),
),
]

View file

@ -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.')
),
]

View file

@ -107,6 +107,12 @@ class OptionalUser(AclMixin, PreferencesModel):
"Not yet active users will be deleted after this number of days." "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( self_adhesion = models.BooleanField(
default=False, help_text=_("A new user can create their account on Re2o.") default=False, help_text=_("A new user can create their account on Re2o.")
) )
@ -117,6 +123,15 @@ class OptionalUser(AclMixin, PreferencesModel):
" If False, only when a valid registration has been paid." " 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( allow_archived_connexion = models.BooleanField(
default=False, help_text=_("If True, archived users are allowed to connect.") default=False, help_text=_("If True, archived users are allowed to connect.")
) )

View file

@ -128,6 +128,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% trans "Allow archived users to log in" %}</th> <th>{% trans "Allow archived users to log in" %}</th>
<td>{{ useroptions.allow_archived_connexion|tick }}</td> <td>{{ useroptions.allow_archived_connexion|tick }}</td>
</tr> </tr>
<tr>
<th>{% trans "Allow directly entering a password during account creation" %}</th>
<td>{{ useroptions.allow_set_password_during_user_creation|tick }}</td>
<th>{% trans "Delay before disabling accounts without a verified email" %}</th>
<td>{% blocktrans with disable_emailnotyetconfirmed=useroptions.disable_emailnotyetconfirmed %}{{ disable_emailnotyetconfirmed }} days{% endblocktrans %}</td>
</tr>
</table> </table>
<h4 id="users">{% trans "Users general permissions" %}</h4> <h4 id="users">{% trans "Users general permissions" %}</h4>

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -117,6 +117,7 @@ def all_has_access(search_time=None, including_asso=True):
search_time = timezone.now() search_time = timezone.now()
filter_user = ( filter_user = (
Q(state=User.STATE_ACTIVE) Q(state=User.STATE_ACTIVE)
& ~Q(email_state=User.EMAIL_STATE_UNVERIFIED)
& ~Q( & ~Q(
ban__in=Ban.objects.filter( ban__in=Ban.objects.filter(
Q(date_start__lt=search_time) & Q(date_end__gt=search_time) Q(date_start__lt=search_time) & Q(date_end__gt=search_time)

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 23:43+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-06-24 20:10+0200\n" "PO-Revision-Date: 2018-06-24 20:10+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"
@ -82,34 +82,33 @@ msgstr "Ports"
msgid "Switches" msgid "Switches"
msgstr "Commutateurs réseau" 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 #: search/templates/search/search.html:48
msgid "Search" msgid "Search"
msgstr "Rechercher" msgstr "Rechercher"
#: search/forms.py:65 search/forms.py:80 #: search/forms.py:65 search/forms.py:81
msgid "" msgid ""
"Use « » and «,» to specify distinct words, «\"query\"» for" "Use « » and «,» to specify distinct words, «\"query\"» for an exact search, "
" an exact search, «\\» to escape a character and «+» to" "«\\» to escape a character and «+» to combine keywords."
" combine keywords."
msgstr "" msgstr ""
"Utilisez « » et «,» pour spécifier différents mots, «\"recherche\"» pour" "Utilisez « » et «,» pour spécifier différents mots, «\"recherche\"» pour une "
" une recherche exacte, «\\» pour échapper un caractère et «+» pour" "recherche exacte, «\\» pour échapper un caractère et «+» pour combiner des "
" combiner des mots clés." "mots clés."
#: search/forms.py:88 #: search/forms.py:90
msgid "Users filter" msgid "Users filter"
msgstr "Filtre utilisateurs" msgstr "Filtre utilisateurs"
#: search/forms.py:95 #: search/forms.py:97
msgid "Display filter" msgid "Display filter"
msgstr "Filtre affichage" msgstr "Filtre affichage"
#: search/forms.py:101 #: search/forms.py:103
msgid "Start date" msgid "Start date"
msgstr "Date de début" msgstr "Date de début"
#: search/forms.py:102 #: search/forms.py:104
msgid "End date" msgid "End date"
msgstr "Date de fin" msgstr "Date de fin"
@ -117,47 +116,47 @@ msgstr "Date de fin"
msgid "Search results" msgid "Search results"
msgstr "Résultats de la recherche" msgstr "Résultats de la recherche"
#: search/templates/search/index.html:33 #: search/templates/search/index.html:34
msgid "Results among users:" msgid "Results among users:"
msgstr "Résultats parmi les utilisateurs :" msgstr "Résultats parmi les utilisateurs :"
#: search/templates/search/index.html:37 #: search/templates/search/index.html:44
msgid "Results among clubs:" msgid "Results among clubs:"
msgstr "Résultats parmi les clubs :" msgstr "Résultats parmi les clubs :"
#: search/templates/search/index.html:41 #: search/templates/search/index.html:54
msgid "Results among machines:" msgid "Results among machines:"
msgstr "Résultats parmi les machines :" msgstr "Résultats parmi les machines :"
#: search/templates/search/index.html:45 #: search/templates/search/index.html:64
msgid "Results among invoices:" msgid "Results among invoices:"
msgstr "Résultats parmi les factures :" msgstr "Résultats parmi les factures :"
#: search/templates/search/index.html:49 #: search/templates/search/index.html:74
msgid "Results among whitelists:" msgid "Results among whitelists:"
msgstr "Résultats parmi les accès à titre gracieux :" 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:" msgid "Results among bans:"
msgstr "Résultats parmi les bannissements :" msgstr "Résultats parmi les bannissements :"
#: search/templates/search/index.html:57 #: search/templates/search/index.html:94
msgid "Results among rooms:" msgid "Results among rooms:"
msgstr "Résultats parmi les chambres :" msgstr "Résultats parmi les chambres :"
#: search/templates/search/index.html:61 #: search/templates/search/index.html:104
msgid "Results among ports:" msgid "Results among ports:"
msgstr "Résultats parmi les ports :" msgstr "Résultats parmi les ports :"
#: search/templates/search/index.html:65 #: search/templates/search/index.html:114
msgid "Results among switches:" msgid "Results among switches:"
msgstr "Résultats parmi les commutateurs réseau :" msgstr "Résultats parmi les commutateurs réseau :"
#: search/templates/search/index.html:69 #: search/templates/search/index.html:123
msgid "No result" msgid "No result"
msgstr "Pas de résultat" msgstr "Pas de résultat"
#: search/templates/search/index.html:71 #: search/templates/search/index.html:125
#, python-format #, python-format
msgid "Only the first %(max_result)s results are displayed in each category." msgid "Only the first %(max_result)s results are displayed in each category."
msgstr "" msgstr ""
@ -171,3 +170,12 @@ msgstr "Recherche simple"
#: search/templates/search/sidebar.html:35 #: search/templates/search/sidebar.html:35
msgid "Advanced search" msgid "Advanced search"
msgstr "Recherche avancée" 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"

View file

@ -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();

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19 23:43+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \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" msgid "Go back to a safe page"
msgstr "Retourner à une page sécurisée" msgstr "Retourner à une page sécurisée"
#: templates/pagination.html:34 #: templates/pagination.html:35
msgid "First" msgid "First"
msgstr "Première page" msgstr "Première page"
#: templates/pagination.html:40 #: templates/pagination.html:41
msgid "Previous" msgid "Previous"
msgstr "Précédent" msgstr "Précédent"
#: templates/pagination.html:60 #: templates/pagination.html:61
msgid "Next" msgid "Next"
msgstr "Suivant" msgstr "Suivant"
#: templates/pagination.html:66 #: templates/pagination.html:67
msgid "Last" msgid "Last"
msgstr "Dernière page" msgstr "Dernière page"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2019-11-16 00:35+0100\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-20 01:24+0100\n" "POT-Creation-Date: 2020-04-18 01:38+0200\n"
"PO-Revision-Date: 2018-06-25 14:53+0200\n" "PO-Revision-Date: 2018-06-25 14:53+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -3,9 +3,11 @@
# se veut agnostique au réseau considéré, de manière à être installable en # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
# #
# Copyright © 2017 Gabriel Détraz # Copyright © 2017-2020 Gabriel Détraz
# Copyright © 2017 Lara Kermarec # Copyright © 2017-2020 Lara Kermarec
# Copyright © 2017 Augustin Lemesle # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -113,6 +115,7 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm):
"""Changement du mot de passe""" """Changement du mot de passe"""
user = super(PassForm, self).save(commit=False) user = super(PassForm, self).save(commit=False)
user.set_password(self.cleaned_data.get("passwd1")) user.set_password(self.cleaned_data.get("passwd1"))
user.state = User.STATE_NOT_YET_ACTIVE
user.set_active() user.set_active()
user.save() user.save()
@ -380,7 +383,42 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
class AdherentCreationForm(AdherentForm): class AdherentCreationForm(AdherentForm):
"""Formulaire de création d'un user. """Formulaire de création d'un user.
AdherentForm auquel on ajoute une checkbox afin d'éviter les 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_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.")
# 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 # Champ permettant d'éviter au maxium les doublons d'utilisateurs
former_user_check_info = _( former_user_check_info = _(
@ -422,6 +460,53 @@ 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")
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)
is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation")
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.save()
return user
class AdherentEditForm(AdherentForm): class AdherentEditForm(AdherentForm):
"""Formulaire d'édition d'un user. """Formulaire d'édition d'un user.
@ -565,22 +650,17 @@ class EditServiceUserForm(ServiceUserForm):
class StateForm(FormRevMixin, ModelForm): 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: class Meta:
model = User model = User
fields = ["state"] fields = ["state", "email_state"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
prefix = kwargs.pop("prefix", self.Meta.model.__name__) prefix = kwargs.pop("prefix", self.Meta.model.__name__)
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs) super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields["state"].label = _("State")
def save(self, commit=True): self.fields["email_state"].label = _("Email state")
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): class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
from users.models import User from users.models import User
from cotisations.models import Facture from cotisations.models import Facture
@ -31,9 +32,9 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
"""First deleting invalid invoices, and then deleting the users""" """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 = ( users_to_delete = (
User.objects.filter(state=User.STATE_NOT_YET_ACTIVE) User.objects.filter(Q(state=User.STATE_NOT_YET_ACTIVE))
.filter(registered__lte=timezone.now() - timedelta(days=days)) .filter(registered__lte=timezone.now() - timedelta(days=days))
.exclude(facture__valid=True) .exclude(facture__valid=True)
.distinct() .distinct()

View file

@ -0,0 +1,43 @@
# 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
# 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 = "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("disable_emailnotyetconfirmed")
users_to_disable = (
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.email_state = User.EMAIL_STATE_UNVERIFIED
user.notif_disable()
user.save()

View file

@ -0,0 +1,29 @@
# -*- 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)
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, undo_flag_verified),
]

View file

@ -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_user_email_state'),
]
operations = [
migrations.AddField(
model_name='user',
name='email_change_date',
field=models.DateTimeField(default=None, null=True),
),
]

View file

@ -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),
),
]

View file

@ -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,
),
]

View file

@ -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),
),
]

View file

@ -3,9 +3,11 @@
# Il se veut agnostique au réseau considéré, de manière à être installable # Il se veut agnostique au réseau considéré, de manière à être installable
# en quelques clics. # en quelques clics.
# #
# Copyright © 2017 Gabriel Détraz # Copyright © 2017-2020 Gabriel Détraz
# Copyright © 2017 Lara Kermarec # Copyright © 2017-2020 Lara Kermarec
# Copyright © 2017 Augustin Lemesle # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -62,6 +64,7 @@ from django.core.mail import send_mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.utils import timezone from django.utils import timezone
from datetime import timedelta
from django.contrib.auth.models import ( from django.contrib.auth.models import (
AbstractBaseUser, AbstractBaseUser,
BaseUserManager, BaseUserManager,
@ -144,6 +147,7 @@ class UserManager(BaseUserManager):
) )
user.set_password(password) user.set_password(password)
user.confirm_mail()
if su: if su:
user.is_superuser = True user.is_superuser = True
user.save(using=self._db) user.save(using=self._db)
@ -184,6 +188,15 @@ class User(
(4, _("Fully archived")), (4, _("Fully archived")),
) )
EMAIL_STATE_VERIFIED = 0
EMAIL_STATE_UNVERIFIED = 1
EMAIL_STATE_PENDING = 2
EMAIL_STATES = (
(0, _("Confirmed")),
(1, _("Not confirmed")),
(2, _("Waiting for email confirmation")),
)
surname = models.CharField(max_length=255) surname = models.CharField(max_length=255)
pseudo = models.CharField( pseudo = models.CharField(
max_length=32, max_length=32,
@ -217,6 +230,7 @@ class User(
) )
pwd_ntlm = models.CharField(max_length=255) pwd_ntlm = models.CharField(max_length=255)
state = models.IntegerField(choices=STATES, default=STATE_NOT_YET_ACTIVE) 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) registered = models.DateTimeField(auto_now_add=True)
telephone = models.CharField(max_length=15, blank=True, null=True) telephone = models.CharField(max_length=15, blank=True, null=True)
uid_number = models.PositiveIntegerField(default=get_fresh_user_uid, unique=True) uid_number = models.PositiveIntegerField(default=get_fresh_user_uid, unique=True)
@ -224,6 +238,7 @@ class User(
shortcuts_enabled = models.BooleanField( shortcuts_enabled = models.BooleanField(
verbose_name=_("enable shortcuts on Re2o website"), default=True verbose_name=_("enable shortcuts on Re2o website"), default=True
) )
email_change_date = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = "pseudo" USERNAME_FIELD = "pseudo"
REQUIRED_FIELDS = ["surname", "email"] REQUIRED_FIELDS = ["surname", "email"]
@ -335,7 +350,7 @@ class User(
def set_active(self): def set_active(self):
"""Enable this user if he subscribed successfully one time before """Enable this user if he subscribed successfully one time before
Reenable it if it was archived 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.state == self.STATE_NOT_YET_ACTIVE:
if self.facture_set.filter(valid=True).filter( if self.facture_set.filter(valid=True).filter(
Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion") Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion")
@ -388,7 +403,7 @@ class User(
@cached_property @cached_property
def get_shadow_expire(self): def get_shadow_expire(self):
"""Return the shadow_expire value for the user""" """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) return str(0)
else: else:
return None return None
@ -481,6 +496,7 @@ class User(
""" Renvoie si un utilisateur a accès à internet """ """ Renvoie si un utilisateur a accès à internet """
return ( return (
self.state == User.STATE_ACTIVE self.state == User.STATE_ACTIVE
and self.email_state != User.EMAIL_STATE_UNVERIFIED
and not self.is_ban() and not self.is_ban()
and (self.is_connected() or self.is_whitelisted()) and (self.is_connected() or self.is_whitelisted())
) or self == AssoOption.get_cached_value("utilisateur_asso") ) or self == AssoOption.get_cached_value("utilisateur_asso")
@ -783,6 +799,90 @@ class User(
) )
return return
def send_confirm_email_if_necessary(self, request):
"""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
# 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()
# 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
# Send the email
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.save()
self.confirm_email_address_mail(request)
return True
def confirm_email_before_date(self):
if self.email_state == self.EMAIL_STATE_VERIFIED:
return None
days = OptionalUser.get_cached_value("disable_emailnotyetconfirmed")
return 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"""
# 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")
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")),
"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 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"),
[req.user.email],
fail_silently=False,
)
return
def autoregister_machine(self, mac_address, nas_type): def autoregister_machine(self, mac_address, nas_type):
""" Fonction appellée par freeradius. Enregistre la mac pour """ Fonction appellée par freeradius. Enregistre la mac pour
une machine inconnue sur le compte de l'user""" une machine inconnue sur le compte de l'user"""
@ -836,6 +936,24 @@ class User(
) )
return 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): def set_password(self, password):
""" A utiliser de préférence, set le password en hash courrant et """ A utiliser de préférence, set le password en hash courrant et
dans la version ntlm""" dans la version ntlm"""
@ -845,6 +963,10 @@ class User(
self.pwd_ntlm = hashNT(password) self.pwd_ntlm = hashNT(password)
return return
def confirm_mail(self):
"""Marque l'email de l'utilisateur comme confirmé"""
self.email_state = self.EMAIL_STATE_VERIFIED
@cached_property @cached_property
def email_address(self): def email_address(self):
if ( if (
@ -1190,6 +1312,7 @@ class User(
"room": self.can_change_room, "room": self.can_change_room,
} }
self.__original_state = self.state self.__original_state = self.state
self.__original_email = self.email
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
"""Check if this pseudo is already used by any mailalias. """Check if this pseudo is already used by any mailalias.
@ -1771,6 +1894,7 @@ class Request(models.Model):
type = models.CharField(max_length=2, choices=TYPE_CHOICES) type = models.CharField(max_length=2, choices=TYPE_CHOICES)
token = models.CharField(max_length=32) token = models.CharField(max_length=32)
user = models.ForeignKey("User", on_delete=models.CASCADE) 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) created_at = models.DateTimeField(auto_now_add=True, editable=False)
expires_at = models.DateTimeField() expires_at = models.DateTimeField()

View file

@ -0,0 +1,46 @@
{% 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-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
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 %}
<form class="form" method="post">
{% csrf_token %}
<h4>{% blocktrans %}Confirmation email{% endblocktrans %}</h4>
<p>{% blocktrans %}Confirm the email{% endblocktrans %} <a href="mailto:{{ email }}">{{ email }}</a> {% blocktrans %}for {{ name }}.{% endblocktrans %}</p>
{% trans "Confirm" as tr_confirm %}
{% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
</form>
<br />
<br />
<br />
{% endblock %}

View file

@ -0,0 +1,39 @@
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.
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_fr }}, votre compte sera suspendu.
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.
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_en }}, your account will be suspended.
Regards,
The {{ asso }} team (contact: {{ asso_mail }}).

View file

@ -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.

View file

@ -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 se veut agnostique au réseau considéré, de manière à être installable en
quelques clics. quelques clics.
Copyright © 2017 Gabriel Détraz Copyright © 2017-2020 Gabriel Détraz
Copyright © 2017 Lara Kermarec Copyright © 2017-2020 Lara Kermarec
Copyright © 2017 Augustin Lemesle 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 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 it under the terms of the GNU General Public License as published by
@ -38,6 +40,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<h2>{% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}</h2> <h2>{% blocktrans with name=users.name surname=users.surname %}Profile of {{ name }} {{ surname }}{% endblocktrans %}</h2>
{% endif %} {% endif %}
</div> </div>
{% if users.email_state == users.EMAIL_STATE_PENDING %}
<div class="alert alert-warning">
{% 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 %}
<br/>
<a href="{% url 'users:resend-confirmation-email' users.id %}">
{% blocktrans %}Didn't receive the email?{% endblocktrans %}
</a>
</div>
{% elif users.email_state == users.EMAIL_STATE_UNVERIFIED %}
<div class="alert alert-danger">
{% blocktrans %}Your account has been suspended, please confirm your email address.{% endblocktrans %}
</div>
{% endif %}
<div class="dashboard_container"> <div class="dashboard_container">
<div class="row"> <div class="row">
<div class="col-sm-6 {% if solde_activated %}col-md-4{% else %}col_md-6{% endif %}"> <div class="col-sm-6 {% if solde_activated %}col-md-4{% else %}col_md-6{% endif %}">
@ -51,7 +70,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% elif not users.has_access %} {% elif not users.has_access %}
<div class="panel panel-danger"> <div class="panel panel-danger">
<div class="panel-heading dashboard">{% trans "No connection" %}</div> <div class="panel-heading dashboard">{% trans "No connection" %}</div>
<div class="panel-body dashboard"> <div class="panel-body dashboard">
{% if users.email_state == users.EMAIL_STATE_UNVERIFIED %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'users:resend-confirmation-email' users.id %}">
<i class="fa fa-sign-in"></i> {% trans "Resend the email" %}
</a>
{% else %}
{% can_create Facture %} {% can_create Facture %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:new-facture' users.id %}"> <a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:new-facture' users.id %}">
<i class="fa fa-sign-in"></i> {% trans "Pay for a connection" %} <i class="fa fa-sign-in"></i> {% trans "Pay for a connection" %}
@ -59,6 +84,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% acl_else %} {% acl_else %}
{% trans "Ask someone with the appropriate rights to pay for a connection." %} {% trans "Ask someone with the appropriate rights to pay for a connection." %}
{% acl_end %} {% acl_end %}
{% endif %}
</div> </div>
</div> </div>
{% else %} {% else %}
@ -181,7 +207,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<div class="col-md-6"> <div class="col-md-6">
<dt>{% trans "Email address" %}</dt> <dt>{% trans "Email address" %}</dt>
<dd><a href="mailto:{{ users.email }}">{{ users.email }}</a></dd> <dd><a href="mailto:{{ users.email }}">{{ users.email }}</a>{% if users.email_state != users.EMAIL_STATE_VERIFIED %}<br/><i class="text-warning">{% trans "Pending confirmation..." %}</i>{% endif %}</dd>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -0,0 +1,42 @@
{% 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 © 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
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 %}
<form class="form" method="post">
{% csrf_token %}
<h4>{% blocktrans %}Re-send confirmation email?{% endblocktrans %}</h4>
<p>{% blocktrans %}The confirmation email will be sent to{% endblocktrans %} <a href="mailto:{{ email }}">{{ email }}</a>.</p>
{% trans "Confirm" as tr_confirm %}
{% bootstrap_button tr_confirm button_type="submit" icon="ok" button_class="btn-success" %}
</form>
<br />
<br />
<br />
{% endblock %}

View file

@ -3,9 +3,11 @@
# se veut agnostique au réseau considéré, de manière à être installable en # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
# #
# Copyright © 2017 Gabriel Détraz # Copyright © 2017-2020 Gabriel Détraz
# Copyright © 2017 Lara Kermarec # Copyright © 2017-2020 Lara Kermarec
# Copyright © 2017 Augustin Lemesle # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -42,6 +44,7 @@ urlpatterns = [
url(r"^state/(?P<userid>[0-9]+)$", views.state, name="state"), url(r"^state/(?P<userid>[0-9]+)$", views.state, name="state"),
url(r"^groups/(?P<userid>[0-9]+)$", views.groups, name="groups"), url(r"^groups/(?P<userid>[0-9]+)$", views.groups, name="groups"),
url(r"^password/(?P<userid>[0-9]+)$", views.password, name="password"), url(r"^password/(?P<userid>[0-9]+)$", views.password, name="password"),
url(r"^confirm_email/(?P<userid>[0-9]+)$", views.resend_confirmation_email, name="resend-confirmation-email"),
url( url(
r"^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$", r"^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$",
views.del_group, views.del_group,

View file

@ -3,9 +3,11 @@
# se veut agnostique au réseau considéré, de manière à être installable en # se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics. # quelques clics.
# #
# Copyright © 2017 Gabriel Détraz # Copyright © 2017-2020 Gabriel Détraz
# Copyright © 2017 Lara Kermarec # Copyright © 2017-2020 Lara Kermarec
# Copyright © 2017 Augustin Lemesle # 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 # 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 # it under the terms of the GNU General Public License as published by
@ -119,26 +121,42 @@ def new_user(request):
user = AdherentCreationForm(request.POST or None, user=request.user) user = AdherentCreationForm(request.POST or None, user=request.user)
GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up") GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up")
GTU = GeneralOption.get_cached_value("GTU") GTU = GeneralOption.get_cached_value("GTU")
is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation")
if user.is_valid(): if user.is_valid():
user = user.save() user = user.save()
if user.password:
user.send_confirm_email_if_necessary(request)
messages.success(
request,
_("The user %s was created, a confirmation email was sent.")
% user.pseudo,
)
else:
user.reset_passwd_mail(request) user.reset_passwd_mail(request)
messages.success( messages.success(
request, request,
_("The user %s was created, an email to set the password was sent.") _("The user %s was created, an email to set the password was sent.")
% user.pseudo, % user.pseudo,
) )
return redirect(reverse("users:profil", kwargs={"userid": str(user.id)})) return redirect(reverse("users:profil", kwargs={"userid": str(user.id)}))
return form(
{ # Anonymous users are allowed to create new accounts
# but they should be treated differently
params = {
"userform": user, "userform": user,
"GTU_sum_up": GTU_sum_up, "GTU_sum_up": GTU_sum_up,
"GTU": GTU, "GTU": GTU,
"showCGU": True, "showCGU": True,
"action_name": _("Commit"), "action_name": _("Commit"),
}, }
"users/user.html",
request, if is_set_password_allowed:
) params["load_js_file"] = "/static/js/toggle_password_fields.js"
return form(params, "users/user.html", request)
@login_required @login_required
@ -204,8 +222,12 @@ def edit_info(request, user, userid):
) )
if user_form.is_valid(): if user_form.is_valid():
if user_form.changed_data: if user_form.changed_data:
user_form.save() user = user_form.save()
messages.success(request, _("The user was edited.")) 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 redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
return form( return form(
{"userform": user_form, "action_name": _("Edit")}, {"userform": user_form, "action_name": _("Edit")},
@ -221,8 +243,10 @@ def state(request, user, userid):
state_form = StateForm(request.POST or None, instance=user) state_form = StateForm(request.POST or None, instance=user)
if state_form.is_valid(): if state_form.is_valid():
if state_form.changed_data: if state_form.changed_data:
state_form.save() user_instance = state_form.save()
messages.success(request, _("The state was edited.")) 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 redirect(reverse("users:profil", kwargs={"userid": str(userid)}))
return form( return form(
{"userform": state_form, "action_name": _("Edit")}, {"userform": state_form, "action_name": _("Edit")},
@ -522,6 +546,10 @@ def edit_email_settings(request, user_instance, **_kwargs):
if email_settings.changed_data: if email_settings.changed_data:
email_settings.save() email_settings.save()
messages.success(request, _("The email settings were edited.")) 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( return redirect(
reverse("users:profil", kwargs={"userid": str(user_instance.id)}) reverse("users:profil", kwargs={"userid": str(user_instance.id)})
) )
@ -974,12 +1002,15 @@ def reset_password(request):
def process(request, token): 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()) valid_reqs = Request.objects.filter(expires_at__gt=timezone.now())
req = get_object_or_404(valid_reqs, token=token) req = get_object_or_404(valid_reqs, token=token)
if req.type == Request.PASSWD: if req.type == Request.PASSWD:
return process_passwd(request, req) return process_passwd(request, req)
elif req.type == Request.EMAIL:
return process_email(request, req)
else: else:
messages.error(request, _("Error: please contact an admin.")) messages.error(request, _("Error: please contact an admin."))
redirect(reverse("index")) redirect(reverse("index"))
@ -992,9 +1023,12 @@ def process_passwd(request, req):
u_form = PassForm(request.POST or None, instance=user, user=request.user) u_form = PassForm(request.POST or None, instance=user, user=request.user)
if u_form.is_valid(): if u_form.is_valid():
with transaction.atomic(), reversion.create_revision(): with transaction.atomic(), reversion.create_revision():
user.confirm_mail()
u_form.save() u_form.save()
reversion.set_comment("Password reset") 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.")) messages.success(request, _("The password was changed."))
return redirect(reverse("index")) return redirect(reverse("index"))
return form( return form(
@ -1004,6 +1038,52 @@ 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")
# 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"))
return form(
{"email": user.email, "name": user.get_full_name()},
"users/confirm_email.html",
request
)
@login_required
@can_edit(User)
def resend_confirmation_email(request, logged_user, userid):
""" Renvoi du mail de confirmation """
try:
user = User.objects.get(
id=userid,
email_state__in=[User.EMAIL_STATE_PENDING, User.EMAIL_STATE_UNVERIFIED],
)
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(
{"email": user.email},
"users/resend_confirmation_email.html",
request
)
@login_required @login_required
def initial_register(request): def initial_register(request):
switch_ip = request.GET.get("switch_ip", None) switch_ip = request.GET.get("switch_ip", None)