Add comprehensive API documentation for ThrillWiki integration and features

- Introduced Next.js integration guide for ThrillWiki API, detailing authentication, core domain APIs, data structures, and implementation patterns.
- Documented the migration to Rich Choice Objects, highlighting changes for frontend developers and enhanced metadata availability.
- Fixed the missing `get_by_slug` method in the Ride model, ensuring proper functionality of ride detail endpoints.
- Created a test script to verify manufacturer syncing with ride models, ensuring data integrity across related models.
This commit is contained in:
pacnpal
2025-09-16 11:29:17 -04:00
parent 61d73a2147
commit c2c26cfd1d
98 changed files with 11476 additions and 4803 deletions

View File

@@ -0,0 +1,288 @@
"""
Rich Choice Objects for Parks Domain
This module defines all choice objects for the parks domain, replacing
the legacy tuple-based choices with rich choice objects.
"""
from apps.core.choices import RichChoice, ChoiceCategory
from apps.core.choices.registry import register_choices
# Park Status Choices
PARK_STATUSES = [
RichChoice(
value="OPERATING",
label="Operating",
description="Park is currently open and operating normally",
metadata={
'color': 'green',
'icon': 'check-circle',
'css_class': 'bg-green-100 text-green-800',
'sort_order': 1
},
category=ChoiceCategory.STATUS
),
RichChoice(
value="CLOSED_TEMP",
label="Temporarily Closed",
description="Park is temporarily closed for maintenance, weather, or seasonal reasons",
metadata={
'color': 'yellow',
'icon': 'pause-circle',
'css_class': 'bg-yellow-100 text-yellow-800',
'sort_order': 2
},
category=ChoiceCategory.STATUS
),
RichChoice(
value="CLOSED_PERM",
label="Permanently Closed",
description="Park has been permanently closed and will not reopen",
metadata={
'color': 'red',
'icon': 'x-circle',
'css_class': 'bg-red-100 text-red-800',
'sort_order': 3
},
category=ChoiceCategory.STATUS
),
RichChoice(
value="UNDER_CONSTRUCTION",
label="Under Construction",
description="Park is currently being built or undergoing major renovation",
metadata={
'color': 'blue',
'icon': 'tool',
'css_class': 'bg-blue-100 text-blue-800',
'sort_order': 4
},
category=ChoiceCategory.STATUS
),
RichChoice(
value="DEMOLISHED",
label="Demolished",
description="Park has been completely demolished and removed",
metadata={
'color': 'gray',
'icon': 'trash',
'css_class': 'bg-gray-100 text-gray-800',
'sort_order': 5
},
category=ChoiceCategory.STATUS
),
RichChoice(
value="RELOCATED",
label="Relocated",
description="Park has been moved to a different location",
metadata={
'color': 'purple',
'icon': 'arrow-right',
'css_class': 'bg-purple-100 text-purple-800',
'sort_order': 6
},
category=ChoiceCategory.STATUS
),
]
# Park Type Choices
PARK_TYPES = [
RichChoice(
value="THEME_PARK",
label="Theme Park",
description="Large-scale amusement park with themed areas and attractions",
metadata={
'color': 'red',
'icon': 'castle',
'css_class': 'bg-red-100 text-red-800',
'sort_order': 1
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="AMUSEMENT_PARK",
label="Amusement Park",
description="Traditional amusement park with rides and games",
metadata={
'color': 'blue',
'icon': 'ferris-wheel',
'css_class': 'bg-blue-100 text-blue-800',
'sort_order': 2
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="WATER_PARK",
label="Water Park",
description="Park featuring water-based attractions and activities",
metadata={
'color': 'cyan',
'icon': 'water',
'css_class': 'bg-cyan-100 text-cyan-800',
'sort_order': 3
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="FAMILY_ENTERTAINMENT_CENTER",
label="Family Entertainment Center",
description="Indoor entertainment facility with games and family attractions",
metadata={
'color': 'green',
'icon': 'family',
'css_class': 'bg-green-100 text-green-800',
'sort_order': 4
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="CARNIVAL",
label="Carnival",
description="Traveling amusement show with rides, games, and entertainment",
metadata={
'color': 'yellow',
'icon': 'carnival',
'css_class': 'bg-yellow-100 text-yellow-800',
'sort_order': 5
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="FAIR",
label="Fair",
description="Temporary event featuring rides, games, and agricultural exhibits",
metadata={
'color': 'orange',
'icon': 'fair',
'css_class': 'bg-orange-100 text-orange-800',
'sort_order': 6
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="PIER",
label="Pier",
description="Seaside entertainment pier with rides and attractions",
metadata={
'color': 'teal',
'icon': 'pier',
'css_class': 'bg-teal-100 text-teal-800',
'sort_order': 7
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="BOARDWALK",
label="Boardwalk",
description="Waterfront entertainment area with rides and attractions",
metadata={
'color': 'indigo',
'icon': 'boardwalk',
'css_class': 'bg-indigo-100 text-indigo-800',
'sort_order': 8
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="SAFARI_PARK",
label="Safari Park",
description="Wildlife park with drive-through animal experiences",
metadata={
'color': 'emerald',
'icon': 'safari',
'css_class': 'bg-emerald-100 text-emerald-800',
'sort_order': 9
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="ZOO",
label="Zoo",
description="Zoological park with animal exhibits and educational programs",
metadata={
'color': 'lime',
'icon': 'zoo',
'css_class': 'bg-lime-100 text-lime-800',
'sort_order': 10
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="OTHER",
label="Other",
description="Park type that doesn't fit into standard categories",
metadata={
'color': 'gray',
'icon': 'other',
'css_class': 'bg-gray-100 text-gray-800',
'sort_order': 11
},
category=ChoiceCategory.CLASSIFICATION
),
]
# Company Role Choices for Parks Domain (OPERATOR and PROPERTY_OWNER only)
PARKS_COMPANY_ROLES = [
RichChoice(
value="OPERATOR",
label="Park Operator",
description="Company that operates and manages theme parks and amusement facilities",
metadata={
'color': 'blue',
'icon': 'building-office',
'css_class': 'bg-blue-100 text-blue-800',
'sort_order': 1,
'domain': 'parks',
'permissions': ['manage_parks', 'view_operations'],
'url_pattern': '/parks/operators/{slug}/'
},
category=ChoiceCategory.CLASSIFICATION
),
RichChoice(
value="PROPERTY_OWNER",
label="Property Owner",
description="Company that owns the land and property where parks are located",
metadata={
'color': 'green',
'icon': 'home',
'css_class': 'bg-green-100 text-green-800',
'sort_order': 2,
'domain': 'parks',
'permissions': ['manage_property', 'view_ownership'],
'url_pattern': '/parks/owners/{slug}/'
},
category=ChoiceCategory.CLASSIFICATION
),
]
def register_parks_choices():
"""Register all parks domain choices with the global registry"""
register_choices(
name="statuses",
choices=PARK_STATUSES,
domain="parks",
description="Park operational status options",
metadata={'domain': 'parks', 'type': 'status'}
)
register_choices(
name="types",
choices=PARK_TYPES,
domain="parks",
description="Park type and category classifications",
metadata={'domain': 'parks', 'type': 'park_type'}
)
register_choices(
name="company_roles",
choices=PARKS_COMPANY_ROLES,
domain="parks",
description="Company role classifications for parks domain (OPERATOR and PROPERTY_OWNER only)",
metadata={'domain': 'parks', 'type': 'company_role'}
)
# Auto-register choices when module is imported
register_parks_choices()

View File

@@ -15,6 +15,7 @@ from django_filters import (
)
from .models import Park, Company
from .querysets import get_base_park_queryset
from apps.core.choices.registry import get_choices
import requests
@@ -46,7 +47,7 @@ class ParkFilter(FilterSet):
# Status filter with clearer label
status = ChoiceFilter(
field_name="status",
choices=Park.STATUS_CHOICES,
choices=lambda: [(choice.value, choice.label) for choice in get_choices("park_statuses", "parks")],
empty_label=_("Any status"),
label=_("Operating Status"),
help_text=_("Filter parks by their current operating status"),

View File

@@ -1,6 +1,6 @@
# Generated by Django 5.2.5 on 2025-09-14 19:12
from django.db import migrations, models
from django.db import migrations
class Migration(migrations.Migration):

View File

@@ -0,0 +1,103 @@
# Generated by Django 5.2.5 on 2025-09-15 17:35
import apps.core.choices.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("parks", "0020_fix_pghistory_update_timezone"),
]
operations = [
migrations.AlterField(
model_name="park",
name="park_type",
field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="types",
choices=[
("THEME_PARK", "Theme Park"),
("AMUSEMENT_PARK", "Amusement Park"),
("WATER_PARK", "Water Park"),
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
("CARNIVAL", "Carnival"),
("FAIR", "Fair"),
("PIER", "Pier"),
("BOARDWALK", "Boardwalk"),
("SAFARI_PARK", "Safari Park"),
("ZOO", "Zoo"),
("OTHER", "Other"),
],
db_index=True,
default="THEME_PARK",
domain="parks",
help_text="Type/category of the park",
max_length=30,
),
),
migrations.AlterField(
model_name="park",
name="status",
field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="statuses",
choices=[
("OPERATING", "Operating"),
("CLOSED_TEMP", "Temporarily Closed"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
],
default="OPERATING",
domain="parks",
max_length=20,
),
),
migrations.AlterField(
model_name="parkevent",
name="park_type",
field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="types",
choices=[
("THEME_PARK", "Theme Park"),
("AMUSEMENT_PARK", "Amusement Park"),
("WATER_PARK", "Water Park"),
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
("CARNIVAL", "Carnival"),
("FAIR", "Fair"),
("PIER", "Pier"),
("BOARDWALK", "Boardwalk"),
("SAFARI_PARK", "Safari Park"),
("ZOO", "Zoo"),
("OTHER", "Other"),
],
default="THEME_PARK",
domain="parks",
help_text="Type/category of the park",
max_length=30,
),
),
migrations.AlterField(
model_name="parkevent",
name="status",
field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="statuses",
choices=[
("OPERATING", "Operating"),
("CLOSED_TEMP", "Temporarily Closed"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
],
default="OPERATING",
domain="parks",
max_length=20,
),
),
]

View File

@@ -0,0 +1,53 @@
# Generated by Django 5.2.5 on 2025-09-15 18:07
import apps.core.choices.fields
import django.contrib.postgres.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("parks", "0021_alter_park_park_type_alter_park_status_and_more"),
]
operations = [
migrations.AlterField(
model_name="company",
name="roles",
field=django.contrib.postgres.fields.ArrayField(
base_field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="company_roles",
choices=[
("OPERATOR", "Park Operator"),
("PROPERTY_OWNER", "Property Owner"),
],
domain="parks",
max_length=20,
),
blank=True,
default=list,
size=None,
),
),
migrations.AlterField(
model_name="companyevent",
name="roles",
field=django.contrib.postgres.fields.ArrayField(
base_field=apps.core.choices.fields.RichChoiceField(
allow_deprecated=False,
choice_group="company_roles",
choices=[
("OPERATOR", "Park Operator"),
("PROPERTY_OWNER", "Property Owner"),
],
domain="parks",
max_length=20,
),
blank=True,
default=list,
size=None,
),
),
]

View File

@@ -15,6 +15,9 @@ from .reviews import ParkReview
from .companies import Company, CompanyHeadquarters
from .media import ParkPhoto
# Import choices to trigger registration
from ..choices import *
# Alias Company as Operator for clarity
Operator = Company

View File

@@ -2,6 +2,7 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.text import slugify
from apps.core.models import TrackedModel
from apps.core.choices.fields import RichChoiceField
import pghistory
@@ -12,14 +13,10 @@ class Company(TrackedModel):
objects = CompanyManager()
class CompanyRole(models.TextChoices):
OPERATOR = "OPERATOR", "Park Operator"
PROPERTY_OWNER = "PROPERTY_OWNER", "Property Owner"
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
roles = ArrayField(
models.CharField(max_length=20, choices=CompanyRole.choices),
RichChoiceField(choice_group="company_roles", domain="parks", max_length=20),
default=list,
blank=True,
)

View File

@@ -6,6 +6,7 @@ from config.django import base as settings
from typing import Optional, Any, TYPE_CHECKING, List
import pghistory
from apps.core.history import TrackedModel
from apps.core.choices import RichChoiceField
if TYPE_CHECKING:
@@ -20,39 +21,21 @@ class Park(TrackedModel):
objects = ParkManager()
id: int # Type hint for Django's automatic id field
STATUS_CHOICES = [
("OPERATING", "Operating"),
("CLOSED_TEMP", "Temporarily Closed"),
("CLOSED_PERM", "Permanently Closed"),
("UNDER_CONSTRUCTION", "Under Construction"),
("DEMOLISHED", "Demolished"),
("RELOCATED", "Relocated"),
]
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(blank=True)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="OPERATING"
status = RichChoiceField(
choice_group="statuses",
domain="parks",
max_length=20,
default="OPERATING",
)
PARK_TYPE_CHOICES = [
("THEME_PARK", "Theme Park"),
("AMUSEMENT_PARK", "Amusement Park"),
("WATER_PARK", "Water Park"),
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
("CARNIVAL", "Carnival"),
("FAIR", "Fair"),
("PIER", "Pier"),
("BOARDWALK", "Boardwalk"),
("SAFARI_PARK", "Safari Park"),
("ZOO", "Zoo"),
("OTHER", "Other"),
]
park_type = models.CharField(
park_type = RichChoiceField(
choice_group="types",
domain="parks",
max_length=30,
choices=PARK_TYPE_CHOICES,
default="THEME_PARK",
db_index=True,
help_text="Type/category of the park"
@@ -291,7 +274,10 @@ class Park(TrackedModel):
"DEMOLISHED": "bg-gray-100 text-gray-800",
"RELOCATED": "bg-purple-100 text-purple-800",
}
return status_colors.get(self.status, "bg-gray-100 text-gray-500")
if self.status in status_colors:
return status_colors[self.status]
else:
raise ValueError(f"Unknown park status: {self.status}")
@property
def formatted_location(self) -> str:

View File

@@ -5,7 +5,7 @@ This module provides intelligent data loading capabilities for the hybrid filter
optimizing database queries and implementing progressive loading strategies.
"""
from typing import Dict, List, Optional, Any, Tuple
from typing import Dict, Optional, Any
from django.db import models
from django.core.cache import cache
from django.conf import settings
@@ -392,7 +392,10 @@ class SmartParkLoader:
'CLOSED_PERM': 'Permanently Closed',
'UNDER_CONSTRUCTION': 'Under Construction',
}
return status_labels.get(status, status)
if status in status_labels:
return status_labels[status]
else:
raise ValueError(f"Unknown park status: {status}")
def _generate_cache_key(self, operation: str, filters: Optional[Dict[str, Any]] = None) -> str:
"""Generate cache key for the given operation and filters."""

View File

@@ -65,25 +65,25 @@ urlpatterns = [
),
# Park-specific category URLs
path(
"<slug:park_slug>/roller_coasters/",
"<slug:park_slug>/roller-coasters/",
ParkSingleCategoryListView.as_view(),
{"category": "RC"},
name="park_roller_coasters",
),
path(
"<slug:park_slug>/dark_rides/",
"<slug:park_slug>/dark-rides/",
ParkSingleCategoryListView.as_view(),
{"category": "DR"},
name="park_dark_rides",
),
path(
"<slug:park_slug>/flat_rides/",
"<slug:park_slug>/flat-rides/",
ParkSingleCategoryListView.as_view(),
{"category": "FR"},
name="park_flat_rides",
),
path(
"<slug:park_slug>/water_rides/",
"<slug:park_slug>/water-rides/",
ParkSingleCategoryListView.as_view(),
{"category": "WR"},
name="park_water_rides",