import uuid from django.db import models from colorfield.fields import ColorField from ckeditor.fields import RichTextField from django.db.models.signals import post_delete from .helpers import file_cleanup from .helpers import RandomFileName from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator class IntListField(models.CharField): description = "An integer list" def __init__(self, *args, **kwargs): if 'max_length' not in kwargs: kwargs['max_length'] = 255 if 'default' not in kwargs: kwargs['default'] = '' super().__init__(*args, **kwargs) def from_db_value(self, value, expression, connection): if value is None: return value if value.strip() == '': return list() return list(map(int, map(str.strip, value.split(',')))) def to_python(self, value): if isinstance(value, list): return value if value is None: return value for i in '[](){}': value = value.replace(i, '') if value.strip() == '': return list() return list(map(int, map(str.strip, value.split(',')))) def get_prep_value(self, value): return ','.join(list(map(str, value))) # Create your models here. class Timestampable(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) resource_uuid = models.UUIDField(default=uuid.uuid4, editable=False, primary_key=False) class Meta: abstract = True class Remindable(Timestampable): reminder_alias = models.CharField(default='', unique=True, max_length=255, validators=[ RegexValidator(r'^[A-Za-z_][A-Za-z0-9_]*$'), ], help_text='All characters must be alphanumerical or underline, except by the first character, which must not be alphanumerical.') kind = '???' @property def reminder_title(self): return self.__class__.__name__ def __str__(self): return f'{self.reminder_title} {self.kind} #{self.pk}: {self.reminder_alias}' class Meta: abstract = True class LanguageString(Remindable): resource_uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=False) reminder_title = 'Language' class Meta: abstract = True ordering = ('reminder_alias', ) class LanguagePhrase(LanguageString): kind = 'phrase' pt = models.CharField(default='', max_length=255) en = models.CharField(default='', max_length=255) es = models.CharField(default='', max_length=255) class LanguageText(LanguageString): kind = 'text' pt = models.TextField(default='') en = models.TextField(default='') es = models.TextField(default='') class LanguageRichText(LanguageString): kind = 'rich text' pt = RichTextField(default='') en = RichTextField(default='') es = RichTextField(default='') class LanguageInvariantImage(LanguageString): kind = 'invariant image' picture = models.ImageField(upload_to=RandomFileName('')) @property def pt(self): return self.picture @property def en(self): return self.picture @property def es(self): return self.picture post_delete.connect(file_cleanup, sender=LanguageInvariantImage, dispatch_uid="webproj.bff.LanguageInvariantImage.file_cleanup") class LanguageImage(LanguageString): kind = 'image' pt_image = models.ForeignKey(LanguageInvariantImage, on_delete=models.PROTECT, related_name='+') en_image = models.ForeignKey(LanguageInvariantImage, on_delete=models.PROTECT, related_name='+') es_image = models.ForeignKey(LanguageInvariantImage, on_delete=models.PROTECT, related_name='+') @property def pt(self): return self.pt_image.picture @property def en(self): return self.en_image.picture @property def es(self): return self.es_image.picture class Announcement(Timestampable): convention = models.ForeignKey('ConventionSeries', on_delete=models.PROTECT, related_name='announcements') title = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') body = models.ForeignKey(LanguageText, on_delete=models.PROTECT, related_name='+') link = models.ForeignKey(LanguagePhrase, blank=True, null=True, on_delete=models.PROTECT, related_name='+') def __str__(self): return f'Announcement #{self.pk}: {self.title}' class Color(Remindable): color = ColorField(default='#000000') opacity = models.IntegerField(default=255, validators=[MinValueValidator(0), MaxValueValidator(255)], help_text="Opacity: 0 is transparent, 255 is opaque.") kind = '' class ActivityCategory(Timestampable): name = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') color = models.ForeignKey(Color, on_delete=models.PROTECT, related_name='+') ordering = models.IntegerField(default=0, blank=True, null=False) def __str__(self): return f'Category #{self.pk}: {self.name.en}' class Meta: ordering = ('ordering', '-pk', ) class Place(Timestampable): name = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') color = models.ForeignKey(Color, on_delete=models.PROTECT, related_name='+') ordering = models.IntegerField(default=0, blank=True, null=False) def __str__(self): return f'Place #{self.pk}: {self.name.en}' class Meta: ordering = ('ordering', '-pk', ) class Timezone(Remindable): kind = '' hour = models.IntegerField(default=-3, validators=[MinValueValidator(-24), MaxValueValidator(24)]) minute = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(60)]) class RegistrationBenefitDefinition(Timestampable): description = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') ordering = models.IntegerField(default=0, blank=True, null=False) def __str__(self): return f'Registration benefit definition #{self.pk}: {self.description.en}' class Meta: ordering = ('ordering', '-pk', ) class RegistrationTierDefinition(Timestampable): title = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') ordering = models.IntegerField(default=0, blank=True, null=False) def __str__(self): return f'Registration tier definition #{self.pk}: {self.title.en}' class Meta: ordering = ('ordering', '-pk', ) TRILINGUAL_CHOICES = ( ('pt', 'Portuguese'), ('en', 'English'), ('es', 'Spanish') ) class ConventionRegistrationTier(Timestampable): level = models.IntegerField(default=0) tier = models.ForeignKey(RegistrationTierDefinition, on_delete=models.PROTECT, related_name='+') benefits = models.ManyToManyField(RegistrationBenefitDefinition, blank=True, related_name='+') convention = models.ForeignKey('ConventionEdition', on_delete=models.PROTECT, related_name='registration_tiers') is_lowest = models.BooleanField(default=False, blank=True, null=False) def __str__(self): return f'Convention registration tier #{self.pk}: {self.convention.name.en} - {self.tier.title.en} ({self.benefits.count()})' class ConventionActivity(Timestampable): conbook_key = models.CharField(default='', max_length=255) conbook_pages = IntListField() title = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') subtitle = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, blank=True, null=True, related_name='+') description = models.ForeignKey(LanguageText, on_delete=models.PROTECT, related_name='+') time_start = models.DateTimeField(help_text="fill with the time on the timezone of the event.") time_end = models.DateTimeField(help_text="fill with the time on the timezone of the event.") places = models.ManyToManyField(Place, related_name='on_activities') categories = models.ManyToManyField(ActivityCategory, blank=True, related_name='on_activities') attendable_by = models.ManyToManyField(ConventionRegistrationTier, related_name='+') language = models.CharField( max_length=2, choices=TRILINGUAL_CHOICES, default='pt', ) hidden_from_time_table = models.BooleanField(default=False, blank=True, null=False) picture = models.ForeignKey(LanguageInvariantImage, blank=True, null=True, related_name='+', on_delete=models.PROTECT) convention = models.ForeignKey('ConventionEdition', related_name='events', on_delete=models.PROTECT) class Meta: ordering = ('-time_start', '-time_end', 'conbook_key') def __str__(self): return f'Convention activity #{self.pk}: {self.convention.name.en} - {self.title.en} [{self.conbook_key}] ({self.time_start} - {self.time_end})' class ConventionSeries(Timestampable): name = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') url = models.SlugField(default='', unique=True, max_length=255) featured = models.ForeignKey('ConventionEdition', null=True, blank=True, on_delete=models.PROTECT, related_name='+') statute = models.ForeignKey(LanguageRichText, on_delete=models.PROTECT, related_name='+') timezone = models.ForeignKey(Timezone, on_delete=models.PROTECT, related_name='+') default_banner = models.ForeignKey(LanguageImage, default=None, on_delete=models.PROTECT, related_name='+') language = models.CharField( max_length=2, choices=TRILINGUAL_CHOICES, default='pt', ) # banners ← foreign_relation # social_medias ← foreign_relation def __str__(self): return f'Convention series #{self.pk}: {self.name.en} (/{self.url})' class SocialMedia(Remindable): ordering = models.IntegerField(default=0) convention = models.ForeignKey(ConventionSeries, on_delete=models.PROTECT, related_name='social_medias') name = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') url = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') icon = models.CharField(default=None, blank=True, null=True, max_length=255, choices=[ (None, '-- no icon --'), ('ic_telegram', 'Telegram'), ('ic_twitter', 'Twitter'), ('ic_facebook_official', 'Facebook'), ('ic_youtube_play', 'YouTube'), ('ic_instagram', 'Instagram'), ('ic_web_black_24dp', 'Web browser'), ]) kind = '' color = models.ForeignKey(Color, on_delete=models.PROTECT, related_name='+') class Meta: ordering = ('ordering', 'name__reminder_alias', '-pk', ) class ConventionEdition(Timestampable): # id ← inherited # events ← foreign_relation # registration_tiers ← foreign_relation # lowestRegistrationTier ← process # places ← process # tags ← process edition_of = models.ForeignKey(ConventionSeries, on_delete=models.PROTECT, related_name='editions') name = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') theme = models.ForeignKey(LanguagePhrase, on_delete=models.PROTECT, related_name='+') hashtag_reminder = models.CharField(default='', max_length=255) timezone = models.ForeignKey(Timezone, on_delete=models.PROTECT, related_name='+') fire_notifications_n_minutes_before = models.PositiveIntegerField(default=15) convention_day_start_time = models.TimeField(help_text="fill with the time on the timezone of the event.") convention_day_end_time = models.TimeField(help_text="fill with the time on the timezone of the event.") rule_621_checker = models.BooleanField(default=False, blank=True, null=False) next_edition_date = models.DateField(help_text="fill with the time on the timezone of the event.") ceremony_opening_time = models.DateTimeField(help_text="fill with the time on the timezone of the event.") ceremony_closing_time = models.DateTimeField(help_text="fill with the time on the timezone of the event.") image_default_event = models.ForeignKey(LanguageImage, default=None, null=True, blank=True, on_delete=models.PROTECT, related_name='+') image_favorites = models.ForeignKey(LanguageImage, default=None, null=True, blank=True, on_delete=models.PROTECT, related_name='+') split_day_into_n_parts = models.IntegerField(default=24, blank=False, null=False, validators=[MaxValueValidator(1440), MinValueValidator(1)]) language = models.CharField( max_length=2, choices=TRILINGUAL_CHOICES, default='pt', ) def __str__(self): return f'Convention #{self.pk}: {self.name.en}' class Meta: ordering = ('ceremony_opening_time', '-pk', ) class BannerChange(Remindable): convention = models.ForeignKey(ConventionSeries, default=None, blank=False, null=False, on_delete=models.PROTECT, related_name="banners") show_up_after = models.DateTimeField(help_text="fill with the time on the timezone of the event.") hide_after = models.DateTimeField(help_text="fill with the time on the timezone of the event.") banner = models.ForeignKey(LanguageImage, default=None, on_delete=models.PROTECT, related_name='+') reminder_title = 'Banner change' kind = '' class Meta: ordering = ('show_up_after', '-pk', ) class MapImage(Remindable): ordering = models.IntegerField(default=0) convention = models.ForeignKey(ConventionEdition, default=None, blank=False, null=False, on_delete=models.PROTECT, related_name="maps") name = models.ForeignKey(LanguagePhrase, blank=False, null=False, on_delete=models.PROTECT, related_name='+') image = models.ForeignKey(LanguageImage, blank=False, null=False, on_delete=models.PROTECT, related_name='+') reminder_title = 'Map' kind = 'image' class Meta: ordering = ('convention__ceremony_opening_time', 'ordering', '-pk', ) class ConventionRegistrationLink(Remindable): ordering = models.IntegerField(default=0) convention = models.ForeignKey(ConventionEdition, default=None, blank=False, null=False, on_delete=models.PROTECT, related_name="registration_links") name = models.ForeignKey(LanguagePhrase, blank=False, null=False, on_delete=models.PROTECT, related_name='+') url = models.ForeignKey(LanguagePhrase, blank=False, null=False, on_delete=models.PROTECT, related_name='+') color = models.ForeignKey(Color, on_delete=models.PROTECT, null=True, blank=True, related_name='+') appears = models.DateTimeField(help_text="fill with the time on the timezone of the event.") vanishes = models.DateTimeField(help_text="fill with the time on the timezone of the event.") reminder_title = 'Registration' kind = 'link' class Meta: ordering = ('convention__ceremony_opening_time', 'ordering', '-pk', ) class AdditionalRules(Timestampable): convention = models.ForeignKey(ConventionEdition, blank=False, null=False, on_delete=models.PROTECT, related_name='additional_rules') name = models.ForeignKey(LanguagePhrase, blank=False, null=False, on_delete=models.PROTECT, related_name='+') rules = models.ForeignKey(LanguageRichText, blank=False, null=False, on_delete=models.PROTECT, related_name='+') ordering = models.IntegerField(default=0, blank=True, null=False) def __str__(self): return f'Additional rules #{self.pk} @ {self.convention.name.en}: {self.name.en}' class Meta: ordering = ('ordering', '-pk',)