generated from bisco/codex-bootstrap
feat: add initial Django domain models
This commit is contained in:
34
backend/shows/admin.py
Normal file
34
backend/shows/admin.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Performance, Show, Venue
|
||||
|
||||
|
||||
@admin.register(Show)
|
||||
class ShowAdmin(admin.ModelAdmin):
|
||||
list_display = ("title", "slug", "is_published", "created_at", "updated_at")
|
||||
list_filter = ("is_published",)
|
||||
search_fields = ("title", "slug", "summary", "description")
|
||||
prepopulated_fields = {"slug": ("title",)}
|
||||
|
||||
|
||||
@admin.register(Venue)
|
||||
class VenueAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "slug", "city", "address", "created_at", "updated_at")
|
||||
list_filter = ("city",)
|
||||
search_fields = ("name", "slug", "address", "city", "notes")
|
||||
prepopulated_fields = {"slug": ("name",)}
|
||||
|
||||
|
||||
@admin.register(Performance)
|
||||
class PerformanceAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"show",
|
||||
"venue",
|
||||
"starts_at",
|
||||
"room_capacity",
|
||||
"additional_seats",
|
||||
"manually_occupied_seats",
|
||||
"is_booking_enabled",
|
||||
)
|
||||
list_filter = ("is_booking_enabled", "starts_at", "show", "venue")
|
||||
search_fields = ("show__title", "venue__name", "venue__city")
|
||||
98
backend/shows/migrations/0001_initial.py
Normal file
98
backend/shows/migrations/0001_initial.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# Generated by Django 5.2.3 on 2026-04-28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.models import F, Q
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Show",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("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)),
|
||||
("is_published", models.BooleanField(db_index=True, default=False)),
|
||||
],
|
||||
options={
|
||||
"ordering": ["title"],
|
||||
"indexes": [
|
||||
models.Index(fields=["slug"], name="shows_show_slug_83daa9_idx"),
|
||||
models.Index(fields=["is_published"], name="shows_show_is_publ_63247e_idx"),
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Venue",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("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)),
|
||||
],
|
||||
options={
|
||||
"ordering": ["name"],
|
||||
"indexes": [
|
||||
models.Index(fields=["slug"], name="shows_venue_slug_0717a3_idx"),
|
||||
models.Index(fields=["city"], name="shows_venue_city_acfb26_idx"),
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Performance",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("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(db_index=True, default=True)),
|
||||
(
|
||||
"show",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="performances",
|
||||
to="shows.show",
|
||||
),
|
||||
),
|
||||
(
|
||||
"venue",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="performances",
|
||||
to="shows.venue",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["starts_at"],
|
||||
"indexes": [
|
||||
models.Index(fields=["show", "starts_at"], name="shows_perfo_show_id_bae2ea_idx"),
|
||||
models.Index(fields=["venue", "starts_at"], name="shows_perfo_venue_i_fcdf27_idx"),
|
||||
models.Index(fields=["is_booking_enabled", "starts_at"], name="shows_perfo_is_book_9371e4_idx"),
|
||||
],
|
||||
"constraints": [
|
||||
models.CheckConstraint(
|
||||
condition=Q(("manually_occupied_seats__lte", F("room_capacity") + F("additional_seats"))),
|
||||
name="performance_manual_seats_within_capacity",
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
1
backend/shows/migrations/__init__.py
Normal file
1
backend/shows/migrations/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1 +1,85 @@
|
||||
# Domain models will be added when show management is implemented.
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user