Setup payment interface

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-02-18 22:36:01 +01:00
parent 7c9083a6b8
commit 4d157b2bd7
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
8 changed files with 366 additions and 131 deletions

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-12 22:29+0100\n"
"POT-Creation-Date: 2024-02-18 22:25+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"
@ -29,12 +29,12 @@ msgstr "équipes"
#: draw/admin.py:40 draw/admin.py:56 draw/models.py:24
#: participation/admin.py:16 participation/admin.py:73
#: participation/admin.py:104 participation/models.py:419
#: participation/models.py:443 participation/models.py:512
#: participation/models.py:443 participation/models.py:513
msgid "tournament"
msgstr "tournoi"
#: draw/admin.py:60 draw/models.py:231 draw/models.py:426
#: participation/models.py:516
#: participation/models.py:517
msgid "round"
msgstr "tour"
@ -168,7 +168,7 @@ msgstr "La poule en cours, où les équipes choisissent leurs problèmes"
msgid "rounds"
msgstr "tours"
#: draw/models.py:254 participation/models.py:530
#: draw/models.py:254 participation/models.py:531
msgid "letter"
msgstr "lettre"
@ -207,17 +207,17 @@ msgid "Pool {letter}{number}"
msgstr "Poule {letter}{number}"
#: draw/models.py:407 draw/models.py:434 participation/admin.py:69
#: participation/admin.py:88 participation/models.py:582
#: participation/models.py:591 participation/tables.py:83
#: participation/admin.py:88 participation/models.py:583
#: participation/models.py:592 participation/tables.py:83
msgid "pool"
msgstr "poule"
#: draw/models.py:408 participation/models.py:583
#: draw/models.py:408 participation/models.py:584
msgid "pools"
msgstr "poules"
#: draw/models.py:420 participation/models.py:502 participation/models.py:752
#: participation/models.py:782 participation/models.py:820
#: draw/models.py:420 participation/models.py:503 participation/models.py:753
#: participation/models.py:783 participation/models.py:821
msgid "participation"
msgstr "participation"
@ -241,8 +241,8 @@ msgid ""
msgstr ""
"L'ordre de choix dans la poule, entre 0 et la taille de la poule moins 1."
#: draw/models.py:457 draw/models.py:480 participation/models.py:605
#: participation/models.py:789
#: draw/models.py:457 draw/models.py:480 participation/models.py:606
#: participation/models.py:790
#, python-brace-format
msgid "Problem #{problem}"
msgstr "Problème n°{problem}"
@ -368,8 +368,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:151 participation/views.py:438
#: participation/views.py:469
#: draw/views.py:31 participation/views.py:151 participation/views.py:444
#: participation/views.py:475
msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe."
@ -447,21 +447,21 @@ msgid "selected for final"
msgstr "sélectionnée pour la finale"
#: participation/admin.py:57 participation/admin.py:116
#: participation/models.py:612 participation/tables.py:111
#: participation/models.py:613 participation/tables.py:111
msgid "defender"
msgstr "défenseur⋅se"
#: participation/admin.py:61 participation/models.py:619
#: participation/models.py:832
#: participation/admin.py:61 participation/models.py:620
#: participation/models.py:833
msgid "opponent"
msgstr "opposant⋅e"
#: participation/admin.py:65 participation/models.py:626
#: participation/models.py:833
#: participation/admin.py:65 participation/models.py:627
#: participation/models.py:834
msgid "reporter"
msgstr "rapporteur⋅e"
#: participation/admin.py:120 participation/models.py:787
#: participation/admin.py:120 participation/models.py:788
msgid "problem"
msgstr "numéro de problème"
@ -484,13 +484,14 @@ msgstr "Aucune équipe n'a été trouvée avec ce code d'accès."
#: participation/forms.py:86 participation/forms.py:351
#: registration/forms.py:122 registration/forms.py:144
#: registration/forms.py:166 registration/forms.py:188
#: registration/forms.py:235
#: registration/forms.py:234 registration/forms.py:267
msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
#: participation/forms.py:88 registration/forms.py:124
#: registration/forms.py:146 registration/forms.py:168
#: registration/forms.py:190 registration/forms.py:237
#: registration/forms.py:190 registration/forms.py:236
#: registration/forms.py:269
msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
@ -818,11 +819,11 @@ msgstr ""
"notification de bourse) pour participer au tournoi.</p><p>Les participant⋅es "
"qui n'ont pas encore payé sont : {participants}.</p>"
#: participation/models.py:477
#: participation/models.py:478
msgid "Missing payments"
msgstr "Paiements manquants"
#: participation/models.py:484
#: participation/models.py:485
msgid ""
"<p>The solutions for the tournament of {tournament} are due on the {date:%Y-"
"%m-%d %H:%M}.</p><p>You have currently sent <strong>{nb_solutions}</strong> "
@ -837,36 +838,36 @@ msgstr ""
"pouvez envoyer vos solutions sur <a href='{url}'>votre page de "
"participation</a>.</p>"
#: participation/models.py:493
#: participation/models.py:494
msgid "Solutions due"
msgstr "Rendu des solutions"
#: participation/models.py:503 participation/models.py:536
#: participation/models.py:504 participation/models.py:537
msgid "participations"
msgstr "participations"
#: participation/models.py:518 participation/models.py:519
#: participation/models.py:519 participation/models.py:520
#, python-brace-format
msgid "Round {round}"
msgstr "Tour {round}"
#: participation/models.py:542
#: participation/models.py:543
msgid "juries"
msgstr "jurys"
#: participation/models.py:549
#: participation/models.py:550
msgid "BigBlueButton URL"
msgstr "Lien BigBlueButton"
#: participation/models.py:550
#: participation/models.py:551
msgid "The link of the BBB visio for this pool."
msgstr "Le lien du salon BBB pour cette poule."
#: participation/models.py:555
#: participation/models.py:556
msgid "results available"
msgstr "résultats disponibles"
#: participation/models.py:556
#: participation/models.py:557
msgid ""
"Check this case when results become accessible to teams. They stay "
"accessible to you. Only averages are given."
@ -875,28 +876,28 @@ msgstr ""
"Ils restent toujours accessibles pour vous. Seules les moyennes sont "
"communiquées."
#: participation/models.py:576
#: participation/models.py:577
#, python-brace-format
msgid "Pool of day {round} for tournament {tournament} with teams {teams}"
msgstr "Poule du jour {round} du tournoi {tournament} avec les équipes {teams}"
#: participation/models.py:596
#: participation/models.py:597
msgid "position"
msgstr "position"
#: participation/models.py:603
#: participation/models.py:604
msgid "defended solution"
msgstr "solution défendue"
#: participation/models.py:636
#: participation/models.py:637
msgid "observer"
msgstr "observateur⋅rice"
#: participation/models.py:641
#: participation/models.py:642
msgid "penalties"
msgstr "pénalités"
#: participation/models.py:643
#: participation/models.py:644
msgid ""
"Number of penalties for the defender. The defender will loose a 0.5 "
"coefficient per penalty."
@ -904,124 +905,124 @@ msgstr ""
"Nombre de pénalités pour l'équipe défenseuse. Elle perd un coefficient 0.5 "
"sur sa présentation orale par pénalité."
#: participation/models.py:719 participation/models.py:722
#: participation/models.py:725 participation/models.py:728
#: participation/models.py:720 participation/models.py:723
#: participation/models.py:726 participation/models.py:729
#, python-brace-format
msgid "Team {trigram} is not registered in the pool."
msgstr "L'équipe {trigram} n'est pas inscrite dans la poule."
#: participation/models.py:733
#: participation/models.py:734
#, python-brace-format
msgid "Passage of {defender} for problem {problem}"
msgstr "Passage de {defender} pour le problème {problem}"
#: participation/models.py:737 participation/models.py:746
#: participation/models.py:827 participation/models.py:869
#: participation/models.py:738 participation/models.py:747
#: participation/models.py:828 participation/models.py:870
msgid "passage"
msgstr "passage"
#: participation/models.py:738
#: participation/models.py:739
msgid "passages"
msgstr "passages"
#: participation/models.py:757
#: participation/models.py:758
msgid "difference"
msgstr "différence"
#: participation/models.py:758
#: participation/models.py:759
msgid "Score to add/remove on the final score"
msgstr "Score à ajouter/retrancher au score final"
#: participation/models.py:765
#: participation/models.py:766
msgid "tweak"
msgstr "harmonisation"
#: participation/models.py:766
#: participation/models.py:767
msgid "tweaks"
msgstr "harmonisations"
#: participation/models.py:794
#: participation/models.py:795
msgid "solution for the final tournament"
msgstr "solution pour la finale"
#: participation/models.py:799 participation/models.py:838
#: participation/models.py:800 participation/models.py:839
msgid "file"
msgstr "fichier"
#: participation/models.py:805
#: participation/models.py:806
#, python-brace-format
msgid "Solution of team {team} for problem {problem}"
msgstr "Solution de l'équipe {team} pour le problème {problem}"
#: participation/models.py:807
#: participation/models.py:808
msgid "for final"
msgstr "pour la finale"
#: participation/models.py:810
#: participation/models.py:811
msgid "solution"
msgstr "solution"
#: participation/models.py:811
#: participation/models.py:812
msgid "solutions"
msgstr "solutions"
#: participation/models.py:844
#: participation/models.py:845
#, python-brace-format
msgid "Synthesis of {team} as {type} for problem {problem} of {defender}"
msgstr ""
"Note de synthèse de l'équipe {team} en tant que {type} pour le problème "
"{problem} de {defender}"
#: participation/models.py:852
#: participation/models.py:853
msgid "synthesis"
msgstr "note de synthèse"
#: participation/models.py:853
#: participation/models.py:854
msgid "syntheses"
msgstr "notes de synthèse"
#: participation/models.py:862
#: participation/models.py:863
msgid "jury"
msgstr "jury"
#: participation/models.py:874
#: participation/models.py:875
msgid "defender writing note"
msgstr "note d'écrit de la défense"
#: participation/models.py:880
#: participation/models.py:881
msgid "defender oral note"
msgstr "note d'oral de la défense"
#: participation/models.py:886
#: participation/models.py:887
msgid "opponent writing note"
msgstr "note d'écrit de l'opposition"
#: participation/models.py:892
#: participation/models.py:893
msgid "opponent oral note"
msgstr "note d'oral de l'opposition"
#: participation/models.py:898
#: participation/models.py:899
msgid "reporter writing note"
msgstr "note d'écrit du rapportage"
#: participation/models.py:904
#: participation/models.py:905
msgid "reporter oral note"
msgstr "note d'oral du rapportage"
#: participation/models.py:910
#: participation/models.py:911
msgid "observer note"
msgstr "note de l'observation"
#: participation/models.py:939
#: participation/models.py:940
#, python-brace-format
msgid "Notes of {jury} for {passage}"
msgstr "Notes de {jury} pour le {passage}"
#: participation/models.py:946
#: participation/models.py:947
msgid "note"
msgstr "note"
#: participation/models.py:947
#: participation/models.py:948
msgid "notes"
msgstr "notes"
@ -1090,7 +1091,6 @@ msgstr "Rejoindre"
#: participation/templates/participation/update_team.html:12
#: registration/templates/registration/update_user.html:16
#: registration/templates/registration/user_detail.html:186
#: registration/templates/registration/user_detail.html:223
msgid "Update"
msgstr "Modifier"
@ -1498,7 +1498,7 @@ msgid "Invalidate"
msgstr "Invalider"
#: participation/templates/participation/team_detail.html:186
#: participation/views.py:323
#: participation/views.py:329
msgid "Upload motivation letter"
msgstr "Envoyer la lettre de motivation"
@ -1507,7 +1507,7 @@ msgid "Update team"
msgstr "Modifier l'équipe"
#: participation/templates/participation/team_detail.html:196
#: participation/views.py:432
#: participation/views.py:438
msgid "Leave team"
msgstr "Quitter l'équipe"
@ -1641,7 +1641,7 @@ msgstr "Vous êtes déjà dans une équipe."
msgid "Join team"
msgstr "Rejoindre une équipe"
#: participation/views.py:152 participation/views.py:470
#: participation/views.py:152 participation/views.py:476
msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
@ -1677,95 +1677,95 @@ msgstr "Vous n'êtes pas un⋅e organisateur⋅rice du tournoi."
msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente."
#: participation/views.py:266
#: participation/views.py:272
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:301
#: participation/views.py:307
#, python-brace-format
msgid "Update team {trigram}"
msgstr "Mise à jour de l'équipe {trigram}"
#: participation/views.py:362 participation/views.py:418
#: participation/views.py:368 participation/views.py:424
#, python-brace-format
msgid "Motivation letter of {team}.{ext}"
msgstr "Lettre de motivation de {team}.{ext}"
#: participation/views.py:393
#: participation/views.py:399
#, python-brace-format
msgid "Photo authorization of {participant}.{ext}"
msgstr "Autorisation de droit à l'image de {participant}.{ext}"
#: participation/views.py:399
#: participation/views.py:405
#, python-brace-format
msgid "Parental authorization of {participant}.{ext}"
msgstr "Autorisation parentale de {participant}.{ext}"
#: participation/views.py:406
#: participation/views.py:412
#, python-brace-format
msgid "Health sheet of {participant}.{ext}"
msgstr "Fiche sanitaire de {participant}.{ext}"
#: participation/views.py:412
#: participation/views.py:418
#, python-brace-format
msgid "Vaccine sheet of {participant}.{ext}"
msgstr "Carnet de vaccination de {participant}.{ext}"
#: participation/views.py:422
#: participation/views.py:428
#, python-brace-format
msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
#: participation/views.py:440
#: participation/views.py:446
msgid "The team is already validated or the validation is pending."
msgstr "La validation de l'équipe est déjà faite ou en cours."
#: participation/views.py:484
#: participation/views.py:490
msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée."
#: participation/views.py:498
#: participation/views.py:504
#, python-brace-format
msgid "Participation of team {trigram}"
msgstr "Participation de l'équipe {trigram}"
#: participation/views.py:634
#: participation/views.py:640
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:742
#: participation/views.py:748
#, 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:743
#: participation/views.py:749
#, python-brace-format
msgid "Syntheses for pool {pool} of tournament {tournament}.zip"
msgstr "Notes de synthèses pour la poule {pool} du tournoi {tournament}.zip"
#: participation/views.py:761
#: participation/views.py:767
#, python-brace-format
msgid "Jurys of {pool}"
msgstr "Juré⋅es de la {pool}"
#: participation/views.py:788
#: participation/views.py:794
msgid "New TFJM² jury account"
msgstr "Nouveau compte de juré⋅e pour le TFJM²"
#: participation/views.py:801
#: participation/views.py:807
#, 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:838
#: participation/views.py:844
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:852
#: participation/views.py:858
msgid "Notes were successfully uploaded."
msgstr "Les notes ont bien été envoyées."
#: participation/views.py:1516
#: participation/views.py:1522
msgid "You can't upload a synthesis after the deadline."
msgstr "Vous ne pouvez pas envoyer de note de synthèse après la date limite."
@ -1799,7 +1799,7 @@ msgstr "encadrant⋅e"
msgid "Pending"
msgstr "En attente"
#: registration/forms.py:245
#: registration/forms.py:244 registration/forms.py:277
msgid "You must upload your receipt."
msgstr "Vous devez envoyer votre justificatif."
@ -1848,6 +1848,7 @@ msgid "Male"
msgstr "Homme"
#: registration/models.py:155
#: registration/templates/registration/payment_form.html:73
msgid "Other"
msgstr "Autre"
@ -2162,11 +2163,17 @@ msgstr "type"
msgid "No payment"
msgstr "Pas de paiement"
#: registration/models.py:529
#: registration/templates/registration/payment_form.html:56
msgid "Credit card"
msgstr "Carte bancaire"
#: registration/models.py:530
msgid "Scholarship"
msgstr "Notification de bourse"
#: registration/models.py:531
#: registration/templates/registration/payment_form.html:61
msgid "Bank transfer"
msgstr "Virement bancaire"
@ -2207,7 +2214,7 @@ msgstr "paiement valide"
#: registration/models.py:571
#, python-brace-format
msgid "Payment of {registrations}"
msgstr "Paiements de {registration}"
msgstr "Paiements de {registrations}"
#: registration/models.py:574
msgid "payment"
@ -2378,6 +2385,54 @@ msgstr ""
msgid "Reset my password"
msgstr "Réinitialiser mon mot de passe"
#: registration/templates/registration/payment_form.html:9
#, python-format
msgid "You must pay %(amount)s € for your registration."
msgstr "Vous devez payez %(amount)s € pour votre inscription."
#: registration/templates/registration/payment_form.html:13
msgid "This price includes the registrations of all members of your team."
msgstr "Ce prix inclut les inscriptions de tous les membres de votre équipe."
#: registration/templates/registration/payment_form.html:17
msgid ""
"This price includes only your own registration. You are exempt from payment "
"if you have a scholarship, but you must then send us a proof of your "
"scholarship."
msgstr ""
"Ce prix inclut seulement votre propre inscription. Vous êtes exempté⋅e de "
"paiement si vous avez une bourse, mais vous devez alors nous envoyer un "
"justificatif de votre bourse."
#: registration/templates/registration/payment_form.html:27
msgid ""
"You want finally that each member pays its own registration? Then click on "
"the button:"
msgstr ""
"Vous voulez finalement que chaque membre paie sa propre inscription ? Alors "
"cliquez sur le bouton :"
#: registration/templates/registration/payment_form.html:32
msgid "Back to single payments"
msgstr "Retour aux paiements individuels"
#: registration/templates/registration/payment_form.html:36
msgid ""
"You want to pay for the registrations of all members of your team, or your "
"school will pay for all registrations? Then click on the button:"
msgstr ""
"Vous voulez payer pour les inscriptions de tous les membres de votre équipe, "
"ou votre école paiera pour toutes les inscriptions ? Alors cliquez sur le "
"bouton :"
#: registration/templates/registration/payment_form.html:42
msgid "Group the payments of my team"
msgstr "Regrouper les paiements de mon équipe"
#: registration/templates/registration/payment_form.html:67
msgid "I have a scholarship"
msgstr "J'ai une bourse"
#: registration/templates/registration/signup.html:5
#: registration/templates/registration/signup.html:12
#: registration/templates/registration/signup.html:19 registration/views.py:44
@ -2526,7 +2581,7 @@ msgid "valid:"
msgstr "valide :"
#: registration/templates/registration/user_detail.html:163
#: registration/templates/registration/user_detail.html:222
#: registration/views.py:458
msgid "Update payment"
msgstr "Modifier le paiement"
@ -2597,30 +2652,30 @@ msgstr "Détails de l'utilisateur⋅rice {user}"
msgid "Update user {user}"
msgstr "Mise à jour de l'utilisateur⋅rice {user}"
#: registration/views.py:492
#: registration/views.py:503
#, python-brace-format
msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}"
#: registration/views.py:515
#: registration/views.py:526
#, python-brace-format
msgid "Health sheet of {student}.{ext}"
msgstr "Fiche sanitaire de {student}.{ext}"
#: registration/views.py:538
#: registration/views.py:549
#, python-brace-format
msgid "Vaccine sheet of {student}.{ext}"
msgstr "Carnet de vaccination de {student}.{ext}"
#: registration/views.py:561
#: registration/views.py:572
#, python-brace-format
msgid "Parental authorization of {student}.{ext}"
msgstr "Autorisation parentale de {student}.{ext}"
#: registration/views.py:583
#: registration/views.py:594
#, python-brace-format
msgid "Scholarship attestation of {user}.{ext}"
msgstr "Notification de bourse de {user}.{ext}"
msgid "Payment receipt of {user}.{ext}"
msgstr "Justificatif de paiement de {user}.{ext}"
#: tfjm/settings.py:164
msgid "English"
@ -2783,3 +2838,7 @@ msgstr "Aucun résultat."
#: tfjm/templates/sidebar.html:10 tfjm/templates/sidebar.html:21
msgid "Informations"
msgstr "Informations"
#, python-brace-format
#~ msgid "Scholarship attestation of {user}.{ext}"
#~ msgstr "Notification de bourse de {user}.{ext}"

View File

@ -219,7 +219,7 @@ class VolunteerRegistrationForm(forms.ModelForm):
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
class PaymentForm(forms.ModelForm):
class PaymentAdminForm(forms.ModelForm):
"""
Indicate payment information
"""
@ -228,7 +228,6 @@ class PaymentForm(forms.ModelForm):
self.fields["valid"].widget.choices[0] = ('unknown', _("Pending"))
def clean_receipt(self):
print(self.files)
if "receipt" in self.files:
file = self.files["receipt"]
if file.size > 2e6:
@ -241,7 +240,7 @@ class PaymentForm(forms.ModelForm):
cleaned_data = super().clean()
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
and "receipt" not in self.files and not self.instance.scholarship_file:
and "receipt" not in self.files and not self.instance.receipt:
self.add_error("receipt", _("You must upload your receipt."))
return cleaned_data
@ -249,3 +248,37 @@ class PaymentForm(forms.ModelForm):
class Meta:
model = Payment
fields = ('type', 'receipt', 'additional_information', 'valid',)
class PaymentForm(forms.ModelForm):
"""
Indicate payment information
"""
def __init__(self, payment_type, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['type'].widget = forms.HiddenInput(attrs={'value': payment_type})
self.fields['receipt'].required = payment_type in ["scholarship", "bank_transfer"]
self.fields['additional_information'].required = payment_type in ["other"]
def clean_receipt(self):
if "receipt" in self.files:
file = self.files["receipt"]
if file.size > 2e6:
raise ValidationError(_("The uploaded file size must be under 2 Mo."))
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
return self.cleaned_data["receipt"]
def clean(self):
cleaned_data = super().clean()
if "type" in cleaned_data and cleaned_data['type'] in ["scholarship", "bank_transfer"] \
and "receipt" not in self.files and not self.instance.receipt:
self.add_error("receipt", _("You must upload your receipt."))
return cleaned_data
class Meta:
model = Payment
fields = ('type', 'receipt', 'additional_information',)

View File

@ -113,7 +113,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(blank=True, choices=[('', 'No payment'), ('helloasso', 'Hello Asso'), ('scholarship', 'Scholarship'), ('bank_transfer', 'Bank transfer'), ('other', 'Other (please indicate)'), ('free', 'The tournament is free')], default='', max_length=16, verbose_name='type')),
('scholarship_file', models.FileField(blank=True, default='', help_text='only if you have a scholarship.', upload_to=registration.models.get_scholarship_filename, verbose_name='scholarship file')),
('scholarship_file', models.FileField(blank=True, default='', help_text='only if you have a scholarship.', upload_to=registration.models.get_receipt_filename, verbose_name='scholarship file')),
('additional_information', models.TextField(blank=True, default='', help_text='To help us to find your payment.', verbose_name='additional information')),
('valid', models.BooleanField(default=False, null=True, verbose_name='valid')),
('registration', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment', to='registration.participantregistration', verbose_name='registration')),

View File

@ -60,7 +60,7 @@ class Migration(migrations.Migration):
blank=True,
default="",
help_text="only if you have a scholarship or if you chose a bank transfer.",
upload_to=registration.models.get_scholarship_filename,
upload_to=registration.models.get_receipt_filename,
verbose_name="receipt",
),
),

View File

@ -492,8 +492,8 @@ class VolunteerRegistration(Registration):
verbose_name_plural = _("volunteer registrations")
def get_scholarship_filename(instance, filename):
return f"authorization/scholarship/scholarship_{instance.registration.pk}"
def get_receipt_filename(instance, filename):
return f"authorization/receipt/receipt_{instance.id}"
class Payment(models.Model):
@ -526,7 +526,7 @@ class Payment(models.Model):
max_length=16,
choices=[
('', _("No payment")),
('helloasso', "Hello Asso"),
('helloasso', _("Credit card")),
('scholarship', _("Scholarship")),
('bank_transfer', _("Bank transfer")),
('other', _("Other (please indicate)")),
@ -546,7 +546,7 @@ class Payment(models.Model):
receipt = models.FileField(
verbose_name=_("receipt"),
help_text=_("only if you have a scholarship or if you chose a bank transfer."),
upload_to=get_scholarship_filename,
upload_to=get_receipt_filename,
blank=True,
default="",
)

View File

@ -3,8 +3,140 @@
{% load crispy_forms_filters i18n %}
{% block content %}
<div class="alert alert-warning">
Le formulaire de paiement est temporairement désactivé. Il sera accessible d'ici quelques jours.
</div>
{% if payment.valid is False %}
<div class="alert alert-info">
<p>
{% blocktrans trimmed with amount=payment.amount %}
You must pay {{ amount }} € for your registration.
{% endblocktrans %}
{% if payment.grouped %}
{% blocktrans trimmed %}
This price includes the registrations of all members of your team.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
This price includes only your own registration.
You are exempt from payment if you have a scholarship,
but you must then send us a proof of your scholarship.
{% endblocktrans %}
{% endif %}
</p>
<p>
{% if payment.grouped %}
{% blocktrans trimmed %}
You want finally that each member pays its own registration? Then click on the button:
{% endblocktrans %}
<div class="text-center">
<button class="btn btn-warning">
<i class="fas fa-user"></i> {% trans "Back to single payments" %}
</button>
</div>
{% else %}
{% blocktrans trimmed %}
You want to pay for the registrations of all members of your team,
or your school will pay for all registrations? Then click on the button:
{% endblocktrans %}
<div class="text-center">
<button class="btn btn-warning">
<i class="fas fa-users"></i> {% trans "Group the payments of my team" %}
</button>
</div>
{% endif %}
</p>
</div>
<div class="card">
<div class="card-header">
<nav>
<div class="nav nav-tabs card-header-tabs" id="payment-method-tab" role="tablist">
<button class="nav-link active" id="credit-card-tab" data-bs-toggle="tab"
data-bs-target="#credit-card" type="button" role="tab"
aria-controls="credit-card" aria-selected="true">
<i class="fas fa-credit-card"></i> {% trans "Credit card" %}
</button>
<button class="nav-link" id="bank-transfer-tab" data-bs-toggle="tab"
data-bs-target="#bank-transfer" type="button" role="tab"
aria-controls="bank-transfer" aria-selected="true">
<i class="fas fa-money-check"></i> {% trans "Bank transfer" %}
</button>
{% if not payment.grouped %}
<button class="nav-link" id="scholarship-tab" data-bs-toggle="tab"
data-bs-target="#scholarship" type="button" role="tab"
aria-controls="scholarship" aria-selected="true">
<i class="fas fa-file-invoice"></i> {% trans "I have a scholarship" %}
</button>
{% endif %}
<button class="nav-link" id="other-tab" data-bs-toggle="tab"
data-bs-target="#other" type="button" role="tab"
aria-controls="other" aria-selected="true">
<i class="fas fa-question"></i> {% trans "Other" %}
</button>
</div>
</nav>
</div>
<div class="card-body">
<div class="tab-content" id="payment-form">
<div class="tab-pane fade show active" id="credit-card" role="tabpanel" aria-labelledby="credit-card-tab">
Le paiement par carte bancaire s'effectue via Hello Asso. Pour cela, vous pouvez cliquer sur
le bouton ci-dessous, qui vous redirigera vers la page de paiement sécurisée de Hello Asso.
La validation du paiement sera ensuite faite automatiquement, sous quelques minutes.
Si un tiers doit payer pour vous (parents, lycée,…), vous pouvez lui transmettre le lien pour
payer pour vous.
<div class="text-center">
<a href="#" class="btn btn-primary">
<i class="fas fa-credit-card"></i> Aller sur la page Hello Asso
</a>
</div>
</div>
<div class="tab-pane fade" id="bank-transfer" role="tabpanel" aria-labelledby="bank-transfer-tab">
<form id="bank-transfer-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ bank_transfer_form|crispy }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
<div class="tab-pane fade" id="scholarship" role="tabpanel" aria-labelledby="scholarship-tab">
<form id="scholarship-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ scholarship_form|crispy }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
<div class="tab-pane fade" id="other" role="tabpanel" aria-labelledby="other-tab">
<form id="other-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ other_form|crispy }}
<input type="submit" class="btn btn-primary" />
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock content %}
{% block extrajavascript %}
<script>
document.addEventListener('DOMContentLoaded', () => {
if (document.location.hash) {
// Open the tab of the tournament that is present in the hash
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(elem => {
if ('#' + elem.getAttribute('aria-controls') === document.location.hash.toLowerCase()) {
elem.click()
}
})
}
// When a tab is opened, add the tournament name in the hash
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(
elem => elem.addEventListener(
'click', () => document.location.hash = '#' + elem.getAttribute('aria-controls')))
})
</script>
{% endblock %}

View File

@ -29,7 +29,7 @@ from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
from .forms import AddOrganizerForm, CoachRegistrationForm, HealthSheetForm, \
ParentalAuthorizationForm, PaymentForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
VaccineSheetForm, VolunteerRegistrationForm
VaccineSheetForm, VolunteerRegistrationForm, PaymentAdminForm
from .models import ParticipantRegistration, Payment, Registration, StudentRegistration
from .tables import RegistrationTable
@ -443,7 +443,7 @@ class InstructionsTemplateView(AuthorizationTemplateView):
class PaymentUpdateView(LoginRequiredMixin, UpdateView):
model = Payment
form_class = PaymentForm
form_class = PaymentAdminForm
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_authenticated or \
@ -453,22 +453,33 @@ class PaymentUpdateView(LoginRequiredMixin, UpdateView):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
def get_form(self, form_class=None):
form = super().get_form(form_class)
if not self.request.user.registration.is_admin:
form.fields["type"].widget.choices = list(form.fields["type"].widget.choices)[:-1]
del form.fields["valid"]
return form
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['title'] = _("Update payment")
context['bank_transfer_form'] = PaymentForm(payment_type='bank_transfer',
data=self.request.POST or None,
instance=self.object)
context['scholarship_form'] = PaymentForm(payment_type='scholarship',
data=self.request.POST or None,
instance=self.object)
context['other_form'] = PaymentForm(payment_type='other',
data=self.request.POST or None,
instance=self.object)
return context
def form_valid(self, form):
if not self.request.user.registration.is_admin:
form.instance.valid = None
form.instance.valid = None
old_instance = Payment.objects.get(pk=self.object.pk)
if old_instance.receipt:
old_instance.receipt.delete()
old_instance.save()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("registration:user_detail", args=(self.object.registrations.first().user.pk,))
class PhotoAuthorizationView(LoginRequiredMixin, View):
"""
@ -562,9 +573,9 @@ class ParentalAuthorizationView(LoginRequiredMixin, View):
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
class ScholarshipView(LoginRequiredMixin, View):
class ReceiptView(LoginRequiredMixin, View):
"""
Display the sent scholarship paper.
Display the sent payment receipt or scholarship notification.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
@ -573,14 +584,14 @@ class ScholarshipView(LoginRequiredMixin, View):
raise Http404
payment = Payment.objects.get(receipt__endswith=filename)
user = request.user
if not (user.registration in payment.registrations or user.registration.is_admin):
if not (user.registration in payment.registrations.all() or user.registration.is_admin):
raise PermissionDenied
# Guess mime type of the file
mime = Magic(mime=True)
mime_type = mime.from_file(path)
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
# Replace file name
true_file_name = _("Scholarship attestation of {user}.{ext}").format(user=str(user.registration), ext=ext)
true_file_name = _("Payment receipt of {user}.{ext}").format(user=str(user.registration), ext=ext)
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)

View File

@ -23,7 +23,7 @@ from django.views.defaults import bad_request, page_not_found, permission_denied
from django.views.generic import TemplateView
from participation.views import MotivationLetterView
from registration.views import HealthSheetView, ParentalAuthorizationView, PhotoAuthorizationView, \
ScholarshipView, SolutionView, SynthesisView, VaccineSheetView
ReceiptView, SolutionView, SynthesisView, VaccineSheetView
from .views import AdminSearchView
@ -49,10 +49,10 @@ urlpatterns = [
name='vaccine_sheet'),
path('media/authorization/parental/<str:filename>/', ParentalAuthorizationView.as_view(),
name='parental_authorization'),
path('media/authorization/scholarship/<str:filename>/', ScholarshipView.as_view(),
name='scholarship'),
path('media/authorization/receipt/<str:filename>/', ReceiptView.as_view(),
name='receipt'),
path('media/authorization/motivation_letters/<str:filename>/', MotivationLetterView.as_view(),
name='scholarship'),
name='motivation_letter'),
path('media/solutions/<str:filename>/', SolutionView.as_view(),
name='solution'),