plateforme-tfjm2/apps/member/models.py

400 lines
11 KiB
Python

import os
from datetime import date
from django.contrib.auth.models import AbstractUser
from django.contrib.sites.models import Site
from django.db import models
from django.template import loader
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel
from tournament.models import Team, Tournament
from .tokens import email_validation_token
class TFJMUser(AbstractUser):
"""
The model of registered users (organizers/juries/admins/coachs/participants)
"""
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
email = models.EmailField(
unique=True,
verbose_name=_("email"),
help_text=_("This should be valid and will be controlled."),
)
team = models.ForeignKey(
Team,
null=True,
on_delete=models.SET_NULL,
related_name="users",
verbose_name=_("team"),
help_text=_("Concerns only coaches and participants."),
)
birth_date = models.DateField(
null=True,
default=None,
verbose_name=_("birth date"),
)
gender = models.CharField(
max_length=16,
null=True,
default=None,
choices=[
("male", _("Male")),
("female", _("Female")),
("non-binary", _("Non binary")),
],
verbose_name=_("gender"),
)
address = models.CharField(
max_length=255,
null=True,
default=None,
verbose_name=_("address"),
)
postal_code = models.PositiveIntegerField(
null=True,
default=None,
verbose_name=_("postal code"),
)
city = models.CharField(
max_length=255,
null=True,
default=None,
verbose_name=_("city"),
)
country = models.CharField(
max_length=255,
default="France",
null=True,
verbose_name=_("country"),
)
phone_number = models.CharField(
max_length=20,
null=True,
blank=True,
default=None,
verbose_name=_("phone number"),
)
school = models.CharField(
max_length=255,
null=True,
default=None,
verbose_name=_("school"),
)
student_class = models.CharField(
max_length=16,
choices=[
('seconde', _("Seconde or less")),
('première', _("Première")),
('terminale', _("Terminale")),
],
null=True,
default=None,
verbose_name="class",
)
responsible_name = models.CharField(
max_length=255,
null=True,
default=None,
verbose_name=_("responsible name"),
)
responsible_phone = models.CharField(
max_length=20,
null=True,
default=None,
verbose_name=_("responsible phone"),
)
responsible_email = models.EmailField(
null=True,
default=None,
verbose_name=_("responsible email"),
)
description = models.TextField(
null=True,
default=None,
verbose_name=_("description"),
)
role = models.CharField(
max_length=16,
choices=[
("0admin", _("Admin")),
("1volunteer", _("Organizer")),
("2coach", _("Coach")),
("3participant", _("Participant")),
]
)
year = models.PositiveIntegerField(
default=os.getenv("TFJM_YEAR", date.today().year),
verbose_name=_("year"),
)
email_confirmed = models.BooleanField(
verbose_name=_("email confirmed"),
default=False,
)
@property
def participates(self):
"""
Return True iff this user is a participant or a coach, ie. if the user is a member of a team that worked
for the tournament.
"""
return self.role == "3participant" or self.role == "2coach"
@property
def organizes(self):
"""
Return True iff this user is a local or global organizer of the tournament. This includes juries.
"""
return self.role == "1volunteer" or self.role == "0admin"
@property
def admin(self):
"""
Return True iff this user is a global organizer, ie. an administrator. This should be equivalent to be
a superuser.
"""
return self.role == "0admin"
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
def save(self, *args, **kwargs):
# We ensure that the username is the email of the user.
self.username = self.email
super().save(*args, **kwargs)
def __str__(self):
return self.first_name + " " + self.last_name
def send_email_validation_link(self):
subject = "[TFJM²] " + str(_("Activate your Note Kfet account"))
token = email_validation_token.make_token(self)
uid = urlsafe_base64_encode(force_bytes(self.pk))
site = Site.objects.first()
message = loader.render_to_string('registration/mails/email_validation_email.txt',
{
'user': self,
'domain': site.domain,
'token': token,
'uid': uid,
})
html = loader.render_to_string('registration/mails/email_validation_email.html',
{
'user': self,
'domain': site.domain,
'token': token,
'uid': uid,
})
self.email_user(subject, message, html_message=html)
class Document(PolymorphicModel):
"""
Abstract model of any saved document (solution, synthesis, motivation letter, authorization)
"""
file = models.FileField(
unique=True,
verbose_name=_("file"),
)
uploaded_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_("uploaded at"),
)
class Meta:
verbose_name = _("document")
verbose_name_plural = _("documents")
def delete(self, *args, **kwargs):
self.file.delete(True)
return super().delete(*args, **kwargs)
class Authorization(Document):
"""
Model for authorization papers (parental consent, photo consent, sanitary plug, ...)
"""
user = models.ForeignKey(
TFJMUser,
on_delete=models.CASCADE,
related_name="authorizations",
verbose_name=_("user"),
)
type = models.CharField(
max_length=32,
choices=[
("parental_consent", _("Parental consent")),
("photo_consent", _("Photo consent")),
("sanitary_plug", _("Sanitary plug")),
("scholarship", _("Scholarship")),
],
verbose_name=_("type"),
)
class Meta:
verbose_name = _("authorization")
verbose_name_plural = _("authorizations")
def __str__(self):
return _("{authorization} for user {user}").format(authorization=self.type, user=str(self.user))
class MotivationLetter(Document):
"""
Model for motivation letters of a team.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="motivation_letters",
verbose_name=_("team"),
)
class Meta:
verbose_name = _("motivation letter")
verbose_name_plural = _("motivation letters")
def __str__(self):
return _("Motivation letter of team {team} ({trigram})").format(team=self.team.name, trigram=self.team.trigram)
class Solution(Document):
"""
Model for solutions of team for a given problem, for the regional or final tournament.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="solutions",
verbose_name=_("team"),
)
problem = models.PositiveSmallIntegerField(
verbose_name=_("problem"),
)
final = models.BooleanField(
default=False,
verbose_name=_("final solution"),
)
@property
def tournament(self):
"""
Get the concerned tournament of a solution.
Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
final tournament.
"""
return Tournament.get_final() if self.final else self.team.tournament
class Meta:
verbose_name = _("solution")
verbose_name_plural = _("solutions")
unique_together = ('team', 'problem', 'final',)
def __str__(self):
if self.final:
return _("Solution of team {trigram} for problem {problem} for final")\
.format(trigram=self.team.trigram, problem=self.problem)
else:
return _("Solution of team {trigram} for problem {problem}")\
.format(trigram=self.team.trigram, problem=self.problem)
class Synthesis(Document):
"""
Model for syntheses of a team for a given round and for a given role, for the regional or final tournament.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="syntheses",
verbose_name=_("team"),
)
source = models.CharField(
max_length=16,
choices=[
("opponent", _("Opponent")),
("rapporteur", _("Rapporteur")),
],
verbose_name=_("source"),
)
round = models.PositiveSmallIntegerField(
choices=[
(1, _("Round 1")),
(2, _("Round 2")),
],
verbose_name=_("round"),
)
final = models.BooleanField(
default=False,
verbose_name=_("final synthesis"),
)
@property
def tournament(self):
"""
Get the concerned tournament of a solution.
Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
final tournament.
"""
return Tournament.get_final() if self.final else self.team.tournament
class Meta:
verbose_name = _("synthesis")
verbose_name_plural = _("syntheses")
unique_together = ('team', 'source', 'round', 'final',)
def __str__(self):
return _("Synthesis of team {trigram} that is {source} for the round {round} of tournament {tournament}")\
.format(trigram=self.team.trigram, source=self.get_source_display().lower(), round=self.round,
tournament=self.tournament)
class Config(models.Model):
"""
Dictionary of configuration variables.
"""
key = models.CharField(
max_length=255,
primary_key=True,
verbose_name=_("key"),
)
value = models.TextField(
default="",
verbose_name=_("value"),
)
class Meta:
verbose_name = _("configuration")
verbose_name_plural = _("configurations")