from django.contrib.auth import get_user_model from django.core.exceptions import FieldDoesNotExist, FieldError, ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ from communication.models import SubscriptionMixin from .validators import ColorValidator User = get_user_model() class Event(SubscriptionMixin, models.Model): title = models.CharField( _("nom de l'évènement"), max_length=200, ) slug = models.SlugField( _("identificateur"), unique=True, help_text=_( "Seulement des lettres, des chiffres ou les caractères '_' ou '-'." ), ) created_by = models.ForeignKey( User, verbose_name=_("créé par"), on_delete=models.SET_NULL, related_name="created_events", editable=False, null=True, ) created_at = models.DateTimeField( _('date de création'), auto_now_add=True, ) description = models.TextField(_('description')) beginning_date = models.DateTimeField( _('date de début'), help_text=_("date publique de l'évènement"), ) ending_date = models.DateTimeField( _('date de fin'), help_text=_("date publique de l'évènement"), ) class Meta: verbose_name = _("évènement") verbose_name_plural = _("évènements") def __str__(self): return self.title class EventSpecificMixin(models.Model): """Mixin allowing for event-specific models instances or not (depending on whether the event field is null)""" event = models.ForeignKey( Event, verbose_name=_("évènement"), help_text=_( "Si spécifié, l'instance du modèle est spécifique à l'évènement " "en question." ), on_delete=models.CASCADE, blank=True, null=True, ) class Meta: abstract = True class Place(EventSpecificMixin, models.Model): name = models.CharField( _("nom du lieu"), max_length=200, ) description = models.TextField(blank=True) class Meta: verbose_name = _("lieu") verbose_name_plural = _("lieux") def __str__(self): return self.name class ActivityTag(EventSpecificMixin, models.Model): name = models.CharField( _("nom du tag"), max_length=200, ) is_public = models.BooleanField( _("est public"), help_text=_( "Sert à faire une distinction dans l'affichage selon que le tag " "soit destiné au public ou à l'organisation." ), ) color = models.CharField( _('couleur'), max_length=7, validators=[ColorValidator], help_text=_("Rentrer une couleur en hexadécimal (#XXX ou #XXXXXX)."), ) class Meta: verbose_name = _("tag") verbose_name_plural = _("tags") def __str__(self): return self.name class AbstractActivityTemplate(SubscriptionMixin, models.Model): title = models.CharField( _("nom de l'activité"), max_length=200, blank=True, null=True, ) # FIXME: voir comment on traite l'héritage de `event` event = models.ForeignKey( Event, verbose_name=_("évènement"), on_delete=models.CASCADE, ) is_public = models.NullBooleanField( _("est public"), blank=True, ) has_perm = models.NullBooleanField( _("inscription de permanents"), blank=True, ) min_perm = models.PositiveSmallIntegerField( _('nombre minimum de permanents'), blank=True, null=True, ) max_perm = models.PositiveSmallIntegerField( _('nombre maximum de permanents'), blank=True, null=True, ) description = models.TextField( _('description'), help_text=_("Visible par tout le monde si l'événément est public."), blank=True, null=True, ) remarks = models.TextField( _('remarques'), help_text=_("Visible uniquement par les organisateurs."), blank=True, null=True, ) tags = models.ManyToManyField( ActivityTag, verbose_name=_('tags'), blank=True, ) places = models.ManyToManyField( Place, verbose_name=_('lieux'), blank=True, ) class Meta: abstract = True class ActivityTemplate(AbstractActivityTemplate): name = models.CharField( _("Nom du template"), max_length=200, help_text=_("Ne sera pas affiché"), ) class Meta: verbose_name = _("template activité") verbose_name_plural = _("templates activité") def __str__(self): return self.name def clean(self): errors = [] # On clean les nombre de permanents if not self.has_perm: self.max_perm = None self.min_perm = None else: if self.min_perm > self.max_perm: errors.append(ValidationError( _("Nombres de permanents incompatibles"), code='wrong-nb-perm', )) if errors != []: raise ValidationError(errors) class Activity(AbstractActivityTemplate): parent = models.ForeignKey( ActivityTemplate, verbose_name=_("template"), on_delete=models.PROTECT, related_name="children", blank=True, null=True, ) staff = models.ManyToManyField( User, verbose_name=_("permanents"), related_name="in_perm_activities", blank=True, ) beginning = models.DateTimeField(_("heure de début")) end = models.DateTimeField(_("heure de fin")) def clean(self): errors = [] # On clean les nombre de permanents if not self.get_herited('has_perm'): self.max_perm = None self.min_perm = None else: if self.get_herited('min_perm') > self.get_herited('max_perm'): errors.append(ValidationError( _("Nombres de permanents incompatibles"), code='wrong-nb-perm', )) # On valide l'héritage for f in self._meta.get_fields(): try: # On réccupère le field du parent attrname = f.name tpl_field = ActivityTemplate._meta.get_field(attrname) # Peut-être que ce n'est pas un field # concerné par l'héritage except FieldDoesNotExist: continue # Y'a certains champs dont on se moque if attrname in ['id', 'staff', 'tags', ]: continue # C'est plus compliqué que ça pour les nb_perm if attrname in ['max_perm', 'min_perm', ]: if not self.get_herited('has_perm'): continue # On a un Many to Many, on lit différement if tpl_field.many_to_many: pass # # On a pas spécifié # if not value.exists(): # # On a pas de parent # if self.parent is None: # errors.append(ValidationError( # _("N'hérite pas d'un template, spécifier le champs : %(attr)s"), # code='bad-overriding', # params={'attr': f.verbose_name}, # )) # else: # pvalue = getattr(self.parent, attrname) # # On a un parent qui ne dit rien # if not pvalue.exists(): # errors.append(ValidationError( # _("Champs non précisé chez le parent, spécifier : %(attr)s"), # code='bad-overriding', # params={'attr': f.verbose_name}, # )) else: value = getattr(self, attrname) # On a pas spécifié if value is None: # On a pas de parent if self.parent is None: errors.append(ValidationError( _("N'hérite pas d'un template, spécifier le champs : %(attr)s"), code='bad-overriding', params={'attr': f.verbose_name}, )) else: pvalue = getattr(self.parent, attrname) # On a un parent qui ne dit rien if pvalue is None: errors.append(ValidationError( _("Champs non précisé chez le parent, spécifier : %(attr)s"), code='bad-overriding', params={'attr': f.verbose_name}, )) if errors != []: raise ValidationError(errors) def get_herited(self, attrname): try: tpl_field = ActivityTemplate._meta.get_field(attrname) except FieldDoesNotExist: raise FieldError( "%(attrname)s field can't be herited.", params={'attrname': attrname}, ) value = getattr(self, attrname) if tpl_field.many_to_many: if value.exists(): return value else: return getattr(self.parent, attrname) elif value is None: return getattr(self.parent, attrname) else: return value class Meta: verbose_name = _("activité") verbose_name_plural = _("activités") def __str__(self): return self.get_herited('title')