Files
thrillwiki_django_no_react/docs/park_domain_analysis.md
pacnpal 1b246eeaa4 Add comprehensive test scripts for various models and services
- Implement tests for RideLocation and CompanyHeadquarters models to verify functionality and data integrity.
- Create a manual trigger test script for trending content calculation endpoint, including authentication and unauthorized access tests.
- Develop a manufacturer sync test to ensure ride manufacturers are correctly associated with ride models.
- Add tests for ParkLocation model, including coordinate setting and distance calculations between parks.
- Implement a RoadTripService test suite covering geocoding, route calculation, park discovery, and error handling.
- Create a unified map service test script to validate map functionality, API endpoints, and performance metrics.
2025-09-27 22:26:40 -04:00

13 KiB

ThrillWiki Park Domain Analysis

Executive Summary

This document provides a complete inventory of all park-related models and their relationships across the ThrillWiki Django codebase. The analysis reveals that park-related functionality is currently distributed across three separate Django apps (parks, operators, and property_owners) that are always used together but artificially separated.

Current Architecture Overview

Apps Structure

  • parks/ - Core park and park area models
  • operators/ - Companies that operate theme parks
  • property_owners/ - Companies that own park property
  • companies/ - Empty models directory (no active models found)

Model Inventory

Parks App Models

Park Model (parks/models.py)

Location: parks.models.Park Inheritance: TrackedModel (provides history tracking) Decorators: @pghistory.track()

Fields:

  • name - CharField(max_length=255)
  • slug - SlugField(max_length=255, unique=True)
  • description - TextField(blank=True)
  • status - CharField with choices: OPERATING, CLOSED_TEMP, CLOSED_PERM, UNDER_CONSTRUCTION, DEMOLISHED, RELOCATED
  • opening_date - DateField(null=True, blank=True)
  • closing_date - DateField(null=True, blank=True)
  • operating_season - CharField(max_length=255, blank=True)
  • size_acres - DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
  • website - URLField(blank=True)
  • average_rating - DecimalField(max_digits=3, decimal_places=2, null=True, blank=True)
  • ride_count - IntegerField(null=True, blank=True)
  • coaster_count - IntegerField(null=True, blank=True)
  • created_at - DateTimeField(auto_now_add=True, null=True)
  • updated_at - DateTimeField(auto_now=True)

Relationships:

  • operator - ForeignKey to Operator (SET_NULL, null=True, blank=True, related_name="parks")
  • property_owner - ForeignKey to PropertyOwner (SET_NULL, null=True, blank=True, related_name="owned_parks")
  • location - GenericRelation to Location (related_query_name='park')
  • photos - GenericRelation to Photo (related_query_name="park")
  • areas - Reverse relation from ParkArea
  • rides - Reverse relation from rides app

Custom Methods:

  • get_absolute_url() - Returns park detail URL
  • get_status_color() - Returns Tailwind CSS classes for status display
  • formatted_location (property) - Returns formatted address string
  • coordinates (property) - Returns (lat, lon) tuple
  • get_by_slug(slug) (classmethod) - Handles current and historical slug lookup
  • save() - Custom save with slug generation and historical slug tracking

Meta Options:

  • ordering = ["name"]

ParkArea Model (parks/models.py)

Location: parks.models.ParkArea Inheritance: TrackedModel Decorators: @pghistory.track()

Fields:

  • name - CharField(max_length=255)
  • slug - SlugField(max_length=255)
  • description - TextField(blank=True)
  • opening_date - DateField(null=True, blank=True)
  • closing_date - DateField(null=True, blank=True)
  • created_at - DateTimeField(auto_now_add=True, null=True)
  • updated_at - DateTimeField(auto_now=True)

Relationships:

  • park - ForeignKey to Park (CASCADE, related_name="areas")

Custom Methods:

  • get_absolute_url() - Returns area detail URL
  • get_by_slug(slug) (classmethod) - Handles current and historical slug lookup
  • save() - Auto-generates slug from name

Meta Options:

  • ordering = ["name"]
  • unique_together = ["park", "slug"]

Operators App Models

Operator Model (operators/models.py)

Location: operators.models.Operator Inheritance: TrackedModel Decorators: @pghistory.track()

Fields:

  • name - CharField(max_length=255)
  • slug - SlugField(max_length=255, unique=True)
  • description - TextField(blank=True)
  • website - URLField(blank=True)
  • founded_year - PositiveIntegerField(blank=True, null=True)
  • headquarters - CharField(max_length=255, blank=True)
  • parks_count - IntegerField(default=0)
  • rides_count - IntegerField(default=0)

Custom Methods:

  • get_absolute_url() - Returns operator detail URL
  • get_by_slug(slug) (classmethod) - Handles current and historical slug lookup
  • save() - Auto-generates slug if missing

Meta Options:

  • ordering = ['name']
  • verbose_name = 'Operator'
  • verbose_name_plural = 'Operators'

Property Owners App Models

PropertyOwner Model (property_owners/models.py)

Location: property_owners.models.PropertyOwner Inheritance: TrackedModel Decorators: @pghistory.track()

Fields:

  • name - CharField(max_length=255)
  • slug - SlugField(max_length=255, unique=True)
  • description - TextField(blank=True)
  • website - URLField(blank=True)

Custom Methods:

  • get_absolute_url() - Returns property owner detail URL
  • get_by_slug(slug) (classmethod) - Handles current and historical slug lookup
  • save() - Auto-generates slug if missing

Meta Options:

  • ordering = ['name']
  • verbose_name = 'Property Owner'
  • verbose_name_plural = 'Property Owners'

TrackedModel (history_tracking/models.py)

Base class for all park-related models

  • Provides created_at and updated_at fields
  • Includes get_history() method for pghistory integration

HistoricalSlug (history_tracking/models.py)

Tracks historical slugs for all models

  • content_type - ForeignKey to ContentType
  • object_id - PositiveIntegerField
  • slug - SlugField(max_length=255)
  • created_at - DateTimeField
  • user - ForeignKey to User (optional)

Relationship Diagram

erDiagram
    Park ||--o{ ParkArea : contains
    Park }o--|| Operator : operated_by
    Park }o--o| PropertyOwner : owned_by
    Park ||--o{ Location : has_location
    Park ||--o{ Photo : has_photos
    Park ||--o{ Ride : contains
    
    Operator {
        int id PK
        string name
        string slug UK
        text description
        string website
        int founded_year
        string headquarters
        int parks_count
        int rides_count
        datetime created_at
        datetime updated_at
    }
    
    PropertyOwner {
        int id PK
        string name
        string slug UK
        text description
        string website
        datetime created_at
        datetime updated_at
    }
    
    Park {
        int id PK
        string name
        string slug UK
        text description
        string status
        date opening_date
        date closing_date
        string operating_season
        decimal size_acres
        string website
        decimal average_rating
        int ride_count
        int coaster_count
        int operator_id FK
        int property_owner_id FK
        datetime created_at
        datetime updated_at
    }
    
    ParkArea {
        int id PK
        string name
        string slug
        text description
        date opening_date
        date closing_date
        int park_id FK
        datetime created_at
        datetime updated_at
    }
    
    Location {
        int id PK
        int content_type_id FK
        int object_id
        string name
        string location_type
        decimal latitude
        decimal longitude
        string street_address
        string city
        string state
        string country
        string postal_code
    }
    
    Photo {
        int id PK
        int content_type_id FK
        int object_id
        string image
        string caption
        int uploaded_by_id FK
    }
    
    Ride {
        int id PK
        string name
        string slug
        text description
        string category
        string status
        int park_id FK
        int park_area_id FK
        int manufacturer_id FK
        int designer_id FK
    }

Admin Configurations

Parks App Admin

  • ParkAdmin: List display includes location, status, operator, property_owner
  • ParkAreaAdmin: List display includes park relationship
  • Both use prepopulated_fields for slug generation
  • Both include created_at and updated_at as readonly fields

Operators App Admin

  • OperatorAdmin: Shows parks_count and rides_count (readonly)
  • Includes founded_year filter
  • Search includes name, description, headquarters

Property Owners App Admin

  • PropertyOwnerAdmin: Basic configuration
  • Search includes name and description
  • No special filters or readonly fields

URL Patterns and Views

Parks App URLs (parks/urls.py)

  • List/Search: / - ParkSearchView with autocomplete
  • Create: /create/ - ParkCreateView
  • Detail: /<slug>/ - ParkDetailView with slug redirect support
  • Update: /<slug>/edit/ - ParkUpdateView
  • Areas: /<park_slug>/areas/<area_slug>/ - ParkAreaDetailView
  • HTMX Endpoints: Various endpoints for dynamic content loading
  • Park-specific ride categories: Multiple URL patterns for different ride types

Operators App URLs (operators/urls.py)

  • List: / - OperatorListView
  • Detail: /<slug>/ - OperatorDetailView with slug redirect support

Property Owners App URLs (property_owners/urls.py)

  • List: / - PropertyOwnerListView
  • Detail: /<slug>/ - PropertyOwnerDetailView with slug redirect support

Template Usage Patterns

Template Structure

  • parks/park_detail.html: Comprehensive park display with operator/property owner links
  • operators/operator_detail.html: Shows operated parks with park links
  • property_owners/property_owner_detail.html: Shows owned properties with operator info

Key Template Features

  • Cross-linking between parks, operators, and property owners
  • Conditional display of property owner (only if different from operator)
  • Status badges with Tailwind CSS classes
  • Photo galleries and location maps
  • History tracking display

Shared Functionality Patterns

Slug Generation

All models use consistent slug generation:

  • Auto-generated from name field in save() method
  • Uses Django's slugify() function
  • Historical slug tracking via HistoricalSlug model

History Tracking

Implemented via two mechanisms:

  1. pghistory: Automatic tracking with @pghistory.track() decorator
  2. Manual tracking: HistoricalSlug model for slug changes
  3. DiffMixin: Provides diff_against_previous() method for change comparison

Slug Redirect Support

All detail views use SlugRedirectMixin and implement get_by_slug() classmethod:

  • Checks current slug first
  • Falls back to pghistory events
  • Falls back to HistoricalSlug records
  • Returns tuple of (object, was_redirect_needed)

Base Classes

  • TrackedModel: Provides created_at, updated_at, and history integration
  • SlugRedirectMixin: Handles historical slug redirects in views

Key Findings

Strengths

  1. Consistent patterns: All models follow similar slug generation and history tracking patterns
  2. Comprehensive history: Both automatic (pghistory) and manual (HistoricalSlug) tracking
  3. Good separation of concerns: Clear distinction between operators and property owners
  4. Rich relationships: Proper foreign key relationships with appropriate related_names

Issues

  1. Artificial separation: Three apps that are always used together
  2. Duplicated code: Similar admin configurations and view patterns across apps
  3. Complex imports: Cross-app imports create coupling
  4. Template redundancy: Similar template patterns across apps

Entity Relationship Compliance

The current implementation follows the specified entity relationship rules:

  • Parks have required Operator relationship
  • Parks have optional PropertyOwner relationship
  • No direct Company entity references
  • Proper foreign key relationships with null/blank settings

Consolidation Recommendations

Based on the analysis, I recommend consolidating the three apps into a single parks app with the following structure:

parks/
├── models/
│   ├── __init__.py       # Import all models here
│   ├── park.py          # Park, ParkArea models
│   ├── operators.py     # Operator model  
│   └── owners.py        # PropertyOwner model
├── admin/
│   ├── __init__.py      # Register all admin classes
│   ├── park.py          # Park and ParkArea admin
│   ├── operators.py     # Operator admin
│   └── owners.py        # PropertyOwner admin
├── views/
│   ├── __init__.py      # Import all views
│   ├── parks.py         # Park and ParkArea views
│   ├── operators.py     # Operator views
│   └── owners.py        # PropertyOwner views
├── templates/parks/
│   ├── parks/           # Park templates
│   ├── operators/       # Operator templates
│   └── owners/          # Property owner templates
└── urls.py              # All URL patterns

Benefits of Consolidation

  1. Reduced complexity: Single app to manage instead of three
  2. Eliminated duplication: Shared admin mixins, view base classes, and template components
  3. Simplified imports: No cross-app dependencies
  4. Better cohesion: Related functionality grouped together
  5. Easier maintenance: Single location for park domain logic

Migration Strategy

  1. Create new model structure within parks app
  2. Move existing models to new locations
  3. Update all imports and references
  4. Consolidate admin configurations
  5. Merge URL patterns
  6. Update template references
  7. Run Django migrations to reflect changes
  8. Remove empty apps

This consolidation maintains all existing functionality while significantly improving code organization and maintainability.