generated from bisco/codex-bootstrap
Merge branch 'feature/admin-demo-data' into develop
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
1
backend/shows/management/__init__.py
Normal file
1
backend/shows/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
backend/shows/management/commands/__init__.py
Normal file
1
backend/shows/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
126
backend/shows/management/commands/seed_demo_data.py
Normal file
126
backend/shows/management/commands/seed_demo_data.py
Normal 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())
|
||||
13
backend/shows/test_admin.py
Normal file
13
backend/shows/test_admin.py
Normal 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))
|
||||
31
backend/shows/test_management.py
Normal file
31
backend/shows/test_management.py
Normal 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)
|
||||
Reference in New Issue
Block a user