Sith/reservation/models.py
2025-04-23 14:23:01 +02:00

108 lines
3.4 KiB
Python

from __future__ import annotations
from typing import Self
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import F
from django.db.models import F, Q
from django.utils.translation import gettext_lazy as _
from club.models import Club
from core.fields import ResizedImageField
from core.models import User
class Room(models.Model):
name = models.CharField(_("room name"), max_length=100)
description = models.TextField(_("description"), blank=True, default="")
logo = ResizedImageField(
width=100,
height=100,
force_format="WEBP",
upload_to="rooms",
verbose_name=_("logo"),
blank=True,
)
club = models.ForeignKey(
Club,
on_delete=models.CASCADE,
related_name="reservable_rooms",
verbose_name=_("room owner"),
)
address = models.CharField(_("address"), max_length=255)
occupancy = models.PositiveSmallIntegerField(
verbose_name=_("maximum occupancy"),
help_text=_("The maximum number of people this room can host at once"),
validators=[MinValueValidator(1)],
)
class Meta:
verbose_name = _("reservable room")
verbose_name_plural = _("reservable rooms")
def __str__(self):
return self.name
class ReservationSlotQuerySet(models.QuerySet):
def overlapping_with(self, other: ReservationSlot) -> Self:
return self.filter(
Q(end_at__gt=other.start_at, end_ad__lt=other.end_at)
| Q(start_at__lt=other.start_at, start_ad__gt=other.start_at)
)
class ReservationSlot(models.Model):
room = models.ForeignKey(
Room,
on_delete=models.CASCADE,
related_name="slots",
verbose_name=_("reserved room"),
)
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("author"))
nb_people = models.PositiveSmallIntegerField(
verbose_name=_("number of people"),
help_text=_("How many people will attend this reservation slot"),
default=1,
validators=[MinValueValidator(1)],
)
comment = models.TextField(_("comment"), blank=True, default="")
start_at = models.DateTimeField(_("slot start"), db_index=True)
duration = models.DurationField(_("duration"))
end_at = models.GeneratedField(
verbose_name=_("slot end"),
expression=F("start_at") + F("duration"),
output_field=models.DateTimeField(),
db_persist=False,
)
created_at = models.DateTimeField(auto_now_add=True)
objects = ReservationSlotQuerySet.as_manager()
class Meta:
verbose_name = _("reservation slot")
verbose_name_plural = _("reservation slots")
def __str__(self):
return f"{self.room.name} : {self.start_at} - {self.end_at}"
def clean(self):
super().clean()
if self.nb_people > self.room.occupancy:
raise ValidationError(
_(
"You declared an attendance of %(nb_people)d, "
"but this room can only host %(occupancy)d people."
)
% {"nb_people": self.nb_people, "occupancy": self.room.occupancy}
)
if (
ReservationSlot.objects.overlapping_with(self)
.filter(room_id=self.room_id)
.exists()
):
raise ValidationError(_("There is already a reservation on this slot."))