Newer
Older
from django.utils import timezone
from accounts.models import EmailUser
from site_settings.models import Colors, SiteSettings
def validate_nonzero(value):
"""Make a positive integer field non-zero"""
if value == 0:
raise ValidationError(
_('Cette valeur doit-être non-nulle'),
)
class ActivityModel(models.Model):
class Status(models.TextChoices):
"""en presentiel ou non"""
PRESENT = "P", _("En présentiel uniquement")
DISTANT = "D", _("En distanciel uniquement")
BOTH = "2", _("Les deux")
class ActivityTypes(models.TextChoices):
"""quantité d'activité"""
GAME = "1 partie", _("Une partie")
GAMES = "2+ parties", _("Quelques parties")
TOURNAMENT = "Tournoi", _("Tournoi")
FREEPLAY = "freeplay", _("Freeplay")
OTHER = "other", _("Autre")
class GameTypes(models.TextChoices):
"""types de jeu"""
CARD_GAME = "jeu cartes", _("Jeu de cartes")
BOARD_GAME = "jeu plateau", _("Jeu de société")
TABLETOP_RPG = "table RPG", _("Jeu de rôle sur table")
LARGE_RPG = "large RPG", _("Jeu de rôle grandeur nature")
VIDEOGAME = "videogame", _("Jeu vidéo")
PARTYGAME = "partygame", _("Party game")
PUZZLE = "puzzle", _("Puzzle ou analogue")
SECRET_ROLES = "secret roles", _("Jeu à rôles secrets")
COOP = "coop", _("Jeu coopératif")
OTHER = "other", _("Autre")
class Availability(models.TextChoices):
"""Diponibilité à un moment donné"""
IDEAL = "0", _("Idéal")
POSSIBLE = "1", _("Acceptable")
UNAVAILABLE = "2", _("Indisponible")
display = models.BooleanField("afficher dans la liste", default=False,
help_text="Si vrai, s'affiche sur la page activités"
)
show_email = models.BooleanField("afficher l'email de l'orga", default=True,
help_text="Si l'affichage d'email global et cette case sont vrai, affiche l'email de l'orga"
)
title = models.CharField("Titre", max_length=200)
act_type = models.CharField("Type d'activité", choices=ActivityTypes.choices, max_length=12)
game_type = models.CharField("Type de jeu", choices=GameTypes.choices, max_length=12)
description = models.TextField(
"description", max_length=10000,
help_text='Texte ou html selon la valeur de "Description HTML".\n'
desc_as_html = models.BooleanField("Description au format HTML", default=False,
help_text="Assurer vous que le texte est bien formaté, cette option peut casser la page activités."
host = models.ForeignKey(
EmailUser, on_delete=models.SET_NULL, verbose_name="Organisateur",
blank=True, null=True
host_name = models.CharField(
"nom de l'organisateur", max_length=50, null=True, blank=True,
help_text="Peut-être laissé vide pour des simples activités sans orga"
)
host_email = models.EmailField(
"email de l'organisateur",
help_text="Utilisé pour communiquer la liste des participants si demandé"
)
host_info = models.TextField(
"Autre orgas/contacts", max_length=1000, blank=True, null=True
must_subscribe = models.BooleanField("sur inscription", default=False,
help_text="Informatif, il faut utiliser les créneaux pour ajouter dans la liste d'inscription"
)
communicate_participants = models.BooleanField("communiquer la liste des participants à l'orga avant l'événement")
max_participants = models.PositiveIntegerField(
"Nombre maximum de participants", help_text="0 pour illimité", default=0
)
min_participants = models.PositiveIntegerField(
"Nombre minimum de participants", default=0
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
## Information fournies par le respo
duration = models.DurationField("Durée", help_text="format hh:mm:ss")
desired_slot_nb = models.PositiveIntegerField(
"Nombre de créneaux souhaités", default=1,
validators=[validate_nonzero]
)
available_friday_evening = models.CharField(
"Crénau vendredi soir", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_friday_night = models.CharField(
"Crénau vendredi nuit", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_saturday_morning = models.CharField(
"Crénau samedi matin", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_saturday_afternoon = models.CharField(
"Crénau samedi après-midi", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_saturday_evening = models.CharField(
"Crénau samedi soir", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_saturday_night = models.CharField(
"Crénau samedi nuit", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_sunday_morning = models.CharField(
"Crénau dimanche matin", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
available_sunday_afternoon = models.CharField(
"Crénau dimanche après-midi", choices=Availability.choices, max_length=1,
default=Availability.POSSIBLE,
)
constraints = models.TextField(
"Contraintes particulières", max_length=2000, blank=True, null=True
)
status = models.CharField("Présentiel/distanciel", choices=Status.choices, max_length=1)
needs = models.TextField(
"Besoin particuliers", max_length=2000, blank=True, null=True
)
comments = models.TextField(
"Commentaires", max_length=2000, blank=True, null=True
)
@property
def nb_participants(self) -> str:
if self.max_participants == 0:
elif self.max_participants == self.min_participants:
ret = "{}".format(self.min_participants)
else:
ret = "{} - {}".format(self.min_participants, self.max_participants)
if self.must_subscribe:
ret += " (sur inscription)"
return ret
@property
def pretty_duration(self) -> str:
hours, rem = divmod(self.duration.seconds, 3600)
minutes = "{:02}".format(rem // 60) if rem // 60 else ""
return "{}h{}".format(hours, minutes)
@property
def pretty_type(self) -> str:
type = self.ActivityTypes(self.act_type).label
game = self.GameTypes(self.game_type).label
return "{}, {}".format(game, type.lower())
@property
def slug(self) -> str:
"""Returns the planning/display slug for this activity"""
return "act-{}".format(self.id)
def slots(self):
"""Returns a list of slots related to self"""
return SlotModel.objects.filter(activity=self, on_activity=True).order_by("start")
def __str__(self):
return self.title
class Meta:
verbose_name = "activité"
class SlotModel(models.Model):
"""Crénaux indiquant ou une activité se place dans le planning
Dans une table à part car un activité peut avoir plusieurs créneaux.
Les inscriptions se font à des créneaux et non des activités"""
TITLE_SPECIFIER = "{act_title}"
activity = models.ForeignKey(ActivityModel, on_delete=models.CASCADE, verbose_name="Activité")
title = models.CharField(
"Titre", max_length=200, default=TITLE_SPECIFIER,
help_text="Utilisez '{}' pour insérer le titre de l'activité correspondante".format(
TITLE_SPECIFIER),
)
start = models.DateTimeField("début")
duration = models.DurationField(
"durée", blank=True, null=True,
help_text="Format 00:00:00. Laisser vide pour prendre la durée de l'activité correspondante"
)
room = models.CharField("salle", max_length=100, null=True, blank=True)
on_planning = models.BooleanField(
"afficher sur le planning", default=True,
)
on_activity = models.BooleanField(
"afficher dans la description de l'activité", default=True,
)
subscribing_open = models.BooleanField("ouvert aux inscriptions", default=False,
help_text="Si vrai, apparaît dans la liste du formulaire d'inscription"
)
"Couleur", choices=Colors.choices, max_length=1, default=Colors.RED,
help_text="La légende des couleurs est modifiable dans les paramètres"
return ActivityChoicesModel.objects.filter(slot=self, accepted=True)
if self.duration:
return self.start + self.duration
return self.start + self.activity.duration
def conflicts(self, other: "SlotModel") -> bool:
"""Check whether these slots overlap"""
if self.start <= other.start:
return other.start <= self.end
return self.start <= other.end
@staticmethod
def relative_day(date: datetime.datetime) -> int:
"""Relative day to start.
- friday 04:00 -> 03:59 = day 0
- saturday 04:00 -> 03:59 = day 1
- sunday 04:00 -> 03:59 = day 2
returns 0 if no date_start is defined in settings"""
settings = SiteSettings.load()
if settings.date_start:
return (date - timezone.datetime.combine(
settings.date_start, datetime.time(hour=4), timezone.get_current_timezone()
)).days
else:
return 0
@staticmethod
def fake_date(date: datetime.datetime):
"""Fake day for display on the (single day planning)"""
settings = SiteSettings.load()
if settings.date_start:
time = date.timetz()
offset = datetime.timedelta(0)
)
return None
@property
def start_day(self) -> int:
"""returns a day (0-2)"""
return self.relative_day(self.start)
@property
def end_day(self) -> int:
"""returns a day (0-2)"""
return self.relative_day(self.end)
@property
def planning_start(self) -> int:
return self.fake_date(self.start)
@property
def planning_end(self) -> int:
def __str__(self) -> str:
return self.title.replace(self.TITLE_SPECIFIER, self.activity.title)
class ParticipantModel(models.Model):
"""un participant aux interludes"""
class ENS(models.TextChoices):
"""enum representant les ENS"""
ENS_ULM = "U", _("ENS Ulm")
ENS_LYON = "L", _("ENS Lyon")
ENS_RENNES = "R", _("ENS Rennes")
ENS_CACHAN = "C", _("ENS Paris Saclay")
user = models.OneToOneField(EmailUser, on_delete=models.CASCADE, related_name="Utilisateur")
school = models.CharField("ENS de rattachement", choices=ENS.choices, max_length=1)
is_registered = models.BooleanField("est inscrit", default=False)
meal_friday_evening = models.BooleanField("repas de vendredi soir", default=False)
meal_saturday_morning = models.BooleanField("repas de samedi matin", default=False)
meal_saturday_midday = models.BooleanField("repas de samedi midi", default=False)
meal_saturday_evening = models.BooleanField("repas de samedi soir", default=False)
meal_sunday_morning = models.BooleanField("repas de dimanche matin", default=False)
meal_sunday_midday = models.BooleanField("repas de dimanche soir", default=False)
sleeps = models.BooleanField("dormir sur place", default=False)
# mug = models.BooleanField("commander une tasse", default=False)
school = self.ENS(self.school).label.replace("ENS ", "") if self.school else ""
return "{} {} ({})".format(self.user.first_name, self.user.last_name, school)
@property
def nb_meals(self) -> int:
return (
self.meal_friday_evening + self.meal_saturday_evening + self.meal_saturday_midday +
self.meal_saturday_morning + self.meal_sunday_midday + self.meal_sunday_morning
)
class Meta:
verbose_name = "participant"
class ActivityChoicesModel(models.Model):
"""liste d'activités souhaitée de chaque participant,
avec un order de priorité"""
priority = models.PositiveIntegerField("priorité")
participant = models.ForeignKey(
ParticipantModel, on_delete=models.CASCADE, verbose_name="participant",
SlotModel, on_delete=models.CASCADE, verbose_name="créneau",
accepted = models.BooleanField("Obtenue", default=False)
unique_together = (("priority", "participant"), ("participant", "slot"))
ordering = ("participant", "priority")
verbose_name = "choix d'activités"
verbose_name_plural = "choix d'activités"
EmailUser.profile = property(lambda user: ParticipantModel.objects.get_or_create(user=user)[0])