import os import random from django.core.mail import send_mail from django.db import models from django.template.loader import render_to_string from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ class Tournament(models.Model): """ Store the information of a tournament. """ name = models.CharField( max_length=255, verbose_name=_("name"), ) organizers = models.ManyToManyField( 'member.TFJMUser', related_name="organized_tournaments", verbose_name=_("organizers"), help_text=_("List of all organizers that can see and manipulate data of the tournament and the teams."), ) size = models.PositiveSmallIntegerField( verbose_name=_("size"), help_text=_("Number of teams that are allowed to join the tournament."), ) place = models.CharField( max_length=255, verbose_name=_("place"), ) price = models.PositiveSmallIntegerField( verbose_name=_("price"), help_text=_("Price asked to participants. Free with a scholarship."), ) description = models.TextField( verbose_name=_("description"), ) date_start = models.DateField( default=timezone.now, verbose_name=_("date start"), ) date_end = models.DateField( default=timezone.now, verbose_name=_("date end"), ) date_inscription = models.DateTimeField( default=timezone.now, verbose_name=_("date of registration closing"), ) date_solutions = models.DateTimeField( default=timezone.now, verbose_name=_("date of maximal solution submission"), ) date_syntheses = models.DateTimeField( default=timezone.now, verbose_name=_("date of maximal syntheses submission for the first round"), ) date_solutions_2 = models.DateTimeField( default=timezone.now, verbose_name=_("date when solutions of round 2 are available"), ) date_syntheses_2 = models.DateTimeField( default=timezone.now, verbose_name=_("date of maximal syntheses submission for the second round"), ) final = models.BooleanField( verbose_name=_("final tournament"), help_text=_("It should be only one final tournament."), ) year = models.PositiveIntegerField( default=os.getenv("TFJM_YEAR", timezone.now().year), verbose_name=_("year"), ) @property def teams(self): """ Get all teams that are registered to this tournament, with a distinction for the final tournament. """ return self._teams if not self.final else Team.objects.filter(selected_for_final=True) @property def linked_organizers(self): """ Display a list of the organizers with links to their personal page. """ return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' for user in self.organizers.all()] @property def solutions(self): """ Get all sent solutions for this tournament. """ from member.models import Solution return Solution.objects.filter(final=self.final) if self.final \ else Solution.objects.filter(team__tournament=self, final=False) @property def syntheses(self): """ Get all sent syntheses for this tournament. """ from member.models import Synthesis return Synthesis.objects.filter(final=self.final) if self.final \ else Synthesis.objects.filter(team__tournament=self, final=False) @classmethod def get_final(cls): """ Get the final tournament. This should exist and be unique. """ return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True) class Meta: verbose_name = _("tournament") verbose_name_plural = _("tournaments") def send_mail_to_organizers(self, template_name, subject="Contact TFJM²", **kwargs): """ Send a mail to all organizers of the tournament. The template of the mail should be found either in templates/mail_templates/.html for the HTML version and in templates/mail_templates/.txt for the plain text version. The context of the template contains the tournament and the user. Extra context can be given through the kwargs. """ context = kwargs context["tournament"] = self for user in self.organizers.all(): context["user"] = user message = render_to_string("mail_templates/" + template_name + ".txt", context=context) message_html = render_to_string("mail_templates/" + template_name + ".html", context=context) send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html) from member.models import TFJMUser for user in TFJMUser.objects.get(is_superuser=True).all(): context["user"] = user message = render_to_string("mail_templates/" + template_name + ".txt", context=context) message_html = render_to_string("mail_templates/" + template_name + ".html", context=context) send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html) def __str__(self): return self.name class Team(models.Model): """ Store information about a registered team. """ name = models.CharField( max_length=255, verbose_name=_("name"), ) trigram = models.CharField( max_length=3, verbose_name=_("trigram"), help_text=_("The trigram should be composed of 3 capitalize letters, that is a funny acronym for the team."), ) tournament = models.ForeignKey( Tournament, on_delete=models.PROTECT, related_name="_teams", verbose_name=_("tournament"), help_text=_("The tournament where the team is registered."), ) inscription_date = models.DateTimeField( auto_now_add=True, verbose_name=_("inscription date"), ) validation_status = models.CharField( max_length=8, choices=[ ("0invalid", _("Registration not validated")), ("1waiting", _("Waiting for validation")), ("2valid", _("Registration validated")), ], verbose_name=_("validation status"), ) selected_for_final = models.BooleanField( default=False, verbose_name=_("selected for final"), ) access_code = models.CharField( max_length=6, unique=True, verbose_name=_("access code"), ) year = models.PositiveIntegerField( default=os.getenv("TFJM_YEAR", timezone.now().year), verbose_name=_("year"), ) @property def valid(self): return self.validation_status == "2valid" @property def waiting(self): return self.validation_status == "1waiting" @property def invalid(self): return self.validation_status == "0invalid" @property def coaches(self): """ Get all coaches of a team. """ return self.users.all().filter(role="2coach") @property def linked_coaches(self): """ Get a list of the coaches of a team with html links to their pages. """ return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' for user in self.coaches] @property def participants(self): """ Get all particpants of a team, coaches excluded. """ return self.users.all().filter(role="3participant") @property def linked_participants(self): """ Get a list of the participants of a team with html links to their pages. """ return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' for user in self.participants] @property def future_tournament(self): """ Get the last tournament where the team is registered. Only matters if the team is selected for final: if this is the case, we return the final tournament. Useful for deadlines. """ return Tournament.get_final() if self.selected_for_final else self.tournament @property def can_validate(self): """ Check if a given team is able to ask for validation. A team can validate if: * All participants filled the photo consent * Minor participants filled the parental consent * Minor participants filled the sanitary plug * Teams sent their motivation letter * The team contains at least 4 participants * The team contains at least 1 coach """ # TODO In a normal time, team needs a motivation letter and authorizations. return self.coaches.exists() and self.participants.count() >= 4\ and self.tournament.date_inscription <= timezone.now() class Meta: verbose_name = _("team") verbose_name_plural = _("teams") unique_together = (('name', 'year',), ('trigram', 'year',),) def send_mail(self, template_name, subject="Contact TFJM²", **kwargs): """ Send a mail to all members of a team with a given template. The template of the mail should be found either in templates/mail_templates/.html for the HTML version and in templates/mail_templates/.txt for the plain text version. The context of the template contains the team and the user. Extra context can be given through the kwargs. """ context = kwargs context["team"] = self for user in self.users.all(): context["user"] = user message = render_to_string("mail_templates/" + template_name + ".txt", context=context) message_html = render_to_string("mail_templates/" + template_name + ".html", context=context) send_mail(subject, message, "contact@tfjm.org", [user.email], html_message=message_html) def __str__(self): return self.trigram + " — " + self.name class Pool(models.Model): """ Store information of a pool. A pool is only a list of accessible solutions to some teams and some juries. TODO: check that the set of teams is equal to the set of the teams that have a solution in this set. TODO: Moreover, a team should send only one solution. """ teams = models.ManyToManyField( Team, related_name="pools", verbose_name=_("teams"), ) solutions = models.ManyToManyField( "member.Solution", related_name="pools", verbose_name=_("solutions"), ) round = models.PositiveIntegerField( choices=[ (1, _("Round 1")), (2, _("Round 2")), ], verbose_name=_("round"), ) juries = models.ManyToManyField( "member.TFJMUser", related_name="pools", verbose_name=_("juries"), ) extra_access_token = models.CharField( max_length=64, default="", verbose_name=_("extra access token"), help_text=_("Let other users access to the pool data without logging in."), ) @property def problems(self): """ Get problem numbers of the sent solutions as a list of integers. """ return list(d["problem"] for d in self.solutions.values("problem").all()) @property def tournament(self): """ Get the concerned tournament. We assume that the pool is correct, so all solutions belong to the same tournament. """ return self.solutions.first().tournament @property def syntheses(self): """ Get the syntheses of the teams that are in this pool, for the correct round. """ from member.models import Synthesis return Synthesis.objects.filter(team__in=self.teams.all(), round=self.round, final=self.tournament.final) def save(self, **kwargs): if not self.extra_access_token: alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" code = "".join(random.choice(alphabet) for _ in range(64)) self.extra_access_token = code super().save(**kwargs) class Meta: verbose_name = _("pool") verbose_name_plural = _("pools") class Payment(models.Model): """ Store some information about payments, to recover data. TODO: handle it... """ user = models.OneToOneField( 'member.TFJMUser', on_delete=models.CASCADE, related_name="payment", verbose_name=_("user"), ) team = models.ForeignKey( Team, on_delete=models.CASCADE, related_name="payments", verbose_name=_("team"), ) method = models.CharField( max_length=16, choices=[ ("not_paid", _("Not paid")), ("credit_card", _("Credit card")), ("check", _("Bank check")), ("transfer", _("Bank transfer")), ("cash", _("Cash")), ("scholarship", _("Scholarship")), ], default="not_paid", verbose_name=_("payment method"), ) validation_status = models.CharField( max_length=8, choices=[ ("0invalid", _("Registration not validated")), ("1waiting", _("Waiting for validation")), ("2valid", _("Registration validated")), ], verbose_name=_("validation status"), ) class Meta: verbose_name = _("payment") verbose_name_plural = _("payments") def __str__(self): return _("Payment of {user}").format(str(self.user))