Merge branch 'open-registrations' into 'dev'

Ouverture et fermeture des inscriptions

See merge request animath/si/plateforme-tfjm!47
This commit is contained in:
Emmy D'Anello 2024-10-28 21:24:24 +00:00
commit 3015361835
12 changed files with 443 additions and 76 deletions

194
docs/dev/transition.rst Normal file
View File

@ -0,0 +1,194 @@
Transition d'années
===================
Entre deux sessions du TFJM², certaines opérations doivent être effectuées chaque année,
afin de réinitialiser les données et de passer à l'année suivante.
Réinitialisation de la base de données
--------------------------------------
Conservation des autorisations de droit à l'image
"""""""""""""""""""""""""""""""""""""""""""""""""
La base de données du TFJM² est supprimée chaque année, avant chaque tournoi. Il n'y a
pas de conservation de données personnelles à l'exception des autorisations de droit
à l'image qui doivent être conservées pour des raisons légales pendant 5 ans.
Elles doivent alors être stockées sur Owncloud. Pour cela, il faut commencer par créer
un dossier dans Owncloud, qui stockera lesdites autorisations.
Rendez-vous ensuite dans le conteneur Docker et exécuter le script :
.. code:: bash
./manage.py export_photo_authorizations
Cela a pour effet de générer un dossier dans ``output/photo_authorizations``, qui contient
un dossier par équipe avec les différentes autorisations de droit à l'image.
Il faut maintenant récupérer ce dossier. Sortir du conteneur, et exécuter dans ``/srv/TFJM`` :
.. code:: bash
sudo docker cp tfjm_plateforme_1:/code/output/photo_authorizations .
sudo mv photo_authorizations/* "data/owncloud/files/Emmy/Autorisations de droit à l'image/Autorisations de droit à l'image 2024/"
sudo chown -R www-data:root "data/owncloud/files/Emmy/Autorisations de droit à l'image/Autorisations de droit à l'image 2024"
sudo rmdir photo_authorizations
Il faut enfin réactualiser Owncloud. Exécuter en tant que www-data :
.. code:: bash
sudo docker compose exec -u www-data cloud php occ files:scan Emmy
Vérifiez enfin que les fichiers sont bien accessibles dans l'interface Web.
Ne pas oublier enfin de partager le dossier.
Sauvegarde de secours
"""""""""""""""""""""
Si les données doivent être supprimées, il peut être utile de réaliser une sauvegarde à conserver
quelques mois.
.. danger::
Cette sauvegarde ne doit être faite qu'à des fins utiles et supprimée dès que plus nécessaire.
Sauvegardez alors le dossier ``/srv/TFJM/data/inscription/media`` et exportez la base de données :
.. code:: bash
sudo cp -r data/inscription/media data/inscription/media-2024
sudo docker compose exec -u postgres postgres pg_dump inscription | sudo tee inscription_bkp_2024.sql
Réinitialisation effective
""""""""""""""""""""""""""
Il est désormais possible de réinitialiser la base de données. Rendez-vous dans le conteneur de
la plateforme, et exécutez :
.. code:: bash
rm -r media/*
./manage.py reset_db
Créez enfin un nouveau compte administrateur⋅rice :
.. code:: bash
./manage.py createsuperuser
Nouveaux paramètres pour la nouvelle année
------------------------------------------
Certains paramètres doivent être modifiés pour prendre en compte la nouvelle année.
Dates d'inscription
"""""""""""""""""""
Les inscriptions sont permises uniquement entre l'ouverture et la fermeture, afin d'éviter
d'avoir des personnes s'inscrivant en dehors du TFJM².
Pour cela, dans votre projet local, rendez-vous dans ``tfjm/settings.py`` et cherchez
le paramètre ``REGISTRATION_DATES`` (pour le TFJM²). Modifiez alors les sous-paramètres
``open`` et ``close`` pour définir les dates pendant lesquelles les inscriptions des
participant⋅es sont permises pour cette nouvelle année. Elles doivent être au format ISO.
Exemple pour l'année 2025 où les inscriptions ouvrent au 8 janvier midi pour fermer
le 2 mars à 22h :
.. code:: python
REGISTRATION_DATES = dict(
open=datetime.fromisoformat("2025-01-08T12:00:00+0100"),
close=datetime.fromisoformat("2025-03-02T22:00:00+0100"),
)
Il faudra ensuite commiter la modification et redémarrer le serveur pour que la modification
prenne effet.
Noms des problèmes
""""""""""""""""""
Toujours dans la configuration dans ``tfjm/settings.py``, la liste des problèmes doit être
modifiée pour que leurs noms s'affichent correctement lors du tirage au sort.
Cherchez le paramètre ``PROBLEMS`` et mettez alors à jour la liste, dans l'ordre, des noms
des problèmes.
À nouveau, il est nécessaire de commiter la modification et redémarrer le serveur.
Paramètres des tournois
"""""""""""""""""""""""
Il faut enfin paramétrer les différentes dates des tournois.
Pour cela, connectez-vous sur la plateforme (avec un compte administrateur⋅rice), et dans l'onglet
« Tournois », vous pouvez créer les différents tournois avec les différentes dates pour chaque tournoi.
Plus d'information sur les différents paramètres dans la `section concernée
<../orga.html#creer-un-tournoi>`_
À la fin du tournoi
-------------------
Lorsque le tournoi est terminé, il faut récupérer les informations à stocker de façon pérenne,
notamment les solutions des équipes, les résultats ainsi que les autorisation de droit à l'image
comme indiqué précédemment.
Conservation des autorisations de droit à l'image
"""""""""""""""""""""""""""""""""""""""""""""""""
Se référer à la section plus haut.
Conservation des solutions des équipes
""""""""""""""""""""""""""""""""""""""
Le processus est très similaire à la conservation des autorisations de droit à l'image.
Il faut d'abord, dans le conteneur, lancer le script dédié pour récupérer les solutions
dans ``/code/output/solutions`` :
.. code:: bash
./manage.py export_solutions
On sort du conteneur et on récupère les solutions pour les déplacer dans Owncloud :
.. code:: bash
sudo docker cp tfjm_plateforme_1:/code/output/solutions .
sudo mv solutions/* "data/owncloud/files/Emmy/Solutions écrites 2024/"
sudo chown -R www-data:root "data/owncloud/files/Emmy/Solutions écrites 2024"
sudo rmdir solutions
Il faut enfin réactualiser Owncloud. Exécuter en tant que www-data :
.. code:: bash
sudo docker compose exec -u www-data cloud php occ files:scan Emmy
Vérifiez enfin que les fichiers sont bien accessibles dans l'interface Web.
Ne pas oublier enfin de partager le dossier.
Génération de la page de résultats Wordpress
""""""""""""""""""""""""""""""""""""""""""""
Pour finir, il est possible de récupérer les notes pour chaque tournoi afin de générer
la page Wordpress dans la section *Éditions précédentes*.
Il suffit de lancer le script ``./manage.py export_results``, qui donne le texte brut pour
Wordpress à ajouter sur la page de l'édition qui vient de se terminer dans l'onglet
*Éditions précédentes*.
Pensez à bien inclure sur cette page le lien vers les problèmes de l'année, ainsi que le
lien vers le dossier partagé dans le Owncloud concernant les solutions des équipes.
Assurez-vous de mettre à jour la page *Éditions précédentes* afin d'inclure le lien vers
la page nouvellement créée.

View File

@ -21,3 +21,4 @@ administrateur⋅rice.
dev/index
dev/install
dev/transition

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-09 13:22+0200\n"
"POT-Creation-Date: 2024-10-28 20:14+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"
@ -217,7 +217,7 @@ msgstr ""
msgid "Toggle fullscreen mode"
msgstr "Inverse le mode plein écran"
#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:125
#: chat/templates/chat/content.html:76 tfjm/templates/navbar.html:126
msgid "Log out"
msgstr "Déconnexion"
@ -244,14 +244,14 @@ msgid "ETEAM Chat"
msgstr "Chat de l'ETEAM"
#: chat/templates/chat/login.html:10 chat/templates/chat/login.html:12
#: chat/urls.py:13 tfjm/templates/navbar.html:74
#: chat/urls.py:13 tfjm/templates/navbar.html:72
msgid "Chat"
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:106
#: tfjm/templates/navbar.html:107
#: tfjm/templates/registration/includes/login.html:22
#: tfjm/templates/registration/login.html:7
#: tfjm/templates/registration/login.html:8
@ -269,7 +269,7 @@ msgstr "équipes"
msgid "round"
msgstr "tour"
#: draw/apps.py:10 draw/consumers.py:1042 tfjm/templates/navbar.html:68
#: draw/apps.py:10 draw/consumers.py:1042 tfjm/templates/navbar.html:66
msgid "Draw"
msgstr "Tirage au sort"
@ -311,7 +311,7 @@ msgstr "Le tirage au sort du tournoi {tournament} va commencer."
#: draw/consumers.py:256 draw/consumers.py:282 draw/consumers.py:692
#: draw/consumers.py:910 draw/consumers.py:1000 draw/consumers.py:1022
#: draw/consumers.py:1125 draw/templates/draw/tournament_content.html:5
#: draw/consumers.py:1124 draw/templates/draw/tournament_content.html:5
msgid "The draw has not started yet."
msgstr "Le tirage au sort n'a pas encore commencé."
@ -446,9 +446,9 @@ msgid ""
"from the ranking of the first round, in order to mix the teams between the "
"two days."
msgstr ""
"Le tirage au sort du tour {round} commence. L'ordre de passage est déterminé à "
"partir du classement du premier tour, afin de mélanger les équipes entre les "
"deux jours."
"Le tirage au sort du tour {round} commence. L'ordre de passage est déterminé "
"à partir du classement du premier tour, afin de mélanger les équipes entre "
"les deux jours."
#: draw/consumers.py:1034
#, python-brace-format
@ -456,8 +456,8 @@ msgid ""
"The draw of the round {round} is starting. The passage order is another time "
"randomly drawn."
msgstr ""
"Le tirage au sort du tour {round} commence. L'ordre de passage est à nouveau tiré "
"au hasard."
"Le tirage au sort du tour {round} commence. L'ordre de passage est à nouveau "
"tiré au hasard."
#: draw/consumers.py:1043
msgid "The draw of the second round is starting!"
@ -1034,18 +1034,18 @@ 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/forms.py:94 participation/forms.py:367
#: registration/forms.py:126 registration/forms.py:148
#: registration/forms.py:170 registration/forms.py:192
#: registration/forms.py:214 registration/forms.py:236
#: registration/forms.py:295 registration/forms.py:328
#: registration/forms.py:141 registration/forms.py:163
#: registration/forms.py:185 registration/forms.py:207
#: registration/forms.py:229 registration/forms.py:251
#: registration/forms.py:310 registration/forms.py:343
msgid "The uploaded file size must be under 2 Mo."
msgstr "Le fichier envoyé doit peser moins de 2 Mo."
#: participation/forms.py:96 registration/forms.py:128
#: registration/forms.py:150 registration/forms.py:172
#: registration/forms.py:194 registration/forms.py:216
#: registration/forms.py:238 registration/forms.py:297
#: registration/forms.py:330
#: participation/forms.py:96 registration/forms.py:143
#: registration/forms.py:165 registration/forms.py:187
#: registration/forms.py:209 registration/forms.py:231
#: registration/forms.py:253 registration/forms.py:312
#: registration/forms.py:345
msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
@ -2509,7 +2509,7 @@ msgid "Edit tournament"
msgstr "Modifier le tournoi"
#: participation/templates/participation/tournament_detail.html:80
#: tfjm/templates/navbar.html:37
#: tfjm/templates/navbar.html:35
msgid "Teams"
msgstr "Équipes"
@ -2652,7 +2652,7 @@ msgid "Warning: non-free format"
msgstr "Attention : format non libre"
#: participation/views.py:62 tfjm/templates/base.html:84
#: tfjm/templates/navbar.html:43
#: tfjm/templates/navbar.html:41
msgid "Create team"
msgstr "Créer une équipe"
@ -2665,7 +2665,7 @@ msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe."
#: participation/views.py:103 tfjm/templates/base.html:79
#: tfjm/templates/navbar.html:48
#: tfjm/templates/navbar.html:46
msgid "Join team"
msgstr "Rejoindre une équipe"
@ -2898,27 +2898,50 @@ msgstr "Marquer comme en attente"
msgid "Mark as invalid"
msgstr "Marquer comme invalide"
#: registration/forms.py:23
#: registration/forms.py:25
msgid "role"
msgstr "rôle"
#: registration/forms.py:25
#: registration/forms.py:27
msgid "participant"
msgstr "participant⋅e"
#: registration/forms.py:26 registration/models.py:516
#: registration/forms.py:28 registration/models.py:516
msgid "coach"
msgstr "encadrant⋅e"
#: registration/forms.py:36 registration/forms.py:61 registration/forms.py:92
#: registration/forms.py:38 registration/forms.py:76 registration/forms.py:107
msgid "This email address is already used."
msgstr "Cette adresse e-mail est déjà utilisée."
#: registration/forms.py:286
#: registration/forms.py:45
#, fuzzy
#| msgid ""
#| "Registrations for the tournament of {tournament} are ending on the {date:"
#| "%Y-%m-%d %H:%M}."
msgid ""
"Registrations are not opened yet. They will open on the {opening_date:%Y-%m-"
"%d %H:%M}."
msgstr ""
"Les inscriptions pour le tournoi de {tournament} se terminent le {date:%d/%m/"
"%Y %H:%M}."
#: registration/forms.py:49
#, fuzzy
#| msgid ""
#| "Registrations for the tournament of {tournament} are ending on the {date:"
#| "%Y-%m-%d %H:%M}."
msgid ""
"Registrations for this year are closed since {opening_date:%Y-%m-%d %H:%M}."
msgstr ""
"Les inscriptions pour le tournoi de {tournament} se terminent le {date:%d/%m/"
"%Y %H:%M}."
#: registration/forms.py:301
msgid "Pending"
msgstr "En attente"
#: registration/forms.py:305 registration/forms.py:338
#: registration/forms.py:320 registration/forms.py:353
msgid "You must upload your receipt."
msgstr "Vous devez envoyer votre justificatif."
@ -3909,15 +3932,37 @@ msgstr ""
#: registration/templates/registration/signup.html:5
#: registration/templates/registration/signup.html:12
#: registration/templates/registration/signup.html:26 registration/views.py:48
#: registration/templates/registration/signup.html:39 registration/views.py:48
msgid "Sign up"
msgstr "Inscription"
#: registration/templates/registration/signup.html:21
#: registration/templates/registration/signup.html:17
msgid ""
"Thank you for your great interest, but registrations are not opened yet!"
msgstr ""
"Merci pour votre grand intérêt, mais les inscriptions ne sont pas encore ouvertes !"
#: registration/templates/registration/signup.html:18
msgid "They will open on:"
msgstr "Elles ouvriront le :"
#: registration/templates/registration/signup.html:19
msgid "Please come back at this time to register!"
msgstr "Merci de revenir à ce moment-là pour vous inscrire !"
#: registration/templates/registration/signup.html:23
msgid "Registrations are closed for this year. We hope to see you next year!"
msgstr "Les inscriptions sont closes pour cette année. Nous espérons vous revoir l'année prochaine !"
#: registration/templates/registration/signup.html:24
msgid "If needed, you can contact us by mail."
msgstr "Si nécessaire, vous pouvez nous contacter par mail."
#: registration/templates/registration/signup.html:34
msgid "By registering, you certify that you have read and accepted our"
msgstr "En vous inscrivant, vous certifiez avoir lu et accepté notre"
#: registration/templates/registration/signup.html:22
#: registration/templates/registration/signup.html:35
msgid "privacy policy"
msgstr "politique de confidentialité"
@ -4274,11 +4319,11 @@ msgstr "Privé, réservé aux utilisateur⋅rices explicitement autorisé⋅es"
msgid "Admin users"
msgstr "Administrateur⋅rices"
#: tfjm/settings.py:173
#: tfjm/settings.py:174
msgid "English"
msgstr "Anglais"
#: tfjm/settings.py:174
#: tfjm/settings.py:175
msgid "French"
msgstr "Français"
@ -4521,39 +4566,39 @@ msgstr ""
"Si vous ne finalisez pas votre inscription avant la date limite indiquée, "
"vous ne pourrez malheureusement pas participer au 𝕋𝔽𝕁𝕄²."
#: tfjm/templates/navbar.html:19 tfjm/urls.py:34
#: tfjm/templates/navbar.html:17 tfjm/urls.py:33
msgid "Home"
msgstr "Accueil"
#: tfjm/templates/navbar.html:24
#: tfjm/templates/navbar.html:22
msgid "Tournament"
msgstr "Tournoi"
#: tfjm/templates/navbar.html:28
#: tfjm/templates/navbar.html:26
msgid "Tournaments"
msgstr "Tournois"
#: tfjm/templates/navbar.html:34
#: tfjm/templates/navbar.html:32
msgid "Users"
msgstr "Utilisateur⋅rices"
#: tfjm/templates/navbar.html:54
#: tfjm/templates/navbar.html:52
msgid "My team"
msgstr "Mon équipe"
#: tfjm/templates/navbar.html:59
#: tfjm/templates/navbar.html:57
msgid "My participation"
msgstr "Ma participation"
#: tfjm/templates/navbar.html:80
#: tfjm/templates/navbar.html:78
msgid "Administration"
msgstr "Administration"
#: tfjm/templates/navbar.html:88
#: tfjm/templates/navbar.html:86
msgid "Search…"
msgstr "Chercher…"
#: tfjm/templates/navbar.html:97
#: tfjm/templates/navbar.html:95
msgid "Return to admin view"
msgstr "Retourner à l'interface administrateur⋅rice"
@ -4561,7 +4606,7 @@ msgstr "Retourner à l'interface administrateur⋅rice"
msgid "Register"
msgstr "S'inscrire"
#: tfjm/templates/navbar.html:118
#: tfjm/templates/navbar.html:119
msgid "My account"
msgstr "Mon compte"

View File

@ -5,16 +5,14 @@ from pathlib import Path
from django.conf import settings
from django.core.management import BaseCommand
from django.utils.translation import activate
from participation.models import Solution, Tournament
class Command(BaseCommand):
def handle(self, *args, **kwargs):
activate(settings.PROBLEMS)
base_dir = Path(__file__).parent.parent.parent.parent
base_dir /= "output"
base_dir /= "solutions"
if not base_dir.is_dir():
base_dir.mkdir()
base_dir /= "Par équipe"

View File

@ -7,6 +7,8 @@ from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.forms import FileInput
from django.utils import timezone
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from .models import CoachRegistration, ParticipantRegistration, Payment, \
@ -36,6 +38,19 @@ class SignupForm(UserCreationForm):
self.add_error("email", _("This email address is already used."))
return email
def clean(self):
# Check that registrations are opened
now = timezone.now()
if now < settings.REGISTRATION_DATES['open']:
self.add_error(None, format_lazy(_("Registrations are not opened yet. "
"They will open on the {opening_date:%Y-%m-%d %H:%M}."),
opening_date=settings.REGISTRATION_DATES['open']))
elif now > settings.REGISTRATION_DATES['close']:
self.add_error(None, format_lazy(_("Registrations for this year are closed since "
"{closing_date:%Y-%m-%d %H:%M}."),
closing_date=settings.REGISTRATION_DATES['close']))
return super().clean()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["first_name"].required = True

View File

@ -0,0 +1,35 @@
# Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path
from django.core.management import BaseCommand
from participation.models import Team
class Command(BaseCommand):
help = """Cette commande permet d'exporter dans le dossier output/photo_authorizations l'ensemble des
autorisations de droit à l'image des participant⋅es, triées par équipe, incluant aussi celles de la finale."""
def handle(self, *args, **kwargs):
base_dir = Path(__file__).parent.parent.parent.parent
base_dir /= "output"
base_dir / "photo_authorizations"
if not base_dir.is_dir():
base_dir.mkdir()
for team in Team.objects.filter(participation__valid=True).all():
team_dir = base_dir / f"{team.trigram} - {team.name}"
if not team_dir.is_dir():
team_dir.mkdir()
for participant in team.participants.all():
if participant.photo_authorization:
with participant.photo_authorization.file as file_input:
with open(team_dir / f"{participant}.pdf", 'wb') as file_output:
file_output.write(file_input.read())
if participant.photo_authorization_final:
with participant.photo_authorization.file as file_input:
with open(team_dir / f"{participant} (finale).pdf", 'wb') as file_output:
file_output.write(file_input.read())

View File

@ -1,7 +1,7 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date, datetime
from datetime import date
from django.conf import settings
from django.contrib.sites.models import Site
@ -774,7 +774,7 @@ class Payment(models.Model):
return checkout_intent
tournament = self.tournament
year = datetime.now().year
year = timezone.now().year
base_site = "https://" + Site.objects.first().domain
checkout_intent = helloasso.create_checkout_intent(
amount=100 * self.amount,

View File

@ -9,30 +9,42 @@
{% endblock %}
{% block content %}
<h2>{% trans "Sign up" %}</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div id="registration_form"></div>
<div class="py-2 text-muted">
<i class="fas fa-info-circle"></i>
{% trans "By registering, you certify that you have read and accepted our" %}
<a href="{% url 'about' %}#politique-confidentialite">{% trans "privacy policy" %}</a>.
{% now "c" as now %}
{% if now < TFJM.REGISTRATION_DATES.open.isoformat %}
<div class="alert alert-warning">
{% trans "Thank you for your great interest, but registrations are not opened yet!" %}
{% trans "They will open on:" %} {{ TFJM.REGISTRATION_DATES.open|date:'DATETIME_FORMAT' }}.
{% trans "Please come back at this time to register!" %}
</div>
<button class="btn btn-success" type="submit">
{% trans "Sign up" %}
</button>
</form>
<div id="student_registration_form" class="d-none">
{{ student_registration_form|crispy }}
</div>
<div id="coach_registration_form" class="d-none">
{{ coach_registration_form|crispy }}
</div>
{% elif now > TFJM.REGISTRATION_DATES.close.isoformat %}
<div class="alert alert-danger">
{% trans "Registrations are closed for this year. We hope to see you next year!" %}
{% trans "If needed, you can contact us by mail." %}
</div>
{% else %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<div id="registration_form"></div>
<div class="py-2 text-muted">
<i class="fas fa-info-circle"></i>
{% trans "By registering, you certify that you have read and accepted our" %}
<a href="{% url 'about' %}#politique-confidentialite">{% trans "privacy policy" %}</a>.
</div>
<button class="btn btn-success" type="submit">
{% trans "Sign up" %}
</button>
</form>
<div id="student_registration_form" class="d-none">
{{ student_registration_form|crispy }}
</div>
<div id="coach_registration_form" class="d-none">
{{ coach_registration_form|crispy }}
</div>
{% endif %}
{% endblock %}
{% block extrajavascript %}

View File

@ -1,14 +1,17 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
import os
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.test import override_settings, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from participation.models import Team
@ -114,6 +117,9 @@ class TestRegistration(TestCase):
self.assertRedirects(response, "http://" + Site.objects.get().domain +
str(self.coach.registration.get_absolute_url()), 302, 200)
# Ensure that we are between registration dates
@override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=1),
'close': timezone.now() + timedelta(days=1)})
def test_registration(self):
"""
Ensure that the signup form is working successfully.
@ -223,6 +229,52 @@ class TestRegistration(TestCase):
response = self.client.get(reverse("registration:email_validation_resend", args=(user.pk,)))
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
def test_registration_dates(self):
"""
Test that registrations are working only between registration dates.
"""
self.client.logout()
# Test that registration between open and close dates are working
with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
'close': timezone.now() + timedelta(days=2)}):
response = self.client.get(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
self.assertIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
self.assertNotIn("registrations are not opened", response.content.decode())
self.assertNotIn("Registrations are closed", response.content.decode())
response = self.client.post(reverse("registration:signup"))
self.assertFormError(response.context['form'], None, [])
# Test that registration before open date is not working
with override_settings(REGISTRATION_DATES={'open': timezone.now() + timedelta(days=1),
'close': timezone.now() + timedelta(days=2)}):
response = self.client.get(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
self.assertIn("registrations are not opened", response.content.decode())
response = self.client.post(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
self.assertFormError(response.context['form'], None,
"Registrations are not opened yet. They will open on the "
f"{settings.REGISTRATION_DATES['open']:%Y-%m-%d %H:%M}.")
# Test that registration after close date is not working
with override_settings(REGISTRATION_DATES={'open': timezone.now() - timedelta(days=2),
'close': timezone.now() - timedelta(days=1)}):
response = self.client.get(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
self.assertNotIn("<i class=\"fas fa-user-plus\"></i> Register", response.content.decode())
self.assertIn("Registrations are closed", response.content.decode())
response = self.client.post(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
self.assertFormError(response.context['form'], None,
"Registrations for this year are closed since "
f"{settings.REGISTRATION_DATES['close']:%Y-%m-%d %H:%M}.")
def test_login(self):
"""
With a registered user, try to log in

View File

@ -18,6 +18,7 @@ def tfjm_context(request):
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
'RECOMMENDED_SOLUTIONS_COUNT': settings.RECOMMENDED_SOLUTIONS_COUNT,
'REGISTRATION_DATES': settings.REGISTRATION_DATES,
'SINGLE_TOURNAMENT': settings.SINGLE_TOURNAMENT,
'HEALTH_SHEET_REQUIRED': settings.HEALTH_SHEET_REQUIRED,
'VACCINE_SHEET_REQUIRED': settings.VACCINE_SHEET_REQUIRED,

View File

@ -13,6 +13,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from datetime import datetime
import os
import sys
@ -365,6 +366,11 @@ if TFJM_APP == "TFJM":
LOGO_FILE = "tfjm.svg"
RULES_LINK = "https://tfjm.org/reglement"
REGISTRATION_DATES = dict(
open=datetime.fromisoformat("2025-01-08T12:00:00+0100"),
close=datetime.fromisoformat("2025-03-02T22:00:00+0100"),
)
PROBLEMS = [
"Triominos",
"Rassemblements mathématiques",
@ -395,6 +401,11 @@ elif TFJM_APP == "ETEAM":
LOGO_FILE = "eteam.png"
RULES_LINK = "https://eteam.tfjm.org/rules/"
REGISTRATION_DATES = dict(
open=datetime.fromisoformat("2024-06-01T12:00:00+0200"),
close=datetime.fromisoformat("2024-07-04T20:00:00+0200"),
)
PROBLEMS = [
"Exploring Flatland",
"A Mazing Hive",

View File

@ -96,9 +96,12 @@
</li>
{% endif %}
{% if not user.is_authenticated %}
<li class="nav-item active">
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a>
</li>
{% now "c" as now %}
{% if TFJM.REGISTRATION_DATES.open.isoformat <= now and now <= TFJM.REGISTRATION_DATES.close.isoformat %}
<li class="nav-item active">
<a class="nav-link" href="{% url "registration:signup" %}"><i class="fas fa-user-plus"></i> {% trans "Register" %}</a>
</li>
{% endif %}
<li class="nav-item active">
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#loginModal">
<i class="fas fa-sign-in-alt"></i> {% trans "Log in" %}