1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2024-11-30 21:26:55 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Emmy D'Anello
85b3da09f6
Add country field in registration
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:52:09 +02:00
Emmy D'Anello
2c15774185
Fix DNS authorization
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:36:05 +02:00
Emmy D'Anello
08ad4f3888
First ETEAM adjustments
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:25:52 +02:00
Emmy D'Anello
872009894d
New index page for ETEAM
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:25:51 +02:00
Emmy D'Anello
fd7fe90fce
Translate index page
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:25:51 +02:00
Emmy D'Anello
2ad538f5cc
Fix tests after moving static files
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-06-07 14:25:37 +02:00
16 changed files with 416 additions and 274 deletions

View File

@ -929,7 +929,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
td.purposed = None td.purposed = None
await td.asave() await td.asave()
remaining = len(settings.PROBLEMS) - 5 - len(td.rejected) remaining = len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)
# Update messages # Update messages
trigram = td.participation.team.trigram trigram = td.participation.team.trigram

View File

@ -0,0 +1,27 @@
# Generated by Django 5.0.6 on 2024-06-07 12:46
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("draw", "0003_alter_teamdraw_options"),
]
operations = [
migrations.AlterField(
model_name="round",
name="number",
field=models.PositiveSmallIntegerField(
choices=[(1, "Round 1"), (2, "Round 2")],
help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(2),
],
verbose_name="number",
),
),
]

View File

@ -147,10 +147,10 @@ class Draw(models.Model):
else: else:
# The problem can be rejected # The problem can be rejected
s += "Elle peut décider d'accepter ou de refuser ce problème. " s += "Elle peut décider d'accepter ou de refuser ce problème. "
if len(td.rejected) >= len(settings.PROBLEMS) - 5: if len(td.rejected) >= len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT:
s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense." s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
else: else:
s += f"Il reste {len(settings.PROBLEMS) - 5 - len(td.rejected)} refus sans pénalité." s += f"Il reste {len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)} refus sans pénalité."
case 'WAITING_FINAL': case 'WAITING_FINAL':
# We are between the two rounds of the final tournament # We are between the two rounds of the final tournament
s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !" s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
@ -193,10 +193,10 @@ class Round(models.Model):
choices=[ choices=[
(1, _('Round 1')), (1, _('Round 1')),
(2, _('Round 2')), (2, _('Round 2')),
], ] + ([] if settings.NB_ROUNDS == 2 else [(3, _('Round 3'))]),
verbose_name=_('number'), verbose_name=_('number'),
help_text=_("The number of the round, 1 or 2"), help_text=_("The number of the round, 1 or 2 (or 3 for ETEAM)"),
validators=[MinValueValidator(1), MaxValueValidator(2)], validators=[MinValueValidator(1), MaxValueValidator(settings.NB_ROUNDS)],
) )
current_pool = models.ForeignKey( current_pool = models.ForeignKey(
@ -524,10 +524,10 @@ class TeamDraw(models.Model):
@property @property
def penalty_int(self): def penalty_int(self):
""" """
The number of penalties, which is the number of rejected problems after the P - 5 free rejects, The number of penalties, which is the number of rejected problems after the P - 5 free rejects
where P is the number of problems. (P - 6 for ETEAM), where P is the number of problems.
""" """
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - 5)) return max(0, len(self.rejected) - (len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT))
@property @property
def penalty(self): def penalty(self):

View File

@ -4,6 +4,9 @@
await Notification.requestPermission() await Notification.requestPermission()
})() })()
// TODO ETEAM Mieux paramétriser (5 pour le TFJM², 6 pour l'ETEAM)
const RECOMMENDED_SOLUTIONS_COUNT = 6
const problems_count = JSON.parse(document.getElementById('problems_count').textContent) const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent) const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
@ -658,15 +661,16 @@ document.addEventListener('DOMContentLoaded', () => {
recapDiv.textContent = `🗑️ ${rejected.join(', ')}` recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`) let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
if (rejected.length > problems_count - 5) { if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender // If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
// This is P - 6 for the ETEAM
if (penaltyDiv === null) { if (penaltyDiv === null) {
penaltyDiv = document.createElement('div') penaltyDiv = document.createElement('div')
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty` penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info') penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
recapDiv.parentNode.append(penaltyDiv) recapDiv.parentNode.append(penaltyDiv)
} }
penaltyDiv.textContent = `${25 * (rejected.length - (problems_count - 5))} %` penaltyDiv.textContent = `${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %`
} else { } else {
// Eventually remove this div // Eventually remove this div
if (penaltyDiv !== null) if (penaltyDiv !== null)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
# Generated by Django 5.0.6 on 2024-06-07 12:46
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("participation", "0013_alter_pool_options_pool_room"),
]
operations = [
migrations.AlterField(
model_name="team",
name="trigram",
field=models.CharField(
help_text="The code must be composed of 3 uppercase letters.",
max_length=3,
unique=True,
validators=[
django.core.validators.RegexValidator("^[A-Z]{3}$"),
django.core.validators.RegexValidator(
"^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
message="This team code is forbidden.",
),
],
verbose_name="code",
),
),
]

View File

@ -1,6 +1,6 @@
# Copyright (C) 2020 by Animath # Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import math
from datetime import date, timedelta from datetime import date, timedelta
import os import os
@ -37,14 +37,15 @@ class Team(models.Model):
) )
trigram = models.CharField( trigram = models.CharField(
max_length=3, max_length=settings.TEAM_CODE_LENGTH,
verbose_name=_("trigram"), verbose_name=_("code"),
help_text=_("The trigram must be composed of three uppercase letters."), help_text=format_lazy(_("The code must be composed of {nb_letters} uppercase letters."),
nb_letters=settings.TEAM_CODE_LENGTH),
unique=True, unique=True,
validators=[ validators=[
RegexValidator(r"^[A-Z]{3}$"), RegexValidator("^[A-Z]{" + str(settings.TEAM_CODE_LENGTH) + "}$"),
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})", RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
message=_("This trigram is forbidden.")), message=_("This team code is forbidden.")),
], ],
) )
@ -940,7 +941,7 @@ class Pool(models.Model):
choices=[ choices=[
(1, format_lazy(_("Round {round}"), round=1)), (1, format_lazy(_("Round {round}"), round=1)),
(2, format_lazy(_("Round {round}"), round=2)), (2, format_lazy(_("Round {round}"), round=2)),
] ] + ([] if settings.NB_ROUNDS == 2 else [(3, format_lazy(_("Round {round}"), round=3))]),
) )
letter = models.PositiveSmallIntegerField( letter = models.PositiveSmallIntegerField(
@ -1010,12 +1011,16 @@ class Pool(models.Model):
def solutions(self): def solutions(self):
return [passage.defended_solution for passage in self.passages.all()] return [passage.defended_solution for passage in self.passages.all()]
@property
def coeff(self):
return 1 if self.round <= 2 else math.pi - 2
def average(self, participation): def average(self, participation):
return sum(passage.average(participation) for passage in self.passages.all()) \ return self.coeff * sum(passage.average(participation) for passage in self.passages.all()) \
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all()) + sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
async def aaverage(self, participation): async def aaverage(self, participation):
return sum([passage.average(participation) async for passage in self.passages.all()]) \ return self.coeff * sum([passage.average(participation) async for passage in self.passages.all()]) \
+ sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()]) + sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()])
def get_absolute_url(self): def get_absolute_url(self):

View File

@ -674,7 +674,7 @@ class TestPayment(TestCase):
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
data={'type': "bank_transfer", data={'type': "bank_transfer",
'additional_information': "This is a bank transfer", 'additional_information': "This is a bank transfer",
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")}) 'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
payment.refresh_from_db() payment.refresh_from_db()
self.assertIsNone(payment.valid) self.assertIsNone(payment.valid)
@ -735,7 +735,7 @@ class TestPayment(TestCase):
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
data={'type': "scholarship", data={'type': "scholarship",
'additional_information': "I don't have to pay because I have a scholarship", 'additional_information': "I don't have to pay because I have a scholarship",
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")}) 'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
payment.refresh_from_db() payment.refresh_from_db()
self.assertIsNone(payment.valid) self.assertIsNone(payment.valid)

View File

@ -711,6 +711,7 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
'Adresse': registration.address, 'Adresse': registration.address,
'Code postal': registration.zip_code, 'Code postal': registration.zip_code,
'Ville': registration.city, 'Ville': registration.city,
'Pays': registration.country,
'Téléphone': registration.phone_number, 'Téléphone': registration.phone_number,
'Classe': registration.get_student_class_display() if registration.is_student 'Classe': registration.get_student_class_display() if registration.is_student
else registration.last_degree, else registration.last_degree,

View File

@ -106,9 +106,10 @@ class StudentRegistrationForm(forms.ModelForm):
class Meta: class Meta:
model = StudentRegistration model = StudentRegistration
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number', fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'country',
'school', 'health_issues', 'housing_constraints', 'responsible_name', 'responsible_phone', 'phone_number', 'school', 'health_issues', 'housing_constraints',
'responsible_email', 'give_contact_to_animath', 'email_confirmed',) 'responsible_name', 'responsible_phone', 'responsible_email', 'give_contact_to_animath',
'email_confirmed',)
class PhotoAuthorizationForm(forms.ModelForm): class PhotoAuthorizationForm(forms.ModelForm):
@ -249,7 +250,7 @@ class CoachRegistrationForm(forms.ModelForm):
""" """
class Meta: class Meta:
model = CoachRegistration model = CoachRegistration
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number', fields = ('team', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints', 'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
'give_contact_to_animath', 'email_confirmed',) 'give_contact_to_animath', 'email_confirmed',)

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.6 on 2024-06-07 12:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"registration",
"0013_participantregistration_photo_authorization_final_and_more",
),
]
operations = [
migrations.AddField(
model_name="participantregistration",
name="country",
field=models.CharField(
default="France", max_length=255, verbose_name="country"
),
),
]

View File

@ -183,6 +183,12 @@ class ParticipantRegistration(Registration):
verbose_name=_("city"), verbose_name=_("city"),
) )
country = models.CharField(
max_length=255,
verbose_name=_("country"),
default="France",
)
phone_number = PhoneNumberField( phone_number = PhoneNumberField(
verbose_name=_("phone number"), verbose_name=_("phone number"),
blank=True, blank=True,

View File

@ -48,7 +48,10 @@
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd> <dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
<dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt> <dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt>
<dd class="col-sm-6">{{ user_object.registration.address }}, {{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }}</dd> <dd class="col-sm-6">
{{ user_object.registration.address }},
{{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }} ({{ user_object.registration.country }})
</dd>
<dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt> <dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt>
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd> <dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>

View File

@ -333,7 +333,7 @@ class TestRegistration(TestCase):
response = self.client.post(reverse(f"registration:upload_user_{auth_type}", response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
args=(self.student.registration.pk,)), data={ args=(self.student.registration.pk,)), data={
auth_type: open("tfjm/static/Fiche_sanitaire.pdf", "rb"), auth_type: open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
}) })
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200) self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
@ -356,7 +356,7 @@ class TestRegistration(TestCase):
old_authoratization = self.student.registration.photo_authorization.path old_authoratization = self.student.registration.photo_authorization.path
response = self.client.post(reverse("registration:upload_user_photo_authorization", response = self.client.post(reverse("registration:upload_user_photo_authorization",
args=(self.student.registration.pk,)), data=dict( args=(self.student.registration.pk,)), data=dict(
photo_authorization=open("tfjm/static/Fiche_sanitaire.pdf", "rb"), photo_authorization=open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
)) ))
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200) self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
self.assertFalse(os.path.isfile(old_authoratization)) self.assertFalse(os.path.isfile(old_authoratization))

View File

@ -340,16 +340,6 @@ GOOGLE_SERVICE_CLIENT = {
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS") NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
# Custom parameters # Custom parameters
PROBLEMS = [
"Triominos",
"Rassemblements mathématiques",
"Tournoi de ping-pong",
"Dépollution de la Seine",
"Électron libre",
"Pièces truquées",
"Drôles de cookies",
"Création d'un jeu",
]
FORBIDDEN_TRIGRAMS = [ FORBIDDEN_TRIGRAMS = [
"BIT", "BIT",
"CNO", "CNO",
@ -379,3 +369,38 @@ try:
from .settings_local import * # noqa: F401,F403 from .settings_local import * # noqa: F401,F403
except ImportError: except ImportError:
pass pass
if TFJM_APP == "TFJM":
TEAM_CODE_LENGTH = 3
RECOMMENDED_SOLUTIONS_COUNT = 5
NB_ROUNDS = 2
PROBLEMS = [
"Triominos",
"Rassemblements mathématiques",
"Tournoi de ping-pong",
"Dépollution de la Seine",
"Électron libre",
"Pièces truquées",
"Drôles de cookies",
"Création d'un jeu",
]
elif TFJM_APP == "ETEAM":
TEAM_CODE_LENGTH = 4
RECOMMENDED_SOLUTIONS_COUNT = 6
NB_ROUNDS = 3
PROBLEMS = [
"Exploring Flatland",
"A Mazing Hive",
"Coin tossing",
"The rainbow bridge",
"Arithmetic and shopping",
"A fence for the goats",
"Generalized Tic-Tac-Toe",
"Polyhedral construction",
"Landing a probe",
"Catching the rabbit",
]
else:
raise ValueError(f"Unknown app: {TFJM_APP}")

View File

@ -7,7 +7,9 @@ import os
DEBUG = False DEBUG = False
# Mandatory ! # Mandatory !
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org'] # TODO ETEAM Meilleur support, et meilleurs DNS surtout
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org',
'register.eteam.tfjm.org', 'registration.eteam.tfjm.org', 'platform.eteam.tfjm.org']
# Emails # Emails
EMAIL_BACKEND = 'mailer.backend.DbBackend' EMAIL_BACKEND = 'mailer.backend.DbBackend'