135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
from collections.abc import Iterable
|
||
|
from datetime import datetime
|
||
|
|
||
|
from django.contrib.auth import get_user_model
|
||
|
from django.db import models
|
||
|
from django.db.models import F, Q, QuerySet
|
||
|
from django.utils import timezone
|
||
|
|
||
|
from model_utils.managers import InheritanceQuerySetMixin
|
||
|
|
||
|
|
||
|
class Resource(models.Model):
|
||
|
name = models.CharField(max_length=256)
|
||
|
parent = models.ForeignKey(
|
||
|
"Resource",
|
||
|
on_delete=models.CASCADE,
|
||
|
blank=True,
|
||
|
null=True,
|
||
|
related_name="children",
|
||
|
)
|
||
|
min_length = models.DurationField()
|
||
|
max_length = models.DurationField()
|
||
|
google_calendar = models.CharField(max_length=1024, unique=True)
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
if self.parent:
|
||
|
return f"{self.parent} / {self.name}"
|
||
|
else:
|
||
|
return self.name
|
||
|
|
||
|
def get_recursive_parents(self) -> Iterable[Resource]:
|
||
|
if self.parent:
|
||
|
yield self.parent
|
||
|
yield from self.parent.get_recursive_parents()
|
||
|
|
||
|
def get_recursive_children(self) -> Iterable[Resource]:
|
||
|
for child in self.children.all():
|
||
|
yield child
|
||
|
yield from child.get_recursive_children()
|
||
|
|
||
|
def get_related(self) -> Iterable[Resource]:
|
||
|
yield self
|
||
|
yield from self.get_recursive_parents()
|
||
|
yield from self.get_recursive_children()
|
||
|
|
||
|
|
||
|
class ReservationQuerySet(InheritanceQuerySetMixin, QuerySet):
|
||
|
def filter_after(self, start: datetime) -> ReservationQuerySet:
|
||
|
"""
|
||
|
Selects events that are after the specified datetime, including
|
||
|
overlaps but excluding exactly matching endpoints.
|
||
|
"""
|
||
|
return self.filter(Q(start__gt=start) | Q(end__gt=start))
|
||
|
|
||
|
def filter_before(self, end: datetime) -> ReservationQuerySet:
|
||
|
"""
|
||
|
Selects events that are before the specified datetime, including
|
||
|
overlaps but excluding exactly matching endpoints.
|
||
|
"""
|
||
|
return self.filter(Q(start__lt=end) | Q(end__lt=end))
|
||
|
|
||
|
def filter_between(self, start: datetime, end: datetime) -> ReservationQuerySet:
|
||
|
"""
|
||
|
Selects events that are between the specified datetime, including
|
||
|
overlaps but excluding exactly matching endpoints.
|
||
|
"""
|
||
|
return self.filter(
|
||
|
Q(start__gt=start, start__lt=end)
|
||
|
| Q(end__gt=start, end__lt=end)
|
||
|
| (Q(start__lt=start) & Q(end__gt=end))
|
||
|
)
|
||
|
|
||
|
|
||
|
class Reservation(models.Model):
|
||
|
resources = models.ManyToManyField(Resource, blank=True)
|
||
|
start = models.DateTimeField()
|
||
|
end = models.DateTimeField()
|
||
|
google_calendar_event_id = models.CharField(
|
||
|
max_length=1024, null=True, blank=True, unique=True
|
||
|
)
|
||
|
|
||
|
duration = models.GeneratedField(
|
||
|
expression=F("end") - F("start"),
|
||
|
output_field=models.DurationField(),
|
||
|
db_persist=False,
|
||
|
)
|
||
|
|
||
|
objects = ReservationQuerySet.as_manager()
|
||
|
|
||
|
class Meta:
|
||
|
constraints = [
|
||
|
models.CheckConstraint(check=Q(end__gt=F("start")), name="end_after_start"),
|
||
|
]
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
resources = ", ".join(str(resource) for resource in self.resources.all())
|
||
|
return f"{resources}: {self.start} - {self.end}"
|
||
|
|
||
|
def make_google_calendar_event(self):
|
||
|
event = {
|
||
|
"summary": "CMSManage Reservation",
|
||
|
"start": {
|
||
|
"dateTime": self.start.isoformat(),
|
||
|
"timeZone": timezone.get_default_timezone_name(),
|
||
|
},
|
||
|
"end": {
|
||
|
"dateTime": self.end.isoformat(),
|
||
|
"timeZone": timezone.get_default_timezone_name(),
|
||
|
},
|
||
|
"extendedProperties": {"private": {"cmsmanage": "1"}},
|
||
|
"status": "confirmed",
|
||
|
}
|
||
|
|
||
|
# use existing id if it exists, otherwise let Google generate it
|
||
|
if self.google_calendar_event_id:
|
||
|
event["id"] = self.google_calendar_event_id
|
||
|
|
||
|
return event
|
||
|
|
||
|
|
||
|
class UserReservation(Reservation):
|
||
|
user = models.ForeignKey(
|
||
|
get_user_model(), on_delete=models.CASCADE, null=True, blank=True
|
||
|
)
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
return f"{self.user} | {super().__str__()}"
|
||
|
|
||
|
def make_google_calendar_event(self):
|
||
|
return super().make_google_calendar_event() | {
|
||
|
"summary": str(self.user),
|
||
|
}
|