mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-21 23:18:25 +02:00
Compare commits
21 Commits
docs
...
1977ffdbc9
Author | SHA1 | Date | |
---|---|---|---|
1977ffdbc9 | |||
a0a282df15
|
|||
603ee76664
|
|||
147cbff7f5
|
|||
8878ae8d8d
|
|||
4c8347072c
|
|||
73ea3d1717
|
|||
e026f49f8d
|
|||
ea03bd314b
|
|||
c12972b718
|
|||
2a775cedc1
|
|||
9bf3b7dff0
|
|||
cf92c78d03
|
|||
38ceef7a54
|
|||
ec2fa43e20
|
|||
85b3da09f6
|
|||
2c15774185
|
|||
08ad4f3888
|
|||
872009894d
|
|||
fd7fe90fce
|
|||
2ad538f5cc
|
@ -3,7 +3,7 @@ FROM python:3.12-alpine
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV DJANGO_ALLOW_ASYNC_UNSAFE 1
|
||||
|
||||
RUN apk add --no-cache gettext nginx gcc git libc-dev libffi-dev libxml2-dev libxslt-dev npm postgresql-dev libmagic texlive texmf-dist-latexextra
|
||||
RUN apk add --no-cache gettext nginx gcc git libc-dev libffi-dev libxml2-dev libxslt-dev npm postgresql-dev libmagic texlive texmf-dist-fontsrecommended texmf-dist-langenglish texmf-dist-latexextra
|
||||
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils.translation import activate
|
||||
from participation.models import Team, Tournament
|
||||
@ -18,7 +19,7 @@ class Command(BaseCommand):
|
||||
help = "Create chat channels for tournaments and teams."
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
activate('fr')
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
# Création de canaux généraux, d'annonces, d'aide jurys et orgas, etc.
|
||||
# Le canal d'annonces est accessibles à tous⋅tes, mais seul⋅es les admins peuvent y écrire.
|
||||
|
@ -15,11 +15,16 @@
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap + Font Awesome CSS #}
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
{# Bootstrap CSS #}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
{# bootstrap-select CSS #}
|
||||
<link href="{% static "bootstrap-select/css/bootstrap-select.min.css" %}" rel="stylesheet" type="text/css">
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
{% javascript 'bootstrap' %}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
|
||||
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
|
||||
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}">
|
||||
|
@ -16,10 +16,13 @@
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap CSS #}
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
{% javascript 'bootstrap' %}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
|
||||
{# Webmanifest PWA permettant l'installation de l'application sur un écran d'accueil, pour navigateurs supportés #}
|
||||
<link rel="manifest" href="{% static "tfjm/chat.webmanifest" %}">
|
||||
|
@ -183,7 +183,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
# Create the draw
|
||||
draw = await Draw.objects.acreate(tournament=self.tournament)
|
||||
r1 = None
|
||||
for i in [1, 2]:
|
||||
for i in range(1, settings.NB_ROUNDS + 1):
|
||||
# Create the round
|
||||
r = await Round.objects.acreate(draw=draw, number=i)
|
||||
if i == 1:
|
||||
@ -532,17 +532,17 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
'visible': True})
|
||||
|
||||
# First send the second pool to have the good team order
|
||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r2.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r2.pool_set.order_by('letter').all()
|
||||
]})
|
||||
async for r in self.tournament.draw.round_set.filter(number__gte=2).all():
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r.pool_set.order_by('letter').all()
|
||||
]})
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r.number,
|
||||
@ -843,11 +843,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
"""
|
||||
msg = self.tournament.draw.last_message
|
||||
|
||||
if r.number == 1 and not self.tournament.final:
|
||||
if r.number < settings.NB_ROUNDS and not self.tournament.final:
|
||||
# Next round
|
||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
||||
self.tournament.draw.current_round = r2
|
||||
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
||||
next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
|
||||
self.tournament.draw.current_round = next_round
|
||||
msg += f"<br><br>Le tirage au sort du tour {r.number} est terminé."
|
||||
self.tournament.draw.last_message = msg
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
@ -866,20 +866,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
# Reorder dices
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r2.number,
|
||||
'round': next_round.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r2.pool_set.order_by('letter').all()
|
||||
async for pool in next_round.pool_set.order_by('letter').all()
|
||||
]})
|
||||
|
||||
# The passage order for the second round is already determined by the first round
|
||||
# Start the first pool of the second round
|
||||
p1: Pool = await r2.pool_set.filter(letter=1).aget()
|
||||
r2.current_pool = p1
|
||||
await r2.asave()
|
||||
p1: Pool = await next_round.pool_set.filter(letter=1).aget()
|
||||
next_round.current_pool = p1
|
||||
await next_round.asave()
|
||||
|
||||
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||
@ -929,7 +929,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
td.purposed = None
|
||||
await td.asave()
|
||||
|
||||
remaining = len(settings.PROBLEMS) - 5 - len(td.rejected)
|
||||
remaining = len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)
|
||||
|
||||
# Update messages
|
||||
trigram = td.participation.team.trigram
|
||||
@ -1372,32 +1372,36 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
'round': r.number,
|
||||
'team': td.participation.team.trigram,
|
||||
'problem': td.accepted})
|
||||
elif r.number == 2:
|
||||
elif r.number >= 2:
|
||||
if not self.tournament.final:
|
||||
# Go to the previous round
|
||||
r1 = await self.tournament.draw.round_set \
|
||||
.prefetch_related('current_pool__current_team__participation__team').aget(number=1)
|
||||
self.tournament.draw.current_round = r1
|
||||
previous_round = await self.tournament.draw.round_set \
|
||||
.prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
|
||||
self.tournament.draw.current_round = previous_round
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
async for td in r1.team_draws.prefetch_related('participation__team').all():
|
||||
async for td in previous_round.team_draws.prefetch_related('participation__team').all():
|
||||
await self.channel_layer.group_send(
|
||||
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
||||
'team': td.participation.team.trigram,
|
||||
'result': td.choice_dice})
|
||||
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||
'round': r1.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in r1.pool_set.order_by('letter').all()
|
||||
]})
|
||||
await self.channel_layer.group_send(
|
||||
f"tournament-{self.tournament.id}",
|
||||
{
|
||||
'tid': self.tournament_id,
|
||||
'type': 'draw.send_poules',
|
||||
'round': previous_round.number,
|
||||
'poules': [
|
||||
{
|
||||
'letter': pool.get_letter_display(),
|
||||
'teams': await pool.atrigrams(),
|
||||
}
|
||||
async for pool in previous_round.pool_set.order_by('letter').all()
|
||||
]
|
||||
})
|
||||
|
||||
previous_pool = r1.current_pool
|
||||
previous_pool = previous_round.current_pool
|
||||
|
||||
td = previous_pool.current_team
|
||||
td.purposed = td.accepted
|
||||
@ -1417,14 +1421,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
|
||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||
{'tid': self.tournament_id, 'type': 'draw.set_problem',
|
||||
'round': r1.number,
|
||||
'round': previous_round.number,
|
||||
'team': td.participation.team.trigram,
|
||||
'problem': td.accepted})
|
||||
else:
|
||||
# Don't continue the final tournament
|
||||
r1 = await self.tournament.draw.round_set \
|
||||
previous_round = await self.tournament.draw.round_set \
|
||||
.prefetch_related('current_pool__current_team__participation__team').aget(number=1)
|
||||
self.tournament.draw.current_round = r1
|
||||
self.tournament.draw.current_round = previous_round
|
||||
await self.tournament.draw.asave()
|
||||
|
||||
async for td in r.teamdraw_set.all():
|
||||
@ -1446,7 +1450,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
||||
]
|
||||
})
|
||||
|
||||
async for td in r1.team_draws.prefetch_related('participation__team').all():
|
||||
async for td in previous_round.team_draws.prefetch_related('participation__team').all():
|
||||
await self.channel_layer.group_send(
|
||||
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
||||
'team': td.participation.team.trigram,
|
||||
|
27
draw/migrations/0004_alter_round_number.py
Normal file
27
draw/migrations/0004_alter_round_number.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("draw", "0003_alter_teamdraw_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="round",
|
||||
name="number",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "Round 1"), (2, "Round 2")],
|
||||
help_text="The number of the round, 1 or 2 (or 3 for ETEAM)",
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(1),
|
||||
django.core.validators.MaxValueValidator(2),
|
||||
],
|
||||
verbose_name="number",
|
||||
),
|
||||
),
|
||||
]
|
@ -147,10 +147,10 @@ class Draw(models.Model):
|
||||
else:
|
||||
# The problem can be rejected
|
||||
s += "Elle peut décider d'accepter ou de refuser ce problème. "
|
||||
if len(td.rejected) >= len(settings.PROBLEMS) - 5:
|
||||
if len(td.rejected) >= len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT:
|
||||
s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
|
||||
else:
|
||||
s += f"Il reste {len(settings.PROBLEMS) - 5 - len(td.rejected)} refus sans pénalité."
|
||||
s += f"Il reste {len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)} refus sans pénalité."
|
||||
case 'WAITING_FINAL':
|
||||
# We are between the two rounds of the final tournament
|
||||
s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
|
||||
@ -193,10 +193,10 @@ class Round(models.Model):
|
||||
choices=[
|
||||
(1, _('Round 1')),
|
||||
(2, _('Round 2')),
|
||||
],
|
||||
] + ([] if settings.NB_ROUNDS == 2 else [(3, _('Round 3'))]),
|
||||
verbose_name=_('number'),
|
||||
help_text=_("The number of the round, 1 or 2"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(2)],
|
||||
help_text=_("The number of the round, 1 or 2 (or 3 for ETEAM)"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(settings.NB_ROUNDS)],
|
||||
)
|
||||
|
||||
current_pool = models.ForeignKey(
|
||||
@ -524,10 +524,10 @@ class TeamDraw(models.Model):
|
||||
@property
|
||||
def penalty_int(self):
|
||||
"""
|
||||
The number of penalties, which is the number of rejected problems after the P - 5 free rejects,
|
||||
where P is the number of problems.
|
||||
The number of penalties, which is the number of rejected problems after the P - 5 free rejects
|
||||
(P - 6 for ETEAM), where P is the number of problems.
|
||||
"""
|
||||
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - 5))
|
||||
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT))
|
||||
|
||||
@property
|
||||
def penalty(self):
|
||||
|
@ -4,6 +4,9 @@
|
||||
await Notification.requestPermission()
|
||||
})()
|
||||
|
||||
// TODO ETEAM Mieux paramétriser (5 pour le TFJM², 6 pour l'ETEAM)
|
||||
const RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||
|
||||
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
||||
|
||||
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
||||
@ -308,7 +311,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Set the different pools for the given round, and update the interface.
|
||||
* @param tid The tournament id
|
||||
* @param round The round number, as integer (1 or 2)
|
||||
* @param round The round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param poules The list of poules, which are represented with their letters and trigrams,
|
||||
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
|
||||
*/
|
||||
@ -430,7 +433,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Update the table for the given round and the given pool, where there will be the chosen problems.
|
||||
* @param tid The tournament id
|
||||
* @param round The round number, as integer (1 or 2)
|
||||
* @param round The round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param poule The current pool, which id represented with its letter and trigrams,
|
||||
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
|
||||
*/
|
||||
@ -587,7 +590,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Highlight the team that is currently choosing its problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round number, as integer (1 or 2)
|
||||
* @param round The current round number, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param pool The current pool letter (A, B, C or D) (null if non-relevant)
|
||||
* @param team The current team trigram (null if non-relevant)
|
||||
*/
|
||||
@ -624,7 +627,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Update the recap and the table when a team accepts a problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param team The current team trigram
|
||||
* @param problem The accepted problem, as integer
|
||||
*/
|
||||
@ -648,7 +651,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* Update the recap when a team rejects a problem.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param team The current team trigram
|
||||
* @param rejected The full list of rejected problems
|
||||
*/
|
||||
@ -658,15 +661,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
|
||||
|
||||
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
||||
if (rejected.length > problems_count - 5) {
|
||||
if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
|
||||
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
|
||||
// This is P - 6 for the ETEAM
|
||||
if (penaltyDiv === null) {
|
||||
penaltyDiv = document.createElement('div')
|
||||
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
|
||||
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
|
||||
recapDiv.parentNode.append(penaltyDiv)
|
||||
}
|
||||
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - 5))} %`
|
||||
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %`
|
||||
} else {
|
||||
// Eventually remove this div
|
||||
if (penaltyDiv !== null)
|
||||
@ -678,7 +682,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
* For a 5-teams pool, we may reorder the pool if two teams select the same problem.
|
||||
* Then, we redraw the table and set the accepted problems.
|
||||
* @param tid The tournament id
|
||||
* @param round The current round, as integer (1 or 2)
|
||||
* @param round The current round, as integer (1 or 2, or 3 for ETEAM)
|
||||
* @param poule The pool represented by its letter
|
||||
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
|
||||
* @param problems The accepted problems in the same order than the teams, [1, 1, 2, 2, 3]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -60,6 +60,7 @@ class TournamentSerializer(serializers.ModelSerializer):
|
||||
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
||||
'description', 'organizers', 'final', 'participations',)
|
||||
|
||||
|
||||
|
@ -66,6 +66,7 @@ class TournamentViewSet(ModelViewSet):
|
||||
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
||||
'description', 'organizers', 'final', ]
|
||||
|
||||
|
||||
|
@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
import pandas
|
||||
from pypdf import PdfReader
|
||||
from registration.models import VolunteerRegistration
|
||||
from tfjm import settings
|
||||
|
||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||
|
||||
@ -74,6 +75,12 @@ class ParticipationForm(forms.ModelForm):
|
||||
"""
|
||||
Form to update the problem of a team participation.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if settings.TFJM_APP == "ETEAM":
|
||||
# One single tournament only
|
||||
del self.fields['tournament']
|
||||
|
||||
class Meta:
|
||||
model = Participation
|
||||
fields = ('tournament', 'final',)
|
||||
@ -125,6 +132,15 @@ class ValidateParticipationForm(forms.Form):
|
||||
|
||||
|
||||
class TournamentForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if settings.NB_ROUNDS < 3:
|
||||
del self.fields['date_third_phase']
|
||||
del self.fields['solutions_available_third_phase']
|
||||
del self.fields['syntheses_third_phase_limit']
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
del self.fields['price']
|
||||
|
||||
class Meta:
|
||||
model = Tournament
|
||||
exclude = ('notes_sheet_id', )
|
||||
@ -134,12 +150,15 @@ class TournamentForm(forms.ModelForm):
|
||||
'inscription_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
||||
'solution_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
||||
'solutions_draw': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%d %H:%M'),
|
||||
'date_first_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'solutions_available_second_phase': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'date_second_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'date_third_phase': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||
format='%Y-%m-%d %H:%M'),
|
||||
'organizers': forms.SelectMultiple(attrs={
|
||||
'class': 'selectpicker',
|
||||
'data-live-search': 'true',
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2021 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import activate
|
||||
@ -9,7 +10,7 @@ from participation.models import Tournament
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
activate('fr')
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
tournaments = Tournament.objects.order_by('-date_start', 'name')
|
||||
for tournament in tournaments:
|
||||
|
@ -11,7 +11,7 @@ from participation.models import Solution, Tournament
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **kwargs):
|
||||
activate('fr')
|
||||
activate(settings.PROBLEMS)
|
||||
|
||||
base_dir = Path(__file__).parent.parent.parent.parent
|
||||
base_dir /= "output"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
from participation.models import Team, Tournament
|
||||
@ -13,6 +13,9 @@ class Command(BaseCommand):
|
||||
"""
|
||||
Create Sympa mailing lists and register teams.
|
||||
"""
|
||||
if not settings.ML_MANAGEMENT:
|
||||
return
|
||||
|
||||
sympa = get_sympa_client()
|
||||
|
||||
sympa.create_list("equipes", "Equipes du TFJM2", "hotline",
|
||||
|
@ -12,7 +12,7 @@ from ...models import Passage, Tournament
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
activate('fr')
|
||||
activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
try:
|
||||
spreadsheet = gc.open("Tableau des deuxièmes", folder_id=settings.NOTES_DRIVE_FOLDER_ID)
|
||||
|
31
participation/migrations/0014_alter_team_trigram.py
Normal file
31
participation/migrations/0014_alter_team_trigram.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0013_alter_pool_options_pool_room"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="trigram",
|
||||
field=models.CharField(
|
||||
help_text="The code must be composed of 3 uppercase letters.",
|
||||
max_length=3,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.core.validators.RegexValidator("^[A-Z]{3}$"),
|
||||
django.core.validators.RegexValidator(
|
||||
"^(?!BIT$|CNO$|CRO$|CUL$|FTG$|FCK$|FUC$|FUK$|FYS$|HIV$|IST$|MST$|KKK$|KYS$|SEX$)",
|
||||
message="This team code is forbidden.",
|
||||
),
|
||||
],
|
||||
verbose_name="code",
|
||||
),
|
||||
),
|
||||
]
|
@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 13:51
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0014_alter_team_trigram"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_second_phase",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_second_phase",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="check this case when solutions for the second round become available",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="solutions_available_third_phase",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="check this case when solutions for the third round become available",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="syntheses_third_phase_limit",
|
||||
field=models.DateTimeField(
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="limit date to upload the syntheses for the third phase",
|
||||
),
|
||||
)
|
||||
]
|
@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 14:01
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("participation", "0015_tournament_solutions_available_third_phase_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_first_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="first phase date"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_second_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="first second date"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tournament",
|
||||
name="date_third_phase",
|
||||
field=models.DateField(
|
||||
default=datetime.date.today, verbose_name="third phase date"
|
||||
),
|
||||
),
|
||||
]
|
@ -1,6 +1,6 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import math
|
||||
from datetime import date, timedelta
|
||||
import os
|
||||
|
||||
@ -37,14 +37,15 @@ class Team(models.Model):
|
||||
)
|
||||
|
||||
trigram = models.CharField(
|
||||
max_length=3,
|
||||
verbose_name=_("trigram"),
|
||||
help_text=_("The trigram must be composed of three uppercase letters."),
|
||||
max_length=settings.TEAM_CODE_LENGTH,
|
||||
verbose_name=_("code"),
|
||||
help_text=format_lazy(_("The code must be composed of {nb_letters} uppercase letters."),
|
||||
nb_letters=settings.TEAM_CODE_LENGTH),
|
||||
unique=True,
|
||||
validators=[
|
||||
RegexValidator(r"^[A-Z]{3}$"),
|
||||
RegexValidator("^[A-Z]{" + str(settings.TEAM_CODE_LENGTH) + "}$"),
|
||||
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
|
||||
message=_("This trigram is forbidden.")),
|
||||
message=_("This team code is forbidden.")),
|
||||
],
|
||||
)
|
||||
|
||||
@ -80,12 +81,12 @@ class Team(models.Model):
|
||||
return False
|
||||
if any(not r.photo_authorization for r in self.participants.all()):
|
||||
return False
|
||||
if not self.motivation_letter:
|
||||
if settings.MOTIVATION_LETTER_REQUIRED and not self.motivation_letter:
|
||||
return False
|
||||
if not self.participation.tournament.remote:
|
||||
if any(r.under_18 and not r.health_sheet for r in self.students.all()):
|
||||
if settings.HEALTH_SHEET_REQUIRED and any(r.under_18 and not r.health_sheet for r in self.students.all()):
|
||||
return False
|
||||
if any(r.under_18 and not r.vaccine_sheet for r in self.students.all()):
|
||||
if settings.VACCINE_SHEET_REQUIRED and any(r.under_18 and not r.vaccine_sheet for r in self.students.all()):
|
||||
return False
|
||||
if any(r.under_18 and not r.parental_authorization for r in self.students.all()):
|
||||
return False
|
||||
@ -118,7 +119,7 @@ class Team(models.Model):
|
||||
'content': content,
|
||||
})
|
||||
|
||||
if not self.motivation_letter:
|
||||
if settings.MOTIVATION_LETTER_REQUIRED and not self.motivation_letter:
|
||||
text = _("The team {trigram} has not uploaded a motivation letter. "
|
||||
"You can upload your motivation letter using <a href='{url}'>this link</a>.")
|
||||
url = reverse_lazy("participation:upload_team_motivation_letter", args=(self.pk,))
|
||||
@ -238,7 +239,8 @@ class Team(models.Model):
|
||||
if not self.access_code:
|
||||
# if the team got created, generate the access code, create the contact mailing list
|
||||
self.access_code = get_random_string(6)
|
||||
self.create_mailing_list()
|
||||
if settings.ML_MANAGEMENT:
|
||||
self.create_mailing_list()
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@ -309,14 +311,24 @@ class Tournament(models.Model):
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
date_first_phase = models.DateField(
|
||||
verbose_name=_("first phase date"),
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
syntheses_first_phase_limit = models.DateTimeField(
|
||||
verbose_name=_("limit date to upload the syntheses for the first phase"),
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
solutions_available_second_phase = models.DateTimeField(
|
||||
verbose_name=_("date when the solutions for the second round become available"),
|
||||
default=timezone.now,
|
||||
date_second_phase = models.DateField(
|
||||
verbose_name=_("first second date"),
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
solutions_available_second_phase = models.BooleanField(
|
||||
verbose_name=_("check this case when solutions for the second round become available"),
|
||||
default=False,
|
||||
)
|
||||
|
||||
syntheses_second_phase_limit = models.DateTimeField(
|
||||
@ -324,6 +336,21 @@ class Tournament(models.Model):
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
date_third_phase = models.DateField(
|
||||
verbose_name=_("third phase date"),
|
||||
default=date.today,
|
||||
)
|
||||
|
||||
solutions_available_third_phase = models.BooleanField(
|
||||
verbose_name=_("check this case when solutions for the third round become available"),
|
||||
default=False,
|
||||
)
|
||||
|
||||
syntheses_third_phase_limit = models.DateTimeField(
|
||||
verbose_name=_("limit date to upload the syntheses for the third phase"),
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_("description"),
|
||||
blank=True,
|
||||
@ -431,7 +458,7 @@ class Tournament(models.Model):
|
||||
self.save()
|
||||
|
||||
def update_ranking_spreadsheet(self): # noqa: C901
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.notes_sheet_id)
|
||||
@ -900,6 +927,49 @@ class Participation(models.Model):
|
||||
for ext in ["pdf", "tex", "odt", "docx"])
|
||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
||||
|
||||
content = defender_content + opponent_content + reporter_content + syntheses_templates_content
|
||||
informations.append({
|
||||
'title': _("Second round"),
|
||||
'type': "info",
|
||||
'priority': 1,
|
||||
'content': content,
|
||||
})
|
||||
elif settings.TFJM_APP == "ETEAM" \
|
||||
and timezone.now() <= tournament.syntheses_third_phase_limit + timedelta(hours=2):
|
||||
defender_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, defender=self)
|
||||
opponent_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, opponent=self)
|
||||
reporter_passage = Passage.objects.get(pool__tournament=self.tournament, pool__round=3, reporter=self)
|
||||
|
||||
defender_text = _("<p>For the third round, you will defend "
|
||||
"<a href='{solution_url}'>your solution of the problem {problem}</a>.</p>")
|
||||
draw_url = reverse_lazy("draw:index")
|
||||
solution_url = defender_passage.defended_solution.file.url
|
||||
defender_content = format_lazy(defender_text, draw_url=draw_url,
|
||||
solution_url=solution_url, problem=defender_passage.solution_number)
|
||||
|
||||
opponent_text = _("<p>You will oppose the solution of the team {opponent} on the "
|
||||
"<a href='{solution_url}'>problem {problem}</a>. "
|
||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
||||
solution_url = opponent_passage.defended_solution.file.url
|
||||
passage_url = reverse_lazy("participation:passage_detail", args=(opponent_passage.pk,))
|
||||
opponent_content = format_lazy(opponent_text, opponent=opponent_passage.defender.team.trigram,
|
||||
solution_url=solution_url,
|
||||
problem=opponent_passage.solution_number, passage_url=passage_url)
|
||||
|
||||
reporter_text = _("<p>You will report the solution of the team {reporter} on the "
|
||||
"<a href='{solution_url}'>problem {problem}. "
|
||||
"You can upload your synthesis sheet on <a href='{passage_url}'>this page</a>.</p>")
|
||||
solution_url = reporter_passage.defended_solution.file.url
|
||||
passage_url = reverse_lazy("participation:passage_detail", args=(reporter_passage.pk,))
|
||||
reporter_content = format_lazy(reporter_text, reporter=reporter_passage.defender.team.trigram,
|
||||
solution_url=solution_url,
|
||||
problem=reporter_passage.solution_number, passage_url=passage_url)
|
||||
|
||||
syntheses_template_begin = f"{settings.STATIC_URL}Fiche_synthèse."
|
||||
syntheses_templates = " — ".join(f"<a href='{syntheses_template_begin}{ext}'>{ext.upper()}</a>"
|
||||
for ext in ["pdf", "tex", "odt", "docx"])
|
||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
||||
|
||||
content = defender_content + opponent_content + reporter_content + syntheses_templates_content
|
||||
informations.append({
|
||||
'title': _("Second round"),
|
||||
@ -940,7 +1010,7 @@ class Pool(models.Model):
|
||||
choices=[
|
||||
(1, format_lazy(_("Round {round}"), round=1)),
|
||||
(2, format_lazy(_("Round {round}"), round=2)),
|
||||
]
|
||||
] + ([] if settings.NB_ROUNDS == 2 else [(3, format_lazy(_("Round {round}"), round=3))]),
|
||||
)
|
||||
|
||||
letter = models.PositiveSmallIntegerField(
|
||||
@ -1010,12 +1080,16 @@ class Pool(models.Model):
|
||||
def solutions(self):
|
||||
return [passage.defended_solution for passage in self.passages.all()]
|
||||
|
||||
@property
|
||||
def coeff(self):
|
||||
return 1 if self.round <= 2 else math.pi - 2
|
||||
|
||||
def average(self, participation):
|
||||
return sum(passage.average(participation) for passage in self.passages.all()) \
|
||||
return self.coeff * sum(passage.average(participation) for passage in self.passages.all()) \
|
||||
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
|
||||
|
||||
async def aaverage(self, participation):
|
||||
return sum([passage.average(participation) async for passage in self.passages.all()]) \
|
||||
return self.coeff * sum([passage.average(participation) async for passage in self.passages.all()]) \
|
||||
+ sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()])
|
||||
|
||||
def get_absolute_url(self):
|
||||
@ -1027,7 +1101,7 @@ class Pool(models.Model):
|
||||
return super().validate_constraints()
|
||||
|
||||
def update_spreadsheet(self): # noqa: C901
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
# Create tournament sheet if it does not exist
|
||||
self.tournament.create_spreadsheet()
|
||||
@ -1372,7 +1446,7 @@ class Pool(models.Model):
|
||||
worksheet.client.batch_update(spreadsheet.id, body)
|
||||
|
||||
def update_juries_lines_spreadsheet(self):
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
spreadsheet = gc.open_by_key(self.tournament.notes_sheet_id)
|
||||
@ -1393,7 +1467,7 @@ class Pool(models.Model):
|
||||
max_row += 1
|
||||
|
||||
def parse_spreadsheet(self):
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
self.tournament.create_spreadsheet()
|
||||
@ -1763,7 +1837,7 @@ class Note(models.Model):
|
||||
if not self.has_any_note():
|
||||
return
|
||||
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
gc = gspread.service_account_from_dict(settings.GOOGLE_SERVICE_CLIENT)
|
||||
passage = Passage.objects.prefetch_related('pool__tournament', 'pool__participations').get(pk=self.passage.pk)
|
||||
|
@ -1,7 +1,9 @@
|
||||
# Copyright (C) 2020 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import Union
|
||||
|
||||
from django.conf import settings
|
||||
from participation.models import Note, Participation, Passage, Pool, Team, Tournament
|
||||
from registration.models import Payment
|
||||
from tfjm.lists import get_sympa_client
|
||||
@ -13,6 +15,8 @@ def create_team_participation(instance, created, raw, **_):
|
||||
"""
|
||||
if not raw:
|
||||
participation = Participation.objects.get_or_create(team=instance)[0]
|
||||
if settings.TFJM_APP == "ETEAM":
|
||||
participation.tournament = Tournament.objects.first()
|
||||
participation.save()
|
||||
if not created:
|
||||
participation.team.create_mailing_list()
|
||||
@ -22,7 +26,7 @@ def update_mailing_list(instance: Team, raw, **_):
|
||||
"""
|
||||
When a team name or trigram got updated, update mailing lists
|
||||
"""
|
||||
if instance.pk and not raw:
|
||||
if instance.pk and not raw and settings.ML_MANAGEMENT:
|
||||
old_team = Team.objects.get(pk=instance.pk)
|
||||
if old_team.trigram != instance.trigram:
|
||||
# Delete old mailing list, create a new one
|
||||
@ -41,7 +45,7 @@ def create_payments(instance: Participation, created, raw, **_):
|
||||
"""
|
||||
When a participation got created, create an associated payment.
|
||||
"""
|
||||
if instance.valid and not raw:
|
||||
if instance.valid and not raw and settings.PAYMENT_MANAGEMENT:
|
||||
for student in instance.team.students.all():
|
||||
payment_qs = Payment.objects.filter(registrations=student, final=False)
|
||||
if payment_qs.exists():
|
||||
|
@ -73,32 +73,36 @@
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if not team.participation.tournament.remote %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.health_sheet %}
|
||||
<a href="{{ student.health_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% if not team.participation.tournament.remote %}
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheets:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% for student in team.students.all %}
|
||||
{% if student.under_18 %}
|
||||
{% if student.health_sheet %}
|
||||
<a href="{{ student.health_sheet.url }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% 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 }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% 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 }}">{{ student }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% else %}
|
||||
{{ student }} ({% trans "Not uploaded yet" %}){% if not forloop.last %},{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Parental authorizations:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
@ -129,17 +133,19 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Motivation letter:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if team.motivation_letter %}
|
||||
<a href="{{ team.motivation_letter.url }}">{% trans "Download" %}</a>
|
||||
{% else %}
|
||||
<em>{% trans "Not uploaded yet" %}</em>
|
||||
{% endif %}
|
||||
{% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadMotivationLetterModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Motivation letter:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if team.motivation_letter %}
|
||||
<a href="{{ team.motivation_letter.url }}">{% trans "Download" %}</a>
|
||||
{% else %}
|
||||
<em>{% trans "Not uploaded yet" %}</em>
|
||||
{% endif %}
|
||||
{% if user.registration.team == team and not user.registration.team.participation.valid or user.registration.is_admin %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadMotivationLetterModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if user.registration.is_volunteer %}
|
||||
{% if user.registration in self.team.participation.tournament.organizers or user.registration.is_admin %}
|
||||
@ -234,10 +240,12 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% trans "Upload motivation letter" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
{% trans "Upload motivation letter" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% url "participation:upload_team_motivation_letter" pk=team.pk as modal_action %}
|
||||
{% include "base_modal.html" with modal_id="uploadMotivationLetter" modal_enctype="multipart/form-data" %}
|
||||
{% endif %}
|
||||
|
||||
{% trans "Update team" as modal_title %}
|
||||
{% trans "Update" as modal_button %}
|
||||
@ -253,7 +261,9 @@
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initModal("uploadMotivationLetter", "{% url "participation:upload_team_motivation_letter" pk=team.pk %}")
|
||||
{% if TFJM.MOTIVATION_LETTER_REQUIRED %}
|
||||
initModal("uploadMotivationLetter", "{% url "participation:upload_team_motivation_letter" pk=team.pk %}")
|
||||
{% endif %}
|
||||
initModal("updateTeam", "{% url "participation:update_team" pk=team.pk %}")
|
||||
initModal("leaveTeam", "{% url "participation:team_leave" %}")
|
||||
})
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
||||
\vspace{3mm}
|
||||
Tour {{ pool.round }} \;-- Poule {{ pool.get_letter_display }}{% if pool.participations.count == 5 %} \;-- {{ pool.get_room_display }}{% endif %} \;-- {% if pool.round == 1 %}{{ pool.tournament.date_start }}{% else %}{{ pool.tournament.date_end }}{% endif %}
|
||||
Tour {{ pool.round }} \;-- Poule {{ pool.get_letter_display }}{% if pool.participations.count == 5 %} \;-- {{ pool.get_room_display }}{% endif %} \;-- {% if pool.round == 1 %}{{ pool.tournament.date_first_phase }}{% elif pool.round == 2 %}{{ pool.tournament.date_second_phase }}{% else %}{{ pool.tournament.date_third_phase }}{% endif %}
|
||||
|
||||
|
||||
\vspace{15mm}
|
||||
|
@ -18,8 +18,10 @@
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'place'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.place }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'price'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{% if tournament.price %}{{ tournament.price }} €{% else %}{% trans "Free" %}{% endif %}</dd>
|
||||
{% if TFJM.PAYMENT_MANAGEMENT %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'price'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{% if tournament.price %}{{ tournament.price }} €{% else %}{% trans "Free" %}{% endif %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'remote'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.remote|yesno }}</dd>
|
||||
@ -39,23 +41,27 @@
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.syntheses_first_phase_limit }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date when solutions of round 2 are available'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.solutions_available_second_phase }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the second round'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.syntheses_second_phase_limit }}</dd>
|
||||
|
||||
{% if TFJM.APP == "ETEAM" %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the third round'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.syntheses_third_phase_limit }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
|
||||
<dd class="col-sm-6">{{ tournament.description }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact organizers' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.organizers_email }}">{{ tournament.organizers_email }}</a></dd>
|
||||
{% if TFJM.ML_MANAGEMENT %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact organizers' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.organizers_email }}">{{ tournament.organizers_email }}</a></dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact juries' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.jurys_email }}">{{ tournament.jurys_email }}</a></dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact juries' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.jurys_email }}">{{ tournament.jurys_email }}</a></dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact valid teams' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.teams_email }}">{{ tournament.teams_email }}</a></dd>
|
||||
<dt class="col-sm-6 text-sm-end">{% trans 'To contact valid teams' %}</dt>
|
||||
<dd class="col-sm-6"><a href="mailto:{{ tournament.teams_email }}">{{ tournament.teams_email }}</a></dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@ -76,12 +82,14 @@
|
||||
{% render_table teams %}
|
||||
</div>
|
||||
|
||||
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
||||
<div class="text-center">
|
||||
<a href="{% url "participation:tournament_payments" pk=tournament.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-money-bill-wave"></i> {% trans "Access to payments list" %}
|
||||
</a>
|
||||
</div>
|
||||
{% if TFJM.PAYMENT_MANAGEMENT %}
|
||||
{% if user.registration.is_admin or user.registration in tournament.organizers.all %}
|
||||
<div class="text-center">
|
||||
<a href="{% url "participation:tournament_payments" pk=tournament.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-money-bill-wave"></i> {% trans "Access to payments list" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if pools.data %}
|
||||
|
@ -674,7 +674,7 @@ class TestPayment(TestCase):
|
||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
||||
data={'type': "bank_transfer",
|
||||
'additional_information': "This is a bank transfer",
|
||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
||||
'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
|
||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
||||
payment.refresh_from_db()
|
||||
self.assertIsNone(payment.valid)
|
||||
@ -735,7 +735,7 @@ class TestPayment(TestCase):
|
||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
||||
data={'type': "scholarship",
|
||||
'additional_information': "I don't have to pay because I have a scholarship",
|
||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
||||
'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
|
||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
||||
payment.refresh_from_db()
|
||||
self.assertIsNone(payment.valid)
|
||||
|
@ -711,6 +711,7 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
|
||||
'Adresse': registration.address,
|
||||
'Code postal': registration.zip_code,
|
||||
'Ville': registration.city,
|
||||
'Pays': registration.country,
|
||||
'Téléphone': registration.phone_number,
|
||||
'Classe': registration.get_student_class_display() if registration.is_student
|
||||
else registration.last_degree,
|
||||
|
@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -103,12 +104,15 @@ class StudentRegistrationForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["birth_date"].widget = forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d')
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = StudentRegistration
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
'school', 'health_issues', 'housing_constraints', 'responsible_name', 'responsible_phone',
|
||||
'responsible_email', 'give_contact_to_animath', 'email_confirmed',)
|
||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'country',
|
||||
'phone_number', 'school', 'health_issues', 'housing_constraints',
|
||||
'responsible_name', 'responsible_phone', 'responsible_email', 'give_contact_to_animath',
|
||||
'email_confirmed',)
|
||||
|
||||
|
||||
class PhotoAuthorizationForm(forms.ModelForm):
|
||||
@ -247,9 +251,14 @@ class CoachRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
A coach can tell its professional activity.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = CoachRegistration
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
|
||||
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
||||
'give_contact_to_animath', 'email_confirmed',)
|
||||
|
||||
@ -258,6 +267,11 @@ class VolunteerRegistrationForm(forms.ModelForm):
|
||||
"""
|
||||
A volunteer can also tell its professional activity.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not settings.SUGGEST_ANIMATH:
|
||||
del self.fields["give_contact_to_animath"]
|
||||
|
||||
class Meta:
|
||||
model = VolunteerRegistration
|
||||
fields = ('professional_activity', 'admin', 'give_contact_to_animath', 'email_confirmed',)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from ...models import Payment
|
||||
@ -15,6 +16,9 @@ class Command(BaseCommand):
|
||||
help = "Vérifie si les paiements Hello Asso initiés sont validés ou non. Si oui, valide les inscriptions."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
return
|
||||
|
||||
for payment in Payment.objects.exclude(valid=True).filter(checkout_intent_id__isnull=False).all():
|
||||
checkout_intent = payment.get_checkout_intent()
|
||||
if checkout_intent is not None and 'order' in checkout_intent:
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2024 by Animath
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from ...models import Payment
|
||||
@ -13,5 +14,8 @@ class Command(BaseCommand):
|
||||
help = "Envoie un mail de rappel à toustes les participant⋅es qui n'ont pas encore payé ou déclaré de paiement."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not settings.PAYMENT_MANAGEMENT:
|
||||
return
|
||||
|
||||
for payment in Payment.objects.filter(valid=False).filter(registrations__team__participation__valid=True).all():
|
||||
payment.send_remind_mail()
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"registration",
|
||||
"0013_participantregistration_photo_authorization_final_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="participantregistration",
|
||||
name="country",
|
||||
field=models.CharField(
|
||||
default="France", max_length=255, verbose_name="country"
|
||||
),
|
||||
),
|
||||
]
|
@ -3,6 +3,7 @@
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import send_mail
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
@ -183,6 +184,12 @@ class ParticipantRegistration(Registration):
|
||||
verbose_name=_("city"),
|
||||
)
|
||||
|
||||
country = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("country"),
|
||||
default="France",
|
||||
)
|
||||
|
||||
phone_number = PhoneNumberField(
|
||||
verbose_name=_("phone number"),
|
||||
blank=True,
|
||||
@ -301,7 +308,7 @@ class ParticipantRegistration(Registration):
|
||||
"""
|
||||
The team is selected for final.
|
||||
"""
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = "[TFJM²] " + str(_("Team selected for the final tournament"))
|
||||
site = Site.objects.first()
|
||||
from participation.models import Tournament
|
||||
@ -419,7 +426,7 @@ class StudentRegistration(ParticipantRegistration):
|
||||
'priority': 5,
|
||||
'content': content,
|
||||
})
|
||||
if not self.health_sheet:
|
||||
if settings.HEALTH_SHEET_REQUIRED and not self.health_sheet:
|
||||
text = _("You have not uploaded your health sheet. "
|
||||
"You can do it by clicking on <a href=\"{health_url}\">this link</a>.")
|
||||
health_url = reverse_lazy("registration:upload_user_health_sheet", args=(self.id,))
|
||||
@ -430,7 +437,7 @@ class StudentRegistration(ParticipantRegistration):
|
||||
'priority': 5,
|
||||
'content': content,
|
||||
})
|
||||
if not self.vaccine_sheet:
|
||||
if settings.VACCINE_SHEET_REQUIRED and not self.vaccine_sheet:
|
||||
text = _("You have not uploaded your vaccine sheet. "
|
||||
"You can do it by clicking on <a href=\"{vaccine_url}\">this link</a>.")
|
||||
vaccine_url = reverse_lazy("registration:upload_user_vaccine_sheet", args=(self.id,))
|
||||
@ -795,7 +802,7 @@ class Payment(models.Model):
|
||||
return checkout_intent
|
||||
|
||||
def send_remind_mail(self):
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = "[TFJM²] " + str(_("Reminder for your payment"))
|
||||
site = Site.objects.first()
|
||||
for registration in self.registrations.all():
|
||||
@ -806,7 +813,7 @@ class Payment(models.Model):
|
||||
registration.user.email_user(subject, message, html_message=html)
|
||||
|
||||
def send_helloasso_payment_confirmation_mail(self):
|
||||
translation.activate('fr')
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
subject = "[TFJM²] " + str(_("Payment confirmation"))
|
||||
site = Site.objects.first()
|
||||
for registration in self.registrations.all():
|
||||
|
@ -13,7 +13,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||
{% trans "You recently registered on the ETEAM platform. Please click on the link below to confirm your registration." %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@ -36,5 +36,5 @@
|
||||
|
||||
--
|
||||
<p>
|
||||
{% trans "The TFJM² team." %}<br>
|
||||
{% trans "The ETEAM team." %}<br>
|
||||
</p>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{% trans "Hi" %} {{ user.registration }},
|
||||
|
||||
{% trans "You recently registered on the TFJM² platform. Please click on the link below to confirm your registration." %}
|
||||
{% trans "You recently registered on the ETEAM platform. Please click on the link below to confirm your registration." %}
|
||||
|
||||
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
|
||||
|
||||
@ -12,4 +12,4 @@ https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=toke
|
||||
|
||||
{% trans "Thanks" %},
|
||||
|
||||
{% trans "The TFJM² team." %}
|
||||
{% trans "The ETEAM team." %}
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/logo_animath.png}\hfill{\fontsize{55pt}{55pt}{$\mathbb{TFJM}^2$}}
|
||||
|
||||
\vfill
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
% \lfoot{\footnotesize Address}
|
||||
% \rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}ETEAM Tournament}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
\Large \bf Parental authorisation for minors
|
||||
\end{center}
|
||||
|
||||
I, \hrulefill,\\
|
||||
legal representative, residing at \writingsep\hrulefill\\
|
||||
\writingsep\hrulefill,\\
|
||||
\writingsep autorise {{ registration|default:"\hrulefill" }},\\
|
||||
born on {{ registration.birth_date }},
|
||||
to participate in the European Tournament of Enthusiastic Apprentice Mathematicians (ETEAM) organised in:
|
||||
{{ tournament.place }}, from {{ tournament.date_start }} to {{ tournament.date_end }}.
|
||||
|
||||
The participant will travel to the abovementioned location on Monday morning and will leave the premises on Friday afternoon by independant means and under the responsibility of the legal representative.
|
||||
|
||||
|
||||
\vspace{8ex}
|
||||
|
||||
Signature:
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
|
||||
\end{document}
|
@ -0,0 +1,112 @@
|
||||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
%\lfoot{\footnotesize Address}
|
||||
%\rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}{ETEAM Tournament}}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
|
||||
|
||||
\LARGE
|
||||
Video and interview consent and release form
|
||||
\end{center}
|
||||
\normalsize
|
||||
|
||||
|
||||
\thispagestyle{empty}
|
||||
|
||||
\bigskip
|
||||
|
||||
|
||||
|
||||
I, {{ registration|safe|default:"\dotfill" }}\\
|
||||
residing at {{ registration.address|safe|default:"\dotfill" }} {{ registration.zip_code|safe|default:"" }} {{ registration.city|safe|default:"" }}
|
||||
{{ registration.country|safe|default:"" }},\\
|
||||
|
||||
\medskip
|
||||
Tick the appropriate box(es).\\
|
||||
|
||||
\medskip
|
||||
|
||||
\fbox{\textcolor{white}{A}} Authorise the ETEAM organizers, for the ETEAM tournament from {{ tournament.date_start }} to {{ tournament.date_end }} in: {{ tournament.place }}, to photograph or film me and to distribute the photos and/or videos taken on this occasion on its website and on partner websites. I hereby grant ETEAM the right to use my image free of charge on all its information media: brochures, websites, social networks. ETEAM hereby becomes the assignee of the rights for these photographs. There is no time limit on the validity of this release nor are there any geographic limitations on where these materials may be distributed.\\
|
||||
|
||||
\medskip
|
||||
ETEAM commits itself, in accordance with the legal regulations in force relating to image rights, to ensuring that the publication and distribution of the image as well as the accompanying comments do not infringe on the private life, dignity and reputation of the person photographed.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} Authorise the broadcasting in the media (Press, Television, Internet) of photographs taken during any media coverage of this event.\\
|
||||
\medskip
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} By signing this form, I acknowledge that I have completely read and fully understand the above consent and release and agree to be bound thereby. I hereby release any and all claims against any person or organisation utilising this material for marketing, educational, promotional, and/or any other lawful purpose whatsoever.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} I agree to be kept informed of other activities organised by ETEAM and its partners.\\
|
||||
\bigskip
|
||||
|
||||
Signature preceded by the words "read and approved"
|
||||
\medskip
|
||||
|
||||
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{Legal representative:}\\
|
||||
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{The participant:}\\
|
||||
|
||||
|
||||
\end{minipage}
|
||||
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
% \footnotesize Address
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
\footnotesize
|
||||
% \begin{flushright}
|
||||
% todo association
|
||||
% \end{flushright}
|
||||
\end{minipage}
|
||||
\end{document}
|
@ -0,0 +1,112 @@
|
||||
\documentclass[a4paper,11pt]{article}
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage[english]{babel}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
%\usepackage{anyfontsize}
|
||||
\usepackage{fancybox}
|
||||
\usepackage{eso-pic,graphicx}
|
||||
\usepackage{xcolor}
|
||||
|
||||
|
||||
% Specials
|
||||
\newcommand{\writingsep}{\vrule height 4ex width 0pt}
|
||||
|
||||
% Page formating
|
||||
\hoffset -1in
|
||||
\voffset -1in
|
||||
\textwidth 180 mm
|
||||
\textheight 250 mm
|
||||
\oddsidemargin 15mm
|
||||
\evensidemargin 15mm
|
||||
\pagestyle{fancy}
|
||||
|
||||
% Headers and footers
|
||||
\fancyfoot{}
|
||||
\lhead{}
|
||||
\rhead{}
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
%\lfoot{\footnotesize Address}
|
||||
%\rfoot{\footnotesize todo association}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\includegraphics[height=2cm]{/code/static/tfjm/img/eteam.png}\hfill{\fontsize{55pt}{55pt}{ETEAM Tournament}}
|
||||
|
||||
\vfill
|
||||
|
||||
\begin{center}
|
||||
|
||||
|
||||
\LARGE
|
||||
Video and interview consent and release form
|
||||
\end{center}
|
||||
\normalsize
|
||||
|
||||
|
||||
\thispagestyle{empty}
|
||||
|
||||
\bigskip
|
||||
|
||||
|
||||
|
||||
I, {{ registration|safe|default:"\dotfill" }}\\
|
||||
residing at {{ registration.address|safe|default:"\dotfill" }} {{ registration.zip_code|safe|default:"" }} {{ registration.city|safe|default:"" }}
|
||||
{{ registration.country|safe|default:"" }},\\
|
||||
|
||||
\medskip
|
||||
Tick the appropriate box(es).\\
|
||||
|
||||
\medskip
|
||||
|
||||
\fbox{\textcolor{white}{A}} Authorise the ETEAM organizers, for the ETEAM tournament from {{ tournament.date_start }} to {{ tournament.date_end }} in: {{ tournament.place }}, to photograph or film me and to distribute the photos and/or videos taken on this occasion on its website and on partner websites. I hereby grant ETEAM the right to use my image free of charge on all its information media: brochures, websites, social networks. ETEAM hereby becomes the assignee of the rights for these photographs. There is no time limit on the validity of this release nor are there any geographic limitations on where these materials may be distributed.\\
|
||||
|
||||
\medskip
|
||||
ETEAM commits itself, in accordance with the legal regulations in force relating to image rights, to ensuring that the publication and distribution of the image as well as the accompanying comments do not infringe on the private life, dignity and reputation of the person photographed.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} Authorise the broadcasting in the media (Press, Television, Internet) of photographs taken during any media coverage of this event.\\
|
||||
\medskip
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} By signing this form, I acknowledge that I have completely read and fully understand the above consent and release and agree to be bound thereby. I hereby release any and all claims against any person or organisation utilising this material for marketing, educational, promotional, and/or any other lawful purpose whatsoever.\\
|
||||
|
||||
\medskip
|
||||
\fbox{\textcolor{white}{A}} I agree to be kept informed of other activities organised by ETEAM and its partners.\\
|
||||
\bigskip
|
||||
|
||||
Signature preceded by the words "read and approved"
|
||||
\medskip
|
||||
|
||||
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{Legal representative:}\\
|
||||
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
|
||||
\underline{The participant:}\\
|
||||
|
||||
|
||||
\end{minipage}
|
||||
|
||||
|
||||
\vfill
|
||||
\vfill
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
% \footnotesize Address
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.5\textwidth}
|
||||
\footnotesize
|
||||
% \begin{flushright}
|
||||
% todo association
|
||||
% \end{flushright}
|
||||
\end{minipage}
|
||||
\end{document}
|
@ -48,7 +48,10 @@
|
||||
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.address }}, {{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }}</dd>
|
||||
<dd class="col-sm-6">
|
||||
{{ user_object.registration.address }},
|
||||
{{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }} ({{ user_object.registration.country }})
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
||||
@ -86,25 +89,29 @@
|
||||
|
||||
{% if user_object.registration.studentregistration %}
|
||||
{% if user_object.registration.under_18 and user_object.registration.team.participation.tournament and not user_object.registration.team.participation.tournament.remote %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.health_sheet %}
|
||||
<a href="{{ user_object.registration.health_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadHealthSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Health sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.health_sheet %}
|
||||
<a href="{{ user_object.registration.health_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadHealthSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.vaccine_sheet %}
|
||||
<a href="{{ user_object.registration.vaccine_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadVaccineSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Vaccine sheet:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
{% if user_object.registration.vaccine_sheet %}
|
||||
<a href="{{ user_object.registration.vaccine_sheet.url }}">{% trans "Download" %}</a>
|
||||
{% endif %}
|
||||
{% if user_object.registration.team and not user_object.registration.team.participation.valid %}
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadVaccineSheetModal">{% trans "Replace" %}</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Parental authorization:" %}</dt>
|
||||
<dd class="col-sm-6">
|
||||
@ -158,11 +165,13 @@
|
||||
<dd class="col-sm-6">{{ user_object.registration.is_admin|yesno }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Grant Animath to contact me in the future about other actions:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.give_contact_to_animath|yesno }}</dd>
|
||||
{% if TFJM.SUGGEST_ANIMATH %}
|
||||
<dt class="col-sm-6 text-sm-end">{% trans "Grant Animath to contact me in the future about other actions:" %}</dt>
|
||||
<dd class="col-sm-6">{{ user_object.registration.give_contact_to_animath|yesno }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% if user_object.registration.participates and user_object.registration.team.participation.valid %}
|
||||
{% if TFJM.PAYMENT_MANAGEMENT and user_object.registration.participates and user_object.registration.team.participation.valid %}
|
||||
<hr>
|
||||
{% for payment in user_object.registration.payments.all %}
|
||||
<dl class="row">
|
||||
@ -223,15 +232,19 @@
|
||||
{% include "base_modal.html" with modal_id="uploadPhotoAuthorization" modal_enctype="multipart/form-data" %}
|
||||
|
||||
{% if user_object.registration.under_18 %}
|
||||
{% trans "Upload health sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% 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" %}
|
||||
{% if TFJM.HEALTH_SHEET_REQUIRED %}
|
||||
{% trans "Upload health sheet" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
{% 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" %}
|
||||
{% endif %}
|
||||
|
||||
{% 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" %}
|
||||
{% if TFJM.VACCINE_SHEET_REQUIRED %}
|
||||
{% 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" %}
|
||||
{% endif %}
|
||||
|
||||
{% trans "Upload parental authorization" as modal_title %}
|
||||
{% trans "Upload" as modal_button %}
|
||||
|
@ -146,6 +146,7 @@ class TestRegistration(TestCase):
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
country="France",
|
||||
phone_number="0123456789",
|
||||
responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
@ -194,6 +195,7 @@ class TestRegistration(TestCase):
|
||||
address="1 Rue de Rivoli",
|
||||
zip_code=75001,
|
||||
city="Paris",
|
||||
country="France",
|
||||
phone_number="0123456789",
|
||||
professional_activity="God",
|
||||
last_degree="Master",
|
||||
@ -274,11 +276,13 @@ class TestRegistration(TestCase):
|
||||
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", zip_code=75001,
|
||||
city="Paris", responsible_name="Toto",
|
||||
city="Paris", country="France",
|
||||
responsible_name="Toto",
|
||||
responsible_phone="0123456789",
|
||||
responsible_email="toto@example.com")),
|
||||
(self.coach, dict(professional_activity="God", last_degree="Médaille Fields", gender="male",
|
||||
address="1 Rue de Rivoli", zip_code=75001, city="Paris"))]:
|
||||
address="1 Rue de Rivoli", zip_code=75001,
|
||||
city="Paris", country="France"))]:
|
||||
response = self.client.get(reverse("registration:update_user", args=(user.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -333,7 +337,7 @@ class TestRegistration(TestCase):
|
||||
|
||||
response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
|
||||
args=(self.student.registration.pk,)), data={
|
||||
auth_type: open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
|
||||
auth_type: open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
|
||||
})
|
||||
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
||||
|
||||
@ -356,7 +360,7 @@ class TestRegistration(TestCase):
|
||||
old_authoratization = self.student.registration.photo_authorization.path
|
||||
response = self.client.post(reverse("registration:upload_user_photo_authorization",
|
||||
args=(self.student.registration.pk,)), data=dict(
|
||||
photo_authorization=open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
|
||||
photo_authorization=open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
|
||||
))
|
||||
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
||||
self.assertFalse(os.path.isfile(old_authoratization))
|
||||
|
@ -436,13 +436,19 @@ class AuthorizationTemplateView(TemplateView):
|
||||
if not Tournament.objects.filter(name__iexact=self.request.GET.get("tournament_name")).exists():
|
||||
raise PermissionDenied("Ce tournoi n'existe pas.")
|
||||
context["tournament"] = Tournament.objects.get(name__iexact=self.request.GET.get("tournament_name"))
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
# One single tournament
|
||||
context["tournament"] = Tournament.objects.first()
|
||||
else:
|
||||
raise PermissionDenied("Merci d'indiquer un tournoi.")
|
||||
|
||||
return context
|
||||
|
||||
def render_to_response(self, context, **response_kwargs):
|
||||
tex = render_to_string(self.template_name, context=context, request=self.request)
|
||||
translation.activate(settings.PREFERRED_LANGUAGE_CODE)
|
||||
|
||||
template_name = self.get_template_names()[0]
|
||||
tex = render_to_string(template_name, context=context, request=self.request)
|
||||
temp_dir = mkdtemp()
|
||||
with open(os.path.join(temp_dir, "texput.tex"), "w") as f:
|
||||
f.write(tex)
|
||||
@ -451,20 +457,34 @@ class AuthorizationTemplateView(TemplateView):
|
||||
process.wait()
|
||||
return FileResponse(open(os.path.join(temp_dir, "texput.pdf"), "rb"),
|
||||
content_type="application/pdf",
|
||||
filename=self.template_name.split("/")[-1][:-3] + "pdf")
|
||||
filename=template_name.split("/")[-1][:-3] + "pdf")
|
||||
|
||||
|
||||
class AdultPhotoAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Autorisation_droit_image_majeur.tex"
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_droit_image_majeur.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/photo_authorization_eteam_adult.tex"]
|
||||
|
||||
|
||||
class ChildPhotoAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Autorisation_droit_image_mineur.tex"
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_droit_image_mineur.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/photo_authorization_eteam_child.tex"]
|
||||
|
||||
|
||||
class ParentalAuthorizationTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Autorisation_parentale.tex"
|
||||
|
||||
def get_template_names(self):
|
||||
if settings.TFJM_APP == "TFJM":
|
||||
return ["registration/tex/Autorisation_parentale.tex"]
|
||||
elif settings.TFJM_APP == "ETEAM":
|
||||
return ["registration/tex/parental_authorization_eteam.tex"]
|
||||
|
||||
|
||||
class InstructionsTemplateView(AuthorizationTemplateView):
|
||||
template_name = "registration/tex/Instructions.tex"
|
||||
@ -837,7 +857,8 @@ class SolutionView(LoginRequiredMixin, View):
|
||||
or user.registration.participates and user.registration.team
|
||||
and (solution.participation.team == user.registration.team or
|
||||
any(passage.pool.round == 1
|
||||
or timezone.now() >= passage.pool.tournament.solutions_available_second_phase
|
||||
or (passage.pool.round == 2 and passage.pool.tournament.solutions_available_second_phase)
|
||||
or (passage.pool.round == 3 and passage.pool.tournament.solutions_available_third_phase)
|
||||
for passage in passage_participant_qs.all()))):
|
||||
raise PermissionDenied
|
||||
# Guess mime type of the file
|
||||
|
19
tfjm/context_processors.py
Normal file
19
tfjm/context_processors.py
Normal file
@ -0,0 +1,19 @@
|
||||
from django.conf import settings
|
||||
|
||||
from participation.models import Tournament
|
||||
|
||||
|
||||
def tfjm_context(request):
|
||||
return {
|
||||
'TFJM': {
|
||||
'APP': settings.TFJM_APP,
|
||||
'ML_MANAGEMENT': settings.ML_MANAGEMENT,
|
||||
'PAYMENT_MANAGEMENT': settings.PAYMENT_MANAGEMENT,
|
||||
'SINGLE_TOURNAMENT':
|
||||
Tournament.objects.first() if Tournament.objects.exists() and settings.TFJM_APP else None,
|
||||
'HEALTH_SHEET_REQUIRED': settings.HEALTH_SHEET_REQUIRED,
|
||||
'VACCINE_SHEET_REQUIRED': settings.VACCINE_SHEET_REQUIRED,
|
||||
'MOTIVATION_LETTER_REQUIRED': settings.MOTIVATION_LETTER_REQUIRED,
|
||||
'SUGGEST_ANIMATH': settings.SUGGEST_ANIMATH,
|
||||
}
|
||||
}
|
@ -118,6 +118,7 @@ TEMPLATES = [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'tfjm.context_processors.tfjm_context',
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -205,20 +206,6 @@ STATICFILES_FINDERS = (
|
||||
PIPELINE = {
|
||||
'DISABLE_WRAPPER': True,
|
||||
'JAVASCRIPT': {
|
||||
'bootstrap': {
|
||||
'source_filenames': {
|
||||
'bootstrap/js/bootstrap.bundle.min.js',
|
||||
},
|
||||
'output_filename': 'tfjm/js/bootstrap.bundle.min.js',
|
||||
},
|
||||
'bootstrap_select': {
|
||||
'source_filenames': {
|
||||
'jquery/jquery.min.js',
|
||||
'bootstrap-select/js/bootstrap-select.min.js',
|
||||
'bootstrap-select/js/defaults-fr_FR.min.js',
|
||||
},
|
||||
'output_filename': 'tfjm/js/bootstrap-select-jquery.min.js',
|
||||
},
|
||||
'main': {
|
||||
'source_filenames': (
|
||||
'tfjm/js/main.js',
|
||||
@ -245,17 +232,6 @@ PIPELINE = {
|
||||
'output_filename': 'tfjm/js/draw.min.js',
|
||||
},
|
||||
},
|
||||
'STYLESHEETS': {
|
||||
'bootstrap_fontawesome': {
|
||||
'source_filenames': (
|
||||
'bootstrap/css/bootstrap.min.css',
|
||||
'fontawesome/css/all.css',
|
||||
'fontawesome/css/v4-shims.css',
|
||||
'bootstrap-select/css/bootstrap-select.min.css',
|
||||
),
|
||||
'output_filename': 'tfjm/css/bootstrap_fontawesome.min.css',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
@ -306,6 +282,12 @@ else:
|
||||
}
|
||||
}
|
||||
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
|
||||
# Custom phone number format
|
||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||
@ -333,16 +315,6 @@ GOOGLE_SERVICE_CLIENT = {
|
||||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||
|
||||
# Custom parameters
|
||||
PROBLEMS = [
|
||||
"Triominos",
|
||||
"Rassemblements mathématiques",
|
||||
"Tournoi de ping-pong",
|
||||
"Dépollution de la Seine",
|
||||
"Électron libre",
|
||||
"Pièces truquées",
|
||||
"Drôles de cookies",
|
||||
"Création d'un jeu",
|
||||
]
|
||||
FORBIDDEN_TRIGRAMS = [
|
||||
"BIT",
|
||||
"CNO",
|
||||
@ -361,11 +333,7 @@ FORBIDDEN_TRIGRAMS = [
|
||||
"SEX",
|
||||
]
|
||||
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||
}
|
||||
}
|
||||
TFJM_APP = os.getenv("TFJM_APP", "TFJM") # Change to ETEAM for the ETEAM tournament
|
||||
|
||||
if TFJM_STAGE == "prod": # pragma: no cover
|
||||
from .settings_prod import * # noqa: F401,F403
|
||||
@ -376,3 +344,52 @@ try:
|
||||
from .settings_local import * # noqa: F401,F403
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if TFJM_APP == "TFJM":
|
||||
PREFERRED_LANGUAGE_CODE = 'fr'
|
||||
TEAM_CODE_LENGTH = 3
|
||||
RECOMMENDED_SOLUTIONS_COUNT = 5
|
||||
NB_ROUNDS = 2
|
||||
ML_MANAGEMENT = True
|
||||
PAYMENT_MANAGEMENT = True
|
||||
HEALTH_SHEET_REQUIRED = True
|
||||
VACCINE_SHEET_REQUIRED = True
|
||||
MOTIVATION_LETTER_REQUIRED = True
|
||||
SUGGEST_ANIMATH = True
|
||||
|
||||
PROBLEMS = [
|
||||
"Triominos",
|
||||
"Rassemblements mathématiques",
|
||||
"Tournoi de ping-pong",
|
||||
"Dépollution de la Seine",
|
||||
"Électron libre",
|
||||
"Pièces truquées",
|
||||
"Drôles de cookies",
|
||||
"Création d'un jeu",
|
||||
]
|
||||
elif TFJM_APP == "ETEAM":
|
||||
PREFERRED_LANGUAGE_CODE = 'en'
|
||||
TEAM_CODE_LENGTH = 4
|
||||
RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||
NB_ROUNDS = 3
|
||||
ML_MANAGEMENT = False
|
||||
PAYMENT_MANAGEMENT = False
|
||||
HEALTH_SHEET_REQUIRED = False
|
||||
VACCINE_SHEET_REQUIRED = False
|
||||
MOTIVATION_LETTER_REQUIRED = False
|
||||
SUGGEST_ANIMATH = False
|
||||
|
||||
PROBLEMS = [
|
||||
"Exploring Flatland",
|
||||
"A Mazing Hive",
|
||||
"Coin tossing",
|
||||
"The rainbow bridge",
|
||||
"Arithmetic and shopping",
|
||||
"A fence for the goats",
|
||||
"Generalized Tic-Tac-Toe",
|
||||
"Polyhedral construction",
|
||||
"Landing a probe",
|
||||
"Catching the rabbit",
|
||||
]
|
||||
else:
|
||||
raise ValueError(f"Unknown app: {TFJM_APP}")
|
||||
|
@ -7,7 +7,9 @@ import os
|
||||
DEBUG = False
|
||||
|
||||
# Mandatory !
|
||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org']
|
||||
# TODO ETEAM Meilleur support, et meilleurs DNS surtout
|
||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org',
|
||||
'register.eteam.tfjm.org', 'registration.eteam.tfjm.org', 'platform.eteam.tfjm.org']
|
||||
|
||||
# Emails
|
||||
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
||||
|
BIN
tfjm/static/tfjm/img/eteam.png
Normal file
BIN
tfjm/static/tfjm/img/eteam.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -9,21 +9,29 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>
|
||||
{% block title %}{{ title }}{% endblock title %} - Plateforme du TFJM²
|
||||
{# TODO ETEAM Plus d'uniformité #}
|
||||
{% block title %}{{ title }}{% endblock title %} - {% trans "ETEAM Platform" %}
|
||||
</title>
|
||||
<meta name="description" content="Plateforme d'inscription au TFJM².">
|
||||
{# TODO ETEAM Plus d'uniformité #}
|
||||
<meta name="description" content="{% trans "Registration platform to the ETEAM." %}">
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap CSS #}
|
||||
{% stylesheet 'bootstrap_fontawesome' %}
|
||||
<link href="{% static "bootstrap/css/bootstrap.min.css" %}" rel="stylesheet" type="text/css">
|
||||
{# Fontawesome CSS #}
|
||||
<link href="{% static "fontawesome/css/all.min.css" %}" rel="stylesheet" type="text/css">
|
||||
<link href="{% static "fontawesome/css/v4-shims.css" %}">
|
||||
{# bootstrap-select CSS #}
|
||||
<link href="{% static "bootstrap-select/css/bootstrap-select.min.css" %}" rel="stylesheet" type="text/css">
|
||||
|
||||
{# Bootstrap JavaScript #}
|
||||
{% javascript 'bootstrap' %}
|
||||
<script type="application/javascript" src="{% static "bootstrap/js/bootstrap.bundle.min.js" %}" charset="utf-8"></script>
|
||||
{# bootstrap-select for beautiful selects and JQuery dependency #}
|
||||
{% javascript 'bootstrap_select' %}
|
||||
<script type="application/javascript" src="{% static "jquery/jquery.min.js" %}" charset="utf-8"></script>
|
||||
<script type="application/javascript" src="{% static "bootstrap-select/js/bootstrap-select.min.js" %}" charset="utf-8"></script>
|
||||
|
||||
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
||||
{% if form.media %}
|
||||
|
@ -1,68 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
Bienvenue sur le site d'inscription au <a href="https://tfjm.org/" target="_blank">𝕋𝔽𝕁𝕄²</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
Tu souhaites participer au 𝕋𝔽𝕁𝕄² ?
|
||||
<br/>
|
||||
Ton équipe est déjà formée ?
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">Inscris-toi maintenant !</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">J'ai déjà un compte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">Comment ça marche ?</h5>
|
||||
<p>
|
||||
Pour participer au 𝕋𝔽𝕁𝕄², il suffit de créer un compte sur la rubrique <strong><a href="{% url "registration:signup" %}">Inscription</a></strong>.
|
||||
Vous devrez ensuite confirmer votre adresse e-mail.
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
Vous pouvez accéder à votre compte via la rubrique <strong><a href="{% url "login" %}">Connexion</a></strong>.
|
||||
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>
|
||||
|
||||
|
||||
<p class="text-justify">
|
||||
Vous pouvez nous contacter à l'adresse <a href="mailto:contact@tfjm.org">contact@tfjm.org</a> pour que nous
|
||||
puissions vous aider à vous mettre en relation avec d'autres participant⋅es qui cherchent également une équipe.
|
||||
</p>
|
||||
|
||||
<h2>J'ai une question</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
N'hésitez pas à consulter la <a href="/doc/" target="_blank">documentation</a> du site, pour vérifier si
|
||||
la réponse ne s'y trouve pas déjà. Référez-vous également bien sûr au
|
||||
<a href="https://tfjm.org/reglement/" target="_blank">règlement du 𝕋𝔽𝕁𝕄²</a>.
|
||||
Pour toute autre question, n'hésitez pas à nous contacter par mail à l'adresse
|
||||
<a href="mailto:contact@tfjm.org">
|
||||
contact@tfjm.org
|
||||
</a>.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Attention aux dates !</strong> Si vous ne finalisez pas votre inscription dans le délai indiqué, vous
|
||||
ne pourrez malheureusement pas participer au 𝕋𝔽𝕁𝕄².
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
67
tfjm/templates/index_eteam.html
Normal file
67
tfjm/templates/index_eteam.html
Normal file
@ -0,0 +1,67 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
{% trans "Welcome onto the registration site of the" %}
|
||||
<a href="https://eteam.tfjm.org/" target="_blank">ETEAM</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
{% trans "You want to participate to the ETEAM ?" %}
|
||||
<br/>
|
||||
{% trans "Your team is selected and already complete?" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">{% trans "Register now!" %}</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">{% trans "I already have an account" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
||||
<p>
|
||||
{% url "registration:signup" as signup_url %}
|
||||
{% blocktrans trimmed %}
|
||||
To participate to the ETEAM, you must be selected by your national organization.
|
||||
If so, you just need to create an account on the <strong><a href="{{ signup_url }}">Registration</a></strong> page.
|
||||
You will then have to confirm your email address.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
{% url "login" as login_url %}
|
||||
{% blocktrans trimmed %}
|
||||
You can access your account via the <strong><a href="{{ login_url }}">Login</a></strong> page.
|
||||
Once logged in, you will be able to create a team or join one already created by one of your comrades
|
||||
via an access code that will have been transmitted to you. You will then be invited to submit a right to image authorization,
|
||||
essential for the smooth running of the ETEAM. Once your team has at least 4 participants (maximum 6)
|
||||
and a supervisor, you can request to validate your team to be able to work on the problems of your choice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I have a question" %}</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
Do not hesitate to consult the <a href="/doc/" target="_blank">documentation</a> of the site, to check if
|
||||
the answer is not already there. Also refer of course to the
|
||||
<a href="https://eteam.tfjm.org/rules/" target="_blank">𝕋𝔽𝕁𝕄² rules</a>.
|
||||
For any other question, do not hesitate to contact us by email at the address
|
||||
<a href="mailto:eteam_moc@proton.me ">
|
||||
eteam_moc@proton.me
|
||||
</a>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
81
tfjm/templates/index_tfjm.html
Normal file
81
tfjm/templates/index_tfjm.html
Normal file
@ -0,0 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron p-5">
|
||||
<div class="row text-center">
|
||||
<h1 class="display-4">
|
||||
{% trans "Welcome onto the registration site of the" %}
|
||||
<a href="https://tfjm.org/" target="_blank">𝕋𝔽𝕁𝕄²</a> !
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-5">
|
||||
<div class="col-sm">
|
||||
<h3>
|
||||
{% trans "You want to participate to the 𝕋𝔽𝕁𝕄² ?" %}
|
||||
<br/>
|
||||
{% trans "Your team is already complete?" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm text-sm-end">
|
||||
<div class="btn-group-vertical">
|
||||
<a class="btn btn-primary btn-lg" href="{% url "registration:signup" %}" role="button">{% trans "Register now!" %}</a>
|
||||
<a class="btn btn-light text-dark btn-lg" href="{% url "login" %}" role="button">{% trans "I already have an account" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron p-5 border rounded-5">
|
||||
<h5 class="display-4">{% trans "How does it work?" %}</h5>
|
||||
<p>
|
||||
{% url "registration:signup" as signup_url %}
|
||||
{% blocktrans trimmed %}
|
||||
To participate to the 𝕋𝔽𝕁𝕄², you just need to create an account on the <strong><a href="{{ signup_url }}">Registration</a></strong> page.
|
||||
You will then have to confirm your email address.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<p class="text-justify">
|
||||
{% url "login" as login_url %}
|
||||
{% blocktrans trimmed %}
|
||||
You can access your account via the <strong><a href="{{ login_url }}">Login</a></strong> page.
|
||||
Once logged in, you will be able to create a team or join one already created by one of your comrades
|
||||
via an access code that will have been transmitted to you. You will then be invited to submit a right to image authorization,
|
||||
essential for the smooth running of the 𝕋𝔽𝕁𝕄². Once your team has at least 4 participants (maximum 6)
|
||||
and a supervisor, you can request to validate your team to be able to work on the problems of your choice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I can't find a team, help me!" %}</h2>
|
||||
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
You can contact us at the address <a href="mailto:contact@tfjm.org">contact@tfjm.org</a> so that we
|
||||
can help you get in touch with other participants who are also looking for a team.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<h2>{% trans "I have a question" %}</h2>
|
||||
|
||||
<p class="text-justify">
|
||||
{% blocktrans trimmed %}
|
||||
Do not hesitate to consult the <a href="/doc/" target="_blank">documentation</a> of the site, to check if
|
||||
the answer is not already there. Also refer of course to the
|
||||
<a href="https://tfjm.org/reglement/" target="_blank">𝕋𝔽𝕁𝕄² rules</a>.
|
||||
For any other question, do not hesitate to contact us by email at the address
|
||||
<a href="mailto:contact@tfjm.org">
|
||||
contact@tfjm.org
|
||||
</a>.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>{% trans "Save the dates!" %}</strong>
|
||||
{% trans "If you don't end your registration by the indicated deadline, you will unfortunately not be able to participate in the 𝕋𝔽𝕁𝕄²." %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -2,8 +2,10 @@
|
||||
|
||||
<nav class="navbar navbar-expand-lg fixed-navbar shadow-sm">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="https://tfjm.org/">
|
||||
<img src="{% static "tfjm/img/tfjm.svg" %}" style="height: 2em;" alt="Logo TFJM²" id="navbar-logo">
|
||||
{# TODO ETEAM Plus d'uniformité #}
|
||||
<a class="navbar-brand" href="https://eteam.tfjm.org/">
|
||||
{# TODO ETEAM Plus d'uniformité #}
|
||||
<img src="{% static "tfjm/img/eteam.png" %}" style="height: 2em;" alt="Logo ETEAM" id="navbar-logo">
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNavDropdown"
|
||||
@ -17,9 +19,15 @@
|
||||
<a href="{% url "index" %}" class="nav-link"><i class="fas fa-home"></i> {% trans "Home" %}</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#tournamentListModal">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
||||
</a>
|
||||
{% if TFJM.SINGLE_TOURNAMENT %}
|
||||
<a href="{% url 'participation:tournament_detail' pk=TFJM.SINGLE_TOURNAMENT.pk %}" class="nav-link">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournament" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#tournamentListModal">
|
||||
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if user.is_authenticated and user.registration.is_admin %}
|
||||
<li class="nav-item active">
|
||||
|
@ -28,7 +28,8 @@ from registration.views import HealthSheetView, ParentalAuthorizationView, Photo
|
||||
from .views import AdminSearchView
|
||||
|
||||
urlpatterns = [
|
||||
path('', TemplateView.as_view(template_name="index.html"), name='index'),
|
||||
# TODO ETEAM Rendre ça plus joli
|
||||
path('', TemplateView.as_view(template_name=f"index_{settings.TFJM_APP.lower()}.html"), name='index'),
|
||||
path('about/', TemplateView.as_view(template_name="about.html"), name='about'),
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
Reference in New Issue
Block a user