Files
web2.0-backend/webapp/models.py
T
Aarni Halinen 673bbc09eb Signup edit URL
2020-07-17 15:50:04 +03:00

328 lines
10 KiB
Python

"""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)