mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:51:08 -05:00
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.
This commit is contained in:
397
docs/park_domain_analysis.md
Normal file
397
docs/park_domain_analysis.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# 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'`
|
||||
|
||||
### Related Models from Other Apps
|
||||
|
||||
#### 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
|
||||
|
||||
```mermaid
|
||||
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.
|
||||
Reference in New Issue
Block a user