Files

711 lines
18 KiB
Python

from django.contrib import admin
from django.contrib.gis.admin import GISModelAdmin
from django.utils.html import format_html
from .models.company import Company
from .models.rides import Ride, RideModel, RollerCoasterStats
from .models.location import RideLocation
from .models.reviews import RideReview
from .models.rankings import RideRanking, RidePairComparison, RankingSnapshot
class ManufacturerAdmin(admin.ModelAdmin):
list_display = ("name", "headquarters", "website", "rides_count")
search_fields = ("name",)
def get_queryset(self, request):
return super().get_queryset(request).filter(roles__contains=["MANUFACTURER"])
class DesignerAdmin(admin.ModelAdmin):
list_display = ("name", "headquarters", "website")
search_fields = ("name",)
def get_queryset(self, request):
return super().get_queryset(request).filter(roles__contains=["DESIGNER"])
class RideLocationInline(admin.StackedInline):
"""Inline admin for RideLocation"""
model = RideLocation
extra = 0
fields = (
"park_area",
"point",
"entrance_notes",
"accessibility_notes",
)
class RideLocationAdmin(GISModelAdmin):
"""Admin for standalone RideLocation management"""
list_display = ("ride", "park_area", "has_coordinates", "created_at")
list_filter = ("park_area", "created_at")
search_fields = ("ride__name", "park_area", "entrance_notes")
readonly_fields = (
"latitude",
"longitude",
"coordinates",
"created_at",
"updated_at",
)
fieldsets = (
("Ride", {"fields": ("ride",)}),
(
"Location Information",
{
"fields": (
"park_area",
"point",
"latitude",
"longitude",
"coordinates",
),
"description": "Optional coordinates - not all rides need precise location tracking",
},
),
(
"Navigation Notes",
{
"fields": ("entrance_notes", "accessibility_notes"),
},
),
(
"Metadata",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
@admin.display(description="Latitude")
def latitude(self, obj):
return obj.latitude
@admin.display(description="Longitude")
def longitude(self, obj):
return obj.longitude
class RollerCoasterStatsInline(admin.StackedInline):
"""Inline admin for RollerCoasterStats"""
model = RollerCoasterStats
extra = 0
fields = (
("height_ft", "length_ft", "speed_mph"),
("track_material", "roller_coaster_type"),
("propulsion_system", "inversions"),
("max_drop_height_ft", "ride_time_seconds"),
("train_style", "trains_count"),
("cars_per_train", "seats_per_car"),
)
classes = ("collapse",)
@admin.register(Ride)
class RideAdmin(admin.ModelAdmin):
"""Enhanced Ride admin with location and coaster stats inlines"""
list_display = (
"name",
"park",
"category_display",
"manufacturer",
"status",
"opening_date",
"average_rating",
)
list_filter = (
"category",
"status",
"park",
"manufacturer",
"designer",
"opening_date",
)
search_fields = (
"name",
"description",
"park__name",
"manufacturer__name",
"designer__name",
)
readonly_fields = ("created_at", "updated_at")
prepopulated_fields = {"slug": ("name",)}
inlines = [RideLocationInline, RollerCoasterStatsInline]
date_hierarchy = "opening_date"
ordering = ("park", "name")
fieldsets = (
(
"Basic Information",
{
"fields": (
"name",
"slug",
"description",
"park",
"park_area",
"category",
)
},
),
(
"Companies",
{
"fields": (
"manufacturer",
"designer",
"ride_model",
)
},
),
(
"Status & Dates",
{
"fields": (
"status",
"post_closing_status",
"opening_date",
"closing_date",
"status_since",
)
},
),
(
"Ride Specifications",
{
"fields": (
"min_height_in",
"max_height_in",
"capacity_per_hour",
"ride_duration_seconds",
"average_rating",
),
"classes": ("collapse",),
},
),
(
"Metadata",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
@admin.display(description="Category")
def category_display(self, obj):
"""Display category with full name"""
choices_dict = dict(obj._meta.get_field("category").choices)
if obj.category in choices_dict:
return choices_dict[obj.category]
else:
raise ValueError(f"Unknown category: {obj.category}")
@admin.register(RideModel)
class RideModelAdmin(admin.ModelAdmin):
"""Admin interface for ride models"""
list_display = (
"name",
"manufacturer",
"category_display",
"ride_count",
)
list_filter = (
"manufacturer",
"category",
)
search_fields = (
"name",
"description",
"manufacturer__name",
)
ordering = ("manufacturer", "name")
fieldsets = (
(
"Model Information",
{
"fields": (
"name",
"manufacturer",
"category",
"description",
)
},
),
)
@admin.display(description="Category")
def category_display(self, obj):
"""Display category with full name"""
choices_dict = dict(obj._meta.get_field("category").choices)
if obj.category in choices_dict:
return choices_dict[obj.category]
else:
raise ValueError(f"Unknown category: {obj.category}")
@admin.display(description="Installations")
def ride_count(self, obj):
"""Display number of ride installations"""
return obj.rides.count()
@admin.register(RollerCoasterStats)
class RollerCoasterStatsAdmin(admin.ModelAdmin):
"""Admin interface for roller coaster statistics"""
list_display = (
"ride",
"height_ft",
"speed_mph",
"length_ft",
"inversions",
"track_material",
"roller_coaster_type",
)
list_filter = (
"track_material",
"roller_coaster_type",
"propulsion_system",
"inversions",
)
search_fields = (
"ride__name",
"ride__park__name",
"track_type",
"train_style",
)
readonly_fields = ("calculated_capacity",)
fieldsets = (
(
"Basic Stats",
{
"fields": (
"ride",
"height_ft",
"length_ft",
"speed_mph",
"max_drop_height_ft",
)
},
),
(
"Track & Design",
{
"fields": (
"track_material",
"track_type",
"roller_coaster_type",
"propulsion_system",
"inversions",
)
},
),
(
"Operation Details",
{
"fields": (
"ride_time_seconds",
"train_style",
"trains_count",
"cars_per_train",
"seats_per_car",
"calculated_capacity",
),
"classes": ("collapse",),
},
),
)
@admin.display(description="Calculated Capacity")
def calculated_capacity(self, obj):
"""Calculate theoretical hourly capacity"""
if all(
[
obj.trains_count,
obj.cars_per_train,
obj.seats_per_car,
obj.ride_time_seconds,
]
):
total_seats = obj.trains_count * obj.cars_per_train * obj.seats_per_car
# Add 2 min loading time
cycles_per_hour = 3600 / (obj.ride_time_seconds + 120)
return f"{int(total_seats * cycles_per_hour)} riders/hour"
return "N/A"
@admin.register(RideReview)
class RideReviewAdmin(admin.ModelAdmin):
"""Admin interface for ride reviews"""
list_display = (
"ride",
"user",
"rating",
"title",
"visit_date",
"is_published",
"created_at",
"moderation_status",
)
list_filter = (
"rating",
"is_published",
"visit_date",
"created_at",
"ride__park",
"moderated_by",
)
search_fields = (
"title",
"content",
"user__username",
"ride__name",
"ride__park__name",
)
readonly_fields = ("created_at", "updated_at")
date_hierarchy = "created_at"
ordering = ("-created_at",)
fieldsets = (
(
"Review Details",
{
"fields": (
"user",
"ride",
"rating",
"title",
"content",
"visit_date",
)
},
),
(
"Publication Status",
{
"fields": ("is_published",),
},
),
(
"Moderation",
{
"fields": (
"moderated_by",
"moderated_at",
"moderation_notes",
),
"classes": ("collapse",),
},
),
(
"Metadata",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
@admin.display(description="Moderation Status", boolean=True)
def moderation_status(self, obj):
"""Display moderation status with color coding"""
if obj.moderated_by:
return format_html(
'<span style="color: {};">{}</span>',
"green" if obj.is_published else "red",
"Approved" if obj.is_published else "Rejected",
)
return format_html('<span style="color: orange;">Pending</span>')
def save_model(self, request, obj, form, change):
"""Auto-set moderation info when status changes"""
if change and "is_published" in form.changed_data:
from django.utils import timezone
obj.moderated_by = request.user
obj.moderated_at = timezone.now()
super().save_model(request, obj, form, change)
@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
"""Enhanced Company admin for rides app"""
list_display = (
"name",
"roles_display",
"website",
"founded_date",
"rides_count",
"coasters_count",
)
list_filter = ("roles", "founded_date")
search_fields = ("name", "description")
readonly_fields = ("created_at", "updated_at")
prepopulated_fields = {"slug": ("name",)}
fieldsets = (
(
"Basic Information",
{
"fields": (
"name",
"slug",
"roles",
"description",
"website",
)
},
),
(
"Company Details",
{
"fields": (
"founded_date",
"rides_count",
"coasters_count",
)
},
),
(
"Metadata",
{
"fields": ("created_at", "updated_at"),
"classes": ("collapse",),
},
),
)
@admin.display(description="Roles")
def roles_display(self, obj):
"""Display roles as a formatted string"""
return ", ".join(obj.roles) if obj.roles else "No roles"
@admin.register(RideRanking)
class RideRankingAdmin(admin.ModelAdmin):
"""Admin interface for ride rankings"""
list_display = (
"rank",
"ride_name",
"park_name",
"winning_percentage_display",
"wins",
"losses",
"ties",
"average_rating",
"mutual_riders_count",
"last_calculated",
)
list_filter = (
"ride__category",
"last_calculated",
"calculation_version",
)
search_fields = (
"ride__name",
"ride__park__name",
)
readonly_fields = (
"ride",
"rank",
"wins",
"losses",
"ties",
"winning_percentage",
"mutual_riders_count",
"comparison_count",
"average_rating",
"last_calculated",
"calculation_version",
"total_comparisons",
)
ordering = ["rank"]
fieldsets = (
(
"Ride Information",
{"fields": ("ride",)},
),
(
"Ranking Metrics",
{
"fields": (
"rank",
"winning_percentage",
"wins",
"losses",
"ties",
"total_comparisons",
)
},
),
(
"Additional Metrics",
{
"fields": (
"average_rating",
"mutual_riders_count",
"comparison_count",
)
},
),
(
"Calculation Info",
{
"fields": (
"last_calculated",
"calculation_version",
),
"classes": ("collapse",),
},
),
)
@admin.display(description="Ride")
def ride_name(self, obj):
return obj.ride.name
@admin.display(description="Park")
def park_name(self, obj):
return obj.ride.park.name
@admin.display(description="Win %")
def winning_percentage_display(self, obj):
return f"{obj.winning_percentage:.1%}"
def has_add_permission(self, request):
# Rankings are calculated automatically
return False
def has_change_permission(self, request, obj=None):
# Rankings are read-only
return False
@admin.register(RidePairComparison)
class RidePairComparisonAdmin(admin.ModelAdmin):
"""Admin interface for ride pair comparisons"""
list_display = (
"comparison_summary",
"ride_a_name",
"ride_b_name",
"winner_display",
"ride_a_wins",
"ride_b_wins",
"ties",
"mutual_riders_count",
"last_calculated",
)
list_filter = ("last_calculated",)
search_fields = (
"ride_a__name",
"ride_b__name",
"ride_a__park__name",
"ride_b__park__name",
)
readonly_fields = (
"ride_a",
"ride_b",
"ride_a_wins",
"ride_b_wins",
"ties",
"mutual_riders_count",
"ride_a_avg_rating",
"ride_b_avg_rating",
"last_calculated",
"winner",
"is_tie",
)
ordering = ["-mutual_riders_count"]
@admin.display(description="Comparison")
def comparison_summary(self, obj):
return f"{obj.ride_a.name} vs {obj.ride_b.name}"
@admin.display(description="Ride A")
def ride_a_name(self, obj):
return obj.ride_a.name
@admin.display(description="Ride B")
def ride_b_name(self, obj):
return obj.ride_b.name
@admin.display(description="Winner")
def winner_display(self, obj):
if obj.is_tie:
return "TIE"
winner = obj.winner
if winner:
return winner.name
return "N/A"
def has_add_permission(self, request):
# Comparisons are calculated automatically
return False
def has_change_permission(self, request, obj=None):
# Comparisons are read-only
return False
@admin.register(RankingSnapshot)
class RankingSnapshotAdmin(admin.ModelAdmin):
"""Admin interface for ranking history snapshots"""
list_display = (
"ride_name",
"park_name",
"rank",
"winning_percentage_display",
"snapshot_date",
)
list_filter = (
"snapshot_date",
"ride__category",
)
search_fields = (
"ride__name",
"ride__park__name",
)
readonly_fields = (
"ride",
"rank",
"winning_percentage",
"snapshot_date",
)
date_hierarchy = "snapshot_date"
ordering = ["-snapshot_date", "rank"]
@admin.display(description="Ride")
def ride_name(self, obj):
return obj.ride.name
@admin.display(description="Park")
def park_name(self, obj):
return obj.ride.park.name
@admin.display(description="Win %")
def winning_percentage_display(self, obj):
return f"{obj.winning_percentage:.1%}"
def has_add_permission(self, request):
# Snapshots are created automatically
return False
def has_change_permission(self, request, obj=None):
# Snapshots are read-only
return False
admin.site.register(RideLocation, RideLocationAdmin)