diff --git a/apps/member/admin.py b/apps/member/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/member/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/member/apps.py b/apps/member/apps.py new file mode 100644 index 0000000..b704170 --- /dev/null +++ b/apps/member/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MemberConfig(AppConfig): + name = 'apps.member' diff --git a/apps/member/migrations/__init__.py b/apps/member/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/member/models.py b/apps/member/models.py new file mode 100644 index 0000000..6f562c5 --- /dev/null +++ b/apps/member/models.py @@ -0,0 +1,219 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.translation import gettext_lazy as _ +from polymorphic.models import PolymorphicModel + +from tournament.models import Team, Tournament + + +class TFJMUser(AbstractUser): + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = [] + + email = models.EmailField( + unique=True, + verbose_name=_("email"), + ) + + team = models.ForeignKey( + Team, + null=True, + on_delete=models.SET_NULL, + related_name="users", + verbose_name=_("team"), + ) + + 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=_("addresss"), + ) + + address = models.CharField( + max_length=255, + null=True, + default=None, + verbose_name=_("address"), + ) + + postal_code = models.PositiveSmallIntegerField( + 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=[ + ("admin", _("admin")), + ("organizer", _("organizer")), + ("encadrant", _("encadrant")), + ("participant", _("participant")), + ] + ) + + year = models.PositiveIntegerField( + verbose_name=_("year"), + ) + + class Meta: + verbose_name = _("user") + verbose_name_plural = _("users") + + +class AbstractDocument(PolymorphicModel): + file = models.FileField( + unique=True, + upload_to="files/", + verbose_name=_("file"), + ) + + team = models.ForeignKey( + Team, + on_delete=models.CASCADE, + related_name="documents", + verbose_name=_("team"), + ) + + tournament = models.ForeignKey( + Tournament, + on_delete=models.CASCADE, + related_name="documents", + verbose_name=_("tournament"), + ) + + type = models.CharField( + max_length=32, + choices=[ + ("parental_consent", _("Parental consent")), + ("photo_consent", _("Photo consent")), + ("sanitary_plug", _("Sanitary plug")), + ("motivation_letter", _("Motivation letter")), + ("scholarship", _("Scholarship")), + ("solution", _("Solution")), + ("synthesis", _("Synthesis")), + ], + verbose_name=_("type"), + ) + + uploaded_at = models.DateTimeField( + auto_now_add=True, + verbose_name=_("uploaded at"), + ) + + class Meta: + verbose_name = _("abstract document") + verbose_name_plural = _("abstract documents") + + +class Document(AbstractDocument): + class Meta: + verbose_name = _("document") + verbose_name_plural = _("documents") + + +class Solution(Document): + problem = models.PositiveSmallIntegerField( + verbose_name=_("problem"), + ) + + def save(self, **kwargs): + self.type = "solution" + super().save(**kwargs) + + class Meta: + verbose_name = _("solution") + verbose_name_plural = _("solutions") + + +class Synthesis(Document): + problem = models.PositiveSmallIntegerField( + verbose_name=_("problem"), + ) + + def save(self, **kwargs): + self.type = "synthesis" + super().save(**kwargs) + + class Meta: + verbose_name = _("synthesis") + verbose_name_plural = _("syntheses") diff --git a/apps/member/tests.py b/apps/member/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/member/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/member/views.py b/apps/member/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/member/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/tournament/admin.py b/apps/tournament/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/tournament/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/tournament/apps.py b/apps/tournament/apps.py new file mode 100644 index 0000000..eec1530 --- /dev/null +++ b/apps/tournament/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class TournamentConfig(AppConfig): + name = 'apps.tournament' diff --git a/apps/tournament/migrations/__init__.py b/apps/tournament/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/tournament/models.py b/apps/tournament/models.py new file mode 100644 index 0000000..0628115 --- /dev/null +++ b/apps/tournament/models.py @@ -0,0 +1,171 @@ +import os + +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class Tournament(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_("name"), + ) + + organizers = models.ManyToManyField( + 'member.TFJMUser', + related_name="organized_tournaments", + verbose_name=_("organizers"), + ) + + size = models.PositiveSmallIntegerField( + verbose_name=_("size"), + ) + + place = models.CharField( + max_length=255, + verbose_name=_("place"), + ) + + price = models.PositiveSmallIntegerField( + verbose_name=_("price"), + ) + + description = models.TextField( + verbose_name=_("description"), + ) + + date_start = models.DateField( + verbose_name=_("date start"), + ) + + date_end = models.DateField( + verbose_name=_("date start"), + ) + + date_inscription = models.DateTimeField( + verbose_name=_("date of registration closing"), + ) + + date_solutions = models.DateTimeField( + verbose_name=_("date of maximal solution submission"), + ) + + date_syntheses = models.DateTimeField( + verbose_name=_("date of maximal syntheses submission"), + ) + + final = models.BooleanField( + verbose_name=_("final tournament"), + ) + + year = models.PositiveIntegerField( + verbose_name=_("year") + ) + + @classmethod + def get_final(cls): + return cls.objects.get(year=os.getenv("TFJM_YEAR"), final=True) + + class Meta: + verbose_name = _("tournament") + verbose_name_plural = _("tournaments") + + +class Team(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_("name"), + ) + + trigram = models.CharField( + max_length=3, + verbose_name=_("trigram"), + ) + + tournament = models.ForeignKey( + Tournament, + on_delete=models.PROTECT, + verbose_name=_("tournament"), + ) + + inscription_date = models.DateTimeField( + auto_now_add=True, + verbose_name=_("inscription date"), + ) + + validation_status = models.CharField( + max_length=8, + choices=[ + ("invalid", _("Registration not validated")), + ("waiting", _("Waiting for validation")), + ("valid", _("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( + verbose_name=_("year"), + ) + + @property + def encadrants(self): + return self.users.filter(role="encadrant") + + @property + def participants(self): + return self.users.filter(role="participant") + + class Meta: + verbose_name = _("team") + verbose_name_plural = _("teams") + unique_together = (('name', 'year',), ('trigram', 'year',),) + + +class Payment(models.Model): + 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=[ + ("invalid", _("Registration not validated")), + ("waiting", _("Waiting for validation")), + ("valid", _("Registration validated")), + ], + verbose_name=_("validation status"), + ) diff --git a/apps/tournament/tests.py b/apps/tournament/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/tournament/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/tournament/views.py b/apps/tournament/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/tournament/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements.txt b/requirements.txt index c1ebf46..9dc350c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ django-allauth==0.39.1 django-crispy-forms==1.7.2 django-extensions==2.1.9 django-filter==2.2.0 -django-polymorphic==2.0.3 +django-polymorphic==2.1.2 django-tables2==2.1.0 docutils==0.14 idna==2.8 diff --git a/tfjm/settings.py b/tfjm/settings.py index 507fe74..fd4844e 100644 --- a/tfjm/settings.py +++ b/tfjm/settings.py @@ -11,9 +11,13 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ """ import os +import sys # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +APPS_DIR = os.path.realpath(os.path.join(BASE_DIR, "apps")) +sys.path.append(APPS_DIR) # Quick-start development settings - unsuitable for production @@ -37,6 +41,9 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + 'member', + 'tournament', ] MIDDLEWARE = [ @@ -100,6 +107,8 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +AUTH_USER_MODEL = 'member.TFJMUser' + # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/