gestioCOF/events/models.py

189 lines
6.5 KiB
Python

"""
Event framework for GestioCOF and GestioBDS.
The events implemented in this module provide two types of customisations to event
creators (the COF and BDS staff): options and extra (text) fields.
Options
-------
An option is an extra field in the registration form with a predefined list of available
choices. Any number of options can be added to an event.
For instance, a typical use-case is events where meals are served to participants
with different possible menus, say: vegeterian / vegan / without pork / etc. This
example can be implemented with an `Option(name="menu")` and an `OptionChoice` for each
available menu.
In this example, the choice was exclusive: participants can only chose one menu. For
situations, where multiple choices can be made at the same time, use the `multi_choices`
flag.
Extra fields
------------
Extra fields can also be added to the registration form that can receive arbitrary text.
Typically, this can be a "remark" field (prefer the LONGTEXT option in this case) or
small form entries such as "phone number" or "emergency contact" (prefer the SHORTTEXT
option in this case).
"""
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
User = get_user_model()
class Event(models.Model):
title = models.CharField(_("titre"), max_length=200)
location = models.CharField(_("lieu"), max_length=200)
start_date = models.DateTimeField(_("date de début"), blank=True, null=True)
end_date = models.DateTimeField(_("date de fin"), blank=True, null=True)
description = models.TextField(_("description"), blank=True)
image = models.ImageField(
_("image"), blank=True, null=True, upload_to="imgs/events/"
)
registration_open = models.BooleanField(_("inscriptions ouvertes"), default=True)
old = models.BooleanField(_("archiver (événement fini)"), default=False)
subscribers = models.ManyToManyField(
User, through="Registration", verbose_name=_("inscrit⋅e⋅s")
)
class Meta:
verbose_name = _("événement")
verbose_name_plural = _("événements")
def __str__(self):
return self.title
class Option(models.Model):
"""Extra form fields with a limited set of available choices.
The available choices are given by `OptionChoice`s (see below). A typical use-case
is events where the participants have the choice between different menus (e.g.
vegan / vegetarian / without-pork / etc).
"""
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="options")
name = models.CharField(_("option d'événement"), max_length=200)
multi_choices = models.BooleanField(_("choix multiples"), default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["event", "name"], name="unique_event_option"
)
]
verbose_name = _("option d'événement")
verbose_name_plural = _("options d'événement")
def __str__(self):
return self.name
class OptionChoice(models.Model):
"""A possible choice for an event option."""
option = models.ForeignKey(Option, on_delete=models.CASCADE, related_name="choices")
choice = models.CharField(_("choix"), max_length=200)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["option", "choice"], name="unique_option_choice"
)
]
verbose_name = _("choix d'option d'événement")
verbose_name_plural = _("choix d'option d'événement")
def __str__(self):
return self.choice
class ExtraField(models.Model):
"""Extra event field receiving arbitrary text.
Extra text field that can be added by event creators to the event registration form.
Typical examples are "remarks" fields (of type LONGTEXT) or more specific fields
such as "emergency contact" (of type SHORTTEXT probably?).
"""
LONGTEXT = "longtext"
SHORTTEXT = "shorttext"
FIELD_TYPE = [
(SHORTTEXT, _("texte court (une ligne)")),
(LONGTEXT, _("texte long (plusieurs lignes)")),
]
event = models.ForeignKey(
Event, on_delete=models.CASCADE, related_name="extra_fields"
)
name = models.CharField(_("champ d'événement supplémentaire"), max_length=200)
field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE)
class Meta:
constraints = [
models.UniqueConstraint(fields=["event", "name"], name="unique_extra_field")
]
class ExtraFieldContent(models.Model):
"""Value entered in an extra field."""
field = models.ForeignKey(ExtraField, on_delete=models.CASCADE)
registration = models.ForeignKey(
"Registration", on_delete=models.CASCADE, related_name="extra_info"
)
content = models.TextField(_("contenu du champ"))
def clean(self):
if self.registration.event != self.field.event:
raise ValidationError(
_("Inscription et champ texte incohérents pour ce commentaire")
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["field", "registration"], name="unique_extra_field_content"
)
]
verbose_name = _("contenu d'un champ événement supplémentaire")
verbose_name_plural = _("contenus d'un champ événement supplémentaire")
def __str__(self):
max_length = 50
if len(self.content) > max_length:
return self.content[: max_length - 1] + ""
else:
return self.content
class Registration(models.Model):
"""A user registration to an event."""
event = models.ForeignKey(Event, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
options_choices = models.ManyToManyField(OptionChoice)
def clean(self):
if not all((ch.option.event == self.event for ch in self.options_choices)):
raise ValidationError(
_("Choix d'options incohérents avec l'événement pour cette inscription")
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["event", "user"], name="unique_registration"
)
]
verbose_name = _("inscription à un événement")
verbose_name_plural = _("inscriptions à un événement")
def __str__(self):
return "inscription de {} à {}".format(self.user, self.event)