mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-07-04 19:44:07 +02:00
Compare commits
9 Commits
docs
...
cf92c78d03
Author | SHA1 | Date | |
---|---|---|---|
cf92c78d03
|
|||
38ceef7a54
|
|||
ec2fa43e20
|
|||
85b3da09f6
|
|||
2c15774185
|
|||
08ad4f3888
|
|||
872009894d
|
|||
fd7fe90fce
|
|||
2ad538f5cc
|
@ -183,7 +183,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
# Create the draw
|
# Create the draw
|
||||||
draw = await Draw.objects.acreate(tournament=self.tournament)
|
draw = await Draw.objects.acreate(tournament=self.tournament)
|
||||||
r1 = None
|
r1 = None
|
||||||
for i in [1, 2]:
|
for i in range(1, settings.NB_ROUNDS + 1):
|
||||||
# Create the round
|
# Create the round
|
||||||
r = await Round.objects.acreate(draw=draw, number=i)
|
r = await Round.objects.acreate(draw=draw, number=i)
|
||||||
if i == 1:
|
if i == 1:
|
||||||
@ -532,16 +532,16 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'visible': True})
|
'visible': True})
|
||||||
|
|
||||||
# First send the second pool to have the good team order
|
# First send the second pool to have the good team order
|
||||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
for r in self.tournament.draw.round_set.filter(number__gte=2).all():
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||||
'round': r2.number,
|
'round': r.number,
|
||||||
'poules': [
|
'poules': [
|
||||||
{
|
{
|
||||||
'letter': pool.get_letter_display(),
|
'letter': pool.get_letter_display(),
|
||||||
'teams': await pool.atrigrams(),
|
'teams': await pool.atrigrams(),
|
||||||
}
|
}
|
||||||
async for pool in r2.pool_set.order_by('letter').all()
|
async for pool in r.pool_set.order_by('letter').all()
|
||||||
]})
|
]})
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||||
@ -843,11 +843,11 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
"""
|
"""
|
||||||
msg = self.tournament.draw.last_message
|
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
|
# Next round
|
||||||
r2 = await self.tournament.draw.round_set.filter(number=2).aget()
|
next_round = await self.tournament.draw.round_set.filter(number=r.number + 1).aget()
|
||||||
self.tournament.draw.current_round = r2
|
self.tournament.draw.current_round = next_round
|
||||||
msg += "<br><br>Le tirage au sort du tour 1 est terminé."
|
msg += f"<br><br>Le tirage au sort du tour {r.number} est terminé."
|
||||||
self.tournament.draw.last_message = msg
|
self.tournament.draw.last_message = msg
|
||||||
await self.tournament.draw.asave()
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
@ -866,20 +866,20 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
# Reorder dices
|
# Reorder dices
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
||||||
'round': r2.number,
|
'round': next_round.number,
|
||||||
'poules': [
|
'poules': [
|
||||||
{
|
{
|
||||||
'letter': pool.get_letter_display(),
|
'letter': pool.get_letter_display(),
|
||||||
'teams': await pool.atrigrams(),
|
'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
|
# The passage order for the second round is already determined by the first round
|
||||||
# Start the first pool of the second round
|
# Start the first pool of the second round
|
||||||
p1: Pool = await r2.pool_set.filter(letter=1).aget()
|
p1: Pool = await next_round.pool_set.filter(letter=1).aget()
|
||||||
r2.current_pool = p1
|
next_round.current_pool = p1
|
||||||
await r2.asave()
|
await next_round.asave()
|
||||||
|
|
||||||
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
async for td in p1.teamdraw_set.prefetch_related('participation__team').all():
|
||||||
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
await self.channel_layer.group_send(f"team-{td.participation.team.trigram}",
|
||||||
@ -929,7 +929,7 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
td.purposed = None
|
td.purposed = None
|
||||||
await td.asave()
|
await td.asave()
|
||||||
|
|
||||||
remaining = len(settings.PROBLEMS) - 5 - len(td.rejected)
|
remaining = len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)
|
||||||
|
|
||||||
# Update messages
|
# Update messages
|
||||||
trigram = td.participation.team.trigram
|
trigram = td.participation.team.trigram
|
||||||
@ -1372,32 +1372,36 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
'round': r.number,
|
'round': r.number,
|
||||||
'team': td.participation.team.trigram,
|
'team': td.participation.team.trigram,
|
||||||
'problem': td.accepted})
|
'problem': td.accepted})
|
||||||
elif r.number == 2:
|
elif r.number >= 2:
|
||||||
if not self.tournament.final:
|
if not self.tournament.final:
|
||||||
# Go to the previous round
|
# Go to the previous round
|
||||||
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)
|
.prefetch_related('current_pool__current_team__participation__team').aget(number=r.number - 1)
|
||||||
self.tournament.draw.current_round = r1
|
self.tournament.draw.current_round = previous_round
|
||||||
await self.tournament.draw.asave()
|
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(
|
await self.channel_layer.group_send(
|
||||||
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
||||||
'team': td.participation.team.trigram,
|
'team': td.participation.team.trigram,
|
||||||
'result': td.choice_dice})
|
'result': td.choice_dice})
|
||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(
|
||||||
{'tid': self.tournament_id, 'type': 'draw.send_poules',
|
f"tournament-{self.tournament.id}",
|
||||||
'round': r1.number,
|
{
|
||||||
|
'tid': self.tournament_id,
|
||||||
|
'type': 'draw.send_poules',
|
||||||
|
'round': previous_round.number,
|
||||||
'poules': [
|
'poules': [
|
||||||
{
|
{
|
||||||
'letter': pool.get_letter_display(),
|
'letter': pool.get_letter_display(),
|
||||||
'teams': await pool.atrigrams(),
|
'teams': await pool.atrigrams(),
|
||||||
}
|
}
|
||||||
async for pool in r1.pool_set.order_by('letter').all()
|
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 = previous_pool.current_team
|
||||||
td.purposed = td.accepted
|
td.purposed = td.accepted
|
||||||
@ -1417,14 +1421,14 @@ class DrawConsumer(AsyncJsonWebsocketConsumer):
|
|||||||
|
|
||||||
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
await self.channel_layer.group_send(f"tournament-{self.tournament.id}",
|
||||||
{'tid': self.tournament_id, 'type': 'draw.set_problem',
|
{'tid': self.tournament_id, 'type': 'draw.set_problem',
|
||||||
'round': r1.number,
|
'round': previous_round.number,
|
||||||
'team': td.participation.team.trigram,
|
'team': td.participation.team.trigram,
|
||||||
'problem': td.accepted})
|
'problem': td.accepted})
|
||||||
else:
|
else:
|
||||||
# Don't continue the final tournament
|
# 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)
|
.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()
|
await self.tournament.draw.asave()
|
||||||
|
|
||||||
async for td in r.teamdraw_set.all():
|
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(
|
await self.channel_layer.group_send(
|
||||||
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
f"tournament-{self.tournament.id}", {'tid': self.tournament_id, 'type': 'draw.dice',
|
||||||
'team': td.participation.team.trigram,
|
'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:
|
else:
|
||||||
# The problem can be rejected
|
# The problem can be rejected
|
||||||
s += "Elle peut décider d'accepter ou de refuser ce problème. "
|
s += "Elle peut décider d'accepter ou de refuser ce problème. "
|
||||||
if len(td.rejected) >= len(settings.PROBLEMS) - 5:
|
if len(td.rejected) >= len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT:
|
||||||
s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
|
s += "Refuser ce problème ajoutera une nouvelle pénalité de 25 % sur le coefficient de l'oral de la défense."
|
||||||
else:
|
else:
|
||||||
s += f"Il reste {len(settings.PROBLEMS) - 5 - len(td.rejected)} refus sans pénalité."
|
s += f"Il reste {len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT - len(td.rejected)} refus sans pénalité."
|
||||||
case 'WAITING_FINAL':
|
case 'WAITING_FINAL':
|
||||||
# We are between the two rounds of the final tournament
|
# We are between the two rounds of the final tournament
|
||||||
s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
|
s += "Le tirage au sort pour le tour 2 aura lieu à la fin du premier tour. Bon courage !"
|
||||||
@ -193,10 +193,10 @@ class Round(models.Model):
|
|||||||
choices=[
|
choices=[
|
||||||
(1, _('Round 1')),
|
(1, _('Round 1')),
|
||||||
(2, _('Round 2')),
|
(2, _('Round 2')),
|
||||||
],
|
] + ([] if settings.NB_ROUNDS == 2 else [(3, _('Round 3'))]),
|
||||||
verbose_name=_('number'),
|
verbose_name=_('number'),
|
||||||
help_text=_("The number of the round, 1 or 2"),
|
help_text=_("The number of the round, 1 or 2 (or 3 for ETEAM)"),
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(2)],
|
validators=[MinValueValidator(1), MaxValueValidator(settings.NB_ROUNDS)],
|
||||||
)
|
)
|
||||||
|
|
||||||
current_pool = models.ForeignKey(
|
current_pool = models.ForeignKey(
|
||||||
@ -524,10 +524,10 @@ class TeamDraw(models.Model):
|
|||||||
@property
|
@property
|
||||||
def penalty_int(self):
|
def penalty_int(self):
|
||||||
"""
|
"""
|
||||||
The number of penalties, which is the number of rejected problems after the P - 5 free rejects,
|
The number of penalties, which is the number of rejected problems after the P - 5 free rejects
|
||||||
where P is the number of problems.
|
(P - 6 for ETEAM), where P is the number of problems.
|
||||||
"""
|
"""
|
||||||
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - 5))
|
return max(0, len(self.rejected) - (len(settings.PROBLEMS) - settings.RECOMMENDED_SOLUTIONS_COUNT))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def penalty(self):
|
def penalty(self):
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
await Notification.requestPermission()
|
await Notification.requestPermission()
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
// TODO ETEAM Mieux paramétriser (5 pour le TFJM², 6 pour l'ETEAM)
|
||||||
|
const RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||||
|
|
||||||
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
const problems_count = JSON.parse(document.getElementById('problems_count').textContent)
|
||||||
|
|
||||||
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
|
||||||
@ -308,7 +311,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
/**
|
/**
|
||||||
* Set the different pools for the given round, and update the interface.
|
* Set the different pools for the given round, and update the interface.
|
||||||
* @param tid The tournament id
|
* @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,
|
* @param poules The list of poules, which are represented with their letters and trigrams,
|
||||||
* [{'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}]
|
* [{'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.
|
* Update the table for the given round and the given pool, where there will be the chosen problems.
|
||||||
* @param tid The tournament id
|
* @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,
|
* @param poule The current pool, which id represented with its letter and trigrams,
|
||||||
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
|
* {'letter': 'A', 'teams': ['ABC', 'DEF', 'GHI']}
|
||||||
*/
|
*/
|
||||||
@ -587,7 +590,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
/**
|
/**
|
||||||
* Highlight the team that is currently choosing its problem.
|
* Highlight the team that is currently choosing its problem.
|
||||||
* @param tid The tournament id
|
* @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 pool The current pool letter (A, B, C or D) (null if non-relevant)
|
||||||
* @param team The current team trigram (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.
|
* Update the recap and the table when a team accepts a problem.
|
||||||
* @param tid The tournament id
|
* @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 team The current team trigram
|
||||||
* @param problem The accepted problem, as integer
|
* @param problem The accepted problem, as integer
|
||||||
*/
|
*/
|
||||||
@ -648,7 +651,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
/**
|
/**
|
||||||
* Update the recap when a team rejects a problem.
|
* Update the recap when a team rejects a problem.
|
||||||
* @param tid The tournament id
|
* @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 team The current team trigram
|
||||||
* @param rejected The full list of rejected problems
|
* @param rejected The full list of rejected problems
|
||||||
*/
|
*/
|
||||||
@ -658,15 +661,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
|
recapDiv.textContent = `🗑️ ${rejected.join(', ')}`
|
||||||
|
|
||||||
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
let penaltyDiv = document.getElementById(`recap-${tid}-round-${round}-team-${team}-penalty`)
|
||||||
if (rejected.length > problems_count - 5) {
|
if (rejected.length > problems_count - RECOMMENDED_SOLUTIONS_COUNT) {
|
||||||
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
|
// If more than P - 5 problems were rejected, add a penalty of 25% of the coefficient of the oral defender
|
||||||
|
// This is P - 6 for the ETEAM
|
||||||
if (penaltyDiv === null) {
|
if (penaltyDiv === null) {
|
||||||
penaltyDiv = document.createElement('div')
|
penaltyDiv = document.createElement('div')
|
||||||
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
|
penaltyDiv.id = `recap-${tid}-round-${round}-team-${team}-penalty`
|
||||||
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
|
penaltyDiv.classList.add('badge', 'rounded-pill', 'text-bg-info')
|
||||||
recapDiv.parentNode.append(penaltyDiv)
|
recapDiv.parentNode.append(penaltyDiv)
|
||||||
}
|
}
|
||||||
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - 5))} %`
|
penaltyDiv.textContent = `❌ ${25 * (rejected.length - (problems_count - RECOMMENDED_SOLUTIONS_COUNT))} %`
|
||||||
} else {
|
} else {
|
||||||
// Eventually remove this div
|
// Eventually remove this div
|
||||||
if (penaltyDiv !== null)
|
if (penaltyDiv !== null)
|
||||||
@ -678,7 +682,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
* For a 5-teams pool, we may reorder the pool if two teams select the same problem.
|
* 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.
|
* Then, we redraw the table and set the accepted problems.
|
||||||
* @param tid The tournament id
|
* @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 poule The pool represented by its letter
|
||||||
* @param teams The teams list represented by their trigrams, ["ABC", "DEF", "GHI", "JKL", "MNO"]
|
* @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]
|
* @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',
|
fields = ('id', 'pk', 'name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||||
|
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
||||||
'description', 'organizers', 'final', 'participations',)
|
'description', 'organizers', 'final', 'participations',)
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ class TournamentViewSet(ModelViewSet):
|
|||||||
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
filterset_fields = ['name', 'date_start', 'date_end', 'place', 'max_teams', 'price', 'remote',
|
||||||
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
'inscription_limit', 'solution_limit', 'solutions_draw', 'syntheses_first_phase_limit',
|
||||||
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
'solutions_available_second_phase', 'syntheses_second_phase_limit',
|
||||||
|
'solutions_available_third_phase', 'syntheses_third_phase_limit',
|
||||||
'description', 'organizers', 'final', ]
|
'description', 'organizers', 'final', ]
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
import pandas
|
import pandas
|
||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
from registration.models import VolunteerRegistration
|
from registration.models import VolunteerRegistration
|
||||||
|
from tfjm import settings
|
||||||
|
|
||||||
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament
|
||||||
|
|
||||||
@ -125,6 +126,13 @@ class ValidateParticipationForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class TournamentForm(forms.ModelForm):
|
class TournamentForm(forms.ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if settings.TFJM_APP != "ETEAM":
|
||||||
|
del self.fields['date_third_phase']
|
||||||
|
del self.fields['solutions_available_third_phase']
|
||||||
|
del self.fields['syntheses_third_phase_limit']
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tournament
|
model = Tournament
|
||||||
exclude = ('notes_sheet_id', )
|
exclude = ('notes_sheet_id', )
|
||||||
@ -136,10 +144,10 @@ class TournamentForm(forms.ModelForm):
|
|||||||
'solutions_draw': 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'),
|
||||||
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
'syntheses_first_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
format='%Y-%m-%d %H:%M'),
|
format='%Y-%m-%d %H:%M'),
|
||||||
'solutions_available_second_phase': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
|
||||||
format='%Y-%m-%d %H:%M'),
|
|
||||||
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
'syntheses_second_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
format='%Y-%m-%d %H:%M'),
|
format='%Y-%m-%d %H:%M'),
|
||||||
|
'syntheses_third_phase_limit': forms.DateTimeInput(attrs={'type': 'datetime-local'},
|
||||||
|
format='%Y-%m-%d %H:%M'),
|
||||||
'organizers': forms.SelectMultiple(attrs={
|
'organizers': forms.SelectMultiple(attrs={
|
||||||
'class': 'selectpicker',
|
'class': 'selectpicker',
|
||||||
'data-live-search': 'true',
|
'data-live-search': 'true',
|
||||||
|
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
|
# Copyright (C) 2020 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import math
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -37,14 +37,15 @@ class Team(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
trigram = models.CharField(
|
trigram = models.CharField(
|
||||||
max_length=3,
|
max_length=settings.TEAM_CODE_LENGTH,
|
||||||
verbose_name=_("trigram"),
|
verbose_name=_("code"),
|
||||||
help_text=_("The trigram must be composed of three uppercase letters."),
|
help_text=format_lazy(_("The code must be composed of {nb_letters} uppercase letters."),
|
||||||
|
nb_letters=settings.TEAM_CODE_LENGTH),
|
||||||
unique=True,
|
unique=True,
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(r"^[A-Z]{3}$"),
|
RegexValidator("^[A-Z]{" + str(settings.TEAM_CODE_LENGTH) + "}$"),
|
||||||
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
|
RegexValidator(fr"^(?!{'|'.join(f'{t}$' for t in settings.FORBIDDEN_TRIGRAMS)})",
|
||||||
message=_("This trigram is forbidden.")),
|
message=_("This team code is forbidden.")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -309,14 +310,24 @@ class Tournament(models.Model):
|
|||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
date_first_phase = models.DateField(
|
||||||
|
verbose_name=_("first phase date"),
|
||||||
|
default=date.today,
|
||||||
|
)
|
||||||
|
|
||||||
syntheses_first_phase_limit = models.DateTimeField(
|
syntheses_first_phase_limit = models.DateTimeField(
|
||||||
verbose_name=_("limit date to upload the syntheses for the first phase"),
|
verbose_name=_("limit date to upload the syntheses for the first phase"),
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
)
|
)
|
||||||
|
|
||||||
solutions_available_second_phase = models.DateTimeField(
|
date_second_phase = models.DateField(
|
||||||
verbose_name=_("date when the solutions for the second round become available"),
|
verbose_name=_("first second date"),
|
||||||
default=timezone.now,
|
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(
|
syntheses_second_phase_limit = models.DateTimeField(
|
||||||
@ -324,6 +335,21 @@ class Tournament(models.Model):
|
|||||||
default=timezone.now,
|
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(
|
description = models.TextField(
|
||||||
verbose_name=_("description"),
|
verbose_name=_("description"),
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -900,6 +926,49 @@ class Participation(models.Model):
|
|||||||
for ext in ["pdf", "tex", "odt", "docx"])
|
for ext in ["pdf", "tex", "odt", "docx"])
|
||||||
syntheses_templates_content = f"<p>{_('Templates:')} {syntheses_templates}</p>"
|
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
|
content = defender_content + opponent_content + reporter_content + syntheses_templates_content
|
||||||
informations.append({
|
informations.append({
|
||||||
'title': _("Second round"),
|
'title': _("Second round"),
|
||||||
@ -940,7 +1009,7 @@ class Pool(models.Model):
|
|||||||
choices=[
|
choices=[
|
||||||
(1, format_lazy(_("Round {round}"), round=1)),
|
(1, format_lazy(_("Round {round}"), round=1)),
|
||||||
(2, format_lazy(_("Round {round}"), round=2)),
|
(2, format_lazy(_("Round {round}"), round=2)),
|
||||||
]
|
] + ([] if settings.NB_ROUNDS == 2 else [(3, format_lazy(_("Round {round}"), round=3))]),
|
||||||
)
|
)
|
||||||
|
|
||||||
letter = models.PositiveSmallIntegerField(
|
letter = models.PositiveSmallIntegerField(
|
||||||
@ -1010,12 +1079,16 @@ class Pool(models.Model):
|
|||||||
def solutions(self):
|
def solutions(self):
|
||||||
return [passage.defended_solution for passage in self.passages.all()]
|
return [passage.defended_solution for passage in self.passages.all()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def coeff(self):
|
||||||
|
return 1 if self.round <= 2 else math.pi - 2
|
||||||
|
|
||||||
def average(self, participation):
|
def average(self, participation):
|
||||||
return sum(passage.average(participation) for passage in self.passages.all()) \
|
return self.coeff * sum(passage.average(participation) for passage in self.passages.all()) \
|
||||||
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
|
+ sum(tweak.diff for tweak in participation.tweaks.filter(pool=self).all())
|
||||||
|
|
||||||
async def aaverage(self, participation):
|
async def aaverage(self, participation):
|
||||||
return sum([passage.average(participation) async for passage in self.passages.all()]) \
|
return self.coeff * sum([passage.average(participation) async for passage in self.passages.all()]) \
|
||||||
+ sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()])
|
+ sum([tweak.diff async for tweak in participation.tweaks.filter(pool=self).all()])
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
\Large {\bf \tfjmedition$^{e}$ Tournoi Fran\c cais des Jeunes Math\'ematiciennes et Math\'ematiciens \tfjm}\\
|
||||||
\vspace{3mm}
|
\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}
|
\vspace{15mm}
|
||||||
|
@ -39,12 +39,14 @@
|
|||||||
<dt class="col-sm-6 text-sm-end">{% trans 'date of maximal syntheses submission for the first round'|capfirst %}</dt>
|
<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>
|
<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>
|
<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>
|
<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>
|
<dt class="col-sm-6 text-sm-end">{% trans 'description'|capfirst %}</dt>
|
||||||
<dd class="col-sm-6">{{ tournament.description }}</dd>
|
<dd class="col-sm-6">{{ tournament.description }}</dd>
|
||||||
|
|
||||||
|
@ -674,7 +674,7 @@ class TestPayment(TestCase):
|
|||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
||||||
data={'type': "bank_transfer",
|
data={'type': "bank_transfer",
|
||||||
'additional_information': "This is a bank transfer",
|
'additional_information': "This is a bank transfer",
|
||||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
||||||
payment.refresh_from_db()
|
payment.refresh_from_db()
|
||||||
self.assertIsNone(payment.valid)
|
self.assertIsNone(payment.valid)
|
||||||
@ -735,7 +735,7 @@ class TestPayment(TestCase):
|
|||||||
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)),
|
||||||
data={'type': "scholarship",
|
data={'type': "scholarship",
|
||||||
'additional_information': "I don't have to pay because I have a scholarship",
|
'additional_information': "I don't have to pay because I have a scholarship",
|
||||||
'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")})
|
'receipt': open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb")})
|
||||||
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200)
|
||||||
payment.refresh_from_db()
|
payment.refresh_from_db()
|
||||||
self.assertIsNone(payment.valid)
|
self.assertIsNone(payment.valid)
|
||||||
|
@ -711,6 +711,7 @@ class TournamentExportCSVView(VolunteerMixin, DetailView):
|
|||||||
'Adresse': registration.address,
|
'Adresse': registration.address,
|
||||||
'Code postal': registration.zip_code,
|
'Code postal': registration.zip_code,
|
||||||
'Ville': registration.city,
|
'Ville': registration.city,
|
||||||
|
'Pays': registration.country,
|
||||||
'Téléphone': registration.phone_number,
|
'Téléphone': registration.phone_number,
|
||||||
'Classe': registration.get_student_class_display() if registration.is_student
|
'Classe': registration.get_student_class_display() if registration.is_student
|
||||||
else registration.last_degree,
|
else registration.last_degree,
|
||||||
|
@ -106,9 +106,10 @@ class StudentRegistrationForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StudentRegistration
|
model = StudentRegistration
|
||||||
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
fields = ('team', 'student_class', 'birth_date', 'gender', 'address', 'zip_code', 'city', 'country',
|
||||||
'school', 'health_issues', 'housing_constraints', 'responsible_name', 'responsible_phone',
|
'phone_number', 'school', 'health_issues', 'housing_constraints',
|
||||||
'responsible_email', 'give_contact_to_animath', 'email_confirmed',)
|
'responsible_name', 'responsible_phone', 'responsible_email', 'give_contact_to_animath',
|
||||||
|
'email_confirmed',)
|
||||||
|
|
||||||
|
|
||||||
class PhotoAuthorizationForm(forms.ModelForm):
|
class PhotoAuthorizationForm(forms.ModelForm):
|
||||||
@ -249,7 +250,7 @@ class CoachRegistrationForm(forms.ModelForm):
|
|||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CoachRegistration
|
model = CoachRegistration
|
||||||
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'phone_number',
|
fields = ('team', 'gender', 'address', 'zip_code', 'city', 'country', 'phone_number',
|
||||||
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
'last_degree', 'professional_activity', 'health_issues', 'housing_constraints',
|
||||||
'give_contact_to_animath', 'email_confirmed',)
|
'give_contact_to_animath', 'email_confirmed',)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -183,6 +183,12 @@ class ParticipantRegistration(Registration):
|
|||||||
verbose_name=_("city"),
|
verbose_name=_("city"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
country = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("country"),
|
||||||
|
default="France",
|
||||||
|
)
|
||||||
|
|
||||||
phone_number = PhoneNumberField(
|
phone_number = PhoneNumberField(
|
||||||
verbose_name=_("phone number"),
|
verbose_name=_("phone number"),
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -48,7 +48,10 @@
|
|||||||
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
|
<dd class="col-sm-6">{{ user_object.registration.get_gender_display }}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans "Address:" %}</dt>
|
||||||
<dd class="col-sm-6">{{ user_object.registration.address }}, {{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }}</dd>
|
<dd class="col-sm-6">
|
||||||
|
{{ user_object.registration.address }},
|
||||||
|
{{ user_object.registration.zip_code|stringformat:'05d' }} {{ user_object.registration.city }} ({{ user_object.registration.country }})
|
||||||
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt>
|
<dt class="col-sm-6 text-sm-end">{% trans "Phone number:" %}</dt>
|
||||||
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
<dd class="col-sm-6">{{ user_object.registration.phone_number }}</dd>
|
||||||
|
@ -333,7 +333,7 @@ class TestRegistration(TestCase):
|
|||||||
|
|
||||||
response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
|
response = self.client.post(reverse(f"registration:upload_user_{auth_type}",
|
||||||
args=(self.student.registration.pk,)), data={
|
args=(self.student.registration.pk,)), data={
|
||||||
auth_type: open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
|
auth_type: open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
|
||||||
})
|
})
|
||||||
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ class TestRegistration(TestCase):
|
|||||||
old_authoratization = self.student.registration.photo_authorization.path
|
old_authoratization = self.student.registration.photo_authorization.path
|
||||||
response = self.client.post(reverse("registration:upload_user_photo_authorization",
|
response = self.client.post(reverse("registration:upload_user_photo_authorization",
|
||||||
args=(self.student.registration.pk,)), data=dict(
|
args=(self.student.registration.pk,)), data=dict(
|
||||||
photo_authorization=open("tfjm/static/Fiche_sanitaire.pdf", "rb"),
|
photo_authorization=open("tfjm/static/tfjm/Fiche_sanitaire.pdf", "rb"),
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
|
||||||
self.assertFalse(os.path.isfile(old_authoratization))
|
self.assertFalse(os.path.isfile(old_authoratization))
|
||||||
|
10
tfjm/context_processors.py
Normal file
10
tfjm/context_processors.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from participation.models import Tournament
|
||||||
|
|
||||||
|
|
||||||
|
def tfjm_context(request):
|
||||||
|
return {
|
||||||
|
'TFJM_APP': settings.TFJM_APP,
|
||||||
|
'SINGLE_TOURNAMENT': Tournament.objects.first() if Tournament.objects.exists() and settings.TFJM_APP else None,
|
||||||
|
}
|
@ -118,6 +118,7 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'tfjm.context_processors.tfjm_context',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -211,9 +212,14 @@ PIPELINE = {
|
|||||||
},
|
},
|
||||||
'output_filename': 'tfjm/js/bootstrap.bundle.min.js',
|
'output_filename': 'tfjm/js/bootstrap.bundle.min.js',
|
||||||
},
|
},
|
||||||
'bootstrap_select': {
|
'jquery': {
|
||||||
'source_filenames': {
|
'source_filenames': {
|
||||||
'jquery/jquery.min.js',
|
'jquery/jquery.min.js',
|
||||||
|
},
|
||||||
|
'output_filename': 'tfjm/js/jquery.min.js',
|
||||||
|
},
|
||||||
|
'bootstrap_select': {
|
||||||
|
'source_filenames': {
|
||||||
'bootstrap-select/js/bootstrap-select.min.js',
|
'bootstrap-select/js/bootstrap-select.min.js',
|
||||||
'bootstrap-select/js/defaults-fr_FR.min.js',
|
'bootstrap-select/js/defaults-fr_FR.min.js',
|
||||||
},
|
},
|
||||||
@ -306,6 +312,12 @@ else:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CHANNEL_LAYERS = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Custom phone number format
|
# Custom phone number format
|
||||||
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||||
PHONENUMBER_DEFAULT_REGION = 'FR'
|
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||||
@ -333,16 +345,6 @@ GOOGLE_SERVICE_CLIENT = {
|
|||||||
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
NOTES_DRIVE_FOLDER_ID = os.getenv("NOTES_DRIVE_FOLDER_ID", "CHANGE_ME_IN_ENV_SETTINGS")
|
||||||
|
|
||||||
# Custom parameters
|
# Custom parameters
|
||||||
PROBLEMS = [
|
|
||||||
"Triominos",
|
|
||||||
"Rassemblements mathématiques",
|
|
||||||
"Tournoi de ping-pong",
|
|
||||||
"Dépollution de la Seine",
|
|
||||||
"Électron libre",
|
|
||||||
"Pièces truquées",
|
|
||||||
"Drôles de cookies",
|
|
||||||
"Création d'un jeu",
|
|
||||||
]
|
|
||||||
FORBIDDEN_TRIGRAMS = [
|
FORBIDDEN_TRIGRAMS = [
|
||||||
"BIT",
|
"BIT",
|
||||||
"CNO",
|
"CNO",
|
||||||
@ -361,11 +363,7 @@ FORBIDDEN_TRIGRAMS = [
|
|||||||
"SEX",
|
"SEX",
|
||||||
]
|
]
|
||||||
|
|
||||||
CHANNEL_LAYERS = {
|
TFJM_APP = os.getenv("TFJM_APP", "TFJM") # Change to ETEAM for the ETEAM tournament
|
||||||
"default": {
|
|
||||||
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if TFJM_STAGE == "prod": # pragma: no cover
|
if TFJM_STAGE == "prod": # pragma: no cover
|
||||||
from .settings_prod import * # noqa: F401,F403
|
from .settings_prod import * # noqa: F401,F403
|
||||||
@ -376,3 +374,38 @@ try:
|
|||||||
from .settings_local import * # noqa: F401,F403
|
from .settings_local import * # noqa: F401,F403
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if TFJM_APP == "TFJM":
|
||||||
|
TEAM_CODE_LENGTH = 3
|
||||||
|
RECOMMENDED_SOLUTIONS_COUNT = 5
|
||||||
|
NB_ROUNDS = 2
|
||||||
|
|
||||||
|
PROBLEMS = [
|
||||||
|
"Triominos",
|
||||||
|
"Rassemblements mathématiques",
|
||||||
|
"Tournoi de ping-pong",
|
||||||
|
"Dépollution de la Seine",
|
||||||
|
"Électron libre",
|
||||||
|
"Pièces truquées",
|
||||||
|
"Drôles de cookies",
|
||||||
|
"Création d'un jeu",
|
||||||
|
]
|
||||||
|
elif TFJM_APP == "ETEAM":
|
||||||
|
TEAM_CODE_LENGTH = 4
|
||||||
|
RECOMMENDED_SOLUTIONS_COUNT = 6
|
||||||
|
NB_ROUNDS = 3
|
||||||
|
|
||||||
|
PROBLEMS = [
|
||||||
|
"Exploring Flatland",
|
||||||
|
"A Mazing Hive",
|
||||||
|
"Coin tossing",
|
||||||
|
"The rainbow bridge",
|
||||||
|
"Arithmetic and shopping",
|
||||||
|
"A fence for the goats",
|
||||||
|
"Generalized Tic-Tac-Toe",
|
||||||
|
"Polyhedral construction",
|
||||||
|
"Landing a probe",
|
||||||
|
"Catching the rabbit",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown app: {TFJM_APP}")
|
||||||
|
@ -7,7 +7,9 @@ import os
|
|||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
# Mandatory !
|
# Mandatory !
|
||||||
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org']
|
# TODO ETEAM Meilleur support, et meilleurs DNS surtout
|
||||||
|
ALLOWED_HOSTS = ['inscription.tfjm.org', 'inscriptions.tfjm.org', 'plateforme.tfjm.org',
|
||||||
|
'register.eteam.tfjm.org', 'registration.eteam.tfjm.org', 'platform.eteam.tfjm.org']
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
||||||
|
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,9 +9,11 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>
|
<title>
|
||||||
{% block title %}{{ title }}{% endblock title %} - Plateforme du TFJM²
|
{# TODO ETEAM Plus d'uniformité #}
|
||||||
|
{% block title %}{{ title }}{% endblock title %} - {% trans "ETEAM Platform" %}
|
||||||
</title>
|
</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 #}
|
{# Favicon #}
|
||||||
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
|
||||||
@ -23,6 +25,7 @@
|
|||||||
{# Bootstrap JavaScript #}
|
{# Bootstrap JavaScript #}
|
||||||
{% javascript 'bootstrap' %}
|
{% javascript 'bootstrap' %}
|
||||||
{# bootstrap-select for beautiful selects and JQuery dependency #}
|
{# bootstrap-select for beautiful selects and JQuery dependency #}
|
||||||
|
{% javascript 'jquery' %}
|
||||||
{% javascript 'bootstrap_select' %}
|
{% javascript 'bootstrap_select' %}
|
||||||
|
|
||||||
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
{# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #}
|
||||||
|
@ -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">
|
<nav class="navbar navbar-expand-lg fixed-navbar shadow-sm">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="https://tfjm.org/">
|
{# TODO ETEAM Plus d'uniformité #}
|
||||||
<img src="{% static "tfjm/img/tfjm.svg" %}" style="height: 2em;" alt="Logo TFJM²" id="navbar-logo">
|
<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>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#navbarNavDropdown"
|
data-bs-target="#navbarNavDropdown"
|
||||||
@ -17,9 +19,15 @@
|
|||||||
<a href="{% url "index" %}" class="nav-link"><i class="fas fa-home"></i> {% trans "Home" %}</a>
|
<a href="{% url "index" %}" class="nav-link"><i class="fas fa-home"></i> {% trans "Home" %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
|
{% if SINGLE_TOURNAMENT %}
|
||||||
|
<a href="{% url 'participation:tournament_detail' pk=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">
|
<a href="#" class="nav-link" data-bs-toggle="modal" data-bs-target="#tournamentListModal">
|
||||||
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
<i class="fas fa-calendar-day"></i> {% trans "Tournaments" %}
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% if user.is_authenticated and user.registration.is_admin %}
|
{% if user.is_authenticated and user.registration.is_admin %}
|
||||||
<li class="nav-item active">
|
<li class="nav-item active">
|
||||||
|
@ -28,7 +28,8 @@ from registration.views import HealthSheetView, ParentalAuthorizationView, Photo
|
|||||||
from .views import AdminSearchView
|
from .views import AdminSearchView
|
||||||
|
|
||||||
urlpatterns = [
|
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('about/', TemplateView.as_view(template_name="about.html"), name='about'),
|
||||||
path('i18n/', include('django.conf.urls.i18n')),
|
path('i18n/', include('django.conf.urls.i18n')),
|
||||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
Reference in New Issue
Block a user