mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:31:07 -05:00
- Add HTMX-powered filtering with instant updates - Add smooth transitions and loading states - Improve visual hierarchy and styling - Add review notes functionality - Add confirmation dialogs for actions - Make navigation sticky - Add hover effects and visual feedback - Improve dark mode support
284 lines
11 KiB
Python
284 lines
11 KiB
Python
from django import forms
|
|
from django.forms import ModelChoiceField
|
|
from django.urls import reverse_lazy
|
|
from .models import Ride, RideModel
|
|
from parks.models import Park, ParkArea
|
|
from companies.models import Manufacturer, Designer
|
|
|
|
|
|
class RideForm(forms.ModelForm):
|
|
park_search = forms.CharField(
|
|
label="Park *",
|
|
required=True,
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Search for a park...",
|
|
"hx-get": "/parks/search/",
|
|
"hx-trigger": "click, input delay:200ms",
|
|
"hx-target": "#park-search-results",
|
|
"name": "q",
|
|
"autocomplete": "off",
|
|
}
|
|
),
|
|
)
|
|
|
|
manufacturer_search = forms.CharField(
|
|
label="Manufacturer",
|
|
required=False,
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Search for a manufacturer...",
|
|
"hx-get": reverse_lazy("rides:search_manufacturers"),
|
|
"hx-trigger": "click, input delay:200ms",
|
|
"hx-target": "#manufacturer-search-results",
|
|
"name": "q",
|
|
"autocomplete": "off",
|
|
}
|
|
),
|
|
)
|
|
|
|
designer_search = forms.CharField(
|
|
label="Designer",
|
|
required=False,
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Search for a designer...",
|
|
"hx-get": reverse_lazy("rides:search_designers"),
|
|
"hx-trigger": "click, input delay:200ms",
|
|
"hx-target": "#designer-search-results",
|
|
"name": "q",
|
|
"autocomplete": "off",
|
|
}
|
|
),
|
|
)
|
|
|
|
ride_model_search = forms.CharField(
|
|
label="Ride Model",
|
|
required=False,
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Search for a ride model...",
|
|
"hx-get": reverse_lazy("rides:search_ride_models"),
|
|
"hx-trigger": "click, input delay:200ms",
|
|
"hx-target": "#ride-model-search-results",
|
|
"hx-include": "[name='manufacturer']",
|
|
"name": "q",
|
|
"autocomplete": "off",
|
|
}
|
|
),
|
|
)
|
|
|
|
park = forms.ModelChoiceField(
|
|
queryset=Park.objects.all(),
|
|
required=True,
|
|
label="",
|
|
widget=forms.HiddenInput()
|
|
)
|
|
|
|
manufacturer = forms.ModelChoiceField(
|
|
queryset=Manufacturer.objects.all(),
|
|
required=False,
|
|
label="",
|
|
widget=forms.HiddenInput()
|
|
)
|
|
|
|
designer = forms.ModelChoiceField(
|
|
queryset=Designer.objects.all(),
|
|
required=False,
|
|
label="",
|
|
widget=forms.HiddenInput()
|
|
)
|
|
|
|
ride_model = forms.ModelChoiceField(
|
|
queryset=RideModel.objects.all(),
|
|
required=False,
|
|
label="",
|
|
widget=forms.HiddenInput()
|
|
)
|
|
|
|
park_area = ModelChoiceField(
|
|
queryset=ParkArea.objects.none(),
|
|
required=False,
|
|
widget=forms.Select(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Select an area within the park..."
|
|
}
|
|
),
|
|
)
|
|
|
|
class Meta:
|
|
model = Ride
|
|
fields = [
|
|
"name",
|
|
"category",
|
|
"manufacturer",
|
|
"designer",
|
|
"ride_model",
|
|
"status",
|
|
"post_closing_status",
|
|
"opening_date",
|
|
"closing_date",
|
|
"status_since",
|
|
"min_height_in",
|
|
"max_height_in",
|
|
"capacity_per_hour",
|
|
"ride_duration_seconds",
|
|
"description",
|
|
]
|
|
widgets = {
|
|
"name": forms.TextInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Official name of the ride"
|
|
}
|
|
),
|
|
"category": forms.Select(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"hx-get": reverse_lazy("rides:coaster_fields"),
|
|
"hx-target": "#coaster-fields",
|
|
"hx-trigger": "change",
|
|
"hx-include": "this",
|
|
"hx-swap": "innerHTML"
|
|
}
|
|
),
|
|
"status": forms.Select(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Current operational status",
|
|
"x-model": "status",
|
|
"@change": "handleStatusChange"
|
|
}
|
|
),
|
|
"post_closing_status": forms.Select(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Status after closing",
|
|
"x-show": "status === 'CLOSING'"
|
|
}
|
|
),
|
|
"opening_date": forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Date when ride first opened"
|
|
}
|
|
),
|
|
"closing_date": forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Date when ride will close",
|
|
"x-show": "['CLOSING', 'SBNO', 'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED'].includes(status)",
|
|
":required": "status === 'CLOSING'"
|
|
}
|
|
),
|
|
"status_since": forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Date when current status took effect"
|
|
}
|
|
),
|
|
"min_height_in": forms.NumberInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"min": "0",
|
|
"placeholder": "Minimum height requirement in inches"
|
|
}
|
|
),
|
|
"max_height_in": forms.NumberInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"min": "0",
|
|
"placeholder": "Maximum height limit in inches (if applicable)"
|
|
}
|
|
),
|
|
"capacity_per_hour": forms.NumberInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"min": "0",
|
|
"placeholder": "Theoretical hourly ride capacity"
|
|
}
|
|
),
|
|
"ride_duration_seconds": forms.NumberInput(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"min": "0",
|
|
"placeholder": "Total duration of one ride cycle in seconds"
|
|
}
|
|
),
|
|
"description": forms.Textarea(
|
|
attrs={
|
|
"rows": 4,
|
|
"class": "w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "General description and notable features of the ride"
|
|
}
|
|
),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
park = kwargs.pop("park", None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Make category required
|
|
self.fields['category'].required = True
|
|
|
|
# Clear any default values for date fields
|
|
self.fields["opening_date"].initial = None
|
|
self.fields["closing_date"].initial = None
|
|
self.fields["status_since"].initial = None
|
|
|
|
# Move fields to the beginning in desired order
|
|
field_order = [
|
|
"park_search", "park", "park_area",
|
|
"name", "manufacturer_search", "manufacturer",
|
|
"designer_search", "designer", "ride_model_search",
|
|
"ride_model", "category", "status",
|
|
"post_closing_status", "opening_date", "closing_date", "status_since",
|
|
"min_height_in", "max_height_in", "capacity_per_hour",
|
|
"ride_duration_seconds", "description"
|
|
]
|
|
self.order_fields(field_order)
|
|
|
|
if park:
|
|
# If park is provided, set it as the initial value
|
|
self.fields["park"].initial = park
|
|
# Hide the park search field since we know the park
|
|
del self.fields["park_search"]
|
|
# Create new park_area field with park's areas
|
|
self.fields["park_area"] = forms.ModelChoiceField(
|
|
queryset=park.areas.all(),
|
|
required=False,
|
|
widget=forms.Select(
|
|
attrs={
|
|
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
|
"placeholder": "Select an area within the park..."
|
|
}
|
|
),
|
|
)
|
|
else:
|
|
# If no park provided, show park search and disable park_area until park is selected
|
|
self.fields["park_area"].widget.attrs["disabled"] = True
|
|
# Initialize park search with current park name if editing
|
|
if self.instance and self.instance.pk and self.instance.park:
|
|
self.fields["park_search"].initial = self.instance.park.name
|
|
self.fields["park"].initial = self.instance.park
|
|
|
|
# Initialize manufacturer, designer, and ride model search fields if editing
|
|
if self.instance and self.instance.pk:
|
|
if self.instance.manufacturer:
|
|
self.fields["manufacturer_search"].initial = self.instance.manufacturer.name
|
|
self.fields["manufacturer"].initial = self.instance.manufacturer
|
|
if self.instance.designer:
|
|
self.fields["designer_search"].initial = self.instance.designer.name
|
|
self.fields["designer"].initial = self.instance.designer
|
|
if self.instance.ride_model:
|
|
self.fields["ride_model_search"].initial = self.instance.ride_model.name
|
|
self.fields["ride_model"].initial = self.instance.ride_model
|