Merge branch 'feature/admin-demo-data' into develop

This commit is contained in:
2026-04-29 12:08:31 +02:00
8 changed files with 209 additions and 3 deletions

View File

@@ -8,6 +8,7 @@ class ReservationTokenInline(admin.TabularInline):
extra = 0
readonly_fields = ("token_hash", "used_at", "created_at")
fields = ("purpose", "token_hash", "expires_at", "used_at", "created_at")
can_delete = False
@admin.register(Reservation)
@@ -19,16 +20,26 @@ class ReservationAdmin(admin.ModelAdmin):
"party_size",
"status",
"confirmed_at",
"qr_code_generated_at",
"created_at",
)
list_filter = ("status", "performance", "created_at", "confirmed_at")
search_fields = ("name", "email", "phone", "performance__show__title")
inlines = (ReservationTokenInline,)
list_select_related = ("performance", "performance__show", "performance__venue")
readonly_fields = ("created_at", "updated_at", "confirmed_at", "qr_code_generated_at")
autocomplete_fields = ("performance",)
@admin.register(ReservationToken)
class ReservationTokenAdmin(admin.ModelAdmin):
list_display = ("reservation", "purpose", "expires_at", "used_at", "created_at")
list_display = ("reservation", "purpose", "expires_at", "used_at", "created_at", "token_preview")
list_filter = ("purpose", "expires_at", "used_at", "created_at")
search_fields = ("reservation__name", "reservation__email", "token_hash")
readonly_fields = ("token_hash", "created_at", "used_at")
list_select_related = ("reservation", "reservation__performance")
autocomplete_fields = ("reservation",)
@admin.display(description="Token hash")
def token_preview(self, obj):
return obj.token_hash[:12]

View File

@@ -5,7 +5,14 @@ from .models import CheckIn
@admin.register(CheckIn)
class CheckInAdmin(admin.ModelAdmin):
list_display = ("reservation", "checked_in_at", "checked_in_by", "source", "created_at")
list_display = (
"reservation",
"performance",
"checked_in_at",
"checked_in_by",
"source",
"created_at",
)
list_filter = ("source", "checked_in_at", "created_at")
search_fields = (
"reservation__name",
@@ -14,4 +21,10 @@ class CheckInAdmin(admin.ModelAdmin):
"checked_in_by__username",
"checked_in_by__email",
)
readonly_fields = ("created_at", "updated_at")
readonly_fields = ("created_at", "updated_at", "checked_in_at")
list_select_related = ("reservation", "reservation__performance", "checked_in_by")
autocomplete_fields = ("reservation", "checked_in_by")
@admin.display(description="Performance")
def performance(self, obj):
return obj.reservation.performance

View File

@@ -9,6 +9,7 @@ class ShowAdmin(admin.ModelAdmin):
list_filter = ("is_published",)
search_fields = ("title", "slug", "summary", "description")
prepopulated_fields = {"slug": ("title",)}
readonly_fields = ("created_at", "updated_at")
@admin.register(Venue)
@@ -17,6 +18,7 @@ class VenueAdmin(admin.ModelAdmin):
list_filter = ("city",)
search_fields = ("name", "slug", "address", "city", "notes")
prepopulated_fields = {"slug": ("name",)}
readonly_fields = ("created_at", "updated_at")
@admin.register(Performance)
@@ -28,7 +30,15 @@ class PerformanceAdmin(admin.ModelAdmin):
"room_capacity",
"additional_seats",
"manually_occupied_seats",
"available_seats_display",
"is_booking_enabled",
)
list_filter = ("is_booking_enabled", "starts_at", "show", "venue")
search_fields = ("show__title", "venue__name", "venue__city")
list_select_related = ("show", "venue")
readonly_fields = ("created_at", "updated_at", "available_seats_display")
autocomplete_fields = ("show", "venue")
@admin.display(description="Available seats")
def available_seats_display(self, obj):
return obj.available_seats()

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,126 @@
import sys
from datetime import datetime
from datetime import timedelta
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from shows.models import Performance, Show, Venue
class Command(BaseCommand):
help = "Create or update local demo data for AzioneLab."
def handle(self, *args, **options):
if not settings.DEBUG and "test" not in sys.argv:
raise CommandError("seed_demo_data is available only in local or test environments.")
today = timezone.localdate()
venues = [
{
"name": "AzioneLab Theatre",
"slug": "azionelab-theatre",
"address": "Via Example 1",
"city": "Rome",
"notes": "Main house for evening performances.",
},
{
"name": "Studio Nuovo",
"slug": "studio-nuovo",
"address": "Via Example 22",
"city": "Rome",
"notes": "Smaller venue for workshops and matinees.",
},
]
shows = [
{
"title": "Open Stage",
"slug": "open-stage",
"summary": "A contemporary theatre performance.",
"description": "A compact demo production for manual backend testing.",
"poster_image": "",
"is_published": True,
},
{
"title": "City Echoes",
"slug": "city-echoes",
"summary": "An ensemble piece set across modern Rome.",
"description": "A second published show with a different venue mix.",
"poster_image": "",
"is_published": True,
},
]
venue_map = {}
show_map = {}
for venue_data in venues:
venue, _ = Venue.objects.update_or_create(
slug=venue_data["slug"],
defaults=venue_data,
)
venue_map[venue.slug] = venue
for show_data in shows:
show, _ = Show.objects.update_or_create(
slug=show_data["slug"],
defaults=show_data,
)
show_map[show.slug] = show
performances = [
{
"show": show_map["open-stage"],
"venue": venue_map["azionelab-theatre"],
"starts_at": self._performance_starts_at(today + timedelta(days=7), hour=20, minute=30),
"room_capacity": 120,
"manually_occupied_seats": 8,
"additional_seats": 4,
"is_booking_enabled": True,
},
{
"show": show_map["open-stage"],
"venue": venue_map["studio-nuovo"],
"starts_at": self._performance_starts_at(today + timedelta(days=14), hour=18, minute=0),
"room_capacity": 60,
"manually_occupied_seats": 2,
"additional_seats": 0,
"is_booking_enabled": True,
},
{
"show": show_map["city-echoes"],
"venue": venue_map["azionelab-theatre"],
"starts_at": self._performance_starts_at(today + timedelta(days=21), hour=20, minute=30),
"room_capacity": 140,
"manually_occupied_seats": 12,
"additional_seats": 6,
"is_booking_enabled": True,
},
]
created_or_updated = 0
for performance_data in performances:
_, _created = Performance.objects.update_or_create(
show=performance_data["show"],
venue=performance_data["venue"],
starts_at=performance_data["starts_at"],
defaults={
"room_capacity": performance_data["room_capacity"],
"manually_occupied_seats": performance_data["manually_occupied_seats"],
"additional_seats": performance_data["additional_seats"],
"is_booking_enabled": performance_data["is_booking_enabled"],
},
)
created_or_updated += 1
self.stdout.write(
self.style.SUCCESS(
f"Demo data ready: {len(show_map)} shows, {len(venue_map)} venues, {created_or_updated} performances."
)
)
def _performance_starts_at(self, day, *, hour, minute):
naive = datetime.combine(day, datetime.min.time()).replace(hour=hour, minute=minute)
return timezone.make_aware(naive, timezone.get_current_timezone())

View File

@@ -0,0 +1,13 @@
from django.contrib import admin
from django.test import SimpleTestCase
from bookings.models import Reservation, ReservationToken
from checkins.models import CheckIn
from shows.models import Performance, Show, Venue
class AdminRegistrationTests(SimpleTestCase):
def test_core_models_are_registered_in_admin(self):
for model in (Show, Venue, Performance, Reservation, ReservationToken, CheckIn):
with self.subTest(model=model.__name__):
self.assertTrue(admin.site.is_registered(model))

View File

@@ -0,0 +1,31 @@
from django.core.management import call_command
from django.test import TestCase
from shows.models import Performance, Show, Venue
class SeedDemoDataCommandTests(TestCase):
def test_seed_demo_data_runs_successfully(self):
call_command("seed_demo_data")
self.assertEqual(Show.objects.count(), 2)
self.assertEqual(Venue.objects.count(), 2)
self.assertEqual(Performance.objects.count(), 3)
self.assertTrue(Show.objects.filter(is_published=True).exists())
def test_seed_demo_data_is_idempotent(self):
call_command("seed_demo_data")
first_counts = (
Show.objects.count(),
Venue.objects.count(),
Performance.objects.count(),
)
call_command("seed_demo_data")
second_counts = (
Show.objects.count(),
Venue.objects.count(),
Performance.objects.count(),
)
self.assertEqual(first_counts, second_counts)