mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 10:51:09 -05:00
update
This commit is contained in:
710
apps/rides/admin.py
Normal file
710
apps/rides/admin.py
Normal file
@@ -0,0 +1,710 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user