mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-03-31 00:51:13 +00:00
Add survey feature
This commit is contained in:
parent
ca0601fb24
commit
702c8d8c9e
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: TFJM\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-09 11:04+0100\n"
|
||||
"POT-Creation-Date: 2025-03-19 23:07+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -114,6 +114,7 @@ msgstr ""
|
||||
#: registration/models.py:158 registration/models.py:754
|
||||
#: registration/tables.py:39
|
||||
#: registration/templates/registration/payment_form.html:52
|
||||
#: survey/templates/survey/survey_detail.html:60
|
||||
msgid "team"
|
||||
msgstr "équipe"
|
||||
|
||||
@ -217,7 +218,7 @@ msgstr ""
|
||||
msgid "Toggle fullscreen mode"
|
||||
msgstr "Inverse le mode plein écran"
|
||||
|
||||
#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:126
|
||||
#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:129
|
||||
msgid "Log out"
|
||||
msgstr "Déconnexion"
|
||||
|
||||
@ -251,7 +252,7 @@ msgstr "Chat"
|
||||
#: chat/templates/chat/login.html:10 chat/templates/chat/login.html:36
|
||||
#: registration/templates/registration/password_reset_complete.html:10
|
||||
#: tfjm/templates/base.html:89 tfjm/templates/base.html:90
|
||||
#: tfjm/templates/navbar.html:107
|
||||
#: tfjm/templates/navbar.html:110
|
||||
#: tfjm/templates/registration/includes/login.html:22
|
||||
#: tfjm/templates/registration/login.html:7
|
||||
#: tfjm/templates/registration/login.html:8
|
||||
@ -713,7 +714,7 @@ msgstr ""
|
||||
|
||||
#: draw/models.py:496 draw/models.py:519 participation/models.py:1258
|
||||
#: participation/models.py:1695 participation/models.py:1933
|
||||
#: participation/views.py:1495 participation/views.py:1760
|
||||
#: participation/views.py:1501 participation/views.py:1766
|
||||
#, python-brace-format
|
||||
msgid "Problem #{problem}"
|
||||
msgstr "Problème n°{problem}"
|
||||
@ -911,8 +912,8 @@ msgstr "Êtes-vous sûr·e de vouloir annuler le tirage au sort ?"
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: draw/views.py:31 participation/views.py:163 participation/views.py:509
|
||||
#: participation/views.py:540
|
||||
#: draw/views.py:31 participation/views.py:163 participation/views.py:512
|
||||
#: participation/views.py:543
|
||||
msgid "You are not in a team."
|
||||
msgstr "Vous n'êtes pas dans une équipe."
|
||||
|
||||
@ -1029,7 +1030,7 @@ msgstr "Ce trigramme est déjà utilisé."
|
||||
msgid "No team was found with this access code."
|
||||
msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
|
||||
|
||||
#: participation/forms.py:59 participation/views.py:511
|
||||
#: participation/forms.py:59 participation/views.py:514
|
||||
msgid "The team is already validated or the validation is pending."
|
||||
msgstr "La validation de l'équipe est déjà faite ou en cours."
|
||||
|
||||
@ -1382,7 +1383,7 @@ msgstr "finale"
|
||||
msgid "Google Sheet ID"
|
||||
msgstr "ID de la feuille Google Sheets"
|
||||
|
||||
#: participation/models.py:467 participation/views.py:1814
|
||||
#: participation/models.py:467 participation/views.py:1820
|
||||
msgid "Notation sheet"
|
||||
msgstr "Feuille de notation"
|
||||
|
||||
@ -1392,7 +1393,8 @@ msgid "Final ranking"
|
||||
msgstr "Classement final"
|
||||
|
||||
#: participation/models.py:487 participation/models.py:559
|
||||
#: participation/models.py:1333 participation/views.py:1734
|
||||
#: participation/models.py:1333 participation/views.py:1740
|
||||
#: survey/templates/survey/survey_detail.html:58
|
||||
msgid "Team"
|
||||
msgstr "Équipe"
|
||||
|
||||
@ -1425,14 +1427,14 @@ msgid "Tweaks day 3"
|
||||
msgstr "Ajustements 3"
|
||||
|
||||
#: participation/models.py:491 participation/models.py:1333
|
||||
#: participation/views.py:1741
|
||||
#: participation/views.py:1747
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: participation/models.py:491 participation/models.py:559
|
||||
#: participation/models.py:1333
|
||||
#: participation/templates/participation/tournament_harmonize.html:14
|
||||
#: participation/views.py:1744
|
||||
#: participation/views.py:1750
|
||||
msgid "Rank"
|
||||
msgstr "Rang"
|
||||
|
||||
@ -1443,7 +1445,7 @@ msgstr "Rang"
|
||||
#: participation/models.py:1360 participation/models.py:1367
|
||||
#: participation/models.py:1371 participation/models.py:1616
|
||||
#: participation/models.py:1638 participation/models.py:2102
|
||||
#: participation/views.py:1815
|
||||
#: participation/views.py:1821
|
||||
msgid "Pool"
|
||||
msgstr "Poule"
|
||||
|
||||
@ -1707,59 +1709,59 @@ msgid "The president of the jury must be part of the jury."
|
||||
msgstr "Læ président⋅e du jury doit faire partie du jury."
|
||||
|
||||
#: participation/models.py:1259 participation/models.py:1333
|
||||
#: participation/views.py:1489 participation/views.py:1738
|
||||
#: participation/views.py:1495 participation/views.py:1744
|
||||
msgid "Problem"
|
||||
msgstr "Problème"
|
||||
|
||||
#: participation/models.py:1260 participation/views.py:1510
|
||||
#: participation/models.py:1260 participation/views.py:1516
|
||||
msgid "Reporter"
|
||||
msgstr "Défenseur⋅se"
|
||||
|
||||
#: participation/models.py:1261 participation/views.py:1516
|
||||
#: participation/models.py:1261 participation/views.py:1522
|
||||
msgid "Opponent"
|
||||
msgstr "Opposant⋅e"
|
||||
|
||||
#: participation/models.py:1262 participation/views.py:1523
|
||||
#: participation/models.py:1262 participation/views.py:1529
|
||||
msgid "Reviewer"
|
||||
msgstr "Rapporteur⋅rice"
|
||||
|
||||
#: participation/models.py:1263 participation/views.py:1530
|
||||
#: participation/models.py:1263 participation/views.py:1536
|
||||
msgid "Observer"
|
||||
msgstr "Observateur⋅rice"
|
||||
|
||||
#: participation/models.py:1264 participation/views.py:1504
|
||||
#: participation/models.py:1264 participation/views.py:1510
|
||||
msgid "Role"
|
||||
msgstr "Rôle"
|
||||
|
||||
#: participation/models.py:1265 participation/models.py:1267
|
||||
#: participation/models.py:1268 participation/views.py:1546
|
||||
#: participation/views.py:1554 participation/views.py:1562
|
||||
#: participation/views.py:1572
|
||||
#: participation/models.py:1268 participation/views.py:1552
|
||||
#: participation/views.py:1560 participation/views.py:1568
|
||||
#: participation/views.py:1578
|
||||
msgid "Writing"
|
||||
msgstr "Écrit"
|
||||
|
||||
#: participation/models.py:1266 participation/models.py:1267
|
||||
#: participation/models.py:1268 participation/views.py:1550
|
||||
#: participation/views.py:1558 participation/views.py:1567
|
||||
#: participation/views.py:1576
|
||||
#: participation/models.py:1268 participation/views.py:1556
|
||||
#: participation/views.py:1564 participation/views.py:1573
|
||||
#: participation/views.py:1582
|
||||
msgid "Oral"
|
||||
msgstr "Oral"
|
||||
|
||||
#: participation/models.py:1269 participation/views.py:1538
|
||||
#: participation/views.py:1539
|
||||
#: participation/models.py:1269 participation/views.py:1544
|
||||
#: participation/views.py:1545
|
||||
msgid "Juree"
|
||||
msgstr "Juré⋅e"
|
||||
|
||||
#: participation/models.py:1292 participation/models.py:1618
|
||||
#: participation/models.py:1640 participation/views.py:1608
|
||||
#: participation/models.py:1640 participation/views.py:1614
|
||||
msgid "Average"
|
||||
msgstr "Moyenne"
|
||||
|
||||
#: participation/models.py:1298 participation/views.py:1627
|
||||
#: participation/models.py:1298 participation/views.py:1633
|
||||
msgid "Coefficient"
|
||||
msgstr "Coefficien"
|
||||
|
||||
#: participation/models.py:1299 participation/views.py:1670
|
||||
#: participation/models.py:1299 participation/views.py:1676
|
||||
msgid "Subtotal"
|
||||
msgstr "Sous-total"
|
||||
|
||||
@ -1972,12 +1974,15 @@ msgstr "Pas d'équipe définie"
|
||||
#: registration/templates/registration/payment_form.html:210
|
||||
#: registration/templates/registration/update_user.html:16
|
||||
#: registration/templates/registration/user_detail.html:220
|
||||
#: survey/templates/survey/survey_detail.html:40
|
||||
#: survey/templates/survey/survey_detail.html:73
|
||||
#: survey/templates/survey/survey_form.html:12
|
||||
msgid "Update"
|
||||
msgstr "Modifier"
|
||||
|
||||
#: participation/templates/participation/create_team.html:11
|
||||
#: participation/templates/participation/tournament_form.html:14
|
||||
#: tfjm/templates/base.html:85
|
||||
#: survey/templates/survey/survey_form.html:14 tfjm/templates/base.html:85
|
||||
msgid "Create"
|
||||
msgstr "Créer"
|
||||
|
||||
@ -2305,6 +2310,7 @@ msgid "Back to pool detail"
|
||||
msgstr "Retour aux détails de la poule"
|
||||
|
||||
#: participation/templates/participation/team_detail.html:13
|
||||
#: survey/templates/survey/survey_detail.html:16
|
||||
msgid "Name:"
|
||||
msgstr "Nom :"
|
||||
|
||||
@ -2465,7 +2471,7 @@ msgid "Invalidate"
|
||||
msgstr "Invalider"
|
||||
|
||||
#: participation/templates/participation/team_detail.html:244
|
||||
#: participation/views.py:341
|
||||
#: participation/views.py:344
|
||||
msgid "Upload motivation letter"
|
||||
msgstr "Envoyer la lettre de motivation"
|
||||
|
||||
@ -2474,7 +2480,7 @@ msgid "Update team"
|
||||
msgstr "Modifier l'équipe"
|
||||
|
||||
#: participation/templates/participation/team_detail.html:255
|
||||
#: participation/views.py:503
|
||||
#: participation/views.py:506
|
||||
msgid "Leave team"
|
||||
msgstr "Quitter l'équipe"
|
||||
|
||||
@ -2736,7 +2742,7 @@ msgstr "Vous êtes déjà dans une équipe."
|
||||
msgid "Join team"
|
||||
msgstr "Rejoindre une équipe"
|
||||
|
||||
#: participation/views.py:164 participation/views.py:541
|
||||
#: participation/views.py:164 participation/views.py:544
|
||||
msgid "You don't participate, so you don't have any team."
|
||||
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
|
||||
|
||||
@ -2764,189 +2770,189 @@ msgstr ""
|
||||
"d'adresse e-mail, soit une autorisation, soit des personnes, soit la lettre "
|
||||
"de motivation, soit le tournoi n'a pas été choisi."
|
||||
|
||||
#: participation/views.py:235
|
||||
#: participation/views.py:236
|
||||
msgid "Team validation"
|
||||
msgstr "Validation d'équipe"
|
||||
|
||||
#: participation/views.py:247
|
||||
#: participation/views.py:248
|
||||
msgid "You are not an organizer of the tournament."
|
||||
msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
|
||||
|
||||
#: participation/views.py:250
|
||||
#: participation/views.py:251
|
||||
msgid "This team has no pending validation."
|
||||
msgstr "L'équipe n'a pas de validation en attente."
|
||||
|
||||
#: participation/views.py:270
|
||||
#: participation/views.py:272
|
||||
msgid "Team validated"
|
||||
msgstr "Équipe validée"
|
||||
|
||||
#: participation/views.py:279
|
||||
#: participation/views.py:282
|
||||
msgid "Team not validated"
|
||||
msgstr "Équipe non validée"
|
||||
|
||||
#: participation/views.py:282
|
||||
#: participation/views.py:285
|
||||
msgid "You must specify if you validate the registration or not."
|
||||
msgstr "Vous devez spécifier si vous validez l'inscription ou non."
|
||||
|
||||
#: participation/views.py:318
|
||||
#: participation/views.py:321
|
||||
#, python-brace-format
|
||||
msgid "Update team {trigram}"
|
||||
msgstr "Mise à jour de l'équipe {trigram}"
|
||||
|
||||
#: participation/views.py:380 participation/views.py:488
|
||||
#: participation/views.py:383 participation/views.py:491
|
||||
#, python-brace-format
|
||||
msgid "Motivation letter of {team}.{ext}"
|
||||
msgstr "Lettre de motivation de {team}.{ext}"
|
||||
|
||||
#: participation/views.py:413
|
||||
#: participation/views.py:416
|
||||
#, python-brace-format
|
||||
msgid "Authorizations of team {trigram}.zip"
|
||||
msgstr "Autorisations de l'équipe {trigram}.zip"
|
||||
|
||||
#: participation/views.py:417
|
||||
#: participation/views.py:420
|
||||
#, python-brace-format
|
||||
msgid "Authorizations of {tournament}.zip"
|
||||
msgstr "Autorisations du tournoi {tournament}.zip"
|
||||
|
||||
#: participation/views.py:436
|
||||
#: participation/views.py:439
|
||||
#, python-brace-format
|
||||
msgid "Photo authorization of {participant}.{ext}"
|
||||
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
|
||||
|
||||
#: participation/views.py:445
|
||||
#: participation/views.py:448
|
||||
#, python-brace-format
|
||||
msgid "Parental authorization of {participant}.{ext}"
|
||||
msgstr "Autorisation parentale de {participant}.{ext}"
|
||||
|
||||
#: participation/views.py:453
|
||||
#: participation/views.py:456
|
||||
#, python-brace-format
|
||||
msgid "Health sheet of {participant}.{ext}"
|
||||
msgstr "Fiche sanitaire de {participant}.{ext}"
|
||||
|
||||
#: participation/views.py:461
|
||||
#: participation/views.py:464
|
||||
#, python-brace-format
|
||||
msgid "Vaccine sheet of {participant}.{ext}"
|
||||
msgstr "Carnet de vaccination de {participant}.{ext}"
|
||||
|
||||
#: participation/views.py:472
|
||||
#: participation/views.py:475
|
||||
#, python-brace-format
|
||||
msgid "Photo authorization of {participant} (final).{ext}"
|
||||
msgstr "Autorisation de droit à l'image de {participant} (finale).{ext}"
|
||||
|
||||
#: participation/views.py:481
|
||||
#: participation/views.py:484
|
||||
#, python-brace-format
|
||||
msgid "Parental authorization of {participant} (final).{ext}"
|
||||
msgstr "Autorisation parentale de {participant} (finale).{ext}"
|
||||
|
||||
#: participation/views.py:555
|
||||
#: participation/views.py:558
|
||||
msgid "The team is not validated yet."
|
||||
msgstr "L'équipe n'est pas encore validée."
|
||||
|
||||
#: participation/views.py:569
|
||||
#: participation/views.py:572
|
||||
#, python-brace-format
|
||||
msgid "Participation of team {trigram}"
|
||||
msgstr "Participation de l'équipe {trigram}"
|
||||
|
||||
#: participation/views.py:658
|
||||
#: participation/views.py:661
|
||||
#, python-brace-format
|
||||
msgid "Payments of {tournament}"
|
||||
msgstr "Paiements de {tournament}"
|
||||
|
||||
#: participation/views.py:758
|
||||
#: participation/views.py:761
|
||||
msgid "Notes published!"
|
||||
msgstr "Notes publiées !"
|
||||
|
||||
#: participation/views.py:760
|
||||
#: participation/views.py:763
|
||||
msgid "Notes hidden!"
|
||||
msgstr "Notes dissimulées !"
|
||||
|
||||
#: participation/views.py:791
|
||||
#: participation/views.py:794
|
||||
#, python-brace-format
|
||||
msgid "Harmonize notes of {tournament} - Day {round}"
|
||||
msgstr "Harmoniser les notes de {tournament} - Jour {round}"
|
||||
|
||||
#: participation/views.py:905
|
||||
#: participation/views.py:908
|
||||
msgid "You can't upload a solution after the deadline."
|
||||
msgstr "Vous ne pouvez pas envoyer de solution après la date limite."
|
||||
|
||||
#: participation/views.py:1025
|
||||
#: participation/views.py:1028
|
||||
#, python-brace-format
|
||||
msgid "Solutions of team {trigram}.zip"
|
||||
msgstr "Solutions de l'équipe {trigram}.zip"
|
||||
|
||||
#: participation/views.py:1026
|
||||
#: participation/views.py:1029
|
||||
#, python-brace-format
|
||||
msgid "Written reviews of team {trigram}.zip"
|
||||
msgstr "Notes de synthèse de l'équipe {trigram}.zip"
|
||||
|
||||
#: participation/views.py:1043 participation/views.py:1059
|
||||
#: participation/views.py:1046 participation/views.py:1062
|
||||
#, python-brace-format
|
||||
msgid "Solutions of {tournament}.zip"
|
||||
msgstr "Solutions de {tournament}.zip"
|
||||
|
||||
#: participation/views.py:1044 participation/views.py:1060
|
||||
#: participation/views.py:1047 participation/views.py:1063
|
||||
#, python-brace-format
|
||||
msgid "Written reviews of {tournament}.zip"
|
||||
msgstr "Notes de synthèse de {tournament}.zip"
|
||||
|
||||
#: participation/views.py:1069
|
||||
#: participation/views.py:1072
|
||||
#, python-brace-format
|
||||
msgid "Solutions for pool {pool} of tournament {tournament}.zip"
|
||||
msgstr "Solutions pour la poule {pool} du tournoi {tournament}.zip"
|
||||
|
||||
#: participation/views.py:1070
|
||||
#: participation/views.py:1073
|
||||
#, python-brace-format
|
||||
msgid "Written reviews for pool {pool} of tournament {tournament}.zip"
|
||||
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
|
||||
|
||||
#: participation/views.py:1112
|
||||
#: participation/views.py:1115
|
||||
#, python-brace-format
|
||||
msgid "Jury of pool {pool} for {tournament} with teams {teams}"
|
||||
msgstr "Jury de la poule {pool} pour {tournament} avec les équipes {teams}"
|
||||
|
||||
#: participation/views.py:1128
|
||||
#: participation/views.py:1131
|
||||
#, python-brace-format
|
||||
msgid "The jury {name} is already in the pool!"
|
||||
msgstr "{name} est déjà dans la poule !"
|
||||
|
||||
#: participation/views.py:1148
|
||||
#: participation/views.py:1151
|
||||
msgid "New jury account"
|
||||
msgstr "Nouveau compte de juré⋅e"
|
||||
|
||||
#: participation/views.py:1169
|
||||
#: participation/views.py:1175
|
||||
#, python-brace-format
|
||||
msgid "The jury {name} has been successfully added!"
|
||||
msgstr "{name} a été ajouté⋅e avec succès en tant que juré⋅e !"
|
||||
|
||||
#: participation/views.py:1205
|
||||
#: participation/views.py:1211
|
||||
#, python-brace-format
|
||||
msgid "The jury {name} has been successfully removed!"
|
||||
msgstr "{name} a été retiré⋅e avec succès du jury !"
|
||||
|
||||
#: participation/views.py:1231
|
||||
#: participation/views.py:1237
|
||||
#, python-brace-format
|
||||
msgid "The jury {name} has been successfully promoted president!"
|
||||
msgstr "{name} a été nommé⋅e président⋅e du jury !"
|
||||
|
||||
#: participation/views.py:1259
|
||||
#: participation/views.py:1265
|
||||
msgid "The following user is not registered as a jury:"
|
||||
msgstr "L'utilisateur⋅rice suivant n'est pas inscrit⋅e en tant que juré⋅e :"
|
||||
|
||||
#: participation/views.py:1275
|
||||
#: participation/views.py:1281
|
||||
msgid "Notes were successfully uploaded."
|
||||
msgstr "Les notes ont bien été envoyées."
|
||||
|
||||
#: participation/views.py:1907
|
||||
#: participation/views.py:1912
|
||||
#, python-brace-format
|
||||
msgid "Notation sheets of pool {pool} of {tournament}.zip"
|
||||
msgstr "Feuilles de notations pour la poule {pool} du tournoi {tournament}.zip"
|
||||
|
||||
#: participation/views.py:1912
|
||||
#: participation/views.py:1917
|
||||
#, python-brace-format
|
||||
msgid "Notation sheets of {tournament}.zip"
|
||||
msgstr "Feuilles de notation de {tournament}.zip"
|
||||
|
||||
#: participation/views.py:2079
|
||||
#: participation/views.py:2091
|
||||
msgid "You can't upload a written review after the deadline."
|
||||
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
|
||||
|
||||
@ -2981,7 +2987,7 @@ msgstr "Marquer comme invalide"
|
||||
msgid "role"
|
||||
msgstr "rôle"
|
||||
|
||||
#: registration/forms.py:25
|
||||
#: registration/forms.py:25 survey/templates/survey/survey_detail.html:50
|
||||
msgid "participant"
|
||||
msgstr "participant⋅e"
|
||||
|
||||
@ -4200,22 +4206,22 @@ msgid "Impersonate"
|
||||
msgstr "Impersonifier"
|
||||
|
||||
#: registration/templates/registration/user_detail.html:229
|
||||
#: registration/views.py:333
|
||||
#: registration/views.py:334
|
||||
msgid "Upload photo authorization"
|
||||
msgstr "Téléverser l'autorisation de droit à l'image"
|
||||
|
||||
#: registration/templates/registration/user_detail.html:236
|
||||
#: registration/views.py:367
|
||||
#: registration/views.py:368
|
||||
msgid "Upload health sheet"
|
||||
msgstr "Téléverser la fiche sanitaire"
|
||||
|
||||
#: registration/templates/registration/user_detail.html:243
|
||||
#: registration/views.py:388
|
||||
#: registration/views.py:389
|
||||
msgid "Upload vaccine sheet"
|
||||
msgstr "Téléverser le carnet de vaccination"
|
||||
|
||||
#: registration/templates/registration/user_detail.html:249
|
||||
#: registration/views.py:408
|
||||
#: registration/views.py:409
|
||||
msgid "Upload parental authorization"
|
||||
msgstr "Téléverser l'autorisation parentale"
|
||||
|
||||
@ -4242,36 +4248,36 @@ msgstr ""
|
||||
"Les inscriptions pour cette année sont closes depuis le {closing_date:%d/%m/"
|
||||
"%Y %H:%M}."
|
||||
|
||||
#: registration/views.py:140
|
||||
#: registration/views.py:141
|
||||
msgid "New organizer account"
|
||||
msgstr "Nouveau compte organisateur⋅rice"
|
||||
|
||||
#: registration/views.py:166
|
||||
#: registration/views.py:167
|
||||
msgid "Email validation"
|
||||
msgstr "Validation de l'adresse mail"
|
||||
|
||||
#: registration/views.py:168
|
||||
#: registration/views.py:169
|
||||
msgid "Validate email"
|
||||
msgstr "Valider l'adresse mail"
|
||||
|
||||
#: registration/views.py:207
|
||||
#: registration/views.py:208
|
||||
msgid "Email validation unsuccessful"
|
||||
msgstr "Échec de la validation de l'adresse mail"
|
||||
|
||||
#: registration/views.py:218
|
||||
#: registration/views.py:219
|
||||
msgid "Email validation email sent"
|
||||
msgstr "Mail de confirmation de l'adresse mail envoyé"
|
||||
|
||||
#: registration/views.py:226
|
||||
#: registration/views.py:227
|
||||
msgid "Resend email validation link"
|
||||
msgstr "Renvoyé le lien de validation de l'adresse mail"
|
||||
|
||||
#: registration/views.py:269
|
||||
#: registration/views.py:270
|
||||
#, python-brace-format
|
||||
msgid "Detail of user {user}"
|
||||
msgstr "Détails de l'utilisateur⋅rice {user}"
|
||||
|
||||
#: registration/views.py:294
|
||||
#: registration/views.py:295
|
||||
#, python-brace-format
|
||||
msgid "Update user {user}"
|
||||
msgstr "Mise à jour de l'utilisateur⋅rice {user}"
|
||||
@ -4358,6 +4364,122 @@ msgstr "Autorisation parentale de {student}.{ext}"
|
||||
msgid "Payment receipt of {registrations}.{ext}"
|
||||
msgstr "Justificatif de paiement de {registrations}.{ext}"
|
||||
|
||||
#: survey/models.py:21 survey/tables.py:14
|
||||
msgid "survey identifier"
|
||||
msgstr "identifiant du sondage"
|
||||
|
||||
#: survey/models.py:22
|
||||
msgid "The numeric identifier of the Limesurvey."
|
||||
msgstr "L'identifiant numérique du Limesurvey."
|
||||
|
||||
#: survey/models.py:27
|
||||
msgid "display name"
|
||||
msgstr "nom affiché"
|
||||
|
||||
#: survey/models.py:32
|
||||
msgid "invite whole team"
|
||||
msgstr "inviter toute l'équipe"
|
||||
|
||||
#: survey/models.py:33
|
||||
msgid ""
|
||||
"When this field is checked, teams will get only one survey invitation "
|
||||
"instead of one per person."
|
||||
msgstr ""
|
||||
"Lorsque ce champ est coché, les équipes n'auront qu'une seule invitation au "
|
||||
"sondage au lieu de une par personne."
|
||||
|
||||
#: survey/models.py:38
|
||||
msgid "invite coaches"
|
||||
msgstr "inviter les encadrant⋅es"
|
||||
|
||||
#: survey/models.py:39
|
||||
msgid ""
|
||||
"When this field is checked, coaches will also be invited in the survey. No "
|
||||
"effect when the whole team is invited."
|
||||
msgstr ""
|
||||
"Lorsque ce champ est coché, les encadrant⋅es seront aussi invité⋅es au "
|
||||
"sondage. Aucun effet lorsque toute l'équipe est invité."
|
||||
|
||||
#: survey/models.py:48
|
||||
msgid "tournament restriction"
|
||||
msgstr "restriction de tournoi"
|
||||
|
||||
#: survey/models.py:49
|
||||
msgid ""
|
||||
"When this field is filled, the survey participants will be restricted to "
|
||||
"this tournament members."
|
||||
msgstr ""
|
||||
"Lorsque ce champ est rempli, les participant⋅es au sondage seront "
|
||||
"restreint⋅es aux membres de ce tournoi."
|
||||
|
||||
#: survey/models.py:55
|
||||
msgid "participants that completed the survey"
|
||||
msgstr "participant⋅es ayant complété le sondage"
|
||||
|
||||
#: survey/models.py:61
|
||||
msgid "teams that completed the survey"
|
||||
msgstr "équipes ayant complété le sondage"
|
||||
|
||||
#: survey/models.py:134 survey/templates/survey/survey_detail.html:10
|
||||
msgid "survey"
|
||||
msgstr "sondage"
|
||||
|
||||
#: survey/models.py:135 tfjm/templates/navbar.html:78
|
||||
msgid "surveys"
|
||||
msgstr "sondages"
|
||||
|
||||
#: survey/tables.py:18 survey/templates/survey/survey_detail.html:51
|
||||
msgid "completed"
|
||||
msgstr "complété"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:19
|
||||
msgid "One answer per team:"
|
||||
msgstr "Une réponse par équipe :"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:23
|
||||
msgid "Coaches can answer the survey:"
|
||||
msgstr "Les encadrant⋅es peuvent répondre au sondage :"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:28
|
||||
msgid "Tournament restriction:"
|
||||
msgstr "Restriction de tournoi :"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:32
|
||||
msgid "Completion rate:"
|
||||
msgstr "Taux de complétion :"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:41
|
||||
msgid "Send invites"
|
||||
msgstr "Envoyer les invitations"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:63
|
||||
msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:65
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
#: survey/templates/survey/survey_detail.html:72
|
||||
msgid "Update survey"
|
||||
msgstr "Modifier le sondage"
|
||||
|
||||
#: survey/templates/survey/survey_list.html:8
|
||||
msgid "Add survey"
|
||||
msgstr "Ajouter un sondage"
|
||||
|
||||
#: survey/views.py:38
|
||||
msgid "Invites sent!"
|
||||
msgstr "Invitations envoyées !"
|
||||
|
||||
#: survey/views.py:40
|
||||
msgid "All invites were already sent."
|
||||
msgstr "Toutes les invitations étaient déjà envoyés."
|
||||
|
||||
#: survey/views.py:50
|
||||
msgid "Completion data refreshed!"
|
||||
msgstr "Données de complétion actualisées !"
|
||||
|
||||
#: tfjm/permissions.py:9
|
||||
msgid "Everyone, including anonymous users"
|
||||
msgstr "Tout le monde, incluant les utilisateur⋅rices anonymes"
|
||||
@ -4402,11 +4524,11 @@ msgstr "Privé, réservé aux utilisateur⋅rices explicitement autorisé⋅es"
|
||||
msgid "Admin users"
|
||||
msgstr "Administrateur⋅rices"
|
||||
|
||||
#: tfjm/settings.py:174
|
||||
#: tfjm/settings.py:175
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: tfjm/settings.py:175
|
||||
#: tfjm/settings.py:176
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
@ -4674,23 +4796,23 @@ msgstr "Mon équipe"
|
||||
msgid "My participation"
|
||||
msgstr "Ma participation"
|
||||
|
||||
#: tfjm/templates/navbar.html:78
|
||||
#: tfjm/templates/navbar.html:81
|
||||
msgid "Administration"
|
||||
msgstr "Administration"
|
||||
|
||||
#: tfjm/templates/navbar.html:86
|
||||
#: tfjm/templates/navbar.html:89
|
||||
msgid "Search…"
|
||||
msgstr "Chercher…"
|
||||
|
||||
#: tfjm/templates/navbar.html:95
|
||||
#: tfjm/templates/navbar.html:98
|
||||
msgid "Return to admin view"
|
||||
msgstr "Retourner à l'interface administrateur⋅rice"
|
||||
|
||||
#: tfjm/templates/navbar.html:102
|
||||
#: tfjm/templates/navbar.html:105
|
||||
msgid "Register"
|
||||
msgstr "S'inscrire"
|
||||
|
||||
#: tfjm/templates/navbar.html:119
|
||||
#: tfjm/templates/navbar.html:122
|
||||
msgid "My account"
|
||||
msgstr "Mon compte"
|
||||
|
||||
|
@ -211,7 +211,7 @@ class Team(models.Model):
|
||||
"""
|
||||
:return: The mailing list to contact the team members.
|
||||
"""
|
||||
return f"equipe-{slugify(self.trigram)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
||||
return f"equipe-{slugify(self.trigram)}@{settings.SYMPA_HOST}"
|
||||
|
||||
def create_mailing_list(self):
|
||||
"""
|
||||
@ -392,21 +392,21 @@ class Tournament(models.Model):
|
||||
"""
|
||||
:return: The mailing list to contact the team members.
|
||||
"""
|
||||
return f"equipes-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
||||
return f"equipes-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||
|
||||
@property
|
||||
def organizers_email(self):
|
||||
"""
|
||||
:return: The mailing list to contact the team members.
|
||||
"""
|
||||
return f"organisateurs-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
||||
return f"organisateurs-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||
|
||||
@property
|
||||
def jurys_email(self):
|
||||
"""
|
||||
:return: The mailing list to contact the team members.
|
||||
"""
|
||||
return f"jurys-{slugify(self.name)}@{os.getenv('SYMPA_HOST', 'localhost')}"
|
||||
return f"jurys-{slugify(self.name)}@{settings.SYMPA_HOST}"
|
||||
|
||||
def create_mailing_lists(self):
|
||||
"""
|
||||
|
@ -1,5 +1,6 @@
|
||||
channels[daphne]~=4.1.0
|
||||
channels-redis~=4.2.0
|
||||
citric~=1.4.0
|
||||
crispy-bootstrap5~=2024.10
|
||||
Django>=5.1.2,<6.0
|
||||
django-crispy-forms~=2.3
|
||||
|
0
survey/__init__.py
Normal file
0
survey/__init__.py
Normal file
13
survey/admin.py
Normal file
13
survey/admin.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Survey
|
||||
|
||||
|
||||
@admin.register(Survey)
|
||||
class SurveyAdmin(admin.ModelAdmin):
|
||||
list_display = ('survey_id', 'name', 'invite_team', 'invite_coaches', 'tournament',)
|
||||
list_filter = ('invite_team', 'invite_coaches', 'tournament',)
|
||||
search_fields = ('name',)
|
6
survey/apps.py
Normal file
6
survey/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SurveyConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "survey"
|
28
survey/forms.py
Normal file
28
survey/forms.py
Normal file
@ -0,0 +1,28 @@
|
||||
from django import forms
|
||||
|
||||
from .models import Survey
|
||||
|
||||
|
||||
class SurveyForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'survey_id' in self.initial:
|
||||
self.fields['survey_id'].disabled = True
|
||||
|
||||
class Meta:
|
||||
model = Survey
|
||||
exclude = ('completed_registrations', 'completed_teams',)
|
||||
widgets = {
|
||||
'completed_registrations': forms.SelectMultiple(attrs={
|
||||
'class': 'selectpicker',
|
||||
'data-live-search': 'true',
|
||||
'data-live-search-normalize': 'true',
|
||||
'data-width': 'fit',
|
||||
}),
|
||||
'completed_teams': forms.SelectMultiple(attrs={
|
||||
'class': 'selectpicker',
|
||||
'data-live-search': 'true',
|
||||
'data-live-search-normalize': 'true',
|
||||
'data-width': 'fit',
|
||||
}),
|
||||
}
|
13
survey/management/commands/fetch_survey_completion_data.py
Normal file
13
survey/management/commands/fetch_survey_completion_data.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from ...models import Survey
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
for survey in Survey.objects.all():
|
||||
survey.fetch_completion_data()
|
83
survey/migrations/0001_initial.py
Normal file
83
survey/migrations/0001_initial.py
Normal file
@ -0,0 +1,83 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-19 21:12
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"participation",
|
||||
"0023_tournament_unified_registration",
|
||||
),
|
||||
("registration", "0014_participantregistration_country"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Survey",
|
||||
fields=[
|
||||
(
|
||||
"survey_id",
|
||||
models.IntegerField(
|
||||
help_text="The numeric identifier of the Limesurvey.",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="survey identifier",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255, verbose_name="display name")),
|
||||
(
|
||||
"invite_team",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="When this field is checked, teams will get only one survey invitation instead of one per person.",
|
||||
verbose_name="invite whole team",
|
||||
),
|
||||
),
|
||||
(
|
||||
"invite_coaches",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="When this field is checked, coaches will also be invited in the survey. No effect when the whole team is invited.",
|
||||
verbose_name="invite coaches",
|
||||
),
|
||||
),
|
||||
(
|
||||
"completed_registrations",
|
||||
models.ManyToManyField(
|
||||
related_name="completed_surveys",
|
||||
to="registration.participantregistration",
|
||||
verbose_name="participants that completed the survey",
|
||||
),
|
||||
),
|
||||
(
|
||||
"completed_teams",
|
||||
models.ManyToManyField(
|
||||
related_name="completed_surveys",
|
||||
to="participation.team",
|
||||
verbose_name="teams that completed the survey",
|
||||
),
|
||||
),
|
||||
(
|
||||
"tournament",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="When this field is filled, the survey participants will be restricted to this tournament members.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="participation.tournament",
|
||||
verbose_name="tournament restriction",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "survey",
|
||||
"verbose_name_plural": "surveys",
|
||||
},
|
||||
),
|
||||
]
|
0
survey/migrations/__init__.py
Normal file
0
survey/migrations/__init__.py
Normal file
134
survey/models.py
Normal file
134
survey/models.py
Normal file
@ -0,0 +1,134 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from citric import Client
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from participation.models import Team, Tournament
|
||||
from registration.models import ParticipantRegistration, StudentRegistration
|
||||
|
||||
|
||||
class Survey(models.Model):
|
||||
"""
|
||||
Ce modèle représente un sondage LimeSurvey afin de faciliter l'import des
|
||||
participant⋅es au sondage et d'effectuer le suivi.
|
||||
"""
|
||||
survey_id = models.IntegerField(
|
||||
primary_key=True,
|
||||
verbose_name=_("survey identifier"),
|
||||
help_text=_("The numeric identifier of the Limesurvey."),
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("display name"),
|
||||
)
|
||||
|
||||
invite_team = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("invite whole team"),
|
||||
help_text=_("When this field is checked, teams will get only one survey invitation instead of one per person."),
|
||||
)
|
||||
|
||||
invite_coaches = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("invite coaches"),
|
||||
help_text=_("When this field is checked, coaches will also be invited in the survey. No effect when the whole team is invited."),
|
||||
)
|
||||
|
||||
tournament = models.ForeignKey(
|
||||
Tournament,
|
||||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("tournament restriction"),
|
||||
help_text=_("When this field is filled, the survey participants will be restricted to this tournament members."),
|
||||
)
|
||||
|
||||
completed_registrations = models.ManyToManyField(
|
||||
ParticipantRegistration,
|
||||
related_name="completed_surveys",
|
||||
verbose_name=_("participants that completed the survey"),
|
||||
)
|
||||
|
||||
completed_teams = models.ManyToManyField(
|
||||
Team,
|
||||
related_name="completed_surveys",
|
||||
verbose_name=_("teams that completed the survey"),
|
||||
)
|
||||
|
||||
@property
|
||||
def participants(self):
|
||||
if self.invite_team:
|
||||
teams = Team.objects.filter(participation__valid=True)
|
||||
if self.tournament:
|
||||
teams = teams.filter(participation__tournament=self.tournament)
|
||||
return teams.all()
|
||||
else:
|
||||
if self.invite_coaches:
|
||||
registrations = ParticipantRegistration.objects.filter(team__participation__valid=True)
|
||||
else:
|
||||
registrations = StudentRegistration.objects.filter(team__participation__valid=True)
|
||||
if self.tournament:
|
||||
registrations = registrations.filter(team__participation__tournament=self.tournament)
|
||||
return registrations.all()
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
if self.invite_team:
|
||||
return self.completed_teams
|
||||
else:
|
||||
return self.completed_registrations
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy("survey:survey_detail", args=(self.survey_id,))
|
||||
|
||||
def generate_participants_data(self):
|
||||
participants_data = []
|
||||
if self.invite_team:
|
||||
for team in self.participants:
|
||||
participant_data = {"firstname": team.name, "lastname": f"(équipe {team.trigram})", "email": team.email}
|
||||
participants_data.append(participant_data)
|
||||
else:
|
||||
for reg in self.participants:
|
||||
participant_data = {"firstname": reg.user.first_name, "lastname": reg.user.last_name, "email": reg.user.email}
|
||||
participants_data.append(participant_data)
|
||||
return participants_data
|
||||
|
||||
def invite_all(self):
|
||||
participants_data = self.generate_participants_data()
|
||||
with Client(f"{settings.LIMESURVEY_URL}/index.php/admin/remotecontrol", settings.LIMESURVEY_USER, settings.LIMESURVEY_PASSWORD) as client:
|
||||
try:
|
||||
current_participants = client.list_participants(self.survey_id, limit=10000)
|
||||
except:
|
||||
current_participants = []
|
||||
current_participants_email = set(participant['participant_info']['email'] for participant in current_participants)
|
||||
participants_data = [participant_data for participant_data in participants_data if participant_data['email'] not in current_participants_email]
|
||||
try:
|
||||
client.activate_tokens(self.survey_id)
|
||||
except:
|
||||
pass
|
||||
new_participants = client.add_participants(self.survey_id, participant_data=participants_data)
|
||||
if new_participants:
|
||||
client.invite_participants(self.survey_id, token_ids=[participant['tid'] for participant in new_participants])
|
||||
return new_participants
|
||||
|
||||
def fetch_completion_data(self):
|
||||
with Client(f"{settings.LIMESURVEY_URL}/index.php/admin/remotecontrol", settings.LIMESURVEY_USER, settings.LIMESURVEY_PASSWORD) as client:
|
||||
participants = client.list_participants(self.survey_id, limit=10000, attributes=['completed'])
|
||||
if self.invite_team:
|
||||
team_names = [participant['participant_info']['firstname'] for participant in participants if participant['completed'] != 'N']
|
||||
self.completed_teams.set(list(Team.objects.filter(name__in=team_names).values_list('id', flat=True)))
|
||||
else:
|
||||
mails = [participant['participant_info']['email'] for participant in participants if participant['completed'] != 'N']
|
||||
self.completed_registrations.set(list(ParticipantRegistration.objects.filter(user__email__in=mails).values_list('id', flat=True)))
|
||||
self.save()
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("survey")
|
||||
verbose_name_plural = _("surveys")
|
31
survey/tables.py
Normal file
31
survey/tables.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
|
||||
from .models import Survey
|
||||
|
||||
|
||||
class SurveyTable(tables.Table):
|
||||
survey_id = tables.LinkColumn(
|
||||
'survey:survey_detail',
|
||||
args=[tables.A('survey_id')],
|
||||
verbose_name=lambda: _("survey identifier").capitalize(),
|
||||
)
|
||||
|
||||
nb_completed = tables.Column(
|
||||
verbose_name=_("completed").capitalize,
|
||||
accessor='survey_id'
|
||||
)
|
||||
|
||||
def render_nb_completed(self, record):
|
||||
return f"{record.completed.count()}/{record.participants.count()}"
|
||||
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table-condensed table-striped',
|
||||
}
|
||||
model = Survey
|
||||
fields = ('survey_id', 'name', 'invite_team', 'invite_coaches', 'tournament', 'nb_completed',)
|
||||
order_by = ('survey_id',)
|
84
survey/templates/survey/survey_detail.html
Normal file
84
survey/templates/survey/survey_detail.html
Normal file
@ -0,0 +1,84 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-body shadow">
|
||||
<div class="card-header text-center">
|
||||
<h4>
|
||||
{% trans "survey"|capfirst %} {{ survey.survey_id }}
|
||||
<a href="{{ TFJM.LIMESURVEY_URL }}/index.php/{{ survey.survey_id }}" target="_blank"><i class="fas fa-arrow-up-right-from-square"></i></a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Name:" %}</dt>
|
||||
<dd class="col-sm-6">{{ survey.name }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "One answer per team:" %}</dt>
|
||||
<dd class="col-sm-6">{{ survey.invite_team|yesno }}</dd>
|
||||
|
||||
{% if not survey.invite_team %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Coaches can answer the survey:" %}</dt>
|
||||
<dd class="col-sm-6">{{ survey.invite_coaches|yesno }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if survey.tournament %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Tournament restriction:" %}</dt>
|
||||
<dd class="col-sm-6">{{ survey.tournament }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Completion rate:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{{ survey.completed.count }}/{{ survey.participants.count }}
|
||||
<a href="{% url "survey:survey_refresh_completed" pk=survey.pk %}"><i class="fas fa-arrow-rotate-right" alt="refresh"></i></a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#updateSurveyModal">{% trans "Update" %}</button>
|
||||
<a class="btn btn-secondary" href="{% url "survey:survey_invite" pk=survey.pk %}">{% trans "Send invites" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "participant"|capfirst %}</th>
|
||||
<th>{% trans "completed"|capfirst %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for participant in survey.participants %}
|
||||
<tr class="{% if participant in survey.completed.all %}table-success{% else %}table-danger{% endif %}">
|
||||
{% if survey.invite_team %}
|
||||
<td>{% trans "Team" %} {{ participant.name }} ({{ participant.trigram }})</td>
|
||||
{% else %}
|
||||
<td>{{ participant.user.first_name }} {{ participant.user.last_name }} ({% trans "team" %} {{ participant.team.trigram }})</td>
|
||||
{% endif %}
|
||||
{% if participant in survey.completed.all %}
|
||||
<td>{% trans "Yes" %}</td>
|
||||
{% else %}
|
||||
<td>{% trans "No" %}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% trans "Update survey" as modal_title %}
|
||||
{% trans "Update" as modal_button %}
|
||||
{% url "survey:survey_update" pk=survey.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="updateSurvey" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initModal("updateSurvey", "{% url "survey:survey_update" pk=survey.pk %}")
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
17
survey/templates/survey/survey_form.html
Normal file
17
survey/templates/survey/survey_form.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends request.content_only|yesno:"empty.html,base.html" %}
|
||||
|
||||
{% load crispy_forms_filters i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<div id="form-content">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
</div>
|
||||
{% if object.pk %}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Update" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-success" type="submit">{% trans "Create" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock content %}
|
14
survey/templates/survey/survey_list.html
Normal file
14
survey/templates/survey/survey_list.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load django_tables2 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-grid">
|
||||
<a href="{% url "survey:survey_create" %}" class="btn gap-0 btn-success">
|
||||
<i class="fas fa-square-poll-horizontal"></i> {% trans "Add survey" %}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
{% render_table table %}
|
||||
{% endblock %}
|
3
survey/tests.py
Normal file
3
survey/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
18
survey/urls.py
Normal file
18
survey/urls.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from .views import SurveyCreateView, SurveyDetailView, SurveyInviteView, \
|
||||
SurveyListView, SurveyRefreshCompletedView, SurveyUpdateView
|
||||
|
||||
app_name = "survey"
|
||||
|
||||
urlpatterns = [
|
||||
path("", SurveyListView.as_view(), name="survey_list"),
|
||||
path("create/", SurveyCreateView.as_view(), name="survey_create"),
|
||||
path("<int:pk>/", SurveyDetailView.as_view(), name="survey_detail"),
|
||||
path("<int:pk>/invite/", SurveyInviteView.as_view(), name="survey_invite"),
|
||||
path("<int:pk>/refresh/", SurveyRefreshCompletedView.as_view(), name="survey_refresh_completed"),
|
||||
path("<int:pk>/update/", SurveyUpdateView.as_view(), name="survey_update"),
|
||||
]
|
56
survey/views.py
Normal file
56
survey/views.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright (C) 2025 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, DetailView, UpdateView
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from tfjm.views import AdminMixin
|
||||
from .forms import SurveyForm
|
||||
from .models import Survey
|
||||
from .tables import SurveyTable
|
||||
|
||||
|
||||
class SurveyListView(AdminMixin, SingleTableView):
|
||||
model = Survey
|
||||
table_class = SurveyTable
|
||||
template_name = "survey/survey_list.html"
|
||||
|
||||
|
||||
class SurveyCreateView(AdminMixin, CreateView):
|
||||
model = Survey
|
||||
form_class = SurveyForm
|
||||
|
||||
|
||||
class SurveyDetailView(AdminMixin, DetailView):
|
||||
model = Survey
|
||||
|
||||
|
||||
class SurveyInviteView(AdminMixin, DetailView):
|
||||
model = Survey
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
survey = self.get_object()
|
||||
new_participants = survey.invite_all()
|
||||
if new_participants:
|
||||
messages.success(request, _("Invites sent!"))
|
||||
else:
|
||||
messages.warning(request, _("All invites were already sent."))
|
||||
return redirect("survey:survey_detail", survey.pk)
|
||||
|
||||
|
||||
class SurveyRefreshCompletedView(AdminMixin, DetailView):
|
||||
model = Survey
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
survey = self.get_object()
|
||||
survey.fetch_completion_data()
|
||||
messages.success(request, _("Completion data refreshed!"))
|
||||
return redirect("survey:survey_detail", survey.pk)
|
||||
|
||||
|
||||
class SurveyUpdateView(AdminMixin, UpdateView):
|
||||
model = Survey
|
||||
form_class = SurveyForm
|
@ -19,5 +19,8 @@
|
||||
# Update Google Drive notifications daily
|
||||
0 0 * * * cd /code && python manage.py renew_gdrive_notifications -v 0
|
||||
|
||||
# Fetch LimeSurvey completion data
|
||||
*/15 * * 03-06 * cd /code && python manage.py fetch_survey_completion_data -v 0
|
||||
|
||||
# Clean temporary files
|
||||
30 * * * * rm -rf /tmp/*
|
||||
|
@ -13,6 +13,7 @@ def tfjm_context(request):
|
||||
'HAS_OBSERVER': settings.HAS_OBSERVER,
|
||||
'HAS_FINAL': settings.HAS_FINAL,
|
||||
'HOME_PAGE_LINK': settings.HOME_PAGE_LINK,
|
||||
'LIMESURVEY_URL': settings.LIMESURVEY_URL,
|
||||
'LOGO_PATH': "tfjm/img/" + settings.LOGO_FILE,
|
||||
'NB_ROUNDS': settings.NB_ROUNDS,
|
||||
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
||||
|
@ -3,16 +3,18 @@
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
_client = None
|
||||
|
||||
|
||||
def get_sympa_client():
|
||||
global _client
|
||||
if _client is None:
|
||||
if os.getenv("SYMPA_PASSWORD", None): # pragma: no cover
|
||||
if settings.SYMPA_PASSWORD is not None: # pragma: no cover
|
||||
from sympasoap import Client
|
||||
_client = Client("https://" + os.getenv("SYMPA_URL"))
|
||||
_client.login(os.getenv("SYMPA_EMAIL"), os.getenv("SYMPA_PASSWORD"))
|
||||
_client = Client("https://" + settings.SYMPA_URL)
|
||||
_client.login(settings.SYMPA_EMAIL, settings.SYMPA_PASSWORD)
|
||||
else:
|
||||
_client = FakeSympaSoapClient()
|
||||
return _client
|
||||
|
@ -74,6 +74,7 @@ INSTALLED_APPS = [
|
||||
'draw',
|
||||
'registration',
|
||||
'participation',
|
||||
'survey',
|
||||
]
|
||||
|
||||
if "test" not in sys.argv: # pragma: no cover
|
||||
@ -300,6 +301,12 @@ CHANNEL_LAYERS = {
|
||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||
|
||||
# Sympa configuration
|
||||
SYMPA_HOST = os.getenv("SYMPA_HOST", "localhost")
|
||||
SYMPA_URL = os.getenv("SYMPA_URL", "localhost")
|
||||
SYMPA_EMAIL = os.getenv("SYMPA_EMAIL", "contact@localhost")
|
||||
SYMPA_PASSWORD = os.getenv("SYMPA_PASSWORD", None)
|
||||
|
||||
# Hello Asso API creds
|
||||
HELLOASSO_CLIENT_ID = os.getenv('HELLOASSO_CLIENT_ID', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||
HELLOASSO_CLIENT_SECRET = os.getenv('HELLOASSO_CLIENT_SECRET', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||
@ -322,6 +329,10 @@ GOOGLE_SERVICE_CLIENT = {
|
||||
# The ID of the Google Drive folder where to store the notation sheets
|
||||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
|
||||
LIMESURVEY_URL = os.getenv("LIMESURVEY_URL", "https://survey.example.com")
|
||||
LIMESURVEY_USER = os.getenv("LIMESURVEY_USER", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
LIMESURVEY_PASSWORD = os.getenv("LIMESURVEY_PASSWORD", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
|
||||
# Custom parameters
|
||||
FORBIDDEN_TRIGRAMS = [
|
||||
"BIT",
|
||||
|
@ -74,6 +74,9 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.registration.is_admin %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url "survey:survey_list" %}"><i class="fas fa-square-poll-horizontal"></i> {% trans "surveys"|capfirst %}</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url "admin:index" %}"><i class="fas fa-cog"></i> {% trans "Administration" %}</a>
|
||||
</li>
|
||||
|
@ -44,6 +44,7 @@ urlpatterns = [
|
||||
path('draw/', include('draw.urls')),
|
||||
path('participation/', include('participation.urls')),
|
||||
path('registration/', include('registration.urls')),
|
||||
path('survey/', include('survey.urls')),
|
||||
|
||||
path('media/authorization/photo/<str:filename>/', PhotoAuthorizationView.as_view(),
|
||||
name='photo_authorization'),
|
||||
|
Loading…
x
Reference in New Issue
Block a user