import arrow import requests from django.contrib.postgres.fields import JSONField from django.core.serializers.json import DjangoJSONEncoder from django.db import models from .parser import process_ical def _convert_arrow_to_datetime(event): event["begin"] = event["begin"].datetime event["end"] = event["end"].datetime return event class EventsJSONField(JSONField): """ JSONField for lists of events which converts `begin` and `end` to datetime on load from DB. """ def from_db_value(self, value, expression, connection): if value: for event in value: event["begin"] = arrow.get(event["begin"]).datetime event["end"] = arrow.get(event["end"]).datetime return value class Calendar(models.Model): ACTUAL_NUM = 6 url = models.URLField() source = models.TextField(null=True) last_update = models.DateTimeField(null=True) past_events = EventsJSONField(encoder=DjangoJSONEncoder, null=True) future_events = EventsJSONField(encoder=DjangoJSONEncoder, null=True) def update_source(self): source = requests.get(self.url).text if self.source != source: self.source = source past, future = process_ical(source) self.past_events = list(map(_convert_arrow_to_datetime, past)) self.future_events = list(map(_convert_arrow_to_datetime, future)) self.last_update = arrow.utcnow().datetime self.save() def actual_events(self): events = self.future_events[-self.ACTUAL_NUM :] num = len(events) if num < 6: events += self.past_events[: self.ACTUAL_NUM - num] return events class CalendarMixin(models.Model): """ Mixin to be used in other models, like site settings, which adds relation to Calendar. """ calendar_url = models.URLField( "URL kalendáře ve formátu iCal", blank=True, null=True ) calendar = models.ForeignKey(Calendar, null=True, on_delete=models.PROTECT) class Meta: abstract = True def save(self, *args, **kwargs): # create or update related Calendar if self.calendar_url: if self.calendar: if self.calendar.url != self.calendar_url: self.calendar.url = self.calendar_url self.calendar.save() else: self.calendar = Calendar.objects.create(url=self.calendar_url) self.calendar.update_source() # delete related Calendar when URL is cleared if not self.calendar_url and self.calendar: self.calendar.delete() self.calendar = None super().save(*args, **kwargs)