Files
azionelab/backend/bookings/admin.py
2026-04-29 22:45:16 +02:00

241 lines
8.1 KiB
Python

from django import forms
from django.contrib import admin, messages
from .models import Reservation, ReservationToken
from .services import PerformanceNotAvailable, create_pending_reservation
class ReservationAdminForm(forms.ModelForm):
class Meta:
model = Reservation
fields = ("performance", "name", "email", "phone", "party_size", "notes")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["performance"].help_text = (
"Choose the exact performance. A pending reservation and confirmation email will be created."
)
self.fields["party_size"].help_text = "Seats requested for this guest or group."
self.fields["notes"].help_text = "Optional internal note for staff."
def clean(self):
cleaned_data = super().clean()
performance = cleaned_data.get("performance")
party_size = cleaned_data.get("party_size")
if performance and not performance.is_booking_enabled:
self.add_error("performance", "Booking is currently disabled for this performance.")
if performance and party_size:
available_seats = performance.available_seats()
if party_size > available_seats:
self.add_error(
"party_size",
f"Only {available_seats} seats are currently available for this performance.",
)
return cleaned_data
class ReservationTokenInline(admin.TabularInline):
model = ReservationToken
extra = 0
readonly_fields = ("used_at", "created_at")
fields = ("purpose", "expires_at", "used_at", "created_at")
can_delete = False
@admin.register(Reservation)
class ReservationAdmin(admin.ModelAdmin):
form = ReservationAdminForm
list_display = (
"show_title",
"performance_starts_at",
"venue_name",
"name",
"email",
"party_size",
"status",
"confirmation_state_display",
"check_in_state_display",
)
list_filter = (
"status",
"performance__show",
"performance__venue",
"performance__starts_at",
"confirmed_at",
"qr_code_generated_at",
)
search_fields = (
"name",
"email",
"phone",
"performance__show__title",
"performance__venue__name",
)
inlines = (ReservationTokenInline,)
list_select_related = ("performance", "performance__show", "performance__venue")
readonly_fields = (
"status",
"show_title",
"performance_starts_at",
"venue_name",
"confirmation_state_display",
"check_in_state_display",
"created_at",
"updated_at",
"confirmed_at",
"qr_code_generated_at",
)
autocomplete_fields = ("performance",)
def get_queryset(self, request):
return super().get_queryset(request).select_related(
"performance",
"performance__show",
"performance__venue",
"check_in",
)
def get_inlines(self, request, obj):
if obj is None:
return ()
return super().get_inlines(request, obj)
def get_changeform_initial_data(self, request):
initial = super().get_changeform_initial_data(request)
performance_id = request.GET.get("performance")
if performance_id:
initial["performance"] = performance_id
return initial
def get_fieldsets(self, request, obj=None):
if obj is None:
return (
(
"Create manual reservation",
{
"description": (
"Use this form when staff needs to enter a reservation manually. "
"The reservation stays pending and the standard confirmation email is sent automatically."
),
"fields": ("performance", "name", "email", "phone", "party_size", "notes"),
},
),
)
return (
(
"Reservation",
{
"fields": (
"performance",
"show_title",
"performance_starts_at",
"venue_name",
"name",
"email",
"phone",
"party_size",
"notes",
),
},
),
(
"Operational status",
{
"fields": (
"status",
"confirmation_state_display",
"check_in_state_display",
"confirmed_at",
"qr_code_generated_at",
),
},
),
(
"Timestamps",
{
"classes": ("collapse",),
"fields": ("created_at", "updated_at"),
},
),
)
def save_model(self, request, obj, form, change):
if change:
super().save_model(request, obj, form, change)
return
try:
result = create_pending_reservation(
performance_id=form.cleaned_data["performance"].id,
name=form.cleaned_data["name"],
email=form.cleaned_data["email"],
phone=form.cleaned_data.get("phone", ""),
party_size=form.cleaned_data["party_size"],
notes=form.cleaned_data.get("notes", ""),
)
except PerformanceNotAvailable as exc:
form.add_error("performance", str(exc))
raise forms.ValidationError(str(exc)) from exc
created = result.reservation
obj.pk = created.pk
obj._state.adding = False
obj.performance = created.performance
obj.status = created.status
obj.name = created.name
obj.email = created.email
obj.phone = created.phone
obj.party_size = created.party_size
obj.notes = created.notes
obj.created_at = created.created_at
obj.updated_at = created.updated_at
obj.confirmed_at = created.confirmed_at
obj.qr_code_generated_at = created.qr_code_generated_at
self.message_user(
request,
"Pending reservation created. The guest must confirm it from the email link before check-in.",
level=messages.SUCCESS,
)
@admin.display(description="Show", ordering="performance__show__title")
def show_title(self, obj):
return obj.performance.show.title
@admin.display(description="Performance", ordering="performance__starts_at")
def performance_starts_at(self, obj):
return obj.performance.starts_at
@admin.display(description="Venue", ordering="performance__venue__name")
def venue_name(self, obj):
return obj.performance.venue.name
@admin.display(description="Confirmation")
def confirmation_state_display(self, obj):
if obj.status == Reservation.Status.CONFIRMED:
return "Confirmed"
if obj.status == Reservation.Status.EXPIRED:
return "Confirmation expired"
if obj.status == Reservation.Status.CANCELLED:
return "Cancelled"
return "Waiting for email confirmation"
@admin.display(description="Check-in")
def check_in_state_display(self, obj):
return "Checked in" if hasattr(obj, "check_in") else "Not checked in"
@admin.register(ReservationToken)
class ReservationTokenAdmin(admin.ModelAdmin):
list_display = ("reservation", "purpose", "expires_at", "used_at", "created_at")
list_filter = ("purpose", "expires_at", "used_at", "created_at")
search_fields = ("reservation__name", "reservation__email", "token_hash")
readonly_fields = ("created_at", "used_at")
exclude = ("token_hash",)
list_select_related = ("reservation", "reservation__performance")
autocomplete_fields = ("reservation",)