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