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")