from django.db import models from django.db.models import F, Q, Sum class TimeStampedModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Show(TimeStampedModel): title = models.CharField(max_length=200) slug = models.SlugField(max_length=220, unique=True) summary = models.TextField(blank=True) description = models.TextField(blank=True) poster_image = models.URLField(blank=True) uploaded_image = models.ImageField(upload_to="shows/", blank=True) is_published = models.BooleanField(default=False, db_index=True) class Meta: ordering = ["title"] indexes = [ models.Index(fields=["slug"]), models.Index(fields=["is_published"]), ] def __str__(self): return self.title def image_url(self, request=None): if self.uploaded_image: image_url = self.uploaded_image.url if request is not None: return request.build_absolute_uri(image_url) return image_url return self.poster_image class Venue(TimeStampedModel): name = models.CharField(max_length=200) slug = models.SlugField(max_length=220, unique=True) address = models.CharField(max_length=255) city = models.CharField(max_length=120) notes = models.TextField(blank=True) class Meta: ordering = ["name"] indexes = [ models.Index(fields=["slug"]), models.Index(fields=["city"]), ] def __str__(self): return self.name class Performance(TimeStampedModel): show = models.ForeignKey(Show, on_delete=models.PROTECT, related_name="performances") venue = models.ForeignKey(Venue, on_delete=models.PROTECT, related_name="performances") starts_at = models.DateTimeField(db_index=True) room_capacity = models.PositiveIntegerField() manually_occupied_seats = models.PositiveIntegerField(default=0) additional_seats = models.PositiveIntegerField(default=0) is_booking_enabled = models.BooleanField(default=True, db_index=True) class Meta: ordering = ["starts_at"] indexes = [ models.Index(fields=["show", "starts_at"]), models.Index(fields=["venue", "starts_at"]), models.Index(fields=["is_booking_enabled", "starts_at"]), ] constraints = [ models.CheckConstraint( condition=Q(manually_occupied_seats__lte=F("room_capacity") + F("additional_seats")), name="performance_manual_seats_within_capacity", ), ] def __str__(self): return f"{self.show} at {self.starts_at:%Y-%m-%d %H:%M}" @property def configured_capacity(self): return self.room_capacity + self.additional_seats - self.manually_occupied_seats def confirmed_seats(self): result = self.reservations.filter(status="confirmed").aggregate(total=Sum("party_size")) return result["total"] or 0 def available_seats(self): return self.configured_capacity - self.confirmed_seats()