Compare commits
17 Commits
7f8934a647
...
0f2c44331c
Author | SHA1 | Date | |
---|---|---|---|
|
0f2c44331c | ||
|
fae4ee7105 | ||
|
600ebd087e | ||
|
4a39d206d5 | ||
|
2faade0156 | ||
|
e17273391d | ||
|
0e7be7e27c | ||
|
b95b41a2ed | ||
|
444bea2440 | ||
|
7bb4e2c8eb | ||
|
0f176ea4c6 | ||
|
63a10c1be5 | ||
|
f7eddd289b | ||
|
6b4553b76b | ||
|
ccfd2c155b | ||
|
814cb10439 | ||
|
df8f6cff2b |
@ -1,4 +1,4 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-21 21:06
|
||||
# Generated by Django 3.2.13 on 2023-01-10 19:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -26,7 +26,7 @@ class Migration(migrations.Migration):
|
||||
('data', models.TextField(blank=True, default='', verbose_name='new data')),
|
||||
('action', models.CharField(choices=[('create', 'create'), ('edit', 'edit'), ('delete', 'delete')], default='edit', max_length=16, verbose_name='action')),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='timestamp')),
|
||||
('model', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='model')),
|
||||
('model', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype', verbose_name='model')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||
],
|
||||
options={
|
||||
|
@ -6,14 +6,12 @@ from io import StringIO
|
||||
import re
|
||||
from typing import Iterable
|
||||
|
||||
from bootstrap_datepicker_plus.widgets import DatePickerInput, DateTimePickerInput
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.utils import formats
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from PyPDF3 import PdfFileReader
|
||||
from pypdf import PdfFileReader
|
||||
from registration.models import VolunteerRegistration
|
||||
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||
@ -125,22 +123,20 @@ class TournamentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields["date_start"].widget = DatePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATE_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["date_end"].widget = DatePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATE_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["inscription_limit"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["solution_limit"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["solutions_draw"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["syntheses_first_phase_limit"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["solutions_available_second_phase"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["syntheses_second_phase_limit"].widget = DateTimePickerInput(
|
||||
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
|
||||
self.fields["date_start"].widget = forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d')
|
||||
self.fields["date_end"].widget = forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d')
|
||||
self.fields["inscription_limit"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["solution_limit"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["solutions_draw"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["syntheses_first_phase_limit"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["solutions_available_second_phase"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["syntheses_second_phase_limit"].widget = forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M')
|
||||
self.fields["organizers"].widget = forms.CheckboxSelectMultiple()
|
||||
self.fields["organizers"].queryset = VolunteerRegistration.objects.all()
|
||||
|
||||
|
@ -8,7 +8,7 @@ from django.core.management import BaseCommand
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import activate
|
||||
from participation.models import Team, Tournament
|
||||
from registration.models import AdminRegistration, Registration, VolunteerRegistration
|
||||
from registration.models import Registration, VolunteerRegistration
|
||||
from tfjm.matrix import Matrix, RoomPreset, RoomVisibility
|
||||
|
||||
|
||||
@ -163,7 +163,7 @@ class Command(BaseCommand):
|
||||
self.stdout.write(f"Invite {volunteer} in #aide-jury-orgas...")
|
||||
|
||||
# Admins are admins
|
||||
for admin in AdminRegistration.objects.all():
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
self.stdout.write(f"Invite {admin} in #cno and #dev-bot...")
|
||||
await Matrix.invite("#cno:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite("#dev-bot:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
@ -264,7 +264,7 @@ class Command(BaseCommand):
|
||||
await Matrix.set_room_avatar(f"#tirage-au-sort-{slug}:tfjm.org", avatar_uri)
|
||||
|
||||
# Invite admins and give permissions
|
||||
for admin in AdminRegistration.objects.all():
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
self.stdout.write(f"Invite {admin} in all channels of the tournament {name}...")
|
||||
await Matrix.invite(f"#annonces-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#flood-{slug}:tfjm.org", f"@{admin.matrix_username}:tfjm.org")
|
||||
@ -374,7 +374,7 @@ class Command(BaseCommand):
|
||||
"customwidget", "Tableau", str(pool))
|
||||
|
||||
# Invite admins and give permissions
|
||||
for admin in AdminRegistration.objects.all():
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}:tfjm.org",
|
||||
f"@{admin.matrix_username}:tfjm.org")
|
||||
await Matrix.invite(f"#poule-{slug}-{pool.id}{suffix}-jurys:tfjm.org",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
from participation.models import Team, Tournament
|
||||
from registration.models import AdminRegistration, ParticipantRegistration, VolunteerRegistration
|
||||
from registration.models import ParticipantRegistration, VolunteerRegistration
|
||||
from tfjm.lists import get_sympa_client
|
||||
|
||||
|
||||
@ -71,5 +71,5 @@ class Command(BaseCommand):
|
||||
slug = jury_in.tournament.name.lower().replace(" ", "-")
|
||||
sympa.subscribe(volunteer.user.email, f"jurys-{slug}", True)
|
||||
|
||||
for admin in AdminRegistration.objects.all():
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
sympa.subscribe(admin.user.email, "admins", True)
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-22 18:26
|
||||
# Generated by Django 3.2.13 on 2023-01-10 19:22
|
||||
|
||||
import datetime
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import participation.models
|
||||
|
||||
@ -47,8 +48,8 @@ class Migration(migrations.Migration):
|
||||
name='Passage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('place', models.CharField(default='Non indiqué', help_text='Where the solution is presented?', max_length=255, verbose_name='place')),
|
||||
('solution_number', models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], verbose_name='defended solution')),
|
||||
('defender_penalties', models.PositiveSmallIntegerField(default=0, help_text='Number of penalties for the defender. The defender will loose a 0.5 coefficient per penalty.', verbose_name='penalties')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'passage',
|
||||
@ -61,6 +62,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('round', models.PositiveSmallIntegerField(choices=[(1, 'Round 1'), (2, 'Round 2')], verbose_name='round')),
|
||||
('bbb_url', models.CharField(blank=True, default='', help_text='The link of the BBB visio for this pool.', max_length=255, verbose_name='BigBlueButton URL')),
|
||||
('results_available', models.BooleanField(default=False, help_text='Check this case when results become accessible to teams. They stay accessible to you. Only averages are given.', verbose_name='results available')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'pool',
|
||||
@ -73,11 +75,12 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('problem', models.PositiveSmallIntegerField(choices=[(1, 'Problem #1'), (2, 'Problem #2'), (3, 'Problem #3'), (4, 'Problem #4'), (5, 'Problem #5'), (6, 'Problem #6'), (7, 'Problem #7'), (8, 'Problem #8')], verbose_name='problem')),
|
||||
('final_solution', models.BooleanField(default=False, verbose_name='solution for the final tournament')),
|
||||
('file', models.FileField(blank=True, default='', unique=True, upload_to=participation.models.get_solution_filename, verbose_name='file')),
|
||||
('file', models.FileField(unique=True, upload_to=participation.models.get_solution_filename, verbose_name='file')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'solution',
|
||||
'verbose_name_plural': 'solutions',
|
||||
'ordering': ('participation__team__trigram', 'final_solution', 'problem'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -85,11 +88,12 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.PositiveSmallIntegerField(choices=[(1, 'opponent'), (2, 'reporter')])),
|
||||
('file', models.FileField(blank=True, default='', unique=True, upload_to=participation.models.get_synthesis_filename, verbose_name='file')),
|
||||
('file', models.FileField(unique=True, upload_to=participation.models.get_synthesis_filename, verbose_name='file')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'synthesis',
|
||||
'verbose_name_plural': 'syntheses',
|
||||
'ordering': ('passage__pool__round', 'type'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -113,8 +117,10 @@ class Migration(migrations.Migration):
|
||||
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
|
||||
('date_start', models.DateField(default=datetime.date.today, verbose_name='start')),
|
||||
('date_end', models.DateField(default=datetime.date.today, verbose_name='end')),
|
||||
('place', models.CharField(max_length=255, verbose_name='place')),
|
||||
('max_teams', models.PositiveSmallIntegerField(default=9, verbose_name='max team count')),
|
||||
('price', models.PositiveSmallIntegerField(default=21, verbose_name='price')),
|
||||
('remote', models.BooleanField(default=False, verbose_name='remote')),
|
||||
('inscription_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date for registrations')),
|
||||
('solution_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date to upload solutions')),
|
||||
('solutions_draw', models.DateTimeField(default=django.utils.timezone.now, verbose_name='random draw for solutions')),
|
||||
@ -129,4 +135,17 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'tournaments',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tweak',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('diff', models.IntegerField(help_text='Score to add/remove on the final score', verbose_name='difference')),
|
||||
('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tweaks', to='participation.participation', verbose_name='participation')),
|
||||
('pool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.pool', verbose_name='passage')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'tweak',
|
||||
'verbose_name_plural': 'tweaks',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-22 18:26
|
||||
# Generated by Django 3.2.13 on 2023-01-10 19:22
|
||||
|
||||
import address.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -10,9 +9,8 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('registration', '0001_initial'),
|
||||
('address', '0003_auto_20200830_1851'),
|
||||
('participation', '0001_initial'),
|
||||
('registration', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -21,11 +19,6 @@ class Migration(migrations.Migration):
|
||||
name='organizers',
|
||||
field=models.ManyToManyField(related_name='organized_tournaments', to='registration.VolunteerRegistration', verbose_name='organizers'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tournament',
|
||||
name='place',
|
||||
field=address.models.AddressField(on_delete=django.db.models.deletion.CASCADE, to='address.Address', verbose_name='place'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='team',
|
||||
index=models.Index(fields=['trigram'], name='participati_trigram_239255_idx'),
|
||||
@ -33,17 +26,17 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='synthesis',
|
||||
name='participation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.Participation', verbose_name='participation'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.participation', verbose_name='participation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='synthesis',
|
||||
name='passage',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syntheses', to='participation.Passage', verbose_name='passage'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syntheses', to='participation.passage', verbose_name='passage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='solution',
|
||||
name='participation',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='participation.Participation', verbose_name='participation'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='participation.participation', verbose_name='participation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pool',
|
||||
@ -58,47 +51,47 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='pool',
|
||||
name='tournament',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pools', to='participation.Tournament', verbose_name='tournament'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pools', to='participation.tournament', verbose_name='tournament'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='passage',
|
||||
name='defender',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.Participation', verbose_name='defender'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.participation', verbose_name='defender'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='passage',
|
||||
name='opponent',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.Participation', verbose_name='opponent'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.participation', verbose_name='opponent'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='passage',
|
||||
name='pool',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='passages', to='participation.Pool', verbose_name='pool'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='passages', to='participation.pool', verbose_name='pool'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='passage',
|
||||
name='reporter',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.Participation', verbose_name='reporter'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='participation.participation', verbose_name='reporter'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participation',
|
||||
name='team',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='participation.Team', verbose_name='team'),
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='participation.team', verbose_name='team'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participation',
|
||||
name='tournament',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='participation.Tournament', verbose_name='tournament'),
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='participation.tournament', verbose_name='tournament'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='note',
|
||||
name='jury',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='registration.VolunteerRegistration', verbose_name='jury'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='registration.volunteerregistration', verbose_name='jury'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='note',
|
||||
name='passage',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='participation.Passage', verbose_name='passage'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='participation.passage', verbose_name='passage'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tournament',
|
19
apps/participation/migrations/0003_alter_team_trigram.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.18 on 2023-02-19 22:13
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='trigram',
|
||||
field=models.CharField(help_text='The trigram must be composed of three 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 trigram is forbidden.')], verbose_name='trigram'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-23 18:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0002_auto_20210122_1926'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tournament',
|
||||
name='remote',
|
||||
field=models.BooleanField(default=False, verbose_name='remote'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.1.7 on 2021-04-03 19:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0003_tournament_remote'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='passage',
|
||||
name='defender_penalties',
|
||||
field=models.PositiveSmallIntegerField(default=0, help_text='Number of penalties for the defender. The defender will loose a 0.5 coefficient per penalty.', verbose_name='penalties'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.1.7 on 2021-04-10 07:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0004_passage_defender_penalties'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='pool',
|
||||
name='results_available',
|
||||
field=models.BooleanField(default=False, help_text='Check this case when results become accessible to teams. They stay accessible to you. Only averages are given.', verbose_name='results available'),
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-04-26 11:46
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0005_pool_results_available'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='solution',
|
||||
options={'ordering': ('participation__team__trigram', 'final_solution', 'problem'), 'verbose_name': 'solution', 'verbose_name_plural': 'solutions'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='synthesis',
|
||||
options={'ordering': ('passage__pool__round', 'type'), 'verbose_name': 'synthesis', 'verbose_name_plural': 'syntheses'},
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-04-26 19:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0006_auto_20220426_1346'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='passage',
|
||||
name='place',
|
||||
),
|
||||
]
|
@ -1,24 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-04-29 16:53
|
||||
|
||||
from django.db import migrations, models
|
||||
import participation.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0007_remove_passage_place'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='solution',
|
||||
name='file',
|
||||
field=models.FileField(unique=True, upload_to=participation.models.get_solution_filename, verbose_name='file'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='synthesis',
|
||||
name='file',
|
||||
field=models.FileField(unique=True, upload_to=participation.models.get_synthesis_filename, verbose_name='file'),
|
||||
),
|
||||
]
|
@ -1,27 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-05-15 14:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('participation', '0008_auto_20220429_1853'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tweak',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('diff', models.IntegerField(help_text='Score to add/remove on the final score', verbose_name='difference')),
|
||||
('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tweaks', to='participation.participation', verbose_name='participation')),
|
||||
('pool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.pool', verbose_name='passage')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'tweak',
|
||||
'verbose_name_plural': 'tweaks',
|
||||
},
|
||||
),
|
||||
]
|
@ -4,7 +4,6 @@
|
||||
from datetime import date
|
||||
import os
|
||||
|
||||
from address.models import AddressField
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator
|
||||
@ -40,7 +39,11 @@ class Team(models.Model):
|
||||
verbose_name=_("trigram"),
|
||||
help_text=_("The trigram must be composed of three uppercase letters."),
|
||||
unique=True,
|
||||
validators=[RegexValidator("[A-Z]{3}")],
|
||||
validators=[
|
||||
RegexValidator(r"^[A-Z]{3}$"),
|
||||
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
|
||||
message=_("This trigram is forbidden.")),
|
||||
],
|
||||
)
|
||||
|
||||
access_code = models.CharField(
|
||||
@ -143,7 +146,8 @@ class Tournament(models.Model):
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
place = AddressField(
|
||||
place = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("place"),
|
||||
)
|
||||
|
||||
|
@ -74,6 +74,19 @@
|
||||
{% endfor %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Vaccine sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.vaccine_sheet %}
|
||||
<a href="{{ student.vaccine_sheet.url }}" data-turbolinks="false">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Parental authorizations:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
|
@ -29,6 +29,9 @@ class TestStudentParticipation(TestCase):
|
||||
StudentRegistration.objects.create(
|
||||
user=self.user,
|
||||
student_class=12,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
school="Earth",
|
||||
give_contact_to_animath=True,
|
||||
email_confirmed=True,
|
||||
@ -49,6 +52,9 @@ class TestStudentParticipation(TestCase):
|
||||
StudentRegistration.objects.create(
|
||||
user=self.second_user,
|
||||
student_class=11,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
school="Moon",
|
||||
give_contact_to_animath=True,
|
||||
email_confirmed=True,
|
||||
@ -65,7 +71,12 @@ class TestStudentParticipation(TestCase):
|
||||
email="coach@example.com",
|
||||
password="coach",
|
||||
)
|
||||
CoachRegistration.objects.create(user=self.coach)
|
||||
CoachRegistration.objects.create(
|
||||
user=self.coach,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
)
|
||||
|
||||
self.tournament = Tournament.objects.create(
|
||||
name="France",
|
||||
@ -214,8 +225,12 @@ class TestStudentParticipation(TestCase):
|
||||
give_contact_to_animath=True,
|
||||
email_confirmed=True,
|
||||
team=self.team,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
photo_authorization="authorization/photo/mai-linh",
|
||||
health_sheet="authorization/health/mai-linh",
|
||||
vaccine_sheet="authorization/vaccine/mai-linh",
|
||||
parental_authorization="authorization/parental/mai-linh",
|
||||
)
|
||||
|
||||
@ -232,9 +247,13 @@ class TestStudentParticipation(TestCase):
|
||||
give_contact_to_animath=False,
|
||||
email_confirmed=True,
|
||||
team=self.team,
|
||||
photo_authorization="authorization/photo/yohann",
|
||||
health_sheet="authorization/health/yohann",
|
||||
parental_authorization="authorization/parental/yohann",
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
photo_authorization="authorization/photo/emmy",
|
||||
health_sheet="authorization/health/emmy",
|
||||
vaccine_sheet="authorization/vaccine/emmy",
|
||||
parental_authorization="authorization/parental/emmy",
|
||||
)
|
||||
|
||||
fourth_user = User.objects.create(
|
||||
@ -250,13 +269,18 @@ class TestStudentParticipation(TestCase):
|
||||
give_contact_to_animath=False,
|
||||
email_confirmed=True,
|
||||
team=self.team,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
photo_authorization="authorization/photo/tfjm",
|
||||
health_sheet="authorization/health/tfjm",
|
||||
vaccine_sheet="authorization/health/tfjm",
|
||||
parental_authorization="authorization/parental/tfjm",
|
||||
)
|
||||
|
||||
self.coach.registration.team = self.team
|
||||
self.coach.registration.health_sheet = "authorization/health/coach"
|
||||
self.coach.registration.vaccine_sheet = "authorization/vaccine/coach"
|
||||
self.coach.registration.photo_authorization = "authorization/photo/coach"
|
||||
self.coach.registration.email_confirmed = True
|
||||
self.coach.registration.save()
|
||||
@ -285,6 +309,7 @@ class TestStudentParticipation(TestCase):
|
||||
|
||||
self.user.registration.photo_authorization = "authorization/photo/ananas"
|
||||
self.user.registration.health_sheet = "authorization/health/ananas"
|
||||
self.user.registration.vaccine_sheet = "authorization/health/ananas"
|
||||
self.user.registration.parental_authorization = "authorization/parental/ananas"
|
||||
self.user.registration.save()
|
||||
|
||||
|
@ -180,6 +180,7 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
|
||||
context["validation_form"] = ValidateParticipationForm(self.request.POST or None)
|
||||
# A team is complete when there are at least 4 members plus a coache that have sent their authorizations,
|
||||
# their health sheet, they confirmed their email address and under-18 people sent their parental authorization.
|
||||
# TODO: Add vaccine sheets
|
||||
context["can_validate"] = team.students.count() >= 4 and team.coaches.exists() and \
|
||||
team.participation.tournament and \
|
||||
all(r.photo_authorization for r in team.participants.all()) and \
|
||||
@ -305,12 +306,16 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["participation_form"] = ParticipationForm(data=self.request.POST or None,
|
||||
instance=self.object.participation)
|
||||
if not self.request.user.registration.is_volunteer:
|
||||
del context["participation_form"].fields['final']
|
||||
context["title"] = _("Update team {trigram}").format(trigram=self.object.trigram)
|
||||
return context
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
participation_form = ParticipationForm(data=self.request.POST or None, instance=self.object.participation)
|
||||
if not self.request.user.registration.is_volunteer:
|
||||
del participation_form.fields['final']
|
||||
if not participation_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
@ -410,6 +415,12 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
|
||||
zf.write("media/" + participant.health_sheet.name,
|
||||
_("Health sheet of {participant}.{ext}").format(participant=str(participant), ext=ext))
|
||||
|
||||
if isinstance(participant, StudentRegistration) and participant.vaccine_sheet:
|
||||
mime_type = magic.from_file("media/" + participant.vaccine_sheet.name)
|
||||
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
||||
zf.write("media/" + participant.vaccine_sheet.name,
|
||||
_("Vaccine sheet of {participant}.{ext}").format(participant=str(participant), ext=ext))
|
||||
|
||||
if team.motivation_letter:
|
||||
mime_type = magic.from_file("media/" + team.motivation_letter.name)
|
||||
ext = mime_type.split("/")[1].replace("jpeg", "jpg")
|
||||
|
@ -5,12 +5,12 @@ from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin
|
||||
|
||||
from .models import AdminRegistration, CoachRegistration, Payment, Registration, StudentRegistration
|
||||
from .models import CoachRegistration, Payment, Registration, StudentRegistration, VolunteerRegistration
|
||||
|
||||
|
||||
@admin.register(Registration)
|
||||
class RegistrationAdmin(PolymorphicParentModelAdmin):
|
||||
child_models = (StudentRegistration, CoachRegistration, AdminRegistration,)
|
||||
child_models = (StudentRegistration, CoachRegistration, VolunteerRegistration,)
|
||||
list_display = ("user", "type", "email_confirmed",)
|
||||
polymorphic_list = True
|
||||
|
||||
@ -25,8 +25,8 @@ class CoachRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(AdminRegistration)
|
||||
class AdminRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||
@admin.register(VolunteerRegistration)
|
||||
class VolunteerRegistrationAdmin(PolymorphicChildModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -4,16 +4,10 @@
|
||||
from rest_framework import serializers
|
||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||
|
||||
from ..models import AdminRegistration, CoachRegistration, ParticipantRegistration, \
|
||||
from ..models import CoachRegistration, ParticipantRegistration, \
|
||||
StudentRegistration, VolunteerRegistration
|
||||
|
||||
|
||||
class AdminSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AdminRegistration
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CoachSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CoachRegistration
|
||||
@ -40,7 +34,6 @@ class VolunteerSerializer(serializers.ModelSerializer):
|
||||
|
||||
class RegistrationSerializer(PolymorphicSerializer):
|
||||
model_serializer_mapping = {
|
||||
AdminRegistration: AdminSerializer,
|
||||
CoachRegistration: CoachSerializer,
|
||||
StudentRegistration: StudentSerializer,
|
||||
VolunteerRegistration: VolunteerSerializer,
|
||||
|
@ -20,4 +20,3 @@ class RegistrationConfig(AppConfig):
|
||||
post_save.connect(create_payment, "registration.Registration")
|
||||
post_save.connect(create_payment, "registration.StudentRegistration")
|
||||
post_save.connect(create_payment, "registration.CoachRegistration")
|
||||
post_save.connect(create_payment, "registration.AdminRegistration")
|
||||
|
@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.forms import FileInput
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import AdminRegistration, CoachRegistration, ParticipantRegistration, Payment, \
|
||||
from .models import CoachRegistration, ParticipantRegistration, Payment, \
|
||||
StudentRegistration, VolunteerRegistration
|
||||
|
||||
|
||||
@ -50,14 +50,6 @@ class AddOrganizerForm(forms.ModelForm):
|
||||
"""
|
||||
Signup form to registers volunteers
|
||||
"""
|
||||
type = forms.ChoiceField(
|
||||
label=lambda: _("role").capitalize(),
|
||||
choices=lambda: [
|
||||
("volunteer", _("volunteer").capitalize()),
|
||||
("admin", _("admin").capitalize()),
|
||||
],
|
||||
initial="volunteer",
|
||||
)
|
||||
|
||||
def clean_email(self):
|
||||
"""
|
||||
@ -76,7 +68,7 @@ class AddOrganizerForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name', 'email', 'type',)
|
||||
fields = ('first_name', 'last_name', 'email',)
|
||||
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
@ -99,10 +91,14 @@ class StudentRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
A student can update its class, its school and if it allows Animath to contact him/her later.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["birth_date"].widget = forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d')
|
||||
|
||||
class Meta:
|
||||
model = StudentRegistration
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'phone_number', 'health_issues',
|
||||
'school', 'responsible_name', 'responsible_phone', 'responsible_email',
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'health_issues', 'school', 'responsible_name', 'responsible_phone', 'responsible_email',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
@ -150,6 +146,28 @@ class HealthSheetForm(forms.ModelForm):
|
||||
fields = ('health_sheet',)
|
||||
|
||||
|
||||
class VaccineSheetForm(forms.ModelForm):
|
||||
"""
|
||||
Form to send a vaccine sheet.
|
||||
"""
|
||||
def clean_vaccine_sheet(self):
|
||||
if "vaccine_sheet" in self.files:
|
||||
file = self.files["vaccine_sheet"]
|
||||
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["vaccine_sheet"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["vaccine_sheet"].widget = FileInput()
|
||||
|
||||
class Meta:
|
||||
model = StudentRegistration
|
||||
fields = ('vaccine_sheet',)
|
||||
|
||||
|
||||
class ParentalAuthorizationForm(forms.ModelForm):
|
||||
"""
|
||||
Form to send a parental authorization.
|
||||
@ -178,8 +196,8 @@ class CoachRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = CoachRegistration
|
||||
fields = ('team', 'birth_date', 'gender', 'address', 'phone_number', 'health_issues', 'professional_activity',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number', 'health_issues',
|
||||
'professional_activity', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class VolunteerRegistrationForm(forms.ModelForm):
|
||||
@ -188,16 +206,7 @@ class VolunteerRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
class Meta:
|
||||
model = VolunteerRegistration
|
||||
fields = ('professional_activity', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class AdminRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
Admins can tell everything they want.
|
||||
"""
|
||||
class Meta:
|
||||
model = AdminRegistration
|
||||
fields = ('role', 'give_contact_to_animath', 'email_confirmed',)
|
||||
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
|
||||
class PaymentForm(forms.ModelForm):
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-22 18:26
|
||||
# Generated by Django 3.2.13 on 2023-01-10 19:22
|
||||
|
||||
import address.models
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import phonenumber_field.modelfields
|
||||
@ -14,10 +14,9 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('address', '0003_auto_20200830_1851'),
|
||||
('participation', '0001_initial'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -27,7 +26,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('give_contact_to_animath', models.BooleanField(default=False, verbose_name='Grant Animath to contact me in the future about other actions')),
|
||||
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
|
||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_registration.registration_set+', to='contenttypes.ContentType')),
|
||||
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_registration.registration_set+', to='contenttypes.contenttype')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||
],
|
||||
options={
|
||||
@ -38,13 +37,16 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='ParticipantRegistration',
|
||||
fields=[
|
||||
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.Registration')),
|
||||
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
|
||||
('birth_date', models.DateField(default=datetime.date.today, verbose_name='birth date')),
|
||||
('gender', models.CharField(choices=[('female', 'Female'), ('male', 'Male'), ('other', 'Other')], default='other', max_length=6, verbose_name='gender')),
|
||||
('address', models.CharField(max_length=255, verbose_name='address')),
|
||||
('zip_code', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1000), django.core.validators.MaxValueValidator(99999)], verbose_name='zip code')),
|
||||
('city', models.CharField(max_length=255, verbose_name='city')),
|
||||
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None, verbose_name='phone number')),
|
||||
('health_issues', models.TextField(blank=True, help_text='You can indicate here your allergies or anything that is important to know for organizers', verbose_name='health issues')),
|
||||
('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_photo_filename, verbose_name='photo authorization')),
|
||||
('address', address.models.AddressField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='address.Address', verbose_name='address')),
|
||||
('team', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='participants', to='participation.Team', verbose_name='team')),
|
||||
('team', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='participants', to='participation.team', verbose_name='team')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
@ -55,7 +57,7 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='VolunteerRegistration',
|
||||
fields=[
|
||||
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.Registration')),
|
||||
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
|
||||
('professional_activity', models.TextField(verbose_name='professional activity')),
|
||||
],
|
||||
options={
|
||||
@ -67,7 +69,7 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='AdminRegistration',
|
||||
fields=[
|
||||
('volunteerregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.VolunteerRegistration')),
|
||||
('volunteerregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.volunteerregistration')),
|
||||
('role', models.TextField(verbose_name='role of the administrator')),
|
||||
],
|
||||
options={
|
||||
@ -79,7 +81,7 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='CoachRegistration',
|
||||
fields=[
|
||||
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.ParticipantRegistration')),
|
||||
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.participantregistration')),
|
||||
('professional_activity', models.TextField(verbose_name='professional activity')),
|
||||
],
|
||||
options={
|
||||
@ -91,7 +93,7 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='StudentRegistration',
|
||||
fields=[
|
||||
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.ParticipantRegistration')),
|
||||
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.participantregistration')),
|
||||
('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')),
|
||||
('school', models.CharField(max_length=255, verbose_name='school')),
|
||||
('responsible_name', models.CharField(default='', max_length=255, verbose_name='responsible name')),
|
||||
@ -110,11 +112,11 @@ class Migration(migrations.Migration):
|
||||
name='Payment',
|
||||
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'), ('free', 'The tournament is free')], default='', max_length=16, verbose_name='type')),
|
||||
('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')),
|
||||
('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')),
|
||||
('registration', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment', to='registration.participantregistration', verbose_name='registration')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'payment',
|
||||
|
29
apps/registration/migrations/0002_auto_20230110_2031.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.13 on 2023-01-10 19:31
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import phonenumber_field.modelfields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='participantregistration',
|
||||
name='birth_date',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='studentregistration',
|
||||
name='birth_date',
|
||||
field=models.DateField(default=datetime.date.today, verbose_name='birth date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='studentregistration',
|
||||
name='responsible_phone',
|
||||
field=phonenumber_field.modelfields.PhoneNumberField(default='', max_length=128, region=None, verbose_name='responsible phone number'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.0.11 on 2021-01-23 20:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='participantregistration',
|
||||
name='health_issues',
|
||||
field=models.TextField(blank=True, help_text='You can indicate here your allergies or anything that is important to know for organizers', verbose_name='health issues'),
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.13 on 2023-01-16 22:13
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0002_auto_20230110_2031'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='participantregistration',
|
||||
name='zip_code',
|
||||
field=models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1000), django.core.validators.MaxValueValidator(99999)], verbose_name='zip code'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.13 on 2022-04-26 11:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0002_participantregistration_health_issues'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='type',
|
||||
field=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'),
|
||||
),
|
||||
]
|
51
apps/registration/migrations/0004_volunteer_admin.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Generated by Django 3.2.18 on 2023-02-19 22:13
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import migrations, models
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
def merge_admins(apps, schema_editor):
|
||||
AdminRegistration = apps.get_model('registration', 'AdminRegistration')
|
||||
VolunteerRegistration = apps.get_model('registration', 'VolunteerRegistration')
|
||||
db_alias = schema_editor.connection.alias
|
||||
AdminRegistration.objects.using(db_alias).update(admin=True)
|
||||
for admin in AdminRegistration.objects.all():
|
||||
admin.professional_activity = admin.role
|
||||
admin.polymorphic_ctype_id = ContentType.objects.get_for_model(VolunteerRegistration).id
|
||||
admin.save()
|
||||
|
||||
|
||||
def separate_admins(apps, schema_editor):
|
||||
AdminRegistration = apps.get_model('registration', 'AdminRegistration')
|
||||
VolunteerRegistration = apps.get_model('registration', 'VolunteerRegistration')
|
||||
for admin in VolunteerRegistration.objects.filter(admin=True).all():
|
||||
admin.delete()
|
||||
AdminRegistration.objects.create(user=admin.user,
|
||||
professional_activity=admin.professional_activity,
|
||||
role=admin.professional_activity)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('participation', '0003_alter_team_trigram'),
|
||||
('registration', '0003_alter_participantregistration_zip_code'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='volunteerregistration',
|
||||
name='admin',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="An administrator has all rights. Please don't give this right to all juries and volunteers.",
|
||||
verbose_name='administrator'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
merge_admins,
|
||||
separate_admins,
|
||||
elidable=True,
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AdminRegistration',
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.2.18 on 2023-02-19 23:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import registration.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0004_volunteer_admin'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='studentregistration',
|
||||
name='vaccine_sheet',
|
||||
field=models.FileField(blank=True, default='', upload_to=registration.models.get_random_vaccine_filename, verbose_name='vaccine sheet'),
|
||||
),
|
||||
]
|
@ -3,8 +3,8 @@
|
||||
|
||||
from datetime import date
|
||||
|
||||
from address.models import AddressField
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.template import loader
|
||||
from django.urls import reverse_lazy
|
||||
@ -22,7 +22,7 @@ class Registration(PolymorphicModel):
|
||||
"""
|
||||
Registrations store extra content that are not asked in the User Model.
|
||||
This is specific to the role of the user, see StudentRegistration,
|
||||
ClassRegistration or AdminRegistration..
|
||||
CoachRegistration or VolunteerRegistration.
|
||||
"""
|
||||
user = models.OneToOneField(
|
||||
"auth.User",
|
||||
@ -79,7 +79,7 @@ class Registration(PolymorphicModel):
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return isinstance(self, AdminRegistration) or self.user.is_superuser
|
||||
return isinstance(self, VolunteerRegistration) and self.admin or self.user.is_superuser
|
||||
|
||||
@property
|
||||
def is_volunteer(self):
|
||||
@ -108,6 +108,10 @@ def get_random_health_filename(instance, filename):
|
||||
return "authorization/health/" + get_random_string(64)
|
||||
|
||||
|
||||
def get_random_vaccine_filename(instance, filename):
|
||||
return "authorization/vaccine/" + get_random_string(64)
|
||||
|
||||
|
||||
def get_random_parental_filename(instance, filename):
|
||||
return "authorization/parental/" + get_random_string(64)
|
||||
|
||||
@ -123,11 +127,6 @@ class ParticipantRegistration(Registration):
|
||||
verbose_name=_("team"),
|
||||
)
|
||||
|
||||
birth_date = models.DateField(
|
||||
verbose_name=_("birth date"),
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
gender = models.CharField(
|
||||
max_length=6,
|
||||
verbose_name=_("gender"),
|
||||
@ -139,10 +138,19 @@ class ParticipantRegistration(Registration):
|
||||
default="other",
|
||||
)
|
||||
|
||||
address = AddressField(
|
||||
address = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("address"),
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
zip_code = models.PositiveIntegerField(
|
||||
verbose_name=_("zip code"),
|
||||
validators=[MinValueValidator(1000), MaxValueValidator(99999)],
|
||||
)
|
||||
|
||||
city = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("city"),
|
||||
)
|
||||
|
||||
phone_number = PhoneNumberField(
|
||||
@ -165,6 +173,8 @@ class ParticipantRegistration(Registration):
|
||||
|
||||
@property
|
||||
def under_18(self):
|
||||
if isinstance(self, CoachRegistration):
|
||||
return False # In normal case
|
||||
important_date = timezone.now().date()
|
||||
if self.team and self.team.participation.tournament:
|
||||
important_date = self.team.participation.tournament.date_start
|
||||
@ -187,6 +197,11 @@ class StudentRegistration(ParticipantRegistration):
|
||||
Specific registration for students.
|
||||
They have a team, a student class and a school.
|
||||
"""
|
||||
birth_date = models.DateField(
|
||||
verbose_name=_("birth date"),
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
student_class = models.IntegerField(
|
||||
choices=[
|
||||
(12, _("12th grade")),
|
||||
@ -209,7 +224,7 @@ class StudentRegistration(ParticipantRegistration):
|
||||
|
||||
responsible_phone = PhoneNumberField(
|
||||
verbose_name=_("responsible phone number"),
|
||||
blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
responsible_email = models.EmailField(
|
||||
@ -231,6 +246,13 @@ class StudentRegistration(ParticipantRegistration):
|
||||
default="",
|
||||
)
|
||||
|
||||
vaccine_sheet = models.FileField(
|
||||
verbose_name=_("vaccine sheet"),
|
||||
upload_to=get_random_vaccine_filename,
|
||||
blank=True,
|
||||
default="",
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _("student")
|
||||
@ -276,13 +298,19 @@ class VolunteerRegistration(Registration):
|
||||
verbose_name=_("professional activity"),
|
||||
)
|
||||
|
||||
admin = models.BooleanField(
|
||||
verbose_name=_("administrator"),
|
||||
help_text=_("An administrator has all rights. Please don't give this right to all juries and volunteers."),
|
||||
default=False,
|
||||
)
|
||||
|
||||
@property
|
||||
def interesting_tournaments(self) -> set:
|
||||
return set(self.organized_tournaments.all()).union(map(lambda pool: pool.tournament, self.jury_in.all()))
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _('volunteer')
|
||||
return _('admin') if self.is_admin else _('volunteer')
|
||||
|
||||
@property
|
||||
def form_class(self):
|
||||
@ -290,29 +318,6 @@ class VolunteerRegistration(Registration):
|
||||
return VolunteerRegistrationForm
|
||||
|
||||
|
||||
class AdminRegistration(VolunteerRegistration):
|
||||
"""
|
||||
Specific registration for admins.
|
||||
They have a field to justify they status.
|
||||
"""
|
||||
role = models.TextField(
|
||||
verbose_name=_("role of the administrator"),
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _("admin")
|
||||
|
||||
@property
|
||||
def form_class(self):
|
||||
from registration.forms import AdminRegistrationForm
|
||||
return AdminRegistrationForm
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("admin registration")
|
||||
verbose_name_plural = _("admin registrations")
|
||||
|
||||
|
||||
def get_scholarship_filename(instance, filename):
|
||||
return f"authorization/scholarship/scholarship_{instance.registration.pk}"
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
from django.contrib.auth.models import User
|
||||
from tfjm.lists import get_sympa_client
|
||||
|
||||
from .models import AdminRegistration, Payment, Registration
|
||||
from .models import Payment, Registration, VolunteerRegistration
|
||||
|
||||
|
||||
def set_username(instance, **_):
|
||||
@ -40,7 +40,7 @@ def create_admin_registration(instance, **_):
|
||||
ensure that an admin registration is created.
|
||||
"""
|
||||
if instance.is_superuser:
|
||||
AdminRegistration.objects.get_or_create(user=instance)
|
||||
VolunteerRegistration.objects.get_or_create(user=instance, admin=True)
|
||||
|
||||
|
||||
def create_payment(instance: Registration, **_):
|
||||
|
@ -19,7 +19,7 @@ class RegistrationTable(tables.Table):
|
||||
)
|
||||
|
||||
def order_type(self, queryset, desc):
|
||||
types = ["volunteerregistration__adminregistration", "volunteerregistration", "participantregistration"]
|
||||
types = ["-volunteerregistration__admin", "volunteerregistration", "participantregistration"]
|
||||
return queryset.order_by(*(("-" if desc else "") + t for t in types)), True
|
||||
|
||||
class Meta:
|
||||
|
@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n static crispy_forms_filters %}
|
||||
|
||||
{% block content %}
|
||||
<a class="btn btn-info" href="{% url "registration:user_detail" pk=object.user.pk %}"><i class="fas fa-arrow-left"></i> {% trans "Back to the user detail" %}</a>
|
||||
<hr>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div id="form-content">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
</div>
|
||||
<button class="btn btn-success" type="submit">{% trans "Upload" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
@ -39,14 +39,16 @@
|
||||
</a>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Birth date:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.birth_date }}</dd>
|
||||
{% if user_object.registration.studentregistration %}
|
||||
<dt class="col-sm-6 text-right">{% trans "Birth date:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.birth_date }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Gender:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Address:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.address }}</dd>
|
||||
<dd class="col-sm-6">{{ user_object.registration.address }}, {{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Phone number:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
||||
@ -77,6 +79,16 @@
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Vaccine sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.vaccine_sheet %}
|
||||
<a href="{{ user_object.registration.vaccine_sheet.url }}" data-turbolinks="false">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-toggle="modal" data-target="#uploadVaccineSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-right">{% trans "Parental authorization:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.parental_authorization %}
|
||||
@ -105,8 +117,8 @@
|
||||
<dd class="col-sm-6"><a href="mailto:{{ email }}">{{ email }}</a></dd>
|
||||
{% endwith %}
|
||||
{% elif user_object.registration.is_admin %}
|
||||
<dt class="col-sm-6 text-right">{% trans "Role:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.role }}</dd>
|
||||
<dt class="col-sm-6 text-right">{% trans "Admin:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.is_admin|yesno }}</dd>
|
||||
{% elif user_object.registration.coachregistration or user_object.registration.is_volunteer %}
|
||||
<dt class="col-sm-6 text-right">{% trans "Profesional activity:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.professional_activity }}</dd>
|
||||
@ -169,6 +181,11 @@
|
||||
{% url "registration:upload_user_health_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadHealthSheet" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% trans "Upload vaccine sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_vaccine_sheet" pk=user_object.registration.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadVaccineSheet" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% trans "Upload parental authorization" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "registration:upload_user_parental_authorization" pk=user_object.registration.pk as modal_action %}
|
||||
|
@ -3,8 +3,9 @@
|
||||
{{ object.user.email }}
|
||||
{{ object.type }}
|
||||
{{ object.professional_activity }}
|
||||
{{ object.birth_date }}
|
||||
{{ object.address }}
|
||||
{{ object.zip_code }}
|
||||
{{ object.city }}
|
||||
{{ object.phone_number }}
|
||||
{{ object.team.name }}
|
||||
{{ object.team.trigram }}
|
||||
|
@ -6,6 +6,8 @@
|
||||
{{ object.school }}
|
||||
{{ object.birth_date }}
|
||||
{{ object.address }}
|
||||
{{ object.zip_code }}
|
||||
{{ object.city }}
|
||||
{{ object.phone_number }}
|
||||
{{ object.responsible_name }}
|
||||
{{ object.reponsible_phone }}
|
||||
|
@ -15,7 +15,7 @@ from django.utils.http import urlsafe_base64_encode
|
||||
from participation.models import Team
|
||||
from tfjm.tokens import email_validation_token
|
||||
|
||||
from .models import AdminRegistration, CoachRegistration, StudentRegistration
|
||||
from .models import CoachRegistration, StudentRegistration, VolunteerRegistration
|
||||
|
||||
|
||||
class TestIndexPage(TestCase):
|
||||
@ -64,9 +64,22 @@ class TestRegistration(TestCase):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
self.student = User.objects.create(email="student@example.com")
|
||||
StudentRegistration.objects.create(user=self.student, student_class=11, school="Earth")
|
||||
StudentRegistration.objects.create(
|
||||
user=self.student,
|
||||
student_class=11,
|
||||
school="Earth",
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
)
|
||||
self.coach = User.objects.create(email="coach@example.com")
|
||||
CoachRegistration.objects.create(user=self.coach, professional_activity="Teacher")
|
||||
CoachRegistration.objects.create(
|
||||
user=self.coach,
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
professional_activity="Teacher",
|
||||
)
|
||||
|
||||
def test_admin_pages(self):
|
||||
"""
|
||||
@ -79,7 +92,7 @@ class TestRegistration(TestCase):
|
||||
+ f"registration/registration/{self.user.registration.pk}/change/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get(reverse("admin:index") +
|
||||
f"r/{ContentType.objects.get_for_model(AdminRegistration).id}/"
|
||||
f"r/{ContentType.objects.get_for_model(VolunteerRegistration).id}/"
|
||||
f"{self.user.registration.pk}/")
|
||||
self.assertRedirects(response, "http://" + Site.objects.get().domain +
|
||||
str(self.user.registration.get_absolute_url()), 302, 200)
|
||||
@ -131,7 +144,9 @@ class TestRegistration(TestCase):
|
||||
school="God",
|
||||
birth_date="2000-01-01",
|
||||
gender="other",
|
||||
address="1 Rue de Rivoli, 75001 Paris, France",
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
phone_number="0123456789",
|
||||
responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
@ -155,7 +170,9 @@ class TestRegistration(TestCase):
|
||||
school="God",
|
||||
birth_date="2000-01-01",
|
||||
gender="other",
|
||||
address="1 Rue de Rivoli, 75001 Paris, France",
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
phone_number="0123456789",
|
||||
responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
@ -174,9 +191,10 @@ class TestRegistration(TestCase):
|
||||
password1="azertyuiopazertyuiop",
|
||||
password2="azertyuiopazertyuiop",
|
||||
role="coach",
|
||||
birth_date="1980-01-01",
|
||||
gender="other",
|
||||
address="1 Rue de Rivoli, 75001 Paris, France",
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
phone_number="0123456789",
|
||||
professional_activity="God",
|
||||
give_contact_to_animath=True,
|
||||
@ -253,12 +271,14 @@ class TestRegistration(TestCase):
|
||||
)
|
||||
self.student.registration.save()
|
||||
|
||||
for user, data in [(self.user, dict(role="Bot")),
|
||||
for user, data in [(self.user, dict(professional_activity="Bot", admin=True)),
|
||||
(self.student, dict(student_class=11, school="Sky", birth_date="2001-01-01",
|
||||
gender="female", address="1 Rue de Rivoli, 75001 Paris, France",
|
||||
responsible_name="Toto", responsible_email="toto@example.com")),
|
||||
(self.coach, dict(professional_activity="God", birth_date="2001-01-01",
|
||||
gender="male", address="1 Rue de Rivoli, 75001 Paris, France"))]:
|
||||
gender="female", address="1 Rue de Rivoli", zip_code=75001,
|
||||
city="Paris", responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
responsible_email="toto@example.com")),
|
||||
(self.coach, dict(professional_activity="God", gender="male",
|
||||
address="1 Rue de Rivoli", zip_code=75001, city="Paris"))]:
|
||||
response = self.client.get(reverse("registration:update_user", args=(user.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from .views import AddOrganizerView, AdultPhotoAuthorizationTemplateView, ChildP
|
||||
InstructionsTemplateView, MyAccountDetailView, ParentalAuthorizationTemplateView, PaymentUpdateView, \
|
||||
ResetAdminView, SignupView, UserDetailView, UserImpersonateView, UserListView, UserResendValidationEmailView, \
|
||||
UserUpdateView, UserUploadHealthSheetView, UserUploadParentalAuthorizationView, UserUploadPhotoAuthorizationView, \
|
||||
UserValidateView, UserValidationEmailSentView
|
||||
UserUploadVaccineSheetView, UserValidateView, UserValidationEmailSentView
|
||||
|
||||
app_name = "registration"
|
||||
|
||||
@ -32,6 +32,8 @@ urlpatterns = [
|
||||
path("instructions-template/", InstructionsTemplateView.as_view(), name="instructions_template"),
|
||||
path("user/<int:pk>/upload-health-sheet/", UserUploadHealthSheetView.as_view(),
|
||||
name="upload_user_health_sheet"),
|
||||
path("user/<int:pk>/upload-vaccine-sheet/", UserUploadVaccineSheetView.as_view(),
|
||||
name="upload_user_vaccine_sheet"),
|
||||
path("user/<int:pk>/upload-parental-authorization/", UserUploadParentalAuthorizationView.as_view(),
|
||||
name="upload_user_parental_authorization"),
|
||||
path("update-payment/<int:pk>/", PaymentUpdateView.as_view(), name="update_payment"),
|
||||
|
@ -27,9 +27,9 @@ from participation.models import Passage, Solution, Synthesis, Tournament
|
||||
from tfjm.tokens import email_validation_token
|
||||
from tfjm.views import UserMixin, UserRegistrationMixin, VolunteerMixin
|
||||
|
||||
from .forms import AddOrganizerForm, AdminRegistrationForm, CoachRegistrationForm, HealthSheetForm, \
|
||||
from .forms import AddOrganizerForm, CoachRegistrationForm, HealthSheetForm, \
|
||||
ParentalAuthorizationForm, PaymentForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm, \
|
||||
VolunteerRegistrationForm
|
||||
VaccineSheetForm, VolunteerRegistrationForm
|
||||
from .models import ParticipantRegistration, Payment, Registration, StudentRegistration
|
||||
from .tables import RegistrationTable
|
||||
|
||||
@ -91,24 +91,21 @@ class AddOrganizerView(VolunteerMixin, CreateView):
|
||||
context = super().get_context_data()
|
||||
|
||||
context["volunteer_registration_form"] = VolunteerRegistrationForm(self.request.POST or None)
|
||||
context["admin_registration_form"] = AdminRegistrationForm(self.request.POST or None)
|
||||
|
||||
del context["volunteer_registration_form"].fields["email_confirmed"]
|
||||
del context["admin_registration_form"].fields["email_confirmed"]
|
||||
|
||||
if not self.request.user.registration.is_admin:
|
||||
context["form"].fields["type"].widget.attrs['readonly'] = True
|
||||
del context["admin_registration_form"]
|
||||
|
||||
return context
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
|
||||
if not self.request.user.registration.is_admin:
|
||||
del form.fields["admin"]
|
||||
|
||||
return form
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
role = form.cleaned_data["type"]
|
||||
if role == "admin":
|
||||
registration_form = AdminRegistrationForm(self.request.POST)
|
||||
else:
|
||||
registration_form = VolunteerRegistrationForm(self.request.POST)
|
||||
registration_form = VolunteerRegistrationForm(self.request.POST)
|
||||
del registration_form.fields["email_confirmed"]
|
||||
|
||||
if not registration_form.is_valid():
|
||||
@ -347,6 +344,27 @@ class UserUploadHealthSheetView(UserRegistrationMixin, UpdateView):
|
||||
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
|
||||
|
||||
|
||||
class UserUploadVaccineSheetView(UserRegistrationMixin, UpdateView):
|
||||
"""
|
||||
A participant can send its vaccine sheet.
|
||||
"""
|
||||
model = StudentRegistration
|
||||
form_class = VaccineSheetForm
|
||||
template_name = "registration/upload_vaccine_sheet.html"
|
||||
extra_context = dict(title=_("Upload vaccine sheet"))
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
old_instance = StudentRegistration.objects.get(pk=self.object.pk)
|
||||
if old_instance.vaccine_sheet:
|
||||
old_instance.vaccine_sheet.delete()
|
||||
old_instance.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
|
||||
|
||||
|
||||
class UserUploadParentalAuthorizationView(UserRegistrationMixin, UpdateView):
|
||||
"""
|
||||
A participant can send its parental authorization.
|
||||
@ -487,6 +505,29 @@ class HealthSheetView(LoginRequiredMixin, View):
|
||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
class VaccineSheetView(LoginRequiredMixin, View):
|
||||
"""
|
||||
Display the sent health sheet.
|
||||
"""
|
||||
def get(self, request, *args, **kwargs):
|
||||
filename = kwargs["filename"]
|
||||
path = f"media/authorization/vaccine/{filename}"
|
||||
if not os.path.exists(path):
|
||||
raise Http404
|
||||
student = StudentRegistration.objects.get(vaccine_sheet__endswith=filename)
|
||||
user = request.user
|
||||
if not (student.user == user or user.registration.is_admin or user.registration.is_volunteer and student.team
|
||||
and student.team.participation.tournament in user.registration.organized_tournaments.all()):
|
||||
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 = _("Vaccine sheet of {student}.{ext}").format(student=str(student), ext=ext)
|
||||
return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
|
||||
|
||||
|
||||
class ParentalAuthorizationView(LoginRequiredMixin, View):
|
||||
"""
|
||||
Display the sent parental authorization.
|
||||
|
BIN
docs/_static/img/choose_tournament.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
docs/_static/img/create_team.png
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/_static/img/join_team.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
docs/_static/img/team_info.png
vendored
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/_static/img/tournament_info.png
vendored
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
docs/_static/img/user_info.png
vendored
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/_static/img/validate_team.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
@ -18,7 +18,7 @@
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Plateforme du TFJM²'
|
||||
copyright = "2020-2021"
|
||||
copyright = "2020-2023"
|
||||
author = "Animath"
|
||||
|
||||
|
||||
|
4
docs/dev/index.rst
Normal file
@ -0,0 +1,4 @@
|
||||
Développer la plateforme
|
||||
========================
|
||||
|
||||
Cette page est dédiée aux responsables informatiques qui cherchent à contribuer à la plateforme.
|
@ -1,23 +1,21 @@
|
||||
Documentation de la plateforme du TFJM²
|
||||
=======================================
|
||||
|
||||
.. image:: https://gitlab.com/animath/si/plateforme-tfjm/badges/master/pipeline.svg
|
||||
:target: https://gitlab.com/animath/si/plateforme-tfjm/-/commits/master
|
||||
:alt: Pipeline status
|
||||
Ce site vise à documenter l'usage de la plateforme de gestion du TFJM², aussi
|
||||
bien du côté utilisateur⋅rice que du côté organisateur⋅rice ou bien
|
||||
administrateur⋅rice.
|
||||
|
||||
.. image:: https://gitlab.com/animath/si/plateforme-tfjm/badges/master/coverage.svg
|
||||
:target: https://gitlab.com/animath/si/plateforme-tfjm/-/commits/master
|
||||
:alt: Coverage report
|
||||
|
||||
.. image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg
|
||||
:target: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
:alt: License: GPL v3
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: Utiliser
|
||||
|
||||
user
|
||||
orga
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: Développer
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: Jouer
|
||||
dev/index
|
||||
|
114
docs/orga.rst
Normal file
@ -0,0 +1,114 @@
|
||||
Partie organisateur⋅rices
|
||||
=========================
|
||||
|
||||
.. contents::
|
||||
|
||||
Cette page est dédiée aux organisateur⋅rices qui souhaitent utiliser la plateforme pour gérer
|
||||
les différentes équipes et les inscriptions.
|
||||
|
||||
|
||||
Ajouter un⋅e nouvelleau organisateur⋅rice
|
||||
-----------------------------------------
|
||||
|
||||
Seul⋅es les actuel⋅les organisateur⋅rices peuvent en ajouter de nouvelleaux. Il n'est pas possible
|
||||
de s'inscrire.
|
||||
|
||||
Pour cela, il faut se connecter, aller dans l'onglet « Utilisateur⋅rices », puis « Ajouter un⋅e organisateur⋅rice ».
|
||||
|
||||
Les informations suivantes sont demandées :
|
||||
|
||||
* Prénom
|
||||
* Nom de famille
|
||||
* Adresse e-mail (préférer une adresse institutionnelle)
|
||||
* Rôle (Bénévole ou administrateur⋅rice)
|
||||
* Activité professionnelle (permet de savoir d'où viennent les bénévoles)
|
||||
|
||||
Les bénévoles peuvent gérer ce qui les concernent (tournois, jurys,…), les administrateur⋅rices ont un accès
|
||||
intégral à l'ensemble de la plateforme, non restreint. Ce dernier statut ne devrait être réservé qu'aux membres du CNO.
|
||||
|
||||
Une fois le formulaire validé, un mail est envoyé permettant de définir son mot de passe. Iel peut ensuite se
|
||||
connecter en utilisant son adresse e-mail et son mot de passe.
|
||||
|
||||
|
||||
Gestion des tournois
|
||||
--------------------
|
||||
|
||||
Créer un tournoi
|
||||
""""""""""""""""
|
||||
|
||||
.. important::
|
||||
Seul⋅es les administrateur⋅rices peuvent créer des tournois.
|
||||
|
||||
Pour créer un tournoi, il suffit de cliquer dans l'onglet « Tournois » puis « Ajouter un tournoi ».
|
||||
Les descriptions des différents paramètres sont dans la section suivante.
|
||||
|
||||
|
||||
Modifier un tournoi
|
||||
"""""""""""""""""""
|
||||
|
||||
.. important::
|
||||
Seul⋅es les administrateur⋅rices ainsi que les organisateur⋅rices dudit tournoi peuvent modifier le tournoi.
|
||||
|
||||
Pour modifier un tournoi, il faut déjà se rendre sur la page du tournoi : onglet « Tournois » puis cliquer sur
|
||||
le bon tournoi. Le bouton « Modifier le tournoi » devrait être accessible.
|
||||
|
||||
.. warning::
|
||||
Si le bouton n'est pas visible, vérifiez que vous êtes bien connecté⋅e, et que vous êtes bien marqué⋅es parmi
|
||||
les organisateur⋅rices. N'hésitez pas à les contacter si ce n'est pas le cas.
|
||||
|
||||
Les informations suivantes peuvent être modifiées :
|
||||
|
||||
* Nom du tournoi
|
||||
* Date de début (le samedi)
|
||||
* Date de fin (le dimanche)
|
||||
* Adresse du lieu physique
|
||||
* Nombre indicatif maximal d'équipes autorisées
|
||||
(un multiple de 3, n'est là qu'à titre indicatif et n'est pas bloquant pour la suite)
|
||||
* Prix demandé aux participant⋅es, normalement 21 € sauf pour la finale 35 € (hors boursièr⋅es et tournois en visio)
|
||||
* La case « À distance » doit rester décochée tant que les tournois en visio n'ont pas repris
|
||||
* Date limite d'inscription : date jusqu'à laquelle les équipes peuvent finaliser leur inscription (non bloquant).
|
||||
En général le mois précédent le tournoi
|
||||
* Date limite pour envoyer les solutions : date jusqu'à laquelle les équipes peuvent soumettre leurs solutions
|
||||
(au-delà, le remplacement de solutions déjà soumises n'est plus permis mais l'envoi de nouvelles reste possible en
|
||||
cas de besoin, à décourager). En général le dimanche avant le tournoi vers 22h
|
||||
* Tirage au sort : date indicative qui dit quand le tirage au sort va se dérouler. En général le mardi précédent
|
||||
le tournoi vers 20h
|
||||
* Date limite pour envoyer les notes de synthèses pour la première/seconde phase : même règle que pour les solutions,
|
||||
mais pour les notes de synthèse. Généralement le vendredi à 22h pour le premier tour et le dimanche à 10h pour le
|
||||
second tour
|
||||
* Date à laquelle les solutions pour le second tour sont accessibles : seules les solutions pour le premier tour sont
|
||||
directement accessible après le tirage au sort, celles pour le second tour sont libérées automatiquement une fois
|
||||
cette date passée. Généralement le samedi entre 17h et 18h (à adapter)
|
||||
* Description
|
||||
* Organisateur⋅rices : liste des personnes qui organisent le tournoi et peuvent le gérer numériquement. N'inclut pas
|
||||
les juré⋅es
|
||||
* Finale (admin uniquement) : cette case ne doit être cochée que pour le tournoi de la finale.
|
||||
|
||||
|
||||
Liste des équipes
|
||||
"""""""""""""""""
|
||||
|
||||
Lorsque les équipes choisissent leur tournoi, elle est répertoriée sur la page du tournoi.
|
||||
|
||||
|
||||
Valider une équipe
|
||||
""""""""""""""""""
|
||||
|
||||
Lorsqu'une équipe a finalisé son inscription et a demandé à être validée, un mail est envoyé à l'ensemble des
|
||||
organisateur⋅rices. Sur l'interface du tournoi, il est possible de cliquer sur l'équipe et accéder à l'ensemble
|
||||
de ses informations :
|
||||
|
||||
* Nom de l'équipe
|
||||
* Trigramme
|
||||
* Encadrant⋅es
|
||||
* Participant⋅es
|
||||
* Diverses autorisations
|
||||
* Lettre de motivation
|
||||
|
||||
Lorsqu'il est temps de valider l'équipe, un formulaire dédié apparaît en bas. Un texte peut être envoyé à l'équipe,
|
||||
et le choix est proposé de valider l'équipe ou non.
|
||||
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette documentation sera complétée à l'avenir pour prendre en compte les enjeux du tournoi au moment venu.
|
222
docs/user.rst
Normal file
@ -0,0 +1,222 @@
|
||||
Utiliser la plateforme
|
||||
======================
|
||||
|
||||
.. contents::
|
||||
|
||||
Cette plateforme est conçue pour que les différentes équipes puissent s'inscrire au TFJM² et envoyer
|
||||
leurs solutions.
|
||||
|
||||
Si vous êtes ici, c'est que vous avez des questions sur l'utilisation du site en tant que participant⋅e.
|
||||
|
||||
Les inscriptions ouvrent dans le courant du mois de janvier, généralement une semaine après la publication
|
||||
des problèmes. Pour l'édition 2023, les inscriptions ouvrent le 11 janvier 2023.
|
||||
|
||||
Tout se passe sur le site https://inscription.tfjm.org/.
|
||||
|
||||
|
||||
S'inscrire
|
||||
----------
|
||||
|
||||
Il est important de noter que chaque personne d'une équipe doit s'inscrire, y compris les encadrant⋅es.
|
||||
|
||||
Rendez-vous sur le site d'inscription, bouton « S'inscrire » en haut à droite.
|
||||
|
||||
Les informations suivantes sont requises pour tout le monde :
|
||||
|
||||
* Prénom d'usage
|
||||
* Nom de famille (ou d'usage)
|
||||
* Adresse électronique (sera vérifiée)
|
||||
* Mot de passe et confirmation
|
||||
* Rôle (participant⋅e ou encadrant⋅e)
|
||||
* Date de naissance
|
||||
* Genre
|
||||
* Adresse postale
|
||||
* Numéro de téléphone de contact
|
||||
* Problèmes de santé à déclarer
|
||||
* Donne son consentement pour se faire recontacter par Animath
|
||||
|
||||
Informations demandées exclusivement aux élèves :
|
||||
|
||||
* Classe (seconde ou avant/première/terminale)
|
||||
* Établissement scolaire
|
||||
* Nom du responsable légal
|
||||
* Numéro de téléphone du responsable légal
|
||||
* Adresse e-mail du responsable légal
|
||||
|
||||
Informations exclusivement demandées aux encadrant⋅es :
|
||||
|
||||
* Activité professionnelle
|
||||
|
||||
Une fois inscrit⋅e, vous recevrez par mail un lien de confirmation, qu'il vous faudra cliquer.
|
||||
|
||||
|
||||
Connexion
|
||||
---------
|
||||
|
||||
Une fois inscrit⋅e, vous pouvez vous connecter en utilisant le bouton en haut à droite.
|
||||
|
||||
Dans le champ « nom d'utilisateur », rentrez votre adresse mail. Si vous avez oublié votre mot de
|
||||
passe, un formulaire est disponible pour vous aider à le réinitialiser. Vous pouvez ensuite vous
|
||||
connecter. Votre prénom et votre nom apparaîtra en haut à droite.
|
||||
|
||||
|
||||
Créer une équipe
|
||||
----------------
|
||||
|
||||
Il suffit d'une seule personne (participant⋅e ou encadrant⋅e) pour créer une équipe. Pour créer une
|
||||
équipe, il faut cliquer sur le bouton « créer une équipe ». Un nom d'équipe et un trigramme seront
|
||||
demandés. Le trigramme est composé de 3 lettres majuscules, c'est ce qui permettra aux
|
||||
organisateur⋅rices d'identifier rapidement votre équipe.
|
||||
|
||||
.. image:: /_static/img/create_team.png
|
||||
:alt: Création d'une équipe
|
||||
|
||||
Une fois l'équipe créée, vous obtenez un code à 6 caractères, lettres ou chiffre. Ce code est à
|
||||
transettre à l'ensemble des membres de votre équipe (et seulement à elleux).
|
||||
|
||||
.. image:: /_static/img/team_info.png
|
||||
:alt: Information sur l'équipe nouvellement créée
|
||||
|
||||
|
||||
Rejoindre une équipe
|
||||
--------------------
|
||||
|
||||
Si l'équipe est déjà créée, vous aurez besoin du code d'accès transmis par la personne ayant créé
|
||||
l'équipe. Vous pouvez cliquer sur « Rejoindre une équipe », et entrer le code.
|
||||
|
||||
.. image:: /_static/img/join_team.png
|
||||
:alt: Rejoindre une équipe par son code d'accès
|
||||
|
||||
Vous avez désormais normalement rejoint l'équipe.
|
||||
|
||||
En cas de problème, ou si vous ne savez pas de quel code on parle, contactez-nous à l'adresse
|
||||
contact@tfjm.org.
|
||||
|
||||
|
||||
Informations sur les tournois
|
||||
-----------------------------
|
||||
|
||||
Les tournois peuvent être trouvés dans l'onglet « Tournois ». Vous avez accès, pour chaque tournoi,
|
||||
à l'ensemble des dates importantes et les lieux des tournois.
|
||||
|
||||
.. image:: /_static/img/tournament_info.png
|
||||
:alt: Informations sur un tournoi
|
||||
|
||||
Davantage d'informations peuvent être trouvées sur le site vitrine : https://tfjm.org/infos-tournois/.
|
||||
|
||||
|
||||
Choisir un tournoi
|
||||
------------------
|
||||
|
||||
Pour accéder aux paramètres de votre équipe, vous pouvez aller sur l'onglet « Mon équipe », dans la
|
||||
barre de navigation.
|
||||
|
||||
Pour choisir votre tournoi, il vous suffit de vous rendre sur la page de votre équipe et de cliquer
|
||||
sur « Modifier ». Un formulaire vous permet alors de choisir votre tournoi.
|
||||
|
||||
.. image:: /_static/img/choose_tournament.png
|
||||
:alt: Formulaire de mise à jour de l'équipe permettant de choisir un tournoi
|
||||
|
||||
Attention cependant : cela ne confirme pas votre inscription. Vous devez pour cela envoyer l'ensemble
|
||||
de vos documents (voir ci-dessous).
|
||||
|
||||
|
||||
Transmettre ses documents
|
||||
-------------------------
|
||||
|
||||
Pour valider votre inscription, vous devez :
|
||||
|
||||
* Avoir choisi un tournoi ;
|
||||
* Que chaque membre de l'équipe ait transmis :
|
||||
|
||||
* Autorisation de droit à l'image ;
|
||||
* Fiche sanitaire de liaison et carnet de vaccination (pour les mineur⋅es) ;
|
||||
* Autorisation parentale (pour les mineur⋅es) ;
|
||||
|
||||
* Transmettre une lettre de motivation.
|
||||
|
||||
La lettre de motivation doit être envoyée une seule fois pour toute l'équipe, peut être envoyée
|
||||
depuis l'interface « Mon équipe », au format PDF, dont le contenu est défini dans l'article 5.3
|
||||
du guide de læ participant⋅e : https://tfjm.org/reglement/.
|
||||
|
||||
Concernant les documents personnels, ils peuvent être envoyés depuis le menu « Mon compte », qui
|
||||
peut être trouvé en haut à droite dans la barre de navigation. Chaque fichier doit être envoyé
|
||||
au format PDF et peser moins de 2 Mo.
|
||||
|
||||
.. image:: /_static/img/user_info.png
|
||||
:alt: Informations sur l'utilisateur⋅rice
|
||||
|
||||
En cas de besoin, contactez-nous à l'adresse contact@tfjm.org.
|
||||
|
||||
|
||||
Valider son équipe
|
||||
------------------
|
||||
|
||||
Pour prétendre à la validation, il faut que l'équipe compte au moins 1 encadrant⋅e et 4 participant⋅es.
|
||||
Il faut ensuite que la lettre de motivation soit transmise, le tournoi choisi et que tous les documents
|
||||
nécessaires ont été transmis (voir section précédente).
|
||||
|
||||
Une fois tous les prérequis réunis, sur la page « Mon équipe », il est possible de cliquer sur le bouton
|
||||
pour demander la validation.
|
||||
|
||||
.. image:: /_static/img/validate_team.png
|
||||
:alt: Formulaire de validation d'équipe
|
||||
|
||||
.. warning::
|
||||
Les places étant limitées, rien ne garantit que vous pourrez avoir votre place dans le tournoi. Nous
|
||||
vous encourageons à respecter un maximum les critères définis dans le règlement :
|
||||
https://tfjm.org/reglement/. Selon les disponiblités et votre position géographique, il pourra
|
||||
vous être proposé de participer à un tournoi voisin.
|
||||
|
||||
Une fois les deadlines dépassées, rien ne vous garantit une place au TFJM², alors attention aux dates.
|
||||
|
||||
Vous recevrez par mail une réponse des organisateur⋅rices locaux⋅ales. En cas de besoin, contactez-nous
|
||||
à l'adresse contact@tfjm.org.
|
||||
|
||||
|
||||
Payer son inscription
|
||||
---------------------
|
||||
|
||||
Une fois votre inscription validée, il vous faudra payer votre inscription. Les frais s'élèvent à
|
||||
23 € par élève, sauf pour les élèves boursièr⋅es qui en sont exonéré⋅es. Les encadrant⋅es n'ont pas
|
||||
à payer.
|
||||
|
||||
.. note::
|
||||
Ces frais couvrent une partie des frais de restauration et d'hébergement. L'organisation reste
|
||||
bénévole.
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette section sera mise à jour plus tard.
|
||||
|
||||
|
||||
Envoyer ses solutions
|
||||
---------------------
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette section sera mise à jour plus tard.
|
||||
|
||||
|
||||
Participer au tirage au sort
|
||||
----------------------------
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette section sera mise à jour plus tard.
|
||||
|
||||
|
||||
Envoyer ses notes de synthèse
|
||||
-----------------------------
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette section sera mise à jour plus tard.
|
||||
|
||||
|
||||
Récupérer les solutions adverses
|
||||
--------------------------------
|
||||
|
||||
.. TODO
|
||||
.. note::
|
||||
Cette section sera mise à jour plus tard.
|
@ -1,24 +1,22 @@
|
||||
Django>=3.2,<4.0
|
||||
django-address~=0.2
|
||||
django-bootstrap-datepicker-plus~=4.0
|
||||
django-cas-server~=1.3
|
||||
django-crispy-forms~=1.9
|
||||
django-extensions~=3.0
|
||||
django-filter~=2.4
|
||||
django-haystack~=3.0
|
||||
django-mailer~=2.1
|
||||
django-phonenumber-field~=5.0.0
|
||||
django-polymorphic~=3.0
|
||||
django-cas-server~=2.0
|
||||
django-crispy-forms~=1.14
|
||||
django-extensions~=3.2
|
||||
django-filter~=22.1
|
||||
django-haystack~=3.2
|
||||
django-mailer~=2.2
|
||||
django-phonenumber-field~=7.0
|
||||
django-polymorphic~=3.1
|
||||
django-tables2~=2.4
|
||||
djangorestframework~=3.12
|
||||
djangorestframework~=3.14
|
||||
django-rest-polymorphic~=0.1
|
||||
gunicorn~=20.1
|
||||
matrix-nio~=0.16
|
||||
phonenumbers~=8.9.10
|
||||
psycopg2-binary~=2.8
|
||||
PyPDF3~=1.0.2
|
||||
ipython~=7.19.0
|
||||
python-magic>=0.4.22
|
||||
requests~=2.25.1
|
||||
sympasoap~=1.0
|
||||
matrix-nio~=0.20
|
||||
phonenumbers~=8.12.57
|
||||
psycopg2-binary~=2.9.5
|
||||
pypdf~=3.4
|
||||
ipython~=8.5.0
|
||||
python-magic~=0.4.26
|
||||
requests~=2.28.1
|
||||
sympasoap~=1.1
|
||||
whoosh~=2.7
|
@ -24,7 +24,7 @@ PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
APPS_DIR = os.path.realpath(os.path.join(BASE_DIR, "apps"))
|
||||
sys.path.append(APPS_DIR)
|
||||
|
||||
ADMINS = [("Yohann D'ANELLO", "yohann.danello@animath.fr")]
|
||||
ADMINS = [("Emmy D'Anello", "emmy.danello@animath.fr")]
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
@ -53,8 +53,6 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.forms',
|
||||
|
||||
'address',
|
||||
'bootstrap_datepicker_plus',
|
||||
'crispy_forms',
|
||||
'django_filters',
|
||||
'django_tables2',
|
||||
@ -226,11 +224,6 @@ else:
|
||||
}
|
||||
}
|
||||
|
||||
if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
else:
|
||||
from .settings_dev import * # noqa: F401,F403
|
||||
|
||||
# Custom phone number format
|
||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||
@ -242,3 +235,25 @@ JQUERY_URL = False
|
||||
|
||||
# Custom parameters
|
||||
PROBLEM_COUNT = 8
|
||||
FORBIDDEN_TRIGRAMS = [
|
||||
"BIT",
|
||||
"CNO",
|
||||
"CRO",
|
||||
"CUL",
|
||||
"FTG",
|
||||
"FCK",
|
||||
"FUC",
|
||||
"FUK",
|
||||
"FYS",
|
||||
"HIV",
|
||||
"IST",
|
||||
"MST",
|
||||
"KKK",
|
||||
"KYS",
|
||||
"SEX",
|
||||
]
|
||||
|
||||
if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
else:
|
||||
from .settings_dev import * # noqa: F401,F403
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="text-justify">
|
||||
<p>
|
||||
La plateforme d'inscription du TFJM² a été développée entre 2019 et 2022
|
||||
par Yohann D'ANELLO, bénévole pour l'association Animath. Elle est vouée à être utilisée par les participants
|
||||
par Emmy D'Anello, bénévole pour l'association Animath. Elle est vouée à être utilisée par les participants
|
||||
pour intéragir avec les organisateurs et les autres participants.
|
||||
</p>
|
||||
|
||||
|
@ -5,11 +5,12 @@
|
||||
|
||||
<div class="alert alert-success">
|
||||
<p>
|
||||
Les inscriptions sont à présent ouvertes, vous pouvez créer votre compte. Prenez garde toutefois
|
||||
aux dates indiquées qui sont pour l'instant provisoires.
|
||||
Les inscriptions pour la session 2023 sont à présent ouvertes, vous pouvez créer votre compte.
|
||||
Prenez garde toutefois aux dates indiquées qui sont pour l'instant provisoires.
|
||||
</p>
|
||||
<p>
|
||||
Une documentation plus complète sera disponible dans les jours à venir et régulièrement mise à jour pour apprendre à utiliser la plateforme.
|
||||
Une documentation plus complète est disponible à l'adresse
|
||||
<a href="https://inscription.tfjm.org/doc/">https://inscription.tfjm.org/doc/</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -36,20 +37,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">
|
||||
<h4><strong><i class="fas fa-newspaper"></i> Informations 2022 :</strong></h4>
|
||||
<p>
|
||||
Après 2 ans de pandémie, le tournoi devrait (enfin !) revenir en présentiel. Selon les mesures
|
||||
gouvernementales en vigueur au moment du tournoi et peut-être même selon le lieu d'accueil du tournoi,
|
||||
le pass sanitaire ou vaccinal pourra être requis.
|
||||
</p>
|
||||
<p>
|
||||
La plateforme <a class="alert-link" href="https://element.tfjm.org">element.tfjm.org</a> reste ouverte
|
||||
cette année pour faciliter d'éventuelles communications. C'est ici notamment que se déroulera le tirage
|
||||
au sort. Nous vous invitons à tenter de vous connecter sur la plateforme une fois votre compte créé.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron">
|
||||
<h5 class="display-4">Comment ça marche ?</h5>
|
||||
<p>
|
||||
@ -59,10 +46,10 @@
|
||||
|
||||
<p class="text-justify">
|
||||
Vous pouvez accéder à votre compte via la rubrique <strong><a href="{% url "login" %}">Connexion</a></strong>.
|
||||
Une fois connecté, vous pourrez créer une équipe ou en rejoindre une déjà créée par l'un de vos camarades
|
||||
via un code d'accès qui vous aura été transmis. Vous serez ensuite invité à soumettre une autorisation de droit à l'image,
|
||||
indispensable au bon déroulement du 𝕋𝔽𝕁𝕄². Une fois que votre équipe comporte au moins 4 participants (maximum 6)
|
||||
et un encadrant, vous pourrez demander à valider votre équipe pour être apte à travailler sur les problèmes de votre choix.
|
||||
Une fois connecté⋅e, vous pourrez créer une équipe ou en rejoindre une déjà créée par l'un⋅e de vos camarades
|
||||
via un code d'accès qui vous aura été transmis. Vous serez ensuite invité⋅e à soumettre une autorisation de droit à l'image,
|
||||
indispensable au bon déroulement du 𝕋𝔽𝕁𝕄². Une fois que votre équipe comporte au moins 4 participant⋅es (maximum 6)
|
||||
et un⋅e encadrant⋅e, vous pourrez demander à valider votre équipe pour être apte à travailler sur les problèmes de votre choix.
|
||||
</p>
|
||||
|
||||
<h2>Je ne trouve pas d'équipe, aidez-moi !</h2>
|
||||
@ -72,13 +59,13 @@
|
||||
au 𝕋𝔽𝕁𝕄², basée sur le protocole <a href="https://matrix.org/">Matrix</a> et le client
|
||||
<a href="https://element.io">Element</a>. Cette interface de chat vous permet de communiquer avec les membres
|
||||
de votre équipe, mais surtout de pouvoir participer au tirage au sort et discuter avec les autres équipes et
|
||||
les organisateurs.
|
||||
les organisateur⋅rices.
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
Ce chat contient également un salon <code>#je-cherche-une-equipe</code> où vous pouvez crier à l'aide pour trouver
|
||||
une équipe, ou compléter la votre s'il vous manque des participants. C'est un petit coin auprès du feu, un parc à
|
||||
jeu où vous pouvez descendre en toboggan (en le désinfectant après utilisation), ne cherchez pas à être trop formel.
|
||||
une équipe, ou compléter la votre s'il vous manque des participant⋅es. C'est un petit coin auprès du feu,
|
||||
ne cherchez pas à être trop formel⋅le.
|
||||
</p>
|
||||
|
||||
<h2>J'ai une question</h2>
|
||||
|
@ -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
|
||||
ScholarshipView, SolutionView, SynthesisView, VaccineSheetView
|
||||
|
||||
from .views import AdminSearchView
|
||||
|
||||
@ -44,6 +44,8 @@ urlpatterns = [
|
||||
name='photo_authorization'),
|
||||
path('media/authorization/health/<str:filename>/', HealthSheetView.as_view(),
|
||||
name='health_sheet'),
|
||||
path('media/authorization/vaccine/<str:filename>/', VaccineSheetView.as_view(),
|
||||
name='vaccine_sheet'),
|
||||
path('media/authorization/parental/<str:filename>/', ParentalAuthorizationView.as_view(),
|
||||
name='parental_authorization'),
|
||||
path('media/authorization/scholarship/<str:filename>/', ScholarshipView.as_view(),
|
||||
|
21
tox.ini
@ -12,19 +12,18 @@ sitepackages = False
|
||||
deps =
|
||||
coverage
|
||||
Django>=3.2,<4.0
|
||||
django-address~=0.2
|
||||
django-bootstrap-datepicker-plus~=4.0
|
||||
django-crispy-forms~=1.9
|
||||
django-filter~=2.4
|
||||
django-haystack~=3.0
|
||||
django-phonenumber-field~=5.0.0
|
||||
django-polymorphic~=3.0
|
||||
django-crispy-forms~=1.14
|
||||
django-filter~=22.1
|
||||
django-haystack~=3.2
|
||||
django-phonenumber-field~=7.0
|
||||
django-polymorphic~=3.1
|
||||
django-tables2~=2.4
|
||||
djangorestframework~=3.12
|
||||
djangorestframework~=3.14
|
||||
django-rest-polymorphic~=0.1
|
||||
phonenumbers~=8.9.10
|
||||
PyPDF3~=1.0.2
|
||||
python-magic==0.4.22
|
||||
phonenumbers~=8.12.57
|
||||
pypdf~=3.4
|
||||
python-magic~=0.4.26
|
||||
requests~=2.28.1
|
||||
whoosh~=2.7
|
||||
commands =
|
||||
coverage run --source=apps,tfjm ./manage.py test apps/ tfjm/
|
||||
|