mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 15:33:45 +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:
commit
f8b6044938
37 changed files with 1689 additions and 776 deletions
|
@ -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"
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
@ -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.'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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.')
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
|
@ -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.")
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
24
static/js/toggle_password_fields.js
Normal file
24
static/js/toggle_password_fields.js
Normal 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();
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
106
users/forms.py
106
users/forms.py
|
@ -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
|
@ -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()
|
||||||
|
|
43
users/management/commands/disable_emailnotyetconfirmed.py
Normal file
43
users/management/commands/disable_emailnotyetconfirmed.py
Normal 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()
|
29
users/migrations/0085_user_email_state.py
Normal file
29
users/migrations/0085_user_email_state.py
Normal 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),
|
||||||
|
]
|
20
users/migrations/0086_user_email_change_date.py
Normal file
20
users/migrations/0086_user_email_change_date.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
20
users/migrations/0087_request_email.py
Normal file
20
users/migrations/0087_request_email.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
23
users/migrations/0088_auto_20200417_2312.py
Normal file
23
users/migrations/0088_auto_20200417_2312.py
Normal 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,
|
||||||
|
),
|
||||||
|
]
|
20
users/migrations/0089_auto_20200418_0112.py
Normal file
20
users/migrations/0089_auto_20200418_0112.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
134
users/models.py
134
users/models.py
|
@ -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()
|
||||||
|
|
||||||
|
|
46
users/templates/users/confirm_email.html
Normal file
46
users/templates/users/confirm_email.html
Normal 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 %}
|
||||||
|
|
39
users/templates/users/email_confirmation_request
Normal file
39
users/templates/users/email_confirmation_request
Normal 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 }}).
|
20
users/templates/users/email_disable_notif
Normal file
20
users/templates/users/email_disable_notif
Normal 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.
|
|
@ -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">
|
||||||
|
|
42
users/templates/users/resend_confirmation_email.html
Normal file
42
users/templates/users/resend_confirmation_email.html
Normal 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 %}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
108
users/views.py
108
users/views.py
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue