"""Webapp app models.""" from django.conf import settings from django.db import models from django.utils import timezone # from datetime import timedelta from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver from webapp.utils import month_from_now, send_signup_email from django.utils.translation import ugettext_lazy as _ from auditlog.registry import auditlog from phonenumber_field.modelfields import PhoneNumberField from django.contrib.postgres.fields import JSONField from uuid import uuid4 import logging VERBOSE_NAME = _('Webapp') EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)" class Tag(models.Model): """Model for tag.""" slug = models.SlugField(unique=True) name = models.CharField(max_length=127) icon = models.ImageField() class Meta: verbose_name = _('Tag') verbose_name_plural = _('Tags') def __str__(self): return _('Tag: {}').format(self.slug) class BaseFeed(models.Model): """Model containing something showing on some info feed.""" tags = models.ManyToManyField(Tag, related_name="feeds", blank=True) visible = models.BooleanField(default=True) title = models.CharField(max_length=255) description = models.CharField(max_length=255) content = models.TextField() class Feed(BaseFeed): """Model representing feed.""" publish_time = models.DateTimeField(default=timezone.now) autohide = models.DateTimeField(default=month_from_now) autohide_enabled = models.BooleanField(default=False) def __str__(self): return _('Feed: {}').format(self.title) class Meta: verbose_name = _('Feed') verbose_name_plural = _('Feeds') class Event(BaseFeed): """Model for event.""" start_time = models.DateTimeField(default=timezone.now) end_time = models.DateTimeField(default=timezone.now) signupForm = models.ManyToManyField( 'SignupForm', blank=True, related_name="event") location = models.CharField(max_length=255, blank=True) def __str__(self): return _('Event: {}').format(self.title) class Meta: verbose_name = _('Event') verbose_name_plural = _('Events') class TemplateQuestion(models.Model): """Stores template questions for signup forms as JSONB""" name = models.CharField(max_length=255) question = JSONField() def __str__(self): return _('Template questions: {}').format(self.name) class Meta: verbose_name = _('Template question') verbose_name_plural = _('Template questions') class SignupForm(models.Model): """Model for event signup form. Stores questions in JSONB.""" title = models.CharField(max_length=255) start_time = models.DateTimeField(default=timezone.now) end_time = models.DateTimeField(default=timezone.now) questions = JSONField() visible = models.BooleanField(default=True) def __str__(self): return _('#{} {}').format(self.id, self.title) @property def signups(self): return Signup.objects.filter(signupForm=self) @property def schema(self): questions = self.questions properties = {} for q in questions: id = q["id"] question_type = q["type"] if question_type == "text": properties[id] = { "type": "string" } elif question_type == "email": # Format is just a "FYI" field, so we also have pattern. properties[id] = { "type": "string", "format": "email", "pattern": EMAIL_REGEX } elif question_type == "radiobutton": options = q["options"] regexes = map(lambda x: f"^{x}$", options) pattern = "|".join(regexes) properties[id] = { "type": "string", # Remove last regex or "pattern": pattern, } elif question_type == "checkbox": options = q["options"] regexes = map(lambda x: f"^{x}$", options) pattern = "|".join(regexes) properties[id] = { "type": "array", "uniqueItems": True, "maxItems": len(options), "items": { "type": "string", "pattern": pattern } } else: raise Exception("invalid question type!") ids = list(map(lambda x: x["id"], questions)) return { "type": "object", "required": ids, "minProperties": len(ids), "maxProperties": len(ids), "properties": properties } class Meta: verbose_name = _('Signup form') verbose_name_plural = _('Signup forms') class Signup(models.Model): signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE) time = models.DateTimeField(default=timezone.now) answer = JSONField() # Answer we use in signupForm signups field. Frontend uses first questions answer as this value. list_name = models.CharField(_('Name'), max_length=255) # If there is email in questions, we save it as own field email = models.EmailField(blank=True, null=True) # Random unique identifier. Used for signup editing by the user. uuid = models.UUIDField(default=uuid4, editable=False) def __str__(self): return f"{self.signupForm}: {self.list_name} ({self.pk})" class Meta: verbose_name = _('Sign-up') verbose_name_plural = _('Sign-ups') @receiver(post_save, sender=Signup) def email_on_signup(sender, instance, created, **kwargs): """Send email validation.""" if created and instance.email: # TODO: Possible bug due to many-to-many relationship with events and forms. # TODO: Subject field crashes with lazy loaded translations. try: # subject = _(f"Olet ilmoittautunut tapahtumaan {instance.signupForm.event.first().title}") subject = f"Olet ilmoittautunut tapahtumaan {instance.signupForm.event.first().title}" except AttributeError: # subject = _(f"Olet ilmoittautunut ilmoon {instance.signupForm.title}") subject = f"Olet ilmoittautunut ilmoon {instance.signupForm.title}" send_signup_email(instance.email, subject, instance.id, instance.uuid) class BaseRole(models.Model): """Base model for occupations/roles.""" name = models.CharField(_('Name'), max_length=255) is_board = models.BooleanField(_('Board member')) def __str__(self): n = self.name.capitalize() return '{} ({})'.format(n, _('board member')) if self.is_board else n class PresetRole(BaseRole): """Model representing a preset occupation in the guild.""" description = models.TextField(_('Description')) class Committee(models.Model): """ Committee model Has many Roles found under variable roles """ class Meta: """Meta class for Committee class.""" verbose_name = _('Committee') verbose_name_plural = _('Committees') def __str__(self): return _('Committee: {}').format(self.name) name = models.CharField(_("Name"), max_length=255) @property def current_roles(self): return self.roles.all().filter(end_date__gte=timezone.now()).filter(start_date__lte=timezone.now()) class Role(PresetRole): """ Model for Role. Model representing an active or historical occupation in the guild. """ class Meta: """Meta class for Role model.""" verbose_name = _('Role') verbose_name_plural = _('Roles') committee = models.ForeignKey('Committee', related_name='roles', on_delete=models.SET_NULL, null=True) def __str__(self): return '{} (Hallitus: {}) ({})'.format(self.name, _("Yes") if self.is_board else _("No"), self.committee) class Occupation(models.Model): """ Model for a occupation in guild. Model links Official into a role he/she has or has had in the guild. """ class Meta: verbose_name = _('Occupation') verbose_name_plural = _('Occupations') start_date = models.DateField(_('Start date')) end_date = models.DateField(_('End date')) role = models.ForeignKey('Role', on_delete=models.SET_NULL, null=True) @staticmethod def by_year(year): return Occupation.objects.filter( end_date__gte=timezone.datetime(year, 1, 1)).filter( start_date__lte=timezone.datetime(year, 12, 31)) def __str__(self): return '{}: {} - {}'.format(self.role.name, self.start_date, self.end_date) class Official(models.Model): """Model representing a guild official.""" class Meta: """Meta class for Official class.""" verbose_name = _('Official') verbose_name_plural = _('Officials') user = models.OneToOneField(User, on_delete=models.CASCADE) first_name = models.CharField(_('First name'), max_length=30) last_name = models.CharField(_('Last name'), max_length=150) email = models.EmailField(_('Email address')) phone_number = PhoneNumberField(_('Phone number')) role_history = models.ManyToManyField('Occupation', 'officials', blank=True) image = models.ImageField(blank=True, null=True) @staticmethod def by_year(year): return Official.objects.filter( role_history__in=Occupation.occupations_by_year(year)).distinct() def __str__(self): return '{} {}'.format(self.first_name, self.last_name) @receiver(post_save, sender=Official) def save_user_official(sender, instance, **kwargs): instance.user.first_name = instance.first_name instance.user.last_name = instance.last_name instance.user.email = instance.email instance.user.save() auditlog.register(Tag) auditlog.register(Feed) auditlog.register(Event) auditlog.register(Signup) auditlog.register(PresetRole) auditlog.register(Role) auditlog.register(Official)