From b4edd7f4dcef4f757098e93ec8eb6cdf2fe1a04f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 22 Apr 2020 18:12:37 +0200 Subject: [PATCH 1/7] Create preferences models and edition template --- preferences/locale/fr/LC_MESSAGES/django.po | 346 ++++++++++---------- preferences/models.py | 26 +- preferences/urls.py | 19 +- preferences/utils/__init__.py | 0 preferences/utils/models.py | 55 ++++ preferences/utils/views.py | 68 ++++ preferences/views.py | 28 +- 7 files changed, 310 insertions(+), 232 deletions(-) create mode 100644 preferences/utils/__init__.py create mode 100644 preferences/utils/models.py create mode 100644 preferences/utils/views.py diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index c655e7a3..d034ac9b 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -256,57 +256,57 @@ msgstr "Modèles de document actuels" msgid "Current attributes" msgstr "Attributs actuels" -#: preferences/models.py:78 +#: preferences/models.py:56 msgid "Users can't select their room" msgstr "Les utilisateurs ne peuvent pas modifier leur chambre" -#: preferences/models.py:79 +#: preferences/models.py:57 msgid "" "Users can only select a room occupied by a user with a disabled connection." msgstr "" "Les utilisateurs peuvent sélectionner la chambre d'un adhérent dont la " "connexion est désactivée." -#: preferences/models.py:80 +#: preferences/models.py:58 msgid "Users can select all rooms" msgstr "Les utilisateurs peuvent choisir toutes les chambres" -#: preferences/models.py:86 +#: preferences/models.py:64 msgid "Users can create a club." msgstr "Les utilisateurs peuvent créer un club." -#: preferences/models.py:89 +#: preferences/models.py:67 msgid "Users can create a member." msgstr "Les utilisateurs peuvent créer un adhérent." -#: preferences/models.py:95 +#: preferences/models.py:73 msgid "Users can edit their shell." msgstr "Les utilisateurs peuvent modifier leur interface en ligne de commande." -#: preferences/models.py:101 +#: preferences/models.py:79 msgid "Policy on self users room edition" msgstr "Autorisation d'édtion du champ chambre par les utilisateurs" -#: preferences/models.py:104 +#: preferences/models.py:82 msgid "Enable local email accounts for users." msgstr "Activer les comptes mail locaux pour les utilisateurs." -#: preferences/models.py:109 +#: preferences/models.py:87 msgid "Domain to use for local email accounts." msgstr "Domaine à utiliser pour les comptes mail locaux." -#: preferences/models.py:113 +#: preferences/models.py:91 msgid "Maximum number of local email addresses for a standard user." msgstr "" "Nombre maximum d'adresses mail locales autorisé pour un utilisateur standard." -#: preferences/models.py:118 +#: preferences/models.py:96 msgid "Not yet active users will be deleted after this number of days." msgstr "" "Les utilisateurs n'ayant jamais adhéré seront supprimés après ce nombre de " "jours." -#: preferences/models.py:124 +#: preferences/models.py:102 msgid "" "Users with an email address not yet confirmed will be disabled after this " "number of days." @@ -314,11 +314,11 @@ msgstr "" "Les utilisateurs n'ayant pas confirmé leur addresse mail seront désactivés " "après ce nombre de jours" -#: preferences/models.py:128 +#: preferences/models.py:106 msgid "A new user can create their account on Re2o." msgstr "Un nouvel utilisateur peut créer son compte sur Re2o." -#: preferences/models.py:133 +#: preferences/models.py:111 msgid "" "If True, all new created and connected users are active. If False, only when " "a valid registration has been paid." @@ -326,7 +326,7 @@ msgstr "" "Si True, tous les nouveaux utilisations créés et connectés sont actifs. Si " "False, seulement quand une inscription validée a été payée." -#: preferences/models.py:140 +#: preferences/models.py:118 msgid "" "If True, users have the choice to receive an email containing a link to " "reset their password during creation, or to directly set their password in " @@ -337,172 +337,172 @@ msgstr "" "de choisir leur mot de passe immédiatement. Si False, un mail est toujours " "envoyé." -#: preferences/models.py:147 +#: preferences/models.py:125 msgid "If True, archived users are allowed to connect." msgstr "Si True, les utilisateurs archivés sont autorisés à se connecter." -#: preferences/models.py:151 +#: preferences/models.py:129 msgid "Can view the user preferences" msgstr "Peut voir les préférences d'utilisateur" -#: preferences/models.py:152 +#: preferences/models.py:130 msgid "user preferences" msgstr "Préférences d'utilisateur" -#: preferences/models.py:159 +#: preferences/models.py:137 msgid "Email domain must begin with @." msgstr "Un domaine mail doit commencer par @." -#: preferences/models.py:177 +#: preferences/models.py:155 msgid "Automatic configuration by RA" msgstr "Configuration automatique par RA" -#: preferences/models.py:178 +#: preferences/models.py:156 msgid "IP addresses assignment by DHCPv6" msgstr "Attribution d'adresses IP par DHCPv6" -#: preferences/models.py:179 +#: preferences/models.py:157 msgid "Disabled" msgstr "Désactivé" -#: preferences/models.py:188 +#: preferences/models.py:166 msgid "default Time To Live (TTL) for CNAME, A and AAAA records" msgstr "" "Temps de vie (TTL) par défault pour des enregistrements CNAME, A et AAAA" -#: preferences/models.py:198 +#: preferences/models.py:176 msgid "Can view the machine preferences" msgstr "Peut voir les préférences de machine" -#: preferences/models.py:199 +#: preferences/models.py:177 msgid "machine preferences" msgstr "Préférences de machine" -#: preferences/models.py:219 preferences/models.py:677 +#: preferences/models.py:197 preferences/models.py:655 msgid "On the IP range's VLAN of the machine" msgstr "Sur le VLAN de la plage d'IP de la machine" -#: preferences/models.py:220 preferences/models.py:678 +#: preferences/models.py:198 preferences/models.py:656 msgid "Preset in \"VLAN for machines accepted by RADIUS\"" msgstr "Prédéfinie dans « VLAN pour les machines acceptées par RADIUS »" -#: preferences/models.py:226 +#: preferences/models.py:204 msgid "Web management, activated in case of automatic provision." msgstr "Gestion web, activée en cas de provision automatique." -#: preferences/models.py:231 +#: preferences/models.py:209 msgid "" "SSL web management, make sure that a certificate is installed on the switch." msgstr "" "Gestion web SSL, vérifiez qu'un certificat est installé sur le commutateur " "réseau." -#: preferences/models.py:237 +#: preferences/models.py:215 msgid "REST management, activated in case of automatic provision." msgstr "Gestion REST, activée en cas de provision automatique." -#: preferences/models.py:244 +#: preferences/models.py:222 msgid "IP range for the management of switches." msgstr "Plage d'IP pour la gestion des commutateurs réseau." -#: preferences/models.py:250 +#: preferences/models.py:228 msgid "Provision of configuration mode for switches." msgstr "Mode de provision de configuration pour les commutateurs réseau." -#: preferences/models.py:253 +#: preferences/models.py:231 msgid "SFTP login for switches." msgstr "Identifiant SFTP pour les commutateurs réseau." -#: preferences/models.py:256 +#: preferences/models.py:234 msgid "SFTP password." msgstr "Mot de passe SFTP." -#: preferences/models.py:360 +#: preferences/models.py:338 msgid "Can view the topology preferences" msgstr "Peut voir les préférences de topologie" -#: preferences/models.py:361 +#: preferences/models.py:339 msgid "topology preferences" msgstr "préférences de topologie" -#: preferences/models.py:374 +#: preferences/models.py:352 msgid "RADIUS key." msgstr "Clé RADIUS." -#: preferences/models.py:376 +#: preferences/models.py:354 msgid "Comment for this key." msgstr "Commentaire pour cette clé." -#: preferences/models.py:379 +#: preferences/models.py:357 msgid "Default key for switches." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:383 +#: preferences/models.py:361 msgid "Can view a RADIUS key object" msgstr "Peut voir un objet clé RADIUS" -#: preferences/models.py:384 preferences/views.py:331 +#: preferences/models.py:362 preferences/views.py:307 msgid "RADIUS key" msgstr "Clé RADIUS" -#: preferences/models.py:385 +#: preferences/models.py:363 #: preferences/templates/preferences/display_preferences.html:221 msgid "RADIUS keys" msgstr "clés RADIUS" -#: preferences/models.py:392 +#: preferences/models.py:370 msgid "Default RADIUS key for switches already exists." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:395 +#: preferences/models.py:373 msgid "RADIUS key " msgstr "clé RADIUS " -#: preferences/models.py:401 +#: preferences/models.py:379 msgid "Switch login." msgstr "Identifiant du commutateur réseau." -#: preferences/models.py:402 +#: preferences/models.py:380 msgid "Password." msgstr "Mot de passe." -#: preferences/models.py:404 +#: preferences/models.py:382 msgid "Default credentials for switches." msgstr "Identifiants par défaut pour les commutateurs réseau." -#: preferences/models.py:411 +#: preferences/models.py:389 msgid "Can view a switch management credentials object" msgstr "Peut voir un objet identifiants de gestion de commutateur réseau" -#: preferences/models.py:414 preferences/views.py:394 +#: preferences/models.py:392 preferences/views.py:370 msgid "switch management credentials" msgstr "identifiants de gestion de commutateur réseau" -#: preferences/models.py:417 +#: preferences/models.py:395 msgid "Switch login " msgstr "Identifiant du commutateur réseau " -#: preferences/models.py:429 +#: preferences/models.py:407 msgid "Delay between the email and the membership's end." msgstr "Délai entre le mail et la fin d'adhésion." -#: preferences/models.py:435 +#: preferences/models.py:413 msgid "Message displayed specifically for this reminder." msgstr "Message affiché spécifiquement pour ce rappel." -#: preferences/models.py:439 +#: preferences/models.py:417 msgid "Can view a reminder object" msgstr "Peut voir un objet rappel" -#: preferences/models.py:440 preferences/views.py:276 +#: preferences/models.py:418 preferences/views.py:252 msgid "reminder" msgstr "rappel" -#: preferences/models.py:441 +#: preferences/models.py:419 msgid "reminders" msgstr "rappels" -#: preferences/models.py:462 +#: preferences/models.py:440 msgid "" "General message displayed on the French version of the website (e.g. in case " "of maintenance)." @@ -510,7 +510,7 @@ msgstr "" "Message général affiché sur la version française du site (ex : en cas de " "maintenance)." -#: preferences/models.py:470 +#: preferences/models.py:448 msgid "" "General message displayed on the English version of the website (e.g. in " "case of maintenance)." @@ -518,75 +518,75 @@ msgstr "" "Message général affiché sur la version anglaise du site (ex : en cas de " "maintenance)." -#: preferences/models.py:485 +#: preferences/models.py:463 msgid "Can view the general preferences" msgstr "Peut voir les préférences générales" -#: preferences/models.py:486 +#: preferences/models.py:464 msgid "general preferences" msgstr "préférences générales" -#: preferences/models.py:506 +#: preferences/models.py:484 msgid "Can view the service preferences" msgstr "Peut voir les préférences de service" -#: preferences/models.py:507 preferences/views.py:227 +#: preferences/models.py:485 preferences/views.py:203 msgid "service" msgstr "service" -#: preferences/models.py:508 +#: preferences/models.py:486 msgid "services" msgstr "services" -#: preferences/models.py:518 +#: preferences/models.py:496 msgid "Contact email address." msgstr "Adresse mail de contact." -#: preferences/models.py:524 +#: preferences/models.py:502 msgid "Description of the associated email address." msgstr "Description de l'adresse mail associée." -#: preferences/models.py:534 +#: preferences/models.py:512 msgid "Can view a contact email address object" msgstr "Peut voir un objet adresse mail de contact" -#: preferences/models.py:536 +#: preferences/models.py:514 msgid "contact email address" msgstr "adresse mail de contact" -#: preferences/models.py:537 +#: preferences/models.py:515 msgid "contact email addresses" msgstr "adresses mail de contact" -#: preferences/models.py:545 preferences/views.py:634 +#: preferences/models.py:523 preferences/views.py:610 msgid "mandate" msgstr "mandat" -#: preferences/models.py:546 +#: preferences/models.py:524 msgid "mandates" msgstr "mandats" -#: preferences/models.py:547 +#: preferences/models.py:525 msgid "Can view a mandate object" msgstr "Peut voir un objet mandat" -#: preferences/models.py:554 +#: preferences/models.py:532 msgid "president of the association" msgstr "président de l'association" -#: preferences/models.py:555 +#: preferences/models.py:533 msgid "Displayed on subscription vouchers." msgstr "Affiché sur les reçus de cotisation." -#: preferences/models.py:557 +#: preferences/models.py:535 msgid "start date" msgstr "date de début" -#: preferences/models.py:558 +#: preferences/models.py:536 msgid "end date" msgstr "date de fin" -#: preferences/models.py:571 +#: preferences/models.py:549 msgid "" "No mandates have been created. Please go to the preferences page to create " "one." @@ -594,140 +594,140 @@ msgstr "" "Aucun mandat n'a été créé. Veuillez vous rendre sur la page de préférences " "pour en créer un." -#: preferences/models.py:586 +#: preferences/models.py:564 msgid "Networking organisation school Something" msgstr "Association de réseau de l'école Machin" -#: preferences/models.py:589 +#: preferences/models.py:567 msgid "Threadneedle Street" msgstr "1 rue de la Vrillière" -#: preferences/models.py:590 +#: preferences/models.py:568 msgid "London EC2R 8AH" msgstr "75001 Paris" -#: preferences/models.py:593 +#: preferences/models.py:571 msgid "Organisation" msgstr "Association" -#: preferences/models.py:600 +#: preferences/models.py:578 msgid "Can view the organisation preferences" msgstr "Peut voir les préférences d'association" -#: preferences/models.py:601 +#: preferences/models.py:579 msgid "organisation preferences" msgstr "préférences d'association" -#: preferences/models.py:619 +#: preferences/models.py:597 msgid "Can view the homepage preferences" msgstr "Peut voir les préférences de page d'accueil" -#: preferences/models.py:620 +#: preferences/models.py:598 msgid "homepage preferences" msgstr "Préférences de page d'accueil" -#: preferences/models.py:634 +#: preferences/models.py:612 msgid "Welcome email in French." msgstr "Mail de bienvenue en français." -#: preferences/models.py:637 +#: preferences/models.py:615 msgid "Welcome email in English." msgstr "Mail de bienvenue en anglais." -#: preferences/models.py:642 +#: preferences/models.py:620 msgid "Can view the email message preferences" msgstr "Peut voir les préférences de message pour les mails" -#: preferences/models.py:644 +#: preferences/models.py:622 msgid "email message preferences" msgstr "préférences de messages pour les mails" -#: preferences/models.py:649 +#: preferences/models.py:627 msgid "RADIUS attribute" msgstr "attribut RADIUS" -#: preferences/models.py:650 +#: preferences/models.py:628 msgid "RADIUS attributes" msgstr "attributs RADIUS" -#: preferences/models.py:654 preferences/views.py:587 +#: preferences/models.py:632 preferences/views.py:563 msgid "attribute" msgstr "attribut" -#: preferences/models.py:655 +#: preferences/models.py:633 msgid "See https://freeradius.org/rfc/attributes.html." msgstr "Voir https://freeradius.org/rfc/attributes.html." -#: preferences/models.py:657 +#: preferences/models.py:635 msgid "value" msgstr "valeur" -#: preferences/models.py:659 +#: preferences/models.py:637 msgid "comment" msgstr "commentaire" -#: preferences/models.py:660 +#: preferences/models.py:638 msgid "Use this field to document this attribute." msgstr "Utilisez ce champ pour documenter cet attribut." -#: preferences/models.py:671 +#: preferences/models.py:649 msgid "RADIUS policy" msgstr "politique de RADIUS" -#: preferences/models.py:672 +#: preferences/models.py:650 #: preferences/templates/preferences/display_preferences.html:299 msgid "RADIUS policies" msgstr "politiques de RADIUS" -#: preferences/models.py:683 +#: preferences/models.py:661 msgid "Reject the machine" msgstr "Rejeter la machine" -#: preferences/models.py:684 +#: preferences/models.py:662 msgid "Place the machine on the VLAN" msgstr "Placer la machine sur le VLAN" -#: preferences/models.py:693 +#: preferences/models.py:671 msgid "policy for unknown machines" msgstr "politique pour les machines inconnues" -#: preferences/models.py:701 +#: preferences/models.py:679 msgid "unknown machines VLAN" msgstr "VLAN pour les machines inconnues" -#: preferences/models.py:702 +#: preferences/models.py:680 msgid "VLAN for unknown machines if not rejected." msgstr "VLAN pour les machines inconnues si non rejeté." -#: preferences/models.py:708 +#: preferences/models.py:686 msgid "unknown machines attributes" msgstr "attributs pour les machines inconnues" -#: preferences/models.py:709 +#: preferences/models.py:687 msgid "Answer attributes for unknown machines." msgstr "Attributs de réponse pour les machines inconnues." -#: preferences/models.py:715 +#: preferences/models.py:693 msgid "policy for unknown ports" msgstr "politique pour les ports inconnus" -#: preferences/models.py:723 +#: preferences/models.py:701 msgid "unknown ports VLAN" msgstr "VLAN pour les ports inconnus" -#: preferences/models.py:724 +#: preferences/models.py:702 msgid "VLAN for unknown ports if not rejected." msgstr "VLAN pour les ports inconnus si non rejeté." -#: preferences/models.py:730 +#: preferences/models.py:708 msgid "unknown ports attributes" msgstr "attributs pour les ports inconnus" -#: preferences/models.py:731 +#: preferences/models.py:709 msgid "Answer attributes for unknown ports." msgstr "Attributs de réponse pour les ports inconnus." -#: preferences/models.py:738 +#: preferences/models.py:716 msgid "" "Policy for machines connecting from unregistered rooms (relevant on ports " "with STRICT RADIUS mode)" @@ -735,87 +735,87 @@ msgstr "" "Politique pour les machines se connectant depuis des chambre non " "enregistrées (pertinent pour les ports avec le mode de RADIUS STRICT)" -#: preferences/models.py:748 +#: preferences/models.py:726 msgid "unknown rooms VLAN" msgstr "VLAN pour les chambres inconnues" -#: preferences/models.py:749 +#: preferences/models.py:727 msgid "VLAN for unknown rooms if not rejected." msgstr "VLAN pour les chambres inconnues si non rejeté." -#: preferences/models.py:755 +#: preferences/models.py:733 msgid "unknown rooms attributes" msgstr "attributs pour les chambres inconnues" -#: preferences/models.py:756 +#: preferences/models.py:734 msgid "Answer attributes for unknown rooms." msgstr "Attributs de réponse pour les chambres inconnues." -#: preferences/models.py:762 +#: preferences/models.py:740 msgid "policy for non members" msgstr "politique pour les non adhérents" -#: preferences/models.py:770 +#: preferences/models.py:748 msgid "non members VLAN" msgstr "VLAN pour les non adhérents" -#: preferences/models.py:771 +#: preferences/models.py:749 msgid "VLAN for non members if not rejected." msgstr "VLAN pour les non adhérents si non rejeté." -#: preferences/models.py:777 +#: preferences/models.py:755 msgid "non members attributes" msgstr "attributs pour les non adhérents" -#: preferences/models.py:778 +#: preferences/models.py:756 msgid "Answer attributes for non members." msgstr "Attributs de réponse pour les non adhérents." -#: preferences/models.py:784 +#: preferences/models.py:762 msgid "policy for banned users" msgstr "politique pour les utilisateurs bannis" -#: preferences/models.py:792 +#: preferences/models.py:770 msgid "banned users VLAN" msgstr "VLAN pour les utilisateurs bannis" -#: preferences/models.py:793 +#: preferences/models.py:771 msgid "VLAN for banned users if not rejected." msgstr "VLAN pour les utilisateurs bannis si non rejeté." -#: preferences/models.py:799 +#: preferences/models.py:777 msgid "banned users attributes" msgstr "attributs pour les utilisateurs bannis" -#: preferences/models.py:800 +#: preferences/models.py:778 msgid "Answer attributes for banned users." msgstr "Attributs de réponse pour les utilisateurs bannis." -#: preferences/models.py:813 +#: preferences/models.py:791 msgid "accepted users attributes" msgstr "attributs pour les utilisateurs acceptés" -#: preferences/models.py:814 +#: preferences/models.py:792 msgid "Answer attributes for accepted users." msgstr "Attributs de réponse pour les utilisateurs acceptés." -#: preferences/models.py:841 +#: preferences/models.py:819 msgid "subscription preferences" msgstr "préférences de cotisation" -#: preferences/models.py:845 +#: preferences/models.py:823 msgid "template for invoices" msgstr "modèle pour les factures" -#: preferences/models.py:852 +#: preferences/models.py:830 msgid "template for subscription vouchers" msgstr "modèle pour les reçus de cotisation" -#: preferences/models.py:858 +#: preferences/models.py:836 msgid "send voucher by email when the invoice is controlled" msgstr "envoyer le reçu par mail quand la facture est contrôlée" -#: preferences/models.py:860 +#: preferences/models.py:838 msgid "" "Be careful, if no mandate is defined on the preferences page, errors will be " "triggered when generating vouchers." @@ -823,19 +823,19 @@ msgstr "" "Faites attention, si aucun mandat n'est défini sur la page de préférences, " "des erreurs seront déclenchées en générant des reçus." -#: preferences/models.py:872 +#: preferences/models.py:850 msgid "template" msgstr "modèle" -#: preferences/models.py:873 +#: preferences/models.py:851 msgid "name" msgstr "nom" -#: preferences/models.py:876 +#: preferences/models.py:854 msgid "document template" msgstr "modèle de document" -#: preferences/models.py:877 +#: preferences/models.py:855 msgid "document templates" msgstr "modèles de document" @@ -1035,9 +1035,9 @@ msgstr "Préférences générales" #: preferences/templates/preferences/display_preferences.html:417 #: preferences/templates/preferences/display_preferences.html:495 #: preferences/templates/preferences/edit_preferences.html:46 -#: preferences/views.py:212 preferences/views.py:261 preferences/views.py:307 -#: preferences/views.py:367 preferences/views.py:431 preferences/views.py:496 -#: preferences/views.py:572 preferences/views.py:619 +#: preferences/views.py:188 preferences/views.py:237 preferences/views.py:283 +#: preferences/views.py:343 preferences/views.py:407 preferences/views.py:472 +#: preferences/views.py:548 preferences/views.py:595 msgid "Edit" msgstr "Modifier" @@ -1359,75 +1359,75 @@ msgstr "URL du compte Facebook" msgid "Editing of preferences" msgstr "Modification des préférences" -#: preferences/views.py:160 +#: preferences/utils/views.py:45 msgid "Unknown object." msgstr "Objet inconnu." -#: preferences/views.py:179 +#: preferences/utils/views.py:64 msgid "The preferences were edited." msgstr "Les préférences ont été modifiées." -#: preferences/views.py:191 +#: preferences/views.py:167 msgid "The service was added." msgstr "Le service a été ajouté." -#: preferences/views.py:194 preferences/views.py:243 preferences/views.py:292 -#: preferences/views.py:349 preferences/views.py:412 preferences/views.py:471 -#: preferences/views.py:554 preferences/views.py:603 +#: preferences/views.py:170 preferences/views.py:219 preferences/views.py:268 +#: preferences/views.py:325 preferences/views.py:388 preferences/views.py:447 +#: preferences/views.py:530 preferences/views.py:579 msgid "Add" msgstr "Ajouter" -#: preferences/views.py:209 +#: preferences/views.py:185 msgid "The service was edited." msgstr "Le service a été modifié." -#: preferences/views.py:224 +#: preferences/views.py:200 msgid "The service was deleted." msgstr "Le service a été supprimé." -#: preferences/views.py:240 +#: preferences/views.py:216 msgid "The reminder was added." msgstr "Le rappel a été ajouté." -#: preferences/views.py:258 +#: preferences/views.py:234 msgid "The reminder was edited." msgstr "Le rappel a été modifié." -#: preferences/views.py:273 +#: preferences/views.py:249 msgid "The reminder was deleted." msgstr "Le rappel a été supprimé." -#: preferences/views.py:289 +#: preferences/views.py:265 msgid "The RADIUS key was added." msgstr "La clé RADIUS a été ajoutée." -#: preferences/views.py:304 +#: preferences/views.py:280 msgid "The RADIUS key was edited." msgstr "La clé RADIUS a été modifiée." -#: preferences/views.py:320 +#: preferences/views.py:296 msgid "The RADIUS key was deleted." msgstr "La clé RADIUS a été supprimée." -#: preferences/views.py:325 +#: preferences/views.py:301 msgid "The RADIUS key is assigned to at least one switch, you can't delete it." msgstr "" "La clé RADIUS est assignée a au moins un commutateur réseau, vous ne pouvez " "pas la supprimer." -#: preferences/views.py:344 +#: preferences/views.py:320 msgid "The switch management credentials were added." msgstr "Les identifiants de gestion de commutateur réseay ont été ajoutés." -#: preferences/views.py:364 +#: preferences/views.py:340 msgid "The switch management credentials were edited." msgstr "Les identifiants de gestion de commutateur réseau ont été modifiés." -#: preferences/views.py:381 +#: preferences/views.py:357 msgid "The switch management credentials were deleted." msgstr "Les identifiants de gestion de commutateur réseau ont été supprimés." -#: preferences/views.py:387 +#: preferences/views.py:363 msgid "" "The switch management credentials are assigned to at least one switch, you " "can't delete them." @@ -1435,44 +1435,44 @@ msgstr "" "Les identifiants de gestion de commutateur réseau sont assignés à au moins " "un commutateur réseau , vous ne pouvez pas les supprimer." -#: preferences/views.py:407 +#: preferences/views.py:383 msgid "The contact email address was created." msgstr "L'adresse mail de contact a été supprimée." -#: preferences/views.py:428 +#: preferences/views.py:404 msgid "The contact email address was edited." msgstr "L'adresse mail de contact a été modifiée." -#: preferences/views.py:446 +#: preferences/views.py:422 msgid "The contact email adress was deleted." msgstr "L'adresse mail de contact a été supprimée." -#: preferences/views.py:449 preferences/views.py:536 +#: preferences/views.py:425 preferences/views.py:512 msgid "Delete" msgstr "Supprimer" -#: preferences/views.py:466 +#: preferences/views.py:442 msgid "The document template was created." msgstr "Le modèle de document a été créé." -#: preferences/views.py:472 +#: preferences/views.py:448 msgid "New document template" msgstr "Nouveau modèle de document" -#: preferences/views.py:491 +#: preferences/views.py:467 msgid "The document template was edited." msgstr "Le modèle de document a été édité." -#: preferences/views.py:497 +#: preferences/views.py:473 msgid "Edit document template" msgstr "Modifier le modèle de document" -#: preferences/views.py:520 +#: preferences/views.py:496 #, python-format msgid "The document template %(document_template)s was deleted." msgstr "Le modèle de document %(document_template)s a été supprimé." -#: preferences/views.py:527 +#: preferences/views.py:503 #, python-format msgid "" "The document template %(document_template)s can't be deleted because it is " @@ -1481,31 +1481,31 @@ msgstr "" "Le modèle de document %(document_template)s ne peut pas être supprimé car il " "est actuellement utilisé." -#: preferences/views.py:537 +#: preferences/views.py:513 msgid "Delete document template" msgstr "Supprimer le modèle de document" -#: preferences/views.py:551 +#: preferences/views.py:527 msgid "The attribute was added." msgstr "L'attribut a été ajouté." -#: preferences/views.py:569 +#: preferences/views.py:545 msgid "The attribute was edited." msgstr "L'attribut a été modifié." -#: preferences/views.py:584 +#: preferences/views.py:560 msgid "The attribute was deleted." msgstr "L'attribut a été supprimé." -#: preferences/views.py:600 +#: preferences/views.py:576 msgid "The mandate was added." msgstr "Le mandat a été ajouté." -#: preferences/views.py:616 +#: preferences/views.py:592 msgid "The mandate was edited." msgstr "Le mandat a été modifié." -#: preferences/views.py:631 +#: preferences/views.py:607 msgid "The mandate was deleted." msgstr "Le mandat été supprimé." diff --git a/preferences/models.py b/preferences/models.py index 66a72f46..b52a5f07 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -37,36 +37,14 @@ from django.utils.translation import ugettext_lazy as _ import machines.models +from .utils.models import PreferencesModel + from re2o.mixins import AclMixin, RevMixin from re2o.aes_field import AESEncryptedField from datetime import timedelta -class PreferencesModel(models.Model): - """ Base object for the Preferences objects - Defines methods to handle the cache of the settings (they should - not change a lot) """ - - @classmethod - def set_in_cache(cls): - """ Save the preferences in a server-side cache """ - instance, _created = cls.objects.get_or_create() - cache.set(cls().__class__.__name__.lower(), instance, None) - return instance - - @classmethod - def get_cached_value(cls, key): - """ Get the preferences from the server-side cache """ - instance = cache.get(cls().__class__.__name__.lower()) - if instance is None: - instance = cls.set_in_cache() - return getattr(instance, key) - - class Meta: - abstract = True - - class OptionalUser(AclMixin, PreferencesModel): """Options pour l'user : obligation ou nom du telephone, activation ou non du solde, autorisation du negatif, fingerprint etc""" diff --git a/preferences/urls.py b/preferences/urls.py index 4e5b1bcf..82780cb8 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -28,52 +28,53 @@ from __future__ import unicode_literals from django.conf.urls import url from . import views +from .views import edit_options urlpatterns = [ url( r"^edit_options/(?P
OptionalUser)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
OptionalMachine)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
OptionalTopologie)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
GeneralOption)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
AssoOption)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
HomeOption)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
MailMessageOption)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
RadiusOption)$", - views.edit_options, + edit_options, name="edit-options", ), url( r"^edit_options/(?P
CotisationsOption)$", - views.edit_options, + edit_options, name="edit-options", ), url(r"^add_service/$", views.add_service, name="add-service"), diff --git a/preferences/utils/__init__.py b/preferences/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/preferences/utils/models.py b/preferences/utils/models.py new file mode 100644 index 00000000..5ade54e1 --- /dev/null +++ b/preferences/utils/models.py @@ -0,0 +1,55 @@ +# 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 Gabriel Détraz +# +# 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. + +# App de gestion des machines pour re2o +# Gabriel Détraz, Augustin Lemesle +# Gplv2 +""" +Utils for preferences +""" + +from __future__ import unicode_literals + +from django.core.cache import cache +from django.db import models + + +class PreferencesModel(models.Model): + """ Base object for the Preferences objects + Defines methods to handle the cache of the settings (they should + not change a lot) """ + + @classmethod + def set_in_cache(cls): + """ Save the preferences in a server-side cache """ + instance, _created = cls.objects.get_or_create() + cache.set(cls().__class__.__name__.lower(), instance, None) + return instance + + @classmethod + def get_cached_value(cls, key): + """ Get the preferences from the server-side cache """ + instance = cache.get(cls().__class__.__name__.lower()) + if instance is None: + instance = cls.set_in_cache() + return getattr(instance, key) + + class Meta: + abstract = True diff --git a/preferences/utils/views.py b/preferences/utils/views.py new file mode 100644 index 00000000..978b9b98 --- /dev/null +++ b/preferences/utils/views.py @@ -0,0 +1,68 @@ +# 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 Gabriel Détraz +# +# 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. + +# App de gestion des machines pour re2o +# Gabriel Détraz, Augustin Lemesle +# Gplv2 +""" +Utils for preferences +""" + +from __future__ import unicode_literals +from django.urls import reverse +from django.shortcuts import redirect +from django.contrib import messages +from django.db.models import ProtectedError +from django.db import transaction +from django.utils.translation import ugettext as _ + +from reversion import revisions as reversion + +from re2o.views import form + +def edit_options_template_function(request, section, forms, models): + """ Edition des préférences générales""" + model = getattr(models, section, None) + form_instance = getattr(forms, "Edit" + section + "Form", None) + if not (model or form_instance): + messages.error(request, _("Unknown object.")) + return redirect(reverse("preferences:display-options")) + + options_instance, _created = model.objects.get_or_create() + can, msg, permissions = options_instance.can_edit(request.user) + if not can: + messages.error(request, acl_error_message(msg, permissions)) + return redirect(reverse("index")) + options = form_instance( + request.POST or None, request.FILES or None, instance=options_instance + ) + if options.is_valid(): + with transaction.atomic(), reversion.create_revision(): + options.save() + reversion.set_user(request.user) + reversion.set_comment( + "Field(s) edited: %s" + % ", ".join(field for field in options.changed_data) + ) + messages.success(request, _("The preferences were edited.")) + return redirect(reverse("preferences:display-options")) + return form({"options": options}, "preferences/edit_preferences.html", request) + + diff --git a/preferences/views.py b/preferences/views.py index a7df9cef..06d9871e 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -86,6 +86,7 @@ from .models import ( from . import models from . import forms +from .utils.views import edit_options_template_function @login_required @can_view_all( @@ -153,32 +154,7 @@ def display_options(request): @login_required def edit_options(request, section): - """ Edition des préférences générales""" - model = getattr(models, section, None) - form_instance = getattr(forms, "Edit" + section + "Form", None) - if not (model or form_instance): - messages.error(request, _("Unknown object.")) - return redirect(reverse("preferences:display-options")) - - options_instance, _created = model.objects.get_or_create() - can, msg, permissions = options_instance.can_edit(request.user) - if not can: - messages.error(request, acl_error_message(msg, permissions)) - return redirect(reverse("index")) - options = form_instance( - request.POST or None, request.FILES or None, instance=options_instance - ) - if options.is_valid(): - with transaction.atomic(), reversion.create_revision(): - options.save() - reversion.set_user(request.user) - reversion.set_comment( - "Field(s) edited: %s" - % ", ".join(field for field in options.changed_data) - ) - messages.success(request, _("The preferences were edited.")) - return redirect(reverse("preferences:display-options")) - return form({"options": options}, "preferences/edit_preferences.html", request) + return edit_options_template_function(request, section, forms, models) @login_required From 9ea8808f755feb97937bfdb9088f206dccee3ef7 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 22 Apr 2020 18:55:33 +0200 Subject: [PATCH 2/7] New organisation for local apps and all settings in folder preferences --- tickets/admin.py | 36 ++++++++++- tickets/forms.py | 26 ++++++++ tickets/locale/fr/LC_MESSAGES/django.po | 63 +++++++++++-------- tickets/migrations/0003_auto_20200422_1839.py | 23 +++++++ tickets/models.py | 36 +++++++++-- tickets/preferences/forms.py | 38 ++++++++++- tickets/preferences/models.py | 33 +++++++++- tickets/preferences/views.py | 60 ++++++++++++++++++ tickets/templates/tickets/preferences.html | 2 +- tickets/urls.py | 34 ++++++++-- tickets/views.py | 46 +------------- 11 files changed, 311 insertions(+), 86 deletions(-) create mode 100644 tickets/migrations/0003_auto_20200422_1839.py create mode 100644 tickets/preferences/views.py diff --git a/tickets/admin.py b/tickets/admin.py index 6a20b775..69f84d0d 100644 --- a/tickets/admin.py +++ b/tickets/admin.py @@ -1,5 +1,37 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Ticket preferences model +""" + + from django.contrib import admin from .models import Ticket -admin.site.register(Ticket) -# Register your models here. +from reversion.admin import VersionAdmin + +class TicketAdmin(VersionAdmin): + """Gestion des ticket""" + + pass + +admin.site.register(Ticket, TicketAdmin) diff --git a/tickets/forms.py b/tickets/forms.py index 00edb0ec..4f62db84 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -1,3 +1,29 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Ticket form +""" + + from django import forms from django.forms import ModelForm, Form from re2o.field_permissions import FieldPermissionFormMixin diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index fbb7cbc2..528bf611 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -30,66 +30,78 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: tickets/models.py:28 +#: tickets/models.py:54 msgid "Title of the ticket." msgstr "Titre du ticket." -#: tickets/models.py:32 +#: tickets/models.py:58 msgid "Description of the ticket." msgstr "Description du ticket." -#: tickets/models.py:38 +#: tickets/models.py:64 msgid "An email address to get back to you." msgstr "Une adresse mail pour vous recontacter." -#: tickets/models.py:44 +#: tickets/models.py:70 msgid "Can view a ticket object" msgstr "Peut voir un objet ticket" -#: tickets/models.py:45 +#: tickets/models.py:71 msgid "ticket" msgstr "ticket" -#: tickets/models.py:46 +#: tickets/models.py:72 msgid "tickets" msgstr "tickets" -#: tickets/models.py:50 +#: tickets/models.py:76 #, python-format msgid "Ticket from %(name)s. Date: %(date)s." msgstr "Ticket de %(name)s. Date : %(date)s." -#: tickets/models.py:52 +#: tickets/models.py:78 #, python-format msgid "Anonymous ticket. Date: %s." msgstr "Ticket anonyme. Date : %s." -#: tickets/models.py:85 +#: tickets/models.py:111 msgid "You don't have the right to view other tickets than yours." msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres." -#: tickets/models.py:97 +#: tickets/models.py:123 msgid "You don't have the right to view the list of tickets." msgstr "Vous n'avez pas le droit de voir la liste des tickets." -#: tickets/preferences/models.py:10 +#: tickets/preferences/forms.py:44 +msgid "Publish address" +msgstr "Adresse mail de publication" + +#: tickets/preferences/forms.py:45 +msgid "Mail language" +msgstr "Langue du mail" + +#: tickets/preferences/models.py:38 msgid "" "Email address to publish the new tickets (leave empty for no publication)." msgstr "" "Adresse mail où publier les nouveaux tickets (laissez vide pour ne pas " "publier)." -#: tickets/preferences/models.py:17 +#: tickets/preferences/models.py:45 msgid "French" msgstr "Français" -#: tickets/preferences/models.py:17 +#: tickets/preferences/models.py:45 msgid "English" msgstr "Anglais" -#: tickets/preferences/models.py:21 -msgid "tickets preferences" -msgstr "préférences de tickets" +#: tickets/preferences/models.py:49 +msgid "tickets options" +msgstr "Options des tickets" + +#: tickets/preferences/models.py:50 +msgid "Can view tickets options" +msgstr "Peut voir les options des tickets" #: tickets/templates/tickets/aff_ticket.html:30 #: tickets/templates/tickets/contact.html:4 @@ -214,7 +226,7 @@ msgstr "Modifier" msgid "Ticket opening" msgstr "Ouverture de ticket" -#: tickets/templates/tickets/form_ticket.html:39 tickets/views.py:90 +#: tickets/templates/tickets/form_ticket.html:39 tickets/views.py:85 msgid "" "You are not authenticated. Please log in or provide an email address so we " "can get back to you." @@ -280,21 +292,22 @@ msgstr "Langue du mail" msgid "No tickets" msgstr "Pas de tickets" -#: tickets/views.py:71 tickets/views.py:82 +#: tickets/views.py:66 tickets/views.py:77 msgid "" "Your ticket has been succesfully opened. We will take care of it as soon as " "possible." msgstr "" "Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible." -#: tickets/views.py:127 tickets/views.py:177 +#: tickets/views.py:122 tickets/views.py:147 msgid "Never" msgstr "Jamais" -#: tickets/views.py:154 -msgid "The tickets preferences were edited." -msgstr "Les préférences de tickets ont été modifiées." +#~ msgid "The tickets preferences were edited." +#~ msgstr "Les préférences de tickets ont été modifiées." -#: tickets/views.py:157 -msgid "Invalid form." -msgstr "Formulaire invalide." +#~ msgid "Invalid form." +#~ msgstr "Formulaire invalide." + +#~ msgid "tickets preferences" +#~ msgstr "préférences de tickets" diff --git a/tickets/migrations/0003_auto_20200422_1839.py b/tickets/migrations/0003_auto_20200422_1839.py new file mode 100644 index 00000000..daa29e42 --- /dev/null +++ b/tickets/migrations/0003_auto_20200422_1839.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-22 16:39 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0002_auto_20191120_0159'), + ] + + operations = [ + migrations.RenameModel( + old_name='Preferences', + new_name='TicketOption', + ), + migrations.AlterModelOptions( + name='ticketoption', + options={'permissions': (('view_ticketoption', 'Can view tickets options'),), 'verbose_name': 'tickets options'}, + ), + ] diff --git a/tickets/models.py b/tickets/models.py index a8adbe87..bf0a3794 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -1,3 +1,29 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Ticket model +""" + + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.template import loader @@ -11,7 +37,7 @@ from preferences.models import GeneralOption import users.models -from .preferences.models import Preferences +from .preferences.models import TicketOption class Ticket(AclMixin, models.Model): @@ -52,11 +78,11 @@ class Ticket(AclMixin, models.Model): return _("Anonymous ticket. Date: %s.") % (self.date) def publish_mail(self, request=None): - site_url = GeneralOption.objects.first().main_site_url - to_addr = Preferences.objects.first().publish_address + site_url = GeneralOption.get_cached_value("main_site_url") + to_addr = TicketOption.get_cached_value("publish_address") context = {"ticket": self, "site_url": site_url} - lang = Preferences.objects.first().mail_language + lang = TicketOption.get_cached_value("mail_language") if lang == 0: obj = "Nouveau ticket ouvert" template = loader.get_template("tickets/publication_mail_fr") @@ -109,6 +135,6 @@ class Ticket(AclMixin, models.Model): def ticket_post_save(**kwargs): """ Send the mail to publish the new ticket """ if kwargs["created"]: - if Preferences.objects.first().publish_address: + if TicketOption.get_cached_value("publish_address"): ticket = kwargs["instance"] ticket.publish_mail(ticket.request) diff --git a/tickets/preferences/forms.py b/tickets/preferences/forms.py index b12bde67..05b40ec3 100644 --- a/tickets/preferences/forms.py +++ b/tickets/preferences/forms.py @@ -1,13 +1,45 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Ticket preferences form +""" + from django import forms from django.forms import ModelForm, Form from django.utils.translation import ugettext_lazy as _ -from .models import Preferences +from re2o.mixins import FormRevMixin +from .models import TicketOption -class EditPreferencesForm(ModelForm): +class EditTicketOptionForm(FormRevMixin, ModelForm): """ Edit the ticket's settings""" class Meta: - model = Preferences + model = TicketOption fields = "__all__" + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop("prefix", self.Meta.model.__name__) + super(EditTicketOptionForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields["publish_address"].label = _("Publish address") + self.fields["mail_language"].label = _("Mail language") diff --git a/tickets/preferences/models.py b/tickets/preferences/models.py index 27922303..8b41c5eb 100644 --- a/tickets/preferences/models.py +++ b/tickets/preferences/models.py @@ -1,8 +1,36 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Ticket preferences model +""" + + from django.db import models from django.utils.translation import ugettext_lazy as _ +from re2o.mixins import AclMixin, RevMixin +from preferences.utils.models import PreferencesModel -class Preferences(models.Model): +class TicketOption(AclMixin, PreferencesModel): """ Definition of the ticket's settings""" publish_address = models.EmailField( @@ -18,4 +46,5 @@ class Preferences(models.Model): mail_language = models.IntegerField(choices=LANGUES, default=LANG_FR) class Meta: - verbose_name = _("tickets preferences") + verbose_name = _("tickets options") + permissions = (("view_ticketoption", _("Can view tickets options")),) diff --git a/tickets/preferences/views.py b/tickets/preferences/views.py new file mode 100644 index 00000000..cfc6a0f2 --- /dev/null +++ b/tickets/preferences/views.py @@ -0,0 +1,60 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 Gabriel Détraz +# Copyright © 2019 Arthur Grisel-Davy +# +# 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. + +# App de gestion des users pour re2o +# Lara Kermarec, Gabriel Détraz, Lemesle Augustin +# Gplv2 + +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import render, redirect +from django.template.loader import render_to_string +from django.views.decorators.cache import cache_page +from django.utils.translation import ugettext as _ +from django.urls import reverse +from django.forms import modelformset_factory +from re2o.views import form + +from re2o.base import re2o_paginator + +from re2o.acl import can_view, can_view_all, can_edit, can_create + +from preferences.utils.views import edit_options_template_function + +from . import forms +from . import models + +def aff_preferences(request): + """ View to display the settings of the tickets in the preferences page""" + pref, created = models.TicketOption.objects.get_or_create() + context = { + "preferences": pref, + "language": str(pref.LANGUES[pref.mail_language][1]), + } + return render_to_string( + "tickets/preferences.html", context=context, request=request, using=None + ) + +@login_required +def edit_options(request, section): + return edit_options_template_function(request, section, forms, models) + diff --git a/tickets/templates/tickets/preferences.html b/tickets/templates/tickets/preferences.html index df51f3e1..5952a496 100644 --- a/tickets/templates/tickets/preferences.html +++ b/tickets/templates/tickets/preferences.html @@ -9,7 +9,7 @@
- + {% trans "Edit" %} diff --git a/tickets/urls.py b/tickets/urls.py index 1dfda475..c6eaefde 100644 --- a/tickets/urls.py +++ b/tickets/urls.py @@ -1,14 +1,40 @@ +# -*- mode: python; coding: utf-8 -*- +# 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 © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz +# +# 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. +""" +Tickets url +""" + from django.conf.urls import url from . import views +from .preferences.views import edit_options urlpatterns = [ url(r"^$", views.aff_tickets, name="aff-tickets"), - url(r"^ticket/(?P[0-9]+)$", views.aff_ticket, name="aff-ticket"), + url(r"^(?P[0-9]+)$", views.aff_ticket, name="aff-ticket"), url( - r"^ticket/edit-preferences-tickets$", - views.edit_preferences, - name="edit-preferences-tickets", + r"^edit_options/(?P
TicketOption)$", + edit_options, + name="edit-options", ), url(r"^new_ticket/$", views.new_ticket, name="new-ticket"), ] diff --git a/tickets/views.py b/tickets/views.py index 03cc535c..f5186cdf 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -3,9 +3,8 @@ # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. # -# Copyright © 2017 Gabriel Détraz -# Copyright © 2017 Lara Kermarec -# Copyright © 2017 Augustin Lemesle +# Copyright © 2019 Arthur Grisel-Davy +# Copyright © 2020 Gabriel Détraz # # 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 @@ -43,12 +42,8 @@ from preferences.models import GeneralOption from .models import Ticket -from .preferences.models import Preferences - from .forms import NewTicketForm, ChangeStatusTicketForm -from .preferences.forms import EditPreferencesForm - def new_ticket(request): """ Ticket creation view""" @@ -140,31 +135,6 @@ def aff_tickets(request): return render(request, "tickets/index.html", context=context) -def edit_preferences(request): - """ View to edit the settings of the tickets """ - - preferences_instance, created = Preferences.objects.get_or_create(id=1) - preferencesform = EditPreferencesForm( - request.POST or None, instance=preferences_instance - ) - - if preferencesform.is_valid(): - if preferencesform.changed_data: - preferencesform.save() - messages.success(request, _("The tickets preferences were edited.")) - return redirect(reverse("preferences:display-options")) - else: - messages.error(request, _("Invalid form.")) - return form( - {"preferencesform": preferencesform}, - "tickets/form_preferences.html", - request, - ) - return form( - {"preferencesform": preferencesform}, "tickets/form_preferences.html", request - ) - - # views cannoniques des apps optionnels def profil(request, user): """ View to display the ticket's module on the profil""" @@ -191,18 +161,6 @@ def profil(request, user): ) -def preferences(request): - """ View to display the settings of the tickets in the preferences page""" - pref, created = Preferences.objects.get_or_create(id=1) - context = { - "preferences": pref, - "language": str(pref.LANGUES[pref.mail_language][1]), - } - return render_to_string( - "tickets/preferences.html", context=context, request=request, using=None - ) - - def contact(request): """View to display a contact address on the contact page used here to display a link to open a ticket""" From cd46e77a03704a2768fcae10234fb43d94cf973f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 22 Apr 2020 18:59:04 +0200 Subject: [PATCH 3/7] New place for local preferences --- preferences/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/preferences/views.py b/preferences/views.py index 06d9871e..1bc31b2b 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -121,9 +121,9 @@ def display_options(request): optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O] optionnal_templates_list = [ - app.views.preferences(request) + app.preferences.views.aff_preferences(request) for app in optionnal_apps - if hasattr(app.views, "preferences") + if hasattr(app.preferences.views, "aff_preferences") ] return form( From acf510a8e7d504c3dc68be82ca67daeef80641cb Mon Sep 17 00:00:00 2001 From: Hugo Levy-Falk Date: Wed, 22 Apr 2020 19:35:45 +0200 Subject: [PATCH 4/7] No need for preferences/utils --- preferences/models.py | 51 ++++++++++++++++++++------ preferences/utils/__init__.py | 0 preferences/utils/models.py | 55 ---------------------------- preferences/utils/views.py | 68 ----------------------------------- preferences/views.py | 45 +++++++++++++++++------ tickets/preferences/models.py | 3 +- tickets/preferences/views.py | 5 +-- 7 files changed, 80 insertions(+), 147 deletions(-) delete mode 100644 preferences/utils/__init__.py delete mode 100644 preferences/utils/models.py delete mode 100644 preferences/utils/views.py diff --git a/preferences/models.py b/preferences/models.py index b52a5f07..58badc18 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -37,14 +37,36 @@ from django.utils.translation import ugettext_lazy as _ import machines.models -from .utils.models import PreferencesModel - from re2o.mixins import AclMixin, RevMixin from re2o.aes_field import AESEncryptedField from datetime import timedelta +class PreferencesModel(models.Model): + """ Base object for the Preferences objects + Defines methods to handle the cache of the settings (they should + not change a lot) """ + + @classmethod + def set_in_cache(cls): + """ Save the preferences in a server-side cache """ + instance, _created = cls.objects.get_or_create() + cache.set(cls().__class__.__name__.lower(), instance, None) + return instance + + @classmethod + def get_cached_value(cls, key): + """ Get the preferences from the server-side cache """ + instance = cache.get(cls().__class__.__name__.lower()) + if instance is None: + instance = cls.set_in_cache() + return getattr(instance, key) + + class Meta: + abstract = True + + class OptionalUser(AclMixin, PreferencesModel): """Options pour l'user : obligation ou nom du telephone, activation ou non du solde, autorisation du negatif, fingerprint etc""" @@ -54,7 +76,12 @@ class OptionalUser(AclMixin, PreferencesModel): ALL_ROOM = "ALL_ROOM" ROOM_POLICY = ( (DISABLED, _("Users can't select their room")), - (ONLY_INACTIVE, _("Users can only select a room occupied by a user with a disabled connection.")), + ( + ONLY_INACTIVE, + _( + "Users can only select a room occupied by a user with a disabled connection." + ), + ), (ALL_ROOM, _("Users can select all rooms")), ) @@ -76,7 +103,7 @@ class OptionalUser(AclMixin, PreferencesModel): max_length=32, choices=ROOM_POLICY, default="DISABLED", - help_text=_("Policy on self users room edition") + help_text=_("Policy on self users room edition"), ) local_email_accounts_enabled = models.BooleanField( default=False, help_text=_("Enable local email accounts for users.") @@ -92,9 +119,7 @@ class OptionalUser(AclMixin, PreferencesModel): ) delete_notyetactive = models.IntegerField( default=15, - help_text=_( - "Not yet active users will be deleted after this number of days." - ), + help_text=_("Not yet active users will be deleted after this number of days."), ) disable_emailnotyetconfirmed = models.IntegerField( default=2, @@ -195,7 +220,7 @@ class OptionalTopologie(AclMixin, PreferencesModel): DEFINED = "DEFINED" CHOICE_RADIUS = ( (MACHINE, _("On the IP range's VLAN of the machine")), - (DEFINED, _("Preset in \"VLAN for machines accepted by RADIUS\"")), + (DEFINED, _('Preset in "VLAN for machines accepted by RADIUS"')), ) CHOICE_PROVISION = (("sftp", "SFTP"), ("tftp", "TFTP")) @@ -335,7 +360,9 @@ class OptionalTopologie(AclMixin, PreferencesModel): ) class Meta: - permissions = (("view_optionaltopologie", _("Can view the topology preferences")),) + permissions = ( + ("view_optionaltopologie", _("Can view the topology preferences")), + ) verbose_name = _("topology preferences") @@ -546,7 +573,9 @@ class Mandate(RevMixin, AclMixin, models.Model): ) if not mandate: raise cls.DoesNotExist( - _("No mandates have been created. Please go to the preferences page to create one.") + _( + "No mandates have been created. Please go to the preferences page to create one." + ) ) return mandate @@ -653,7 +682,7 @@ class RadiusOption(AclMixin, PreferencesModel): DEFINED = "DEFINED" CHOICE_RADIUS = ( (MACHINE, _("On the IP range's VLAN of the machine")), - (DEFINED, _("Preset in \"VLAN for machines accepted by RADIUS\"")), + (DEFINED, _('Preset in "VLAN for machines accepted by RADIUS"')), ) REJECT = "REJECT" SET_VLAN = "SET_VLAN" diff --git a/preferences/utils/__init__.py b/preferences/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/preferences/utils/models.py b/preferences/utils/models.py deleted file mode 100644 index 5ade54e1..00000000 --- a/preferences/utils/models.py +++ /dev/null @@ -1,55 +0,0 @@ -# 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 Gabriel Détraz -# -# 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. - -# App de gestion des machines pour re2o -# Gabriel Détraz, Augustin Lemesle -# Gplv2 -""" -Utils for preferences -""" - -from __future__ import unicode_literals - -from django.core.cache import cache -from django.db import models - - -class PreferencesModel(models.Model): - """ Base object for the Preferences objects - Defines methods to handle the cache of the settings (they should - not change a lot) """ - - @classmethod - def set_in_cache(cls): - """ Save the preferences in a server-side cache """ - instance, _created = cls.objects.get_or_create() - cache.set(cls().__class__.__name__.lower(), instance, None) - return instance - - @classmethod - def get_cached_value(cls, key): - """ Get the preferences from the server-side cache """ - instance = cache.get(cls().__class__.__name__.lower()) - if instance is None: - instance = cls.set_in_cache() - return getattr(instance, key) - - class Meta: - abstract = True diff --git a/preferences/utils/views.py b/preferences/utils/views.py deleted file mode 100644 index 978b9b98..00000000 --- a/preferences/utils/views.py +++ /dev/null @@ -1,68 +0,0 @@ -# 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 Gabriel Détraz -# -# 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. - -# App de gestion des machines pour re2o -# Gabriel Détraz, Augustin Lemesle -# Gplv2 -""" -Utils for preferences -""" - -from __future__ import unicode_literals -from django.urls import reverse -from django.shortcuts import redirect -from django.contrib import messages -from django.db.models import ProtectedError -from django.db import transaction -from django.utils.translation import ugettext as _ - -from reversion import revisions as reversion - -from re2o.views import form - -def edit_options_template_function(request, section, forms, models): - """ Edition des préférences générales""" - model = getattr(models, section, None) - form_instance = getattr(forms, "Edit" + section + "Form", None) - if not (model or form_instance): - messages.error(request, _("Unknown object.")) - return redirect(reverse("preferences:display-options")) - - options_instance, _created = model.objects.get_or_create() - can, msg, permissions = options_instance.can_edit(request.user) - if not can: - messages.error(request, acl_error_message(msg, permissions)) - return redirect(reverse("index")) - options = form_instance( - request.POST or None, request.FILES or None, instance=options_instance - ) - if options.is_valid(): - with transaction.atomic(), reversion.create_revision(): - options.save() - reversion.set_user(request.user) - reversion.set_comment( - "Field(s) edited: %s" - % ", ".join(field for field in options.changed_data) - ) - messages.success(request, _("The preferences were edited.")) - return redirect(reverse("preferences:display-options")) - return form({"options": options}, "preferences/edit_preferences.html", request) - - diff --git a/preferences/views.py b/preferences/views.py index 1bc31b2b..6420dad3 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -86,7 +86,35 @@ from .models import ( from . import models from . import forms -from .utils.views import edit_options_template_function + +def edit_options_template_function(request, section, forms, models): + """ Edition des préférences générales""" + model = getattr(models, section, None) + form_instance = getattr(forms, "Edit" + section + "Form", None) + if not (model or form_instance): + messages.error(request, _("Unknown object.")) + return redirect(reverse("preferences:display-options")) + + options_instance, _created = model.objects.get_or_create() + can, msg, permissions = options_instance.can_edit(request.user) + if not can: + messages.error(request, acl_error_message(msg, permissions)) + return redirect(reverse("index")) + options = form_instance( + request.POST or None, request.FILES or None, instance=options_instance + ) + if options.is_valid(): + with transaction.atomic(), reversion.create_revision(): + options.save() + reversion.set_user(request.user) + reversion.set_comment( + "Field(s) edited: %s" + % ", ".join(field for field in options.changed_data) + ) + messages.success(request, _("The preferences were edited.")) + return redirect(reverse("preferences:display-options")) + return form({"options": options}, "preferences/edit_preferences.html", request) + @login_required @can_view_all( @@ -320,10 +348,7 @@ def add_switchmanagementcred(request): messages.success(request, _("The switch management credentials were added.")) return redirect(reverse("preferences:display-options")) return form( - { - "preferenceform": switchmanagementcred, - "action_name": _("Add"), - }, + {"preferenceform": switchmanagementcred, "action_name": _("Add"),}, "preferences/preferences.html", request, ) @@ -367,7 +392,10 @@ def del_switchmanagementcred(request, switchmanagementcred_instance, **_kwargs): ) return redirect(reverse("preferences:display-options")) return form( - {"objet": switchmanagementcred_instance, "objet_name": _("switch management credentials")}, + { + "objet": switchmanagementcred_instance, + "objet_name": _("switch management credentials"), + }, "preferences/delete.html", request, ) @@ -383,10 +411,7 @@ def add_mailcontact(request): messages.success(request, _("The contact email address was created.")) return redirect(reverse("preferences:display-options")) return form( - { - "preferenceform": mailcontact, - "action_name": _("Add"), - }, + {"preferenceform": mailcontact, "action_name": _("Add"),}, "preferences/preferences.html", request, ) diff --git a/tickets/preferences/models.py b/tickets/preferences/models.py index 8b41c5eb..abc4d5fd 100644 --- a/tickets/preferences/models.py +++ b/tickets/preferences/models.py @@ -28,7 +28,8 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from re2o.mixins import AclMixin, RevMixin -from preferences.utils.models import PreferencesModel +from preferences.models import PreferencesModel + class TicketOption(AclMixin, PreferencesModel): """ Definition of the ticket's settings""" diff --git a/tickets/preferences/views.py b/tickets/preferences/views.py index cfc6a0f2..f26473a4 100644 --- a/tickets/preferences/views.py +++ b/tickets/preferences/views.py @@ -38,11 +38,12 @@ from re2o.base import re2o_paginator from re2o.acl import can_view, can_view_all, can_edit, can_create -from preferences.utils.views import edit_options_template_function +from preferences.views import edit_options_template_function from . import forms from . import models + def aff_preferences(request): """ View to display the settings of the tickets in the preferences page""" pref, created = models.TicketOption.objects.get_or_create() @@ -54,7 +55,7 @@ def aff_preferences(request): "tickets/preferences.html", context=context, request=request, using=None ) + @login_required def edit_options(request, section): return edit_options_template_function(request, section, forms, models) - From c8c89fab1d439f9ade49ba53bf8864fe5c0aa807 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Wed, 22 Apr 2020 21:37:21 +0200 Subject: [PATCH 5/7] Fix and harmonize some issues in ticket app --- tickets/forms.py | 25 ++- tickets/locale/fr/LC_MESSAGES/django.po | 149 +++++++++--------- tickets/migrations/0004_auto_20200422_2127.py | 24 +++ tickets/models.py | 18 +-- tickets/preferences/forms.py | 1 - tickets/preferences/models.py | 4 - tickets/preferences/views.py | 1 - tickets/templates/tickets/aff_ticket.html | 19 +-- .../{form_preferences.html => edit.html} | 20 +-- tickets/templates/tickets/form_ticket.html | 59 ------- tickets/templates/tickets/help_text.html | 14 ++ tickets/templates/tickets/preferences.html | 6 +- tickets/templates/tickets/publication_mail_en | 4 +- tickets/templates/tickets/publication_mail_fr | 4 +- tickets/urls.py | 2 + tickets/views.py | 103 ++++++------ 16 files changed, 216 insertions(+), 237 deletions(-) create mode 100644 tickets/migrations/0004_auto_20200422_2127.py rename tickets/templates/tickets/{form_preferences.html => edit.html} (67%) delete mode 100644 tickets/templates/tickets/form_ticket.html create mode 100644 tickets/templates/tickets/help_text.html diff --git a/tickets/forms.py b/tickets/forms.py index 4f62db84..b1be6597 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -25,6 +25,7 @@ Ticket form from django import forms +from django.template.loader import render_to_string from django.forms import ModelForm, Form from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin @@ -33,19 +34,31 @@ from django.utils.translation import ugettext_lazy as _ from .models import Ticket -class NewTicketForm(ModelForm): +class NewTicketForm(FormRevMixin, ModelForm): """ Creation of a ticket""" - email = forms.EmailField(required=False) - class Meta: model = Ticket fields = ["title", "description", "email"] + def __init__(self, *args, **kwargs): + request = kwargs.pop("request") + super(NewTicketForm, self).__init__(*args, **kwargs) + if request.user.is_authenticated: + self.fields.pop('email') + self.instance.user = request.user + self.fields['description'].help_text = render_to_string('tickets/help_text.html') + self.instance.request = request -class ChangeStatusTicketForm(ModelForm): - """ Change ticket status""" + +class EditTicketForm(FormRevMixin, ModelForm): + """ Creation of a ticket""" class Meta: model = Ticket - fields = [] + fields = "__all__" + + def __init__(self, *args, **kwargs): + super(EditTicketForm, self).__init__(*args, **kwargs) + self.fields['email'].required = False + diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 528bf611..05ce5d9b 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-22 19:00+0200\n" +"POT-Creation-Date: 2020-04-22 22:06+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -34,32 +34,28 @@ msgstr "" msgid "Title of the ticket." msgstr "Titre du ticket." -#: tickets/models.py:58 -msgid "Description of the ticket." -msgstr "Description du ticket." - -#: tickets/models.py:64 +#: tickets/models.py:63 msgid "An email address to get back to you." msgstr "Une adresse mail pour vous recontacter." -#: tickets/models.py:70 +#: tickets/models.py:69 msgid "Can view a ticket object" msgstr "Peut voir un objet ticket" -#: tickets/models.py:71 +#: tickets/models.py:70 msgid "ticket" msgstr "ticket" -#: tickets/models.py:72 +#: tickets/models.py:71 msgid "tickets" msgstr "tickets" -#: tickets/models.py:76 +#: tickets/models.py:75 #, python-format msgid "Ticket from %(name)s. Date: %(date)s." msgstr "Ticket de %(name)s. Date : %(date)s." -#: tickets/models.py:78 +#: tickets/models.py:77 #, python-format msgid "Anonymous ticket. Date: %s." msgstr "Ticket anonyme. Date : %s." @@ -76,34 +72,22 @@ msgstr "Vous n'avez pas le droit de voir la liste des tickets." msgid "Publish address" msgstr "Adresse mail de publication" -#: tickets/preferences/forms.py:45 -msgid "Mail language" -msgstr "Langue du mail" - -#: tickets/preferences/models.py:38 +#: tickets/preferences/models.py:39 msgid "" "Email address to publish the new tickets (leave empty for no publication)." msgstr "" "Adresse mail où publier les nouveaux tickets (laissez vide pour ne pas " "publier)." -#: tickets/preferences/models.py:45 -msgid "French" -msgstr "Français" - -#: tickets/preferences/models.py:45 -msgid "English" -msgstr "Anglais" - -#: tickets/preferences/models.py:49 +#: tickets/preferences/models.py:46 msgid "tickets options" msgstr "Options des tickets" -#: tickets/preferences/models.py:50 +#: tickets/preferences/models.py:47 msgid "Can view tickets options" msgstr "Peut voir les options des tickets" -#: tickets/templates/tickets/aff_ticket.html:30 +#: tickets/templates/tickets/aff_ticket.html:31 #: tickets/templates/tickets/contact.html:4 #: tickets/templates/tickets/index.html:29 #: tickets/templates/tickets/preferences.html:6 @@ -111,59 +95,62 @@ msgstr "Peut voir les options des tickets" msgid "Tickets" msgstr "Tickets" -#: tickets/templates/tickets/aff_ticket.html:34 +#: tickets/templates/tickets/aff_ticket.html:35 #, python-format msgid "Ticket #%(id)s" msgstr "Ticket #%(id)s" -#: tickets/templates/tickets/aff_ticket.html:36 +#: tickets/templates/tickets/aff_ticket.html:37 #: tickets/templates/tickets/aff_tickets.html:58 msgid "Solved" msgstr "Résolu" -#: tickets/templates/tickets/aff_ticket.html:38 +#: tickets/templates/tickets/aff_ticket.html:39 msgid "Not solved" msgstr "Non résolu" -#: tickets/templates/tickets/aff_ticket.html:44 +#: tickets/templates/tickets/aff_ticket.html:45 msgid "Opened by" msgstr "Ouvert par" -#: tickets/templates/tickets/aff_ticket.html:50 +#: tickets/templates/tickets/aff_ticket.html:51 msgid "Anonymous user" msgstr "Utilisateur anonyme" -#: tickets/templates/tickets/aff_ticket.html:54 +#: tickets/templates/tickets/aff_ticket.html:55 msgid "Response address: " msgstr "Adresse de réponse : " -#: tickets/templates/tickets/aff_ticket.html:54 +#: tickets/templates/tickets/aff_ticket.html:55 msgid "Response to your ticket" msgstr "Réponse à votre ticket" -#: tickets/templates/tickets/aff_ticket.html:59 +#: tickets/templates/tickets/aff_ticket.html:60 msgid "Title:" msgstr "Titre :" -#: tickets/templates/tickets/aff_ticket.html:60 +#: tickets/templates/tickets/aff_ticket.html:61 msgid "Description:" msgstr "Description :" -#: tickets/templates/tickets/aff_ticket.html:68 +#: tickets/templates/tickets/aff_ticket.html:65 +msgid "Edit this ticket" +msgstr "Modifier le ticket." + +#: tickets/templates/tickets/aff_ticket.html:67 msgid "Mark as solved" msgstr "Marquer comme résolu" -#: tickets/templates/tickets/aff_ticket.html:71 -msgid "Mark as not solved" +#: tickets/templates/tickets/aff_ticket.html:69 +msgid "Mark as unsolved" msgstr "Marquer comme non résolu" -#: tickets/templates/tickets/aff_ticket.html:81 +#: tickets/templates/tickets/aff_ticket.html:78 msgid "All tickets" msgstr "Tous les tickets" #: tickets/templates/tickets/aff_tickets.html:35 -#: tickets/templates/tickets/form_preferences.html:30 -#: tickets/templates/tickets/form_ticket.html:31 +#: tickets/templates/tickets/edit.html:31 msgid "Ticket" msgid_plural "Tickets" msgstr[0] "Ticket" @@ -213,28 +200,11 @@ msgstr "" msgid "Open a ticket" msgstr "Ouvrir un ticket" -#: tickets/templates/tickets/form_preferences.html:33 -msgid "Editing of tickets preferences" -msgstr "Modification des préférences de tickets" - -#: tickets/templates/tickets/form_preferences.html:46 -#: tickets/templates/tickets/preferences.html:14 -msgid "Edit" -msgstr "Modifier" - -#: tickets/templates/tickets/form_ticket.html:34 +#: tickets/templates/tickets/edit.html:34 msgid "Ticket opening" msgstr "Ouverture de ticket" -#: tickets/templates/tickets/form_ticket.html:39 tickets/views.py:85 -msgid "" -"You are not authenticated. Please log in or provide an email address so we " -"can get back to you." -msgstr "" -"Vous n'êtes pas authentifié. Veuillez vous connecter ou fournir une adresse " -"mail pour que nous puissions vous recontacter." - -#: tickets/templates/tickets/form_ticket.html:44 +#: tickets/templates/tickets/help_text.html:3 msgid "" "Description of your problem. Please give as much information as possible to " "help us searching for a solution. Here is some information we might need:" @@ -243,11 +213,11 @@ msgstr "" "possible pour nous aider à chercher une solution. Voici quelques " "informations dont nous pourrions avoir besoin :" -#: tickets/templates/tickets/form_ticket.html:47 +#: tickets/templates/tickets/help_text.html:6 msgid "The type of your problem (membership, connection, payment etc.)." msgstr "Le type de votre problème (adhésion, connexion, paiement etc.)." -#: tickets/templates/tickets/form_ticket.html:50 +#: tickets/templates/tickets/help_text.html:9 msgid "" "The conditions in which you encounter the problem (Wi-Fi/wired connection, " "on every machines or only one, on a new machine etc.)." @@ -256,7 +226,7 @@ msgstr "" "filaire, sur toutes les machines ou une seule, sur une nouvelle machine " "etc.)." -#: tickets/templates/tickets/form_ticket.html:53 +#: tickets/templates/tickets/help_text.html:12 msgid "" "The locations where you encounter the problem (in your room, in a common " "space, in a specific building etc.)." @@ -264,10 +234,6 @@ msgstr "" "Les lieux où vous rencontrez le problème (dans votre chambre, dans un espace " "commun, dans un bâtiment en particulier etc.)." -#: tickets/templates/tickets/form_ticket.html:56 -msgid "Open the ticket" -msgstr "Ouvrir le ticket" - #: tickets/templates/tickets/index.html:32 msgid "List of tickets" msgstr "Liste des tickets" @@ -276,6 +242,10 @@ msgstr "Liste des tickets" msgid "Manage the tickets" msgstr "Gérer les tickets" +#: tickets/templates/tickets/preferences.html:14 +msgid "Edit" +msgstr "Modifier" + #: tickets/templates/tickets/preferences.html:21 msgid "Publication email address" msgstr "Adresse mail de publication" @@ -284,25 +254,56 @@ msgstr "Adresse mail de publication" msgid "No email address, the tickets will not be published." msgstr "Pas d'adresse mail, les tickets ne seront pas publiés." -#: tickets/templates/tickets/preferences.html:29 -msgid "Email language" -msgstr "Langue du mail" - #: tickets/templates/tickets/profil.html:19 msgid "No tickets" msgstr "Pas de tickets" -#: tickets/views.py:66 tickets/views.py:77 +#: tickets/views.py:56 msgid "" "Your ticket has been succesfully opened. We will take care of it as soon as " "possible." msgstr "" "Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible." -#: tickets/views.py:122 tickets/views.py:147 +#: tickets/views.py:102 +msgid "Ticket has been updated successfully" +msgstr "Le ticket a été mis à jour" + +#: tickets/views.py:123 tickets/views.py:148 msgid "Never" msgstr "Jamais" +#~ msgid "Description of the ticket." +#~ msgstr "Description du ticket." + +#~ msgid "Mail language" +#~ msgstr "Langue du mail" + +#~ msgid "French" +#~ msgstr "Français" + +#~ msgid "English" +#~ msgstr "Anglais" + +#~ msgid "Mark as not solved" +#~ msgstr "Marquer comme non résolu" + +#~ msgid "Editing of tickets preferences" +#~ msgstr "Modification des préférences de tickets" + +#~ msgid "" +#~ "You are not authenticated. Please log in or provide an email address so " +#~ "we can get back to you." +#~ msgstr "" +#~ "Vous n'êtes pas authentifié. Veuillez vous connecter ou fournir une " +#~ "adresse mail pour que nous puissions vous recontacter." + +#~ msgid "Open the ticket" +#~ msgstr "Ouvrir le ticket" + +#~ msgid "Email language" +#~ msgstr "Langue du mail" + #~ msgid "The tickets preferences were edited." #~ msgstr "Les préférences de tickets ont été modifiées." diff --git a/tickets/migrations/0004_auto_20200422_2127.py b/tickets/migrations/0004_auto_20200422_2127.py new file mode 100644 index 00000000..c1dfa3f4 --- /dev/null +++ b/tickets/migrations/0004_auto_20200422_2127.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-22 19:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0003_auto_20200422_1839'), + ] + + operations = [ + migrations.RemoveField( + model_name='ticketoption', + name='mail_language', + ), + migrations.AlterField( + model_name='ticket', + name='description', + field=models.TextField(max_length=3000), + ), + ] diff --git a/tickets/models.py b/tickets/models.py index bf0a3794..5cc7d003 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -31,7 +31,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from re2o.mixins import AclMixin -from re2o.mail_utils import send_mail +from django.core.mail import EmailMessage from preferences.models import GeneralOption @@ -55,7 +55,6 @@ class Ticket(AclMixin, models.Model): ) description = models.TextField( max_length=3000, - help_text=_("Description of the ticket."), blank=False, null=False, ) @@ -77,27 +76,28 @@ class Ticket(AclMixin, models.Model): else: return _("Anonymous ticket. Date: %s.") % (self.date) - def publish_mail(self, request=None): + def publish_mail(self): site_url = GeneralOption.get_cached_value("main_site_url") to_addr = TicketOption.get_cached_value("publish_address") context = {"ticket": self, "site_url": site_url} - lang = TicketOption.get_cached_value("mail_language") - if lang == 0: + language = getattr(self.request, "LANGUAGE_CODE", "en") + if language == "fr": obj = "Nouveau ticket ouvert" template = loader.get_template("tickets/publication_mail_fr") else: obj = "New ticket opened" template = loader.get_template("tickets/publication_mail_en") - send_mail( - request, + mail_to_send = EmailMessage( obj, template.render(context), GeneralOption.get_cached_value("email_from"), [to_addr], - fail_silently=False, + reply_to=[self.email], ) + mail_to_send.send(fail_silently=False) + def can_view(self, user_request, *_args, **_kwargs): """ Check that the user has the right to view the ticket @@ -137,4 +137,4 @@ def ticket_post_save(**kwargs): if kwargs["created"]: if TicketOption.get_cached_value("publish_address"): ticket = kwargs["instance"] - ticket.publish_mail(ticket.request) + ticket.publish_mail() diff --git a/tickets/preferences/forms.py b/tickets/preferences/forms.py index 05b40ec3..04845ab8 100644 --- a/tickets/preferences/forms.py +++ b/tickets/preferences/forms.py @@ -42,4 +42,3 @@ class EditTicketOptionForm(FormRevMixin, ModelForm): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(EditTicketOptionForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields["publish_address"].label = _("Publish address") - self.fields["mail_language"].label = _("Mail language") diff --git a/tickets/preferences/models.py b/tickets/preferences/models.py index abc4d5fd..d02202f3 100644 --- a/tickets/preferences/models.py +++ b/tickets/preferences/models.py @@ -41,10 +41,6 @@ class TicketOption(AclMixin, PreferencesModel): max_length=1000, null=True, ) - LANG_FR = 0 - LANG_EN = 1 - LANGUES = ((0, _("French")), (1, _("English"))) - mail_language = models.IntegerField(choices=LANGUES, default=LANG_FR) class Meta: verbose_name = _("tickets options") diff --git a/tickets/preferences/views.py b/tickets/preferences/views.py index f26473a4..29465b1c 100644 --- a/tickets/preferences/views.py +++ b/tickets/preferences/views.py @@ -49,7 +49,6 @@ def aff_preferences(request): pref, created = models.TicketOption.objects.get_or_create() context = { "preferences": pref, - "language": str(pref.LANGUES[pref.mail_language][1]), } return render_to_string( "tickets/preferences.html", context=context, request=request, using=None diff --git a/tickets/templates/tickets/aff_ticket.html b/tickets/templates/tickets/aff_ticket.html index b66ad1c5..c4b2625d 100644 --- a/tickets/templates/tickets/aff_ticket.html +++ b/tickets/templates/tickets/aff_ticket.html @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load i18n %} {% load humanize %} +{% load acl %} {% block title %}{% trans "Tickets" %}{% endblock %} @@ -57,21 +58,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "Title:" %} {{ticket.title}}

-

{% trans "Description:" %} {{ ticket.description }}

- +

{% trans "Description:" %} {{ ticket.description | linebreaks }}

+
-
- {% csrf_token %} - {% bootstrap_form changestatusform %} - + {% can_edit ticket %} +

{% trans "Edit this ticket" %}

{% if not ticket.solved %} - {% trans "Mark as solved" as tr_mark_solved %} - {% bootstrap_button tr_mark_solved button_type="submit" button_class='btn-info' %} +

{% trans "Mark as solved" %}

{% else %} - {% trans "Mark as not solved" as tr_mark_not_solved %} - {% bootstrap_button tr_mark_not_solved button_type="submit" button_class='btn-warning' %} +

{% trans "Mark as unsolved" %}

{% endif %} -
+ {% acl_end %}
diff --git a/tickets/templates/tickets/form_preferences.html b/tickets/templates/tickets/edit.html similarity index 67% rename from tickets/templates/tickets/form_preferences.html rename to tickets/templates/tickets/edit.html index d5dd223b..29725fc4 100644 --- a/tickets/templates/tickets/form_preferences.html +++ b/tickets/templates/tickets/edit.html @@ -1,4 +1,4 @@ -{% extends 'machines/sidebar.html' %} +{% 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 @@ -25,25 +25,21 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load massive_bootstrap_form %} {% load i18n %} {% block title %}{% trans "Ticket" %}{% endblock %} {% block content %} -

{% trans "Editing of tickets preferences" %}

+

{% trans "Ticket opening" %}

-{% for message in messages %} -
- - {{ message | safe }} -
-{% endfor %} +{% bootstrap_form_errors ticketform %}
{% csrf_token %} - {% bootstrap_field preferencesform.publish_address %} - {% bootstrap_field preferencesform.mail_language %} - {% trans "Edit" as tr_edit %} - {% bootstrap_button tr_edit button_type="submit" icon='ok' button_class='btn-success' %} + {% bootstrap_form ticketform %} + {% bootstrap_button action_name button_type="submit" icon='ok' button_class='btn-success' %}
+ + {% endblock %} diff --git a/tickets/templates/tickets/form_ticket.html b/tickets/templates/tickets/form_ticket.html deleted file mode 100644 index b05803bc..00000000 --- a/tickets/templates/tickets/form_ticket.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends 'users/sidebar.html' %} -{% comment %} -Re2o est un logiciel d'administration développé initiallement au rezometz. Il -se veut agnostique au réseau considéré, de manière à être installable en -quelques clics. - -Copyright © 2017 Gabriel Détraz -Copyright © 2017 Lara Kermarec -Copyright © 2017 Augustin Lemesle -Copyright © 2017 Maël Kervella - -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 massive_bootstrap_form %} -{% load i18n %} - -{% block title %}{% trans "Ticket" %}{% endblock %} - -{% block content %} -

{% trans "Ticket opening" %}

- -
- {% csrf_token %} - {% if not user.is_authenticated %} -

{% trans "You are not authenticated. Please log in or provide an email address so we can get back to you." %}

- {% bootstrap_field ticketform.email %} - {% endif %} - {% bootstrap_field ticketform.title %} -
-

{% trans "Description of your problem. Please give as much information as possible to help us searching for a solution. Here is some information we might need:" %}

-
    -
  • -

    {% trans "The type of your problem (membership, connection, payment etc.)." %}

    -
  • -
  • -

    {% trans "The conditions in which you encounter the problem (Wi-Fi/wired connection, on every machines or only one, on a new machine etc.)." %}

    -
  • -
  • -

    {% trans "The locations where you encounter the problem (in your room, in a common space, in a specific building etc.)." %}

    -
- {% bootstrap_field ticketform.description %} - {% trans "Open the ticket" as tr_open %} - {% bootstrap_button tr_open button_type="submit" icon='ok' button_class='btn-success' %} -
-{% endblock %} diff --git a/tickets/templates/tickets/help_text.html b/tickets/templates/tickets/help_text.html new file mode 100644 index 00000000..599d00f7 --- /dev/null +++ b/tickets/templates/tickets/help_text.html @@ -0,0 +1,14 @@ +{% load i18n %} +
+

{% trans "Description of your problem. Please give as much information as possible to help us searching for a solution. Here is some information we might need:" %}

+
    +
  • +

    {% trans "The type of your problem (membership, connection, payment etc.)." %}

    +
  • +
  • +

    {% trans "The conditions in which you encounter the problem (Wi-Fi/wired connection, on every machines or only one, on a new machine etc.)." %}

    +
  • +
  • +

    {% trans "The locations where you encounter the problem (in your room, in a common space, in a specific building etc.)." %}

    +
+ diff --git a/tickets/templates/tickets/preferences.html b/tickets/templates/tickets/preferences.html index 5952a496..5808ce3b 100644 --- a/tickets/templates/tickets/preferences.html +++ b/tickets/templates/tickets/preferences.html @@ -25,12 +25,8 @@

{% trans "No email address, the tickets will not be published." %}

{% endif %} - -

{% trans "Email language" %}

-

{{ language }}

-
- + diff --git a/tickets/templates/tickets/publication_mail_en b/tickets/templates/tickets/publication_mail_en index 5224a80b..1e436877 100644 --- a/tickets/templates/tickets/publication_mail_en +++ b/tickets/templates/tickets/publication_mail_en @@ -7,6 +7,6 @@ An anonymous user (not authenticated) opened a ticket Answer to the address:{{ticket.email}}. {% endif %} -Title: {{ticket.title}} +Title: {{ ticket.title | safe }} -Description: {{ticket.description}} +Description: {{ ticket.description | safe }} diff --git a/tickets/templates/tickets/publication_mail_fr b/tickets/templates/tickets/publication_mail_fr index ef1099da..3b733e8f 100644 --- a/tickets/templates/tickets/publication_mail_fr +++ b/tickets/templates/tickets/publication_mail_fr @@ -7,6 +7,6 @@ Un utilisateur anonyme (non connecté) a ouvert un ticket. Répondre à l'adresse : {{ticket.email}}. {% endif %} -Titre : {{ticket.title}} +Titre : {{ ticket.title | safe }} -Description : {{ticket.description}} +Description : {{ ticket.description | safe }} diff --git a/tickets/urls.py b/tickets/urls.py index c6eaefde..dee41a14 100644 --- a/tickets/urls.py +++ b/tickets/urls.py @@ -31,6 +31,8 @@ from .preferences.views import edit_options urlpatterns = [ url(r"^$", views.aff_tickets, name="aff-tickets"), url(r"^(?P[0-9]+)$", views.aff_ticket, name="aff-ticket"), + url(r"^change_ticket_status/(?P[0-9]+)$", views.change_ticket_status, name="change-ticket-status"), + url(r"^edit_ticket/(?P[0-9]+)$", views.edit_ticket, name="edit-ticket"), url( r"^edit_options/(?P
TicketOption)$", edit_options, diff --git a/tickets/views.py b/tickets/views.py index f5186cdf..f8342476 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -42,70 +42,71 @@ from preferences.models import GeneralOption from .models import Ticket -from .forms import NewTicketForm, ChangeStatusTicketForm +from .forms import NewTicketForm, EditTicketForm def new_ticket(request): """ Ticket creation view""" - ticketform = NewTicketForm(request.POST or None) - - if request.method == "POST": - ticketform = NewTicketForm(request.POST) - - if ticketform.is_valid(): - email = ticketform.cleaned_data.get("email") - ticket = ticketform.save(commit=False) - ticket.request = request - - if request.user.is_authenticated: - ticket.user = request.user - ticket.save() - messages.success( - request, - _( - "Your ticket has been succesfully opened. We will take care of it as soon as possible." - ), - ) - return redirect( - reverse("users:profil", kwargs={"userid": str(request.user.id)}) - ) - if not request.user.is_authenticated and email != "": - ticket.save() - messages.success( - request, - _( - "Your ticket has been succesfully opened. We will take care of it as soon as possible." - ), - ) - return redirect(reverse("index")) - else: - messages.error( - request, - _( - "You are not authenticated. Please log in or provide an email address so we can get back to you." - ), - ) - return form( - {"ticketform": ticketform}, "tickets/form_ticket.html", request - ) - - else: - ticketform = NewTicketForm - return form({"ticketform": ticketform}, "tickets/form_ticket.html", request) + ticketform = NewTicketForm(request.POST or None, request=request) + if ticketform.is_valid(): + ticketform.save() + messages.success( + request, + _( + "Your ticket has been succesfully opened. We will take care of it as soon as possible." + ), + ) + if not request.user.is_authenticated: + return redirect(reverse("index")) + else: + return redirect( + reverse("users:profil", kwargs={"userid": str(request.user.id)}) + ) + return form( + {"ticketform": ticketform, 'action_name': ("Create a ticket")}, "tickets/edit.html", request + ) @login_required @can_view(Ticket) def aff_ticket(request, ticket, ticketid): """View to display only one ticket""" - changestatusform = ChangeStatusTicketForm(request.POST) - if request.method == "POST": - ticket.solved = not ticket.solved - ticket.save() return render( request, "tickets/aff_ticket.html", - {"ticket": ticket, "changestatusform": changestatusform}, + {"ticket": ticket}, + ) + + +@login_required +@can_edit(Ticket) +def change_ticket_status(request, ticket, ticketid): + """View to edit ticket state""" + ticket.solved = not ticket.solved + ticket.save() + return redirect( + reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) + ) + + +@login_required +@can_edit(Ticket) +def edit_ticket(request, ticket, ticketid): + """ Ticket creation view""" + ticketform = EditTicketForm(request.POST or None, instance=ticket) + if ticketform.is_valid(): + ticketform.save() + messages.success( + request, + _( + "Ticket has been updated successfully" + ), + ) + return redirect( + reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) + ) + return form( + {"ticketform": ticketform, 'action_name': ("Edit this ticket")}, "tickets/edit.html", request ) From 38b119bb8ece6a5aa1709a345a18d607872bb649 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 23 Apr 2020 02:42:18 +0200 Subject: [PATCH 6/7] Add comment to tikets + send_notif mails --- tickets/admin.py | 6 +- tickets/forms.py | 17 +- tickets/locale/fr/LC_MESSAGES/django.po | 160 +++++++++++++----- tickets/migrations/0005_auto_20200422_2309.py | 40 +++++ tickets/migrations/0006_auto_20200423_0202.py | 31 ++++ tickets/migrations/0007_ticket_language.py | 20 +++ tickets/models.py | 135 ++++++++++++++- tickets/templates/tickets/aff_ticket.html | 45 +++-- tickets/templates/tickets/delete.html | 43 +++++ tickets/templates/tickets/publication_mail_en | 4 +- tickets/templates/tickets/publication_mail_fr | 4 +- tickets/templates/tickets/update_mail_en | 12 ++ tickets/templates/tickets/update_mail_fr | 12 ++ tickets/urls.py | 3 + tickets/views.py | 68 +++++++- 15 files changed, 524 insertions(+), 76 deletions(-) create mode 100644 tickets/migrations/0005_auto_20200422_2309.py create mode 100644 tickets/migrations/0006_auto_20200423_0202.py create mode 100644 tickets/migrations/0007_ticket_language.py create mode 100644 tickets/templates/tickets/delete.html create mode 100644 tickets/templates/tickets/update_mail_en create mode 100644 tickets/templates/tickets/update_mail_fr diff --git a/tickets/admin.py b/tickets/admin.py index 69f84d0d..1644ee75 100644 --- a/tickets/admin.py +++ b/tickets/admin.py @@ -25,13 +25,15 @@ Ticket preferences model from django.contrib import admin -from .models import Ticket +from .models import Ticket, CommentTicket from reversion.admin import VersionAdmin class TicketAdmin(VersionAdmin): - """Gestion des ticket""" + pass +class CommentTicketAdmin(VersionAdmin): pass admin.site.register(Ticket, TicketAdmin) +admin.site.register(CommentTicket, CommentTicketAdmin) diff --git a/tickets/forms.py b/tickets/forms.py index b1be6597..a9f6617d 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -31,7 +31,7 @@ from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin from django.utils.translation import ugettext_lazy as _ -from .models import Ticket +from .models import Ticket, CommentTicket class NewTicketForm(FormRevMixin, ModelForm): @@ -48,7 +48,7 @@ class NewTicketForm(FormRevMixin, ModelForm): self.fields.pop('email') self.instance.user = request.user self.fields['description'].help_text = render_to_string('tickets/help_text.html') - self.instance.request = request + self.instance.language = getattr(request, "LANGUAGE_CODE", "en") class EditTicketForm(FormRevMixin, ModelForm): @@ -62,3 +62,16 @@ class EditTicketForm(FormRevMixin, ModelForm): super(EditTicketForm, self).__init__(*args, **kwargs) self.fields['email'].required = False + +class CommentTicketForm(FormRevMixin, ModelForm): + """Edit and create comment to a ticket""" + + class Meta: + model = CommentTicket + fields = ["comment"] + + def __init__(self, *args, **kwargs): + prefix = kwargs.pop("prefix", self.Meta.model.__name__) + super(CommentTicketForm, self).__init__(*args, prefix=prefix, **kwargs) + self.fields["comment"].label = _("comment") + diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 05ce5d9b..415c618f 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-22 22:06+0200\n" +"POT-Creation-Date: 2020-04-23 03:10+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -30,44 +30,68 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: tickets/models.py:54 +#: tickets/forms.py:76 +msgid "comment" +msgstr "commentaire" + +#: tickets/models.py:57 msgid "Title of the ticket." msgstr "Titre du ticket." -#: tickets/models.py:63 +#: tickets/models.py:66 msgid "An email address to get back to you." msgstr "Une adresse mail pour vous recontacter." -#: tickets/models.py:69 +#: tickets/models.py:70 +msgid "Language of the ticket." +msgstr "Langue des tickets" + +#: tickets/models.py:74 tickets/models.py:170 msgid "Can view a ticket object" msgstr "Peut voir un objet ticket" -#: tickets/models.py:70 +#: tickets/models.py:75 tickets/models.py:171 msgid "ticket" msgstr "ticket" -#: tickets/models.py:71 +#: tickets/models.py:76 tickets/models.py:172 msgid "tickets" msgstr "tickets" -#: tickets/models.py:75 +#: tickets/models.py:80 #, python-format msgid "Ticket from %(name)s. Date: %(date)s." msgstr "Ticket de %(name)s. Date : %(date)s." -#: tickets/models.py:77 +#: tickets/models.py:82 #, python-format msgid "Anonymous ticket. Date: %s." msgstr "Ticket anonyme. Date : %s." -#: tickets/models.py:111 +#: tickets/models.py:90 tickets/templates/tickets/aff_ticket.html:52 +msgid "Anonymous user" +msgstr "Utilisateur anonyme" + +#: tickets/models.py:128 msgid "You don't have the right to view other tickets than yours." msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres." -#: tickets/models.py:123 +#: tickets/models.py:140 tickets/models.py:214 msgid "You don't have the right to view the list of tickets." msgstr "Vous n'avez pas le droit de voir la liste des tickets." +#: tickets/models.py:187 +msgid "You don't have the right to view other tickets comments than yours." +msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres." + +#: tickets/models.py:202 +msgid "You don't have the right to edit other tickets comments than yours." +msgstr "Vous n'avez pas le droit d'éditer d'autres tickets que les vôtres." + +#: tickets/models.py:232 +msgid "Update of your ticket" +msgstr "Mise à jour de votre ticket" + #: tickets/preferences/forms.py:44 msgid "Publish address" msgstr "Adresse mail de publication" @@ -87,7 +111,7 @@ msgstr "Options des tickets" msgid "Can view tickets options" msgstr "Peut voir les options des tickets" -#: tickets/templates/tickets/aff_ticket.html:31 +#: tickets/templates/tickets/aff_ticket.html:32 #: tickets/templates/tickets/contact.html:4 #: tickets/templates/tickets/index.html:29 #: tickets/templates/tickets/preferences.html:6 @@ -95,57 +119,79 @@ msgstr "Peut voir les options des tickets" msgid "Tickets" msgstr "Tickets" -#: tickets/templates/tickets/aff_ticket.html:35 +#: tickets/templates/tickets/aff_ticket.html:36 #, python-format msgid "Ticket #%(id)s" msgstr "Ticket #%(id)s" -#: tickets/templates/tickets/aff_ticket.html:37 +#: tickets/templates/tickets/aff_ticket.html:38 #: tickets/templates/tickets/aff_tickets.html:58 msgid "Solved" msgstr "Résolu" -#: tickets/templates/tickets/aff_ticket.html:39 +#: tickets/templates/tickets/aff_ticket.html:40 msgid "Not solved" msgstr "Non résolu" -#: tickets/templates/tickets/aff_ticket.html:45 +#: tickets/templates/tickets/aff_ticket.html:46 msgid "Opened by" msgstr "Ouvert par" -#: tickets/templates/tickets/aff_ticket.html:51 -msgid "Anonymous user" -msgstr "Utilisateur anonyme" - -#: tickets/templates/tickets/aff_ticket.html:55 +#: tickets/templates/tickets/aff_ticket.html:56 msgid "Response address: " msgstr "Adresse de réponse : " -#: tickets/templates/tickets/aff_ticket.html:55 +#: tickets/templates/tickets/aff_ticket.html:56 msgid "Response to your ticket" msgstr "Réponse à votre ticket" -#: tickets/templates/tickets/aff_ticket.html:60 -msgid "Title:" -msgstr "Titre :" - #: tickets/templates/tickets/aff_ticket.html:61 -msgid "Description:" -msgstr "Description :" +msgid "Add a comment " +msgstr "Ajouter un commentaire" -#: tickets/templates/tickets/aff_ticket.html:65 -msgid "Edit this ticket" -msgstr "Modifier le ticket." +#: tickets/templates/tickets/aff_ticket.html:64 +#: tickets/templates/tickets/preferences.html:14 tickets/views.py:153 +msgid "Edit" +msgstr "Modifier" -#: tickets/templates/tickets/aff_ticket.html:67 +#: tickets/templates/tickets/aff_ticket.html:66 msgid "Mark as solved" msgstr "Marquer comme résolu" -#: tickets/templates/tickets/aff_ticket.html:69 +#: tickets/templates/tickets/aff_ticket.html:68 msgid "Mark as unsolved" msgstr "Marquer comme non résolu" -#: tickets/templates/tickets/aff_ticket.html:78 +#: tickets/templates/tickets/aff_ticket.html:76 +msgid "Title:" +msgstr "Titre :" + +#: tickets/templates/tickets/aff_ticket.html:77 +#: tickets/templates/tickets/aff_ticket.html:84 +msgid "Description:" +msgstr "Description :" + +#: tickets/templates/tickets/aff_ticket.html:83 +msgid "Comment " +msgstr "Commentaire" + +#: tickets/templates/tickets/aff_ticket.html:83 +msgid " added by " +msgstr " ajouté par " + +#: tickets/templates/tickets/aff_ticket.html:83 +msgid " on " +msgstr " le " + +#: tickets/templates/tickets/aff_ticket.html:87 +msgid "Edit this comment " +msgstr "Modifier le commentaire" + +#: tickets/templates/tickets/aff_ticket.html:90 +msgid "Delete this comment " +msgstr "Supprimer ce commentaire" + +#: tickets/templates/tickets/aff_ticket.html:99 msgid "All tickets" msgstr "Tous les tickets" @@ -200,6 +246,21 @@ msgstr "" msgid "Open a ticket" msgstr "Ouvrir un ticket" +#: tickets/templates/tickets/delete.html:29 +msgid "Deletion of tickets" +msgstr "Suppression de tickets" + +#: tickets/templates/tickets/delete.html:35 +#, python-format +msgid "" +"Warning: are you sure you want to delete this %(objet_name)s object " +"( %(objet)s )?" +msgstr "" + +#: tickets/templates/tickets/delete.html:36 +msgid "Confirm" +msgstr "Confirmer" + #: tickets/templates/tickets/edit.html:34 msgid "Ticket opening" msgstr "Ouverture de ticket" @@ -242,10 +303,6 @@ msgstr "Liste des tickets" msgid "Manage the tickets" msgstr "Gérer les tickets" -#: tickets/templates/tickets/preferences.html:14 -msgid "Edit" -msgstr "Modifier" - #: tickets/templates/tickets/preferences.html:21 msgid "Publication email address" msgstr "Adresse mail de publication" @@ -258,18 +315,38 @@ msgstr "Pas d'adresse mail, les tickets ne seront pas publiés." msgid "No tickets" msgstr "Pas de tickets" -#: tickets/views.py:56 +#: tickets/views.py:62 msgid "" "Your ticket has been succesfully opened. We will take care of it as soon as " "possible." msgstr "" "Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible." -#: tickets/views.py:102 +#: tickets/views.py:109 msgid "Ticket has been updated successfully" msgstr "Le ticket a été mis à jour" -#: tickets/views.py:123 tickets/views.py:148 +#: tickets/views.py:130 +msgid "This comment was added." +msgstr "Le commentaire a été ajouté" + +#: tickets/views.py:135 +msgid "Add a comment" +msgstr "Ajouter un commentaire" + +#: tickets/views.py:148 +msgid "This comment was edited." +msgstr "Le commentaire a été édité" + +#: tickets/views.py:164 +msgid "The comment was deleted." +msgstr "Le commentaire a été supprimé" + +#: tickets/views.py:169 +msgid "Ticket Comment" +msgstr "Commentaire de ticket" + +#: tickets/views.py:183 tickets/views.py:208 msgid "Never" msgstr "Jamais" @@ -298,9 +375,6 @@ msgstr "Jamais" #~ "Vous n'êtes pas authentifié. Veuillez vous connecter ou fournir une " #~ "adresse mail pour que nous puissions vous recontacter." -#~ msgid "Open the ticket" -#~ msgstr "Ouvrir le ticket" - #~ msgid "Email language" #~ msgstr "Langue du mail" diff --git a/tickets/migrations/0005_auto_20200422_2309.py b/tickets/migrations/0005_auto_20200422_2309.py new file mode 100644 index 00000000..43fdbdb3 --- /dev/null +++ b/tickets/migrations/0005_auto_20200422_2309.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-22 21:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0004_auto_20200422_2127'), + ] + + operations = [ + migrations.CreateModel( + name='CommentTicket', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True)), + ('comment', models.TextField(max_length=4095)), + ], + options={ + 'verbose_name': 'ticket', + 'verbose_name_plural': 'tickets', + 'permissions': (('view_commentticket', 'Can view a ticket object'),), + }, + bases=(re2o.mixins.AclMixin, models.Model), + ), + migrations.AlterModelOptions( + name='ticket', + options={'permissions': (('view_ticket', 'Can view a ticket object'),), 'verbose_name': 'ticket', 'verbose_name_plural': 'tickets'}, + ), + migrations.AddField( + model_name='commentticket', + name='parent_ticket', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.Ticket'), + ), + ] diff --git a/tickets/migrations/0006_auto_20200423_0202.py b/tickets/migrations/0006_auto_20200423_0202.py new file mode 100644 index 00000000..565a8b7a --- /dev/null +++ b/tickets/migrations/0006_auto_20200423_0202.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-23 00:02 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('tickets', '0005_auto_20200422_2309'), + ] + + operations = [ + migrations.AddField( + model_name='commentticket', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='commentticket', + name='created_by', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_comment', to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/tickets/migrations/0007_ticket_language.py b/tickets/migrations/0007_ticket_language.py new file mode 100644 index 00000000..351d2359 --- /dev/null +++ b/tickets/migrations/0007_ticket_language.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.28 on 2020-04-23 01:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0006_auto_20200423_0202'), + ] + + operations = [ + migrations.AddField( + model_name='ticket', + name='language', + field=models.CharField(default='en', help_text='Language of the ticket.', max_length=16), + ), + ] diff --git a/tickets/models.py b/tickets/models.py index 5cc7d003..67bf8f34 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -29,6 +29,9 @@ from django.utils.translation import ugettext_lazy as _ from django.template import loader from django.db.models.signals import post_save from django.dispatch import receiver +from django.utils.functional import cached_property + +from reversion.models import Version from re2o.mixins import AclMixin from django.core.mail import EmailMessage @@ -63,10 +66,12 @@ class Ticket(AclMixin, models.Model): help_text=_("An email address to get back to you."), max_length=100, null=True ) solved = models.BooleanField(default=False) - request = None + language = models.CharField( + max_length=16, help_text=_("Language of the ticket."), default="en" + ) class Meta: - permissions = (("view_tickets", _("Can view a ticket object")),) + permissions = (("view_ticket", _("Can view a ticket object")),) verbose_name = _("ticket") verbose_name_plural = _("tickets") @@ -76,13 +81,25 @@ class Ticket(AclMixin, models.Model): else: return _("Anonymous ticket. Date: %s.") % (self.date) + @cached_property + def opened_by(self): + """Return full name of this ticket opener""" + if self.user: + return self.user.get_full_name() + else: + return _("Anonymous user") + + @cached_property + def get_mail(self): + """Return the mail of the owner of this ticket""" + return self.email or self.user.get_mail + def publish_mail(self): site_url = GeneralOption.get_cached_value("main_site_url") to_addr = TicketOption.get_cached_value("publish_address") context = {"ticket": self, "site_url": site_url} - language = getattr(self.request, "LANGUAGE_CODE", "en") - if language == "fr": + if self.language == "fr": obj = "Nouveau ticket ouvert" template = loader.get_template("tickets/publication_mail_fr") else: @@ -94,7 +111,7 @@ class Ticket(AclMixin, models.Model): template.render(context), GeneralOption.get_cached_value("email_from"), [to_addr], - reply_to=[self.email], + reply_to=[self.get_mail], ) mail_to_send.send(fail_silently=False) @@ -103,13 +120,13 @@ class Ticket(AclMixin, models.Model): """ Check that the user has the right to view the ticket or that it is the author""" if ( - not user_request.has_perm("tickets.view_tickets") + not user_request.has_perm("tickets.view_ticket") and self.user != user_request ): return ( False, _("You don't have the right to view other tickets than yours."), - ("tickets.view_tickets",), + ("tickets.view_ticket",), ) else: return True, None, None @@ -117,13 +134,13 @@ class Ticket(AclMixin, models.Model): @staticmethod def can_view_all(user_request, *_args, **_kwargs): """ Check that the user has access to the list of all tickets""" - can = user_request.has_perm("tickets.view_tickets") + can = user_request.has_perm("tickets.view_ticket") return ( can, _("You don't have the right to view the list of tickets.") if not can else None, - ("tickets.view_tickets",), + ("tickets.view_ticket",), ) def can_create(user_request, *_args, **_kwargs): @@ -131,6 +148,97 @@ class Ticket(AclMixin, models.Model): return True, None, None +class CommentTicket(AclMixin, models.Model): + """A comment of a ticket""" + date = models.DateTimeField(auto_now_add=True) + comment = models.TextField( + max_length=4095, + blank=False, + null=False, + ) + parent_ticket = models.ForeignKey( + "Ticket", on_delete=models.CASCADE + ) + created_at = models.DateTimeField(auto_now_add=True) + created_by = models.ForeignKey( + "users.User", + on_delete=models.CASCADE, + related_name="ticket_comment", + ) + + class Meta: + permissions = (("view_commentticket", _("Can view a ticket object")),) + verbose_name = _("ticket") + verbose_name_plural = _("tickets") + + @cached_property + def comment_id(self): + return CommentTicket.objects.filter(parent_ticket=self.parent_ticket, pk__lt=self.pk).count() + 1 + + def can_view(self, user_request, *_args, **_kwargs): + """ Check that the user has the right to view the ticket comment + or that it is the author""" + if ( + not user_request.has_perm("tickets.view_commentticket") + and self.parent_ticket.user != user_request + ): + return ( + False, + _("You don't have the right to view other tickets comments than yours."), + ("tickets.view_commentticket",), + ) + else: + return True, None, None + + def can_edit(self, user_request, *_args, **_kwargs): + """ Check that the user has the right to edit the ticket comment + or that it is the author""" + if ( + not user_request.has_perm("tickets.edit_commentticket") + and (self.parent_ticket.user != user_request or self.parent_ticket.user != self.created_by) + ): + return ( + False, + _("You don't have the right to edit other tickets comments than yours."), + ("tickets.edit_commentticket",), + ) + else: + return True, None, None + + @staticmethod + def can_view_all(user_request, *_args, **_kwargs): + """ Check that the user has access to the list of all tickets comments""" + can = user_request.has_perm("tickets.view_commentticket") + return ( + can, + _("You don't have the right to view the list of tickets.") + if not can + else None, + ("tickets.view_commentticket",), + ) + + def __str__(self): + return "Comment " + str(self.comment_id) + " on " + str(self.parent_ticket) + + def publish_mail(self): + site_url = GeneralOption.get_cached_value("main_site_url") + to_addr = TicketOption.get_cached_value("publish_address") + context = {"comment": self, "site_url": site_url} + + if self.parent_ticket.language == "fr": + template = loader.get_template("tickets/update_mail_fr") + else: + template = loader.get_template("tickets/update_mail_en") + obj = _("Update of your ticket") + mail_to_send = EmailMessage( + obj, + template.render(context), + GeneralOption.get_cached_value("email_from"), + [to_addr, self.parent_ticket.get_mail], + ) + mail_to_send.send(fail_silently=False) + + @receiver(post_save, sender=Ticket) def ticket_post_save(**kwargs): """ Send the mail to publish the new ticket """ @@ -138,3 +246,12 @@ def ticket_post_save(**kwargs): if TicketOption.get_cached_value("publish_address"): ticket = kwargs["instance"] ticket.publish_mail() + + +@receiver(post_save, sender=CommentTicket) +def comment_post_save(**kwargs): + """ Send the mail to publish the new comment """ + if kwargs["created"]: + if TicketOption.get_cached_value("publish_address"): + comment = kwargs["instance"] + comment.publish_mail() diff --git a/tickets/templates/tickets/aff_ticket.html b/tickets/templates/tickets/aff_ticket.html index c4b2625d..da7d0c39 100644 --- a/tickets/templates/tickets/aff_ticket.html +++ b/tickets/templates/tickets/aff_ticket.html @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load i18n %} {% load humanize %} +{% load logs_extra %} {% load acl %} {% block title %}{% trans "Tickets" %}{% endblock %} @@ -54,28 +55,48 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if not ticket.user %} {% trans "Response address: " %}{{ticket.email}} {% endif %} + +
+ {% can_view ticket %} + {% trans "Add a comment " %} + {% acl_end %} + {% can_edit ticket %} + {% trans "Edit" %} + {% if not ticket.solved %} + {% trans "Mark as solved" %} + {% else %} + {% trans "Mark as unsolved" %} + {% endif %} + {% acl_end %} + {% history_button ticket text=True %} +

{% trans "Title:" %} {{ticket.title}}

-

{% trans "Description:" %} {{ ticket.description | linebreaks }}

+ {% trans "Description:" %} {{ ticket.description | linebreaks }} -
- {% can_edit ticket %} -

{% trans "Edit this ticket" %}

- {% if not ticket.solved %} -

{% trans "Mark as solved" %}

- {% else %} -

{% trans "Mark as unsolved" %}

- {% endif %} +
+ +{% for comment in comments %} + +{% endfor %}
- - {% endblock %} diff --git a/tickets/templates/tickets/delete.html b/tickets/templates/tickets/delete.html new file mode 100644 index 00000000..c583ee5b --- /dev/null +++ b/tickets/templates/tickets/delete.html @@ -0,0 +1,43 @@ +{% extends 'users/sidebar.html' %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Lara Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} +{% load i18n %} + +{% block title %}{% trans "Deletion of tickets" %}{% endblock %} + +{% block content %} + +
+ {% csrf_token %} +

{% blocktrans %}Warning: are you sure you want to delete this {{ objet_name }} object ( {{ objet }} )?{% endblocktrans %}

+ {% trans "Confirm" as tr_confirm %} + {% bootstrap_button tr_confirm button_type="submit" icon="trash" button_class='btn-danger' %} +
+
+
+
+{% endblock %} + diff --git a/tickets/templates/tickets/publication_mail_en b/tickets/templates/tickets/publication_mail_en index 1e436877..97ad4b2c 100644 --- a/tickets/templates/tickets/publication_mail_en +++ b/tickets/templates/tickets/publication_mail_en @@ -1,12 +1,12 @@ {% if ticket.user %} {{ ticket.user.get_full_name }} opened a ticket. Profile: {{site_url}}{% url 'users:profil' ticket.user.id%} -Answer to the address: {{ticket.user.get_mail}}. {% else %} An anonymous user (not authenticated) opened a ticket -Answer to the address:{{ticket.email}}. {% endif %} +Answer to the address: {{{ticket.get_mail}}. Title: {{ ticket.title | safe }} Description: {{ ticket.description | safe }} + diff --git a/tickets/templates/tickets/publication_mail_fr b/tickets/templates/tickets/publication_mail_fr index 3b733e8f..3491a8d8 100644 --- a/tickets/templates/tickets/publication_mail_fr +++ b/tickets/templates/tickets/publication_mail_fr @@ -1,11 +1,11 @@ {% if ticket.user %} {{ ticket.user.get_full_name }} a ouvert un ticket. Profil : {{site_url}}{% url 'users:profil' ticket.user.id%} -Répondre à l'adresse : {{ticket.user.get_mail}}. {% else %} Un utilisateur anonyme (non connecté) a ouvert un ticket. -Répondre à l'adresse : {{ticket.email}}. {% endif %} +Répondre à l'adresse : {{ticket.get_mail}}. + Titre : {{ ticket.title | safe }} diff --git a/tickets/templates/tickets/update_mail_en b/tickets/templates/tickets/update_mail_en new file mode 100644 index 00000000..8c25d4fd --- /dev/null +++ b/tickets/templates/tickets/update_mail_en @@ -0,0 +1,12 @@ +Hello, + +The ticket {{ comment.parent_ticket.title | safe }} n°{{ comment.parent_ticket.id }}, opened by {{ comment.parent_ticket.opened_by }}, has been updated by {{ comment.created_by.get_full_name | safe }}. +{% if comment.parent_ticket.user %} +The complete re2o profil can be found here : {{site_url}}{% url 'users:profil' comment.parent_ticket.user.id%} +{% endif %} + +Description : {{ comment.comment | safe }} + +Best regards, + +The member of the association diff --git a/tickets/templates/tickets/update_mail_fr b/tickets/templates/tickets/update_mail_fr new file mode 100644 index 00000000..3b1ec8ee --- /dev/null +++ b/tickets/templates/tickets/update_mail_fr @@ -0,0 +1,12 @@ +Bonjour, + +Le ticket {{ comment.parent_ticket.title | safe }} n°{{ comment.parent_ticket.id }}, ouvert par {{ comment.parent_ticket.opened_by }}, a reçu une mise à jour par {{ comment.created_by.get_full_name | safe }}. +{% if comment.parent_ticket.user %} +Le profil re2o est accessible à l'adresse suivante : {{site_url}}{% url 'users:profil' comment.parent_ticket.user.id%} +{% endif %} + +Description : {{ comment.comment | safe }} + +Cordialement, + +Les membres actifs de l'association diff --git a/tickets/urls.py b/tickets/urls.py index dee41a14..fe0b88ec 100644 --- a/tickets/urls.py +++ b/tickets/urls.py @@ -39,4 +39,7 @@ urlpatterns = [ name="edit-options", ), url(r"^new_ticket/$", views.new_ticket, name="new-ticket"), + url(r"^add_comment/(?P[0-9]+)$", views.add_comment, name="add-comment"), + url(r"^edit_comment/(?P[0-9]+)$", views.edit_comment, name="edit-comment"), + url(r"^del_comment/(?P[0-9]+)$", views.del_comment, name="del-comment"), ] diff --git a/tickets/views.py b/tickets/views.py index f8342476..bda14df7 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -36,13 +36,19 @@ from re2o.views import form from re2o.base import re2o_paginator -from re2o.acl import can_view, can_view_all, can_edit, can_create +from re2o.acl import ( + can_view, + can_view_all, + can_edit, + can_create, + can_delete +) from preferences.models import GeneralOption -from .models import Ticket +from .models import Ticket, CommentTicket -from .forms import NewTicketForm, EditTicketForm +from .forms import NewTicketForm, EditTicketForm, CommentTicketForm def new_ticket(request): @@ -71,10 +77,11 @@ def new_ticket(request): @can_view(Ticket) def aff_ticket(request, ticket, ticketid): """View to display only one ticket""" + comments = CommentTicket.objects.filter(parent_ticket=ticket) return render( request, "tickets/aff_ticket.html", - {"ticket": ticket}, + {"ticket": ticket, "comments": comments}, ) @@ -110,6 +117,59 @@ def edit_ticket(request, ticket, ticketid): ) +@login_required +@can_view(Ticket) +def add_comment(request, ticket, ticketid): + """ Add a comment to a ticket""" + commentticket = CommentTicketForm(request.POST or None) + if commentticket.is_valid(): + commentticket = commentticket.save(commit=False) + commentticket.parent_ticket = ticket + commentticket.created_by = request.user + commentticket.save() + messages.success(request, _("This comment was added.")) + return redirect( + reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) + ) + return form( + {"ticketform": commentticket, "action_name": _("Add a comment")}, "tickets/edit.html", request + ) + + +@login_required +@can_edit(CommentTicket) +def edit_comment(request, commentticket_instance, **_kwargs): + """ Edit a comment of a ticket""" + commentticket = CommentTicketForm(request.POST or None, instance=commentticket_instance) + if commentticket.is_valid(): + ticketid = commentticket_instance.parent_ticket.id + if commentticket.changed_data: + commentticket.save() + messages.success(request, _("This comment was edited.")) + return redirect( + reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) + ) + return form( + {"ticketform": commentticket, "action_name": _("Edit")}, "tickets/edit.html", request, + ) + + +@login_required +@can_delete(CommentTicket) +def del_comment(request, commentticket, **_kwargs): + """Delete a comment of a ticket""" + if request.method == "POST": + ticketid = commentticket.parent_ticket.id + commentticket.delete() + messages.success(request, _("The comment was deleted.")) + return redirect( + reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) + ) + return form( + {"objet": commentticket, "objet_name": _("Ticket Comment")}, "tickets/delete.html", request + ) + + @login_required @can_view_all(Ticket) def aff_tickets(request): From 4a1d0a1026a10aed8369347968f819878c5c1fd6 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 23 Apr 2020 03:21:11 +0200 Subject: [PATCH 7/7] Lerennais remarques --- preferences/views.py | 4 ++-- tickets/forms.py | 5 ++++- tickets/models.py | 8 ++++++-- tickets/preferences/views.py | 3 --- tickets/views.py | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/preferences/views.py b/preferences/views.py index 6420dad3..25e7c009 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -96,8 +96,8 @@ def edit_options_template_function(request, section, forms, models): return redirect(reverse("preferences:display-options")) options_instance, _created = model.objects.get_or_create() - can, msg, permissions = options_instance.can_edit(request.user) - if not can: + _is_allowed_to_edit, msg, permissions = options_instance.can_edit(request.user) + if not _is_allowed_to_edit: messages.error(request, acl_error_message(msg, permissions)) return redirect(reverse("index")) options = form_instance( diff --git a/tickets/forms.py b/tickets/forms.py index a9f6617d..3001de8a 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -42,13 +42,14 @@ class NewTicketForm(FormRevMixin, ModelForm): fields = ["title", "description", "email"] def __init__(self, *args, **kwargs): - request = kwargs.pop("request") + request = kwargs.pop("request", None) super(NewTicketForm, self).__init__(*args, **kwargs) if request.user.is_authenticated: self.fields.pop('email') self.instance.user = request.user self.fields['description'].help_text = render_to_string('tickets/help_text.html') self.instance.language = getattr(request, "LANGUAGE_CODE", "en") + self.instance.request = request class EditTicketForm(FormRevMixin, ModelForm): @@ -71,7 +72,9 @@ class CommentTicketForm(FormRevMixin, ModelForm): fields = ["comment"] def __init__(self, *args, **kwargs): + request = kwargs.pop("request", None) prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(CommentTicketForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields["comment"].label = _("comment") + self.instance.request = request diff --git a/tickets/models.py b/tickets/models.py index 67bf8f34..98a006ac 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -34,6 +34,7 @@ from django.utils.functional import cached_property from reversion.models import Version from re2o.mixins import AclMixin +from re2o.mail_utils import send_mail_object from django.core.mail import EmailMessage from preferences.models import GeneralOption @@ -69,6 +70,7 @@ class Ticket(AclMixin, models.Model): language = models.CharField( max_length=16, help_text=_("Language of the ticket."), default="en" ) + request = None class Meta: permissions = (("view_ticket", _("Can view a ticket object")),) @@ -113,7 +115,7 @@ class Ticket(AclMixin, models.Model): [to_addr], reply_to=[self.get_mail], ) - mail_to_send.send(fail_silently=False) + send_mail_object(mail_to_send, self.request) def can_view(self, user_request, *_args, **_kwargs): @@ -165,6 +167,7 @@ class CommentTicket(AclMixin, models.Model): on_delete=models.CASCADE, related_name="ticket_comment", ) + request = None class Meta: permissions = (("view_commentticket", _("Can view a ticket object")),) @@ -221,6 +224,7 @@ class CommentTicket(AclMixin, models.Model): return "Comment " + str(self.comment_id) + " on " + str(self.parent_ticket) def publish_mail(self): + """Send mail to user and admin after new comment""" site_url = GeneralOption.get_cached_value("main_site_url") to_addr = TicketOption.get_cached_value("publish_address") context = {"comment": self, "site_url": site_url} @@ -236,7 +240,7 @@ class CommentTicket(AclMixin, models.Model): GeneralOption.get_cached_value("email_from"), [to_addr, self.parent_ticket.get_mail], ) - mail_to_send.send(fail_silently=False) + send_mail_object(mail_to_send, self.request) @receiver(post_save, sender=Ticket) diff --git a/tickets/preferences/views.py b/tickets/preferences/views.py index 29465b1c..70637db8 100644 --- a/tickets/preferences/views.py +++ b/tickets/preferences/views.py @@ -28,11 +28,8 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect from django.template.loader import render_to_string -from django.views.decorators.cache import cache_page from django.utils.translation import ugettext as _ from django.urls import reverse -from django.forms import modelformset_factory -from re2o.views import form from re2o.base import re2o_paginator diff --git a/tickets/views.py b/tickets/views.py index bda14df7..aab6aeae 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -121,7 +121,7 @@ def edit_ticket(request, ticket, ticketid): @can_view(Ticket) def add_comment(request, ticket, ticketid): """ Add a comment to a ticket""" - commentticket = CommentTicketForm(request.POST or None) + commentticket = CommentTicketForm(request.POST or None, request=request) if commentticket.is_valid(): commentticket = commentticket.save(commit=False) commentticket.parent_ticket = ticket