""" 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: verbose_name = _("option d'événement") verbose_name_plural = _("options d'événement") unique_together = [["event", "name"]] 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: verbose_name = _("choix d'option d'événement") verbose_name_plural = _("choix d'option d'événement") unique_together = [["option", "choice"]] 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: unique_together = [["event", "name"]] 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: verbose_name = _("contenu d'un champ événement supplémentaire") verbose_name_plural = _("contenus d'un champ événement supplémentaire") unique_together = [["field", "registration"]] 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: verbose_name = _("inscription à un événement") verbose_name_plural = _("inscriptions à un événement") unique_together = [["event", "user"]] def __str__(self): return "inscription de {} à {}".format(self.user, self.event)