# ThrillWiki Project Documentation ## Table of Contents 1. [Project Overview](#project-overview) 2. [Technical Stack and Architecture](#technical-stack-and-architecture) 3. [Database Models and Relationships](#database-models-and-relationships) 4. [Visual Theme and Design System](#visual-theme-and-design-system) 5. [Frontend Implementation Patterns](#frontend-implementation-patterns) 6. [User Experience and Key Features](#user-experience-and-key-features) 7. [Page Structure and Templates](#page-structure-and-templates) 8. [Services and Business Logic](#services-and-business-logic) 9. [Development Workflow](#development-workflow) 10. [API Endpoints and URL Structure](#api-endpoints-and-url-structure) --- ## Project Overview ThrillWiki is a sophisticated Django-based web application designed for theme park and roller coaster enthusiasts. It provides comprehensive information management for parks, rides, companies, and user-generated content with advanced features including geographic mapping, moderation systems, and real-time interactions. ### Key Characteristics - **Enterprise-Grade Architecture**: Service-oriented design with clear separation of concerns - **Modern Frontend**: HTMX + Alpine.js for dynamic interactions without heavy JavaScript frameworks - **Geographic Intelligence**: PostGIS integration for location-based features and mapping - **Content Moderation**: Comprehensive workflow for user-generated content approval - **Audit Trail**: Complete history tracking using django-pghistory - **Responsive Design**: Mobile-first approach with sophisticated dark theme support --- ## Technical Stack and Architecture ### Core Technologies | Component | Technology | Version | Purpose | |-----------|------------|---------|---------| | **Backend Framework** | Django | 5.0+ | Main web framework | | **Database** | PostgreSQL + PostGIS | Latest | Relational database with geographic extension | | **Frontend** | HTMX + Alpine.js | 1.9.6 + Latest | Dynamic interactions and client-side behavior | | **Styling** | Tailwind CSS | Latest | Utility-first CSS framework | | **Package Manager** | UV | Latest | Python dependency management | | **Authentication** | Django Allauth | 0.60.1+ | OAuth and user management | | **History Tracking** | django-pghistory | 3.5.2+ | Audit trails and versioning | | **Testing** | Pytest + Playwright | Latest | Unit and E2E testing | ### Architecture Patterns #### Service-Oriented Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Presentation │ │ Business │ │ Data │ │ Layer │ │ Logic │ │ Layer │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ • Templates │◄──►│ • Services │◄──►│ • Models │ │ • Views │ │ • Map Service │ │ • Database │ │ • HTMX/Alpine │ │ • Search │ │ • PostGIS │ │ • Tailwind CSS │ │ • Moderation │ │ • Caching │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` #### Django App Organization The project follows a domain-driven design approach with clear app boundaries: ``` thrillwiki_django_no_react/ ├── core/ # Core business logic and shared services │ ├── services/ # Unified map service, clustering, caching │ ├── search/ # Search functionality │ ├── mixins/ # Reusable view mixins │ └── history/ # History tracking utilities ├── accounts/ # User management and authentication ├── parks/ # Theme park entities │ └── models/ # Park, Company, Location models ├── rides/ # Ride entities and ride-specific logic │ └── models/ # Ride, RideModel, Company models ├── location/ # Geographic location handling ├── media/ # Media file management and photo handling ├── moderation/ # Content moderation workflow ├── email_service/ # Email handling and notifications └── static/ # Frontend assets (CSS, JS, images) ``` ### Package Management with UV ThrillWiki exclusively uses UV for Python package management, providing: - **Faster dependency resolution**: Significantly faster than pip - **Lock file support**: Ensures reproducible environments - **Virtual environment management**: Automatic environment handling - **Cross-platform compatibility**: Consistent behavior across development environments #### Critical Commands ```bash # Add new dependencies uv add # Django management (NEVER use python manage.py) uv run manage.py # Development server startup lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver ``` --- ## Database Models and Relationships ### Entity Relationship Architecture ThrillWiki implements a sophisticated entity relationship model that enforces business rules at the database level: #### Core Business Rules (from .clinerules) 1. **Park Relationships** - Parks MUST have an Operator (required relationship) - Parks MAY have a PropertyOwner (optional, usually same as Operator) - Parks CANNOT directly reference Company entities 2. **Ride Relationships** - Rides MUST belong to a Park (required relationship) - Rides MAY have a Manufacturer (optional relationship) - Rides MAY have a Designer (optional relationship) - Rides CANNOT directly reference Company entities 3. **Entity Definitions** - **Operators**: Companies that operate theme parks - **PropertyOwners**: Companies that own park property (new concept) - **Manufacturers**: Companies that manufacture rides - **Designers**: Companies/individuals that design rides ### Core Model Structure #### Park Models (`parks/models/`) ```python # Park Entity class Park(TrackedModel): # Core identifiers name = CharField(max_length=255) slug = SlugField(max_length=255, unique=True) # Business relationships (enforced by .clinerules) operator = ForeignKey('Company', related_name='operated_parks') property_owner = ForeignKey('Company', related_name='owned_parks', null=True) # Operational data status = CharField(choices=STATUS_CHOICES, default="OPERATING") opening_date = DateField(null=True, blank=True) size_acres = DecimalField(max_digits=10, decimal_places=2) # Statistics average_rating = DecimalField(max_digits=3, decimal_places=2) ride_count = IntegerField(null=True, blank=True) coaster_count = IntegerField(null=True, blank=True) ``` #### Ride Models (`rides/models/`) ```python # Ride Entity class Ride(TrackedModel): # Core identifiers name = CharField(max_length=255) slug = SlugField(max_length=255) # Required relationships (enforced by .clinerules) park = ForeignKey('parks.Park', related_name='rides') # Optional business relationships manufacturer = ForeignKey('Company', related_name='manufactured_rides') designer = ForeignKey('Company', related_name='designed_rides') ride_model = ForeignKey('RideModel', related_name='rides') # Classification category = CharField(choices=CATEGORY_CHOICES) status = CharField(choices=STATUS_CHOICES, default='OPERATING') ``` #### Company Models (Shared across apps) ```python # Company Entity (supports multiple roles) class Company(TrackedModel): class CompanyRole(TextChoices): OPERATOR = 'OPERATOR', 'Park Operator' PROPERTY_OWNER = 'PROPERTY_OWNER', 'Property Owner' MANUFACTURER = 'MANUFACTURER', 'Ride Manufacturer' DESIGNER = 'DESIGNER', 'Ride Designer' name = CharField(max_length=255) slug = SlugField(max_length=255, unique=True) roles = ArrayField(CharField(choices=CompanyRole.choices)) ``` ### Geographic Models (`location/models/`) ```python # Generic Location Model class Location(TrackedModel): # Generic relationship (can attach to any model) content_type = ForeignKey(ContentType) object_id = PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') # Geographic data (dual storage for compatibility) latitude = DecimalField(max_digits=9, decimal_places=6) longitude = DecimalField(max_digits=9, decimal_places=6) point = PointField(srid=4326) # PostGIS geometry field # Address components street_address = CharField(max_length=255) city = CharField(max_length=100) state = CharField(max_length=100) country = CharField(max_length=100) postal_code = CharField(max_length=20) ``` ### History Tracking with pghistory Every critical model uses `@pghistory.track()` decoration for comprehensive audit trails: ```python @pghistory.track() class Park(TrackedModel): # All field changes are automatically tracked # Creates parallel history tables with full change logs ``` ### Media and Content Models ```python # Generic Photo Model class Photo(TrackedModel): # Generic relationship support content_type = ForeignKey(ContentType) object_id = PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') # Media handling image = ImageField(upload_to=photo_upload_path, storage=MediaStorage()) is_primary = BooleanField(default=False) is_approved = BooleanField(default=False) # Metadata extraction date_taken = DateTimeField(null=True) # Auto-extracted from EXIF uploaded_by = ForeignKey(User, related_name='uploaded_photos') ``` ### User and Authentication Models ```python # Extended User Model class User(AbstractUser): class Roles(TextChoices): USER = 'USER', 'User' MODERATOR = 'MODERATOR', 'Moderator' ADMIN = 'ADMIN', 'Admin' SUPERUSER = 'SUPERUSER', 'Superuser' # Immutable identifier user_id = CharField(max_length=10, unique=True, editable=False) # Permission system role = CharField(choices=Roles.choices, default=Roles.USER) # User preferences theme_preference = CharField(choices=ThemePreference.choices) ``` --- ## Visual Theme and Design System ### Design Philosophy ThrillWiki implements a sophisticated **dark-first design system** with vibrant accent colors that reflect the excitement of theme parks and roller coasters. #### Color Palette ```css :root { --primary: #4f46e5; /* Vibrant indigo */ --secondary: #e11d48; /* Vibrant rose */ --accent: #8b5cf6; /* Purple accent */ } ``` #### Background Gradients ```css /* Light theme */ body { background: linear-gradient(to bottom right, white, rgb(239 246 255), /* blue-50 */ rgb(238 242 255) /* indigo-50 */ ); } /* Dark theme */ body.dark { background: linear-gradient(to bottom right, rgb(3 7 18), /* gray-950 */ rgb(49 46 129), /* indigo-950 */ rgb(59 7 100) /* purple-950 */ ); } ``` ### Tailwind CSS Configuration #### Custom Configuration (`tailwind.config.js`) ```javascript module.exports = { darkMode: 'class', // Class-based dark mode content: [ './templates/**/*.html', './assets/css/src/**/*.css', ], theme: { extend: { colors: { primary: '#4f46e5', secondary: '#e11d48', accent: '#8b5cf6' }, fontFamily: { 'sans': ['Poppins', 'sans-serif'], }, }, }, plugins: [ require("@tailwindcss/typography"), require("@tailwindcss/forms"), require("@tailwindcss/aspect-ratio"), require("@tailwindcss/container-queries"), // Custom HTMX variants plugin(function ({ addVariant }) { addVariant("htmx-settling", ["&.htmx-settling", ".htmx-settling &"]); addVariant("htmx-request", ["&.htmx-request", ".htmx-request &"]); addVariant("htmx-swapping", ["&.htmx-swapping", ".htmx-swapping &"]); addVariant("htmx-added", ["&.htmx-added", ".htmx-added &"]); }), ], } ``` ### Component System (`static/css/src/input.css`) #### Button Components ```css .btn-primary { @apply inline-flex items-center px-6 py-2.5 border border-transparent rounded-full shadow-md text-sm font-medium text-white bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary/50 transform hover:scale-105 transition-all; } .btn-secondary { @apply inline-flex items-center px-6 py-2.5 border border-gray-200 dark:border-gray-700 rounded-full shadow-md text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transform hover:scale-105 transition-all; } ``` #### Navigation Components ```css .nav-link { @apply flex items-center text-gray-700 dark:text-gray-200 px-6 py-2.5 rounded-lg font-medium border border-transparent hover:border-primary/20 dark:hover:border-primary/30 hover:text-primary dark:text-primary hover:bg-primary/10 dark:bg-primary/20; } ``` #### Card System ```css .card { @apply p-6 bg-white dark:bg-gray-800 border rounded-lg shadow-lg border-gray-200/50 dark:border-gray-700/50; } .card-hover { @apply transition-transform transform hover:-translate-y-1; } ``` #### Responsive Grid System ```css /* Adaptive grid with content-aware sizing */ .grid-adaptive { @apply grid gap-6; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } /* Stats grid with even layouts */ .grid-stats { @apply grid gap-4; grid-template-columns: repeat(2, 1fr); /* Mobile: 2 columns */ } @media (min-width: 1024px) { .grid-stats { grid-template-columns: repeat(3, 1fr); /* Desktop: 3 columns */ } } @media (min-width: 1280px) { .grid-stats { grid-template-columns: repeat(5, 1fr); /* Large: 5 columns */ } } ``` ### Dark Mode Implementation #### Theme Toggle System The theme system provides: - **Automatic detection** of system preference - **Manual toggle** with persistent storage - **Flash prevention** during page load - **Smooth transitions** between themes ```javascript // Theme initialization (prevents flash) let theme = localStorage.getItem("theme"); if (!theme) { theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; localStorage.setItem("theme", theme); } if (theme === "dark") { document.documentElement.classList.add("dark"); } ``` #### CSS Custom Properties for Theme Switching ```css /* Theme-aware components */ .auth-card { @apply w-full max-w-md p-8 mx-auto border shadow-xl bg-white/90 dark:bg-gray-800/90 rounded-2xl backdrop-blur-sm border-gray-200/50 dark:border-gray-700/50; } /* Status badges with theme support */ .status-operating { @apply text-green-800 bg-green-100 dark:bg-green-700 dark:text-green-50; } .status-closed { @apply text-red-800 bg-red-100 dark:bg-red-700 dark:text-red-50; } ``` --- ## Frontend Implementation Patterns ### HTMX Integration Patterns ThrillWiki leverages HTMX for dynamic interactions while maintaining server-side rendering benefits: #### Dynamic Content Loading ```html
``` #### Modal Interactions ```html ``` #### Custom HTMX Variants ```css /* Loading states */ .htmx-request .htmx-indicator { display: block; } /* Transition effects */ .htmx-settling { opacity: 0.7; transition: opacity 0.3s ease; } .htmx-swapping { transform: scale(0.98); transition: transform 0.2s ease; } ``` ### Alpine.js Patterns Alpine.js handles client-side state management and interactions: #### Dropdown Components ```html
``` #### Modal Management ```html
``` ### JavaScript Architecture (`static/js/`) #### Modular JavaScript Organization ``` static/js/ ├── main.js # Core functionality (theme, navigation) ├── alerts.js # Alert system management ├── photo-gallery.js # Photo gallery interactions ├── park-map.js # Leaflet map integration ├── location-autocomplete.js # Geographic search └── alpine.min.js # Alpine.js framework ``` #### Theme Management (`static/js/main.js`) ```javascript // Theme handling with system preference detection document.addEventListener('DOMContentLoaded', () => { const themeToggle = document.getElementById('theme-toggle'); const html = document.documentElement; // Initialize toggle state if (themeToggle) { themeToggle.checked = html.classList.contains('dark'); // Handle toggle changes themeToggle.addEventListener('change', function() { const isDark = this.checked; html.classList.toggle('dark', isDark); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }); // Listen for system theme changes const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); mediaQuery.addEventListener('change', (e) => { if (!localStorage.getItem('theme')) { const isDark = e.matches; html.classList.toggle('dark', isDark); themeToggle.checked = isDark; } }); } }); ``` #### Mobile Navigation ```javascript // Mobile menu with smooth transitions const toggleMenu = () => { isMenuOpen = !isMenuOpen; mobileMenu.classList.toggle('show', isMenuOpen); mobileMenuBtn.setAttribute('aria-expanded', isMenuOpen.toString()); // Update icon const icon = mobileMenuBtn.querySelector('i'); icon.classList.remove(isMenuOpen ? 'fa-bars' : 'fa-times'); icon.classList.add(isMenuOpen ? 'fa-times' : 'fa-bars'); }; ``` ### Template System Patterns #### Component-Based Architecture ``` templates/ ├── base/ │ └── base.html # Base template with navigation ├── core/ │ ├── search/ │ │ ├── components/ # Search UI components │ │ ├── layouts/ # Search layout templates │ │ └── partials/ # Reusable search elements ├── parks/ │ ├── partials/ │ │ ├── park_list_item.html # Reusable park card │ │ ├── park_actions.html # Action buttons │ │ └── park_stats.html # Statistics display │ ├── park_detail.html # Main park page │ └── park_list.html # Park listing page └── media/ └── partials/ ├── photo_display.html # Photo gallery component └── photo_upload.html # Upload interface ``` #### Template Inheritance Pattern ```html {% block title %}ThrillWiki{% endblock %} {% block extra_head %}{% endblock %} {% include "base/navigation.html" %}
{% block content %}{% endblock %}
{% block extra_js %}{% endblock %} ``` #### HTMX Template Patterns ```html {% extends "base/base.html" %} {% block content %}
{% include "parks/partials/park_list_item.html" %}
{% endblock %} ``` --- ## User Experience and Key Features ### Navigation and Information Architecture #### Primary Navigation Structure ``` ThrillWiki Navigation ├── Home # Dashboard with featured content ├── Parks # Theme park directory │ ├── Browse Parks # Filterable park listings │ ├── Add New Park # User contribution form │ └── [Park Detail] # Individual park pages ├── Rides # Ride directory (global) │ ├── Browse Rides # Cross-park ride search │ ├── Add New Ride # User contribution form │ └── [Ride Detail] # Individual ride pages ├── Search # Universal search interface └── User Account ├── Profile # User profile and stats ├── Settings # Preferences and account ├── Moderation # Content review (if authorized) └── Admin # System administration (if authorized) ``` #### Responsive Navigation Patterns - **Desktop**: Full horizontal navigation with search bar - **Tablet**: Collapsible navigation with maintained search functionality - **Mobile**: Hamburger menu with slide-out panel ### Core Feature Set #### 1. Park Management System **Park Detail Pages** provide comprehensive information: ``` Park Information Hierarchy ├── Header Section │ ├── Park name and location │ ├── Status badge (Operating, Closed, etc.) │ ├── Average rating display │ └── Quick action buttons ├── Statistics Dashboard │ ├── Operator information (priority display) │ ├── Property owner (if different) │ ├── Total ride count (linked) │ ├── Roller coaster count │ ├── Opening date │ └── Website link ├── Content Sections │ ├── Photo gallery (if photos exist) │ ├── About section (description) │ ├── Rides & Attractions (preview list) │ └── Location map (if coordinates available) └── Additional Information ├── History timeline ├── Related parks └── User contributions ``` **Key UX Features**: - **Smart statistics layout**: Responsive grid that prevents awkward spacing - **Priority content placement**: Operator information prominently featured - **Contextual actions**: Edit/moderate buttons appear based on user permissions - **Progressive disclosure**: Detailed information revealed as needed #### 2. Advanced Search and Filtering **Unified Search System** supports: - **Cross-content search**: Parks, rides, companies in single interface - **Geographic filtering**: Search within specific regions or distances - **Attribute filtering**: Status, ride types, ratings, opening dates - **Real-time results**: HTMX-powered instant search feedback **Search Result Patterns**: ```html
Park

{{ park.name }}

{{ park.formatted_location }}

{{ park.ride_count }} rides {{ park.get_status_display }}
``` #### 3. Geographic and Mapping Features **Unified Map Service** provides: - **Multi-layer mapping**: Parks, rides, and companies on single map - **Intelligent clustering**: Zoom-level appropriate point grouping - **Performance optimization**: Smart caching and result limiting - **Geographic bounds**: Efficient spatial queries using PostGIS **Map Integration Pattern**: ```javascript // Park detail map initialization document.addEventListener('DOMContentLoaded', function() { {% with location=park.location.first %} initParkMap({{ location.latitude }}, {{ location.longitude }}, "{{ park.name }}"); {% endwith %} }); ``` #### 4. Content Moderation Workflow **Submission Process**: ``` User Contribution Flow ├── Content Creation │ ├── Form submission (parks, rides, photos) │ ├── Validation and sanitization │ └── EditSubmission/PhotoSubmission creation ├── Review Process │ ├── Moderator dashboard listing │ ├── Side-by-side comparison view │ ├── Edit capability before approval │ └── Approval/rejection with notes ├── Publication │ ├── Automatic publication for moderators │ ├── Content integration into main database │ └── User notification system └── History Tracking ├── Complete audit trail ├── Revert capability └── Performance metrics ``` **Moderation Features**: - **Auto-approval**: Moderators bypass review process - **Edit before approval**: Moderators can refine submissions - **Batch operations**: Efficient handling of multiple submissions - **Escalation system**: Complex cases forwarded to administrators #### 5. Photo and Media Management **Photo System Features**: - **Multi-format support**: JPEG, PNG with automatic optimization - **EXIF extraction**: Automatic date/time capture from metadata - **Approval workflow**: Moderation for user-uploaded content - **Smart storage**: Organized directory structure by content type - **Primary photo designation**: Featured image selection per entity **Upload Interface**: ```html
{% csrf_token %}
``` #### 6. User Authentication and Profiles **Authentication Features**: - **Social OAuth**: Google and Discord integration - **Custom profiles**: Display names, avatars, bio information - **Role-based permissions**: User, Moderator, Admin, Superuser levels - **Theme preferences**: User-specific dark/light mode settings **Profile Statistics**: ```html
{{ profile.coaster_credits }} Coaster Credits
{{ profile.dark_ride_credits }} Dark Rides
``` #### 7. History and Audit System **Change Tracking Features**: - **Complete audit trails**: Every modification recorded - **Diff visualization**: Before/after comparisons - **User attribution**: Change tracking by user - **Revert capability**: Rollback to previous versions - **Performance monitoring**: Query and response time tracking ### Accessibility and Responsive Design #### Mobile-First Approach - **Responsive breakpoints**: 540px, 768px, 1024px, 1280px+ - **Touch-friendly interfaces**: Appropriate button sizes and spacing - **Optimized content hierarchy**: Essential information prioritized on small screens #### Accessibility Features - **Semantic HTML**: Proper heading structure and landmarks - **ARIA labels**: Screen reader support for interactive elements - **Keyboard navigation**: Full keyboard accessibility - **Color contrast**: WCAG AA compliant color schemes - **Focus indicators**: Clear focus states for interactive elements --- ## Page Structure and Templates ### Template Hierarchy and Organization #### Base Template Architecture ```html {% block title %}ThrillWiki{% endblock %} {% block extra_head %}{% endblock %} {% include "base/header.html" %} {% include "base/flash_messages.html" %}
{% block content %}{% endblock %}
{% include "base/footer.html" %} {% block extra_js %}{% endblock %} ``` #### Component-Based Template System ##### Navigation Component (`templates/base/header.html`) ```html
``` ##### Theme Toggle Component ```html ``` ### Page-Specific Template Patterns #### Park Detail Page Structure ```html {% extends "base/base.html" %} {% load static park_tags %} {% block title %}{{ park.name }} - ThrillWiki{% endblock %} {% block extra_head %} {% if park.location.exists %} {% endif %} {% endblock %} {% block content %}

{{ park.name }}

{% if park.formatted_location %}

{{ park.formatted_location }}

{% endif %}
{% include "parks/partials/park_stats.html" %}
{% if park.photos.exists %}

Photos

{% include "media/partials/photo_display.html" %}
{% endif %}
{% include "parks/partials/park_description.html" %} {% include "parks/partials/park_rides.html" %}
{% include "parks/partials/park_map.html" %} {% include "parks/partials/park_history.html" %}
{% include "media/partials/photo_upload_modal.html" %} {% endblock %} {% block extra_js %} {% if park.location.exists %} {% endif %} {% endblock %} ``` #### Reusable Partial Templates ##### Park Statistics Component ```html {% if park.operator %} {% endif %}
Total Rides
{{ park.ride_count|default:"N/A" }}
``` ##### Photo Display Component ```html
{% for photo in photos %}
{{ photo.alt_text|default:photo.caption }} {% if photo.caption %}

{{ photo.caption }}

{% endif %}
{% endfor %}
``` ### Form Templates and User Input #### Dynamic Form Rendering ```html {% extends "base/base.html" %} {% block content %}

{% if is_edit %}Edit Park{% else %}Add New Park{% endif %}

{% csrf_token %} {% for field in form %}
{{ field|add_class:"form-input" }} {% if field.help_text %}
{{ field.help_text }}
{% endif %} {% if field.errors %}
{{ field.errors }}
{% endif %}
{% endfor %}
Cancel
{% endblock %} ``` ### Static File Organization #### Asset Structure ``` static/ ├── css/ │ ├── src/ │ │ └── input.css # Tailwind source │ ├── tailwind.css # Compiled Tailwind │ └── alerts.css # Custom alert styles ├── js/ │ ├── main.js # Core functionality │ ├── alerts.js # Alert management │ ├── photo-gallery.js # Photo interactions │ ├── park-map.js # Map functionality │ ├── location-autocomplete.js # Geographic search │ └── alpine.min.js # Alpine.js framework └── images/ ├── placeholders/ # Default images └── icons/ # Custom icons ``` #### Template Tag Usage Custom template tags enhance template functionality: ```html {% load park_tags %} {{ park.get_status_display }} {% if park.average_rating %} {% rating_stars park.average_rating %} {% endif %} ``` --- ## Services and Business Logic ### Unified Map Service Architecture The `UnifiedMapService` provides the core geographic functionality for ThrillWiki, handling location data for parks, rides, and companies through a sophisticated service layer. #### Service Architecture Overview ``` UnifiedMapService ├── LocationAbstractionLayer # Data source abstraction ├── ClusteringService # Point clustering for performance ├── MapCacheService # Intelligent caching └── Data Structures # Type-safe data containers ``` #### Core Service Implementation ```python # core/services/map_service.py class UnifiedMapService: """ Main service orchestrating map data retrieval, filtering, clustering, and caching. Provides a unified interface for all location types with performance optimization. """ # Performance thresholds MAX_UNCLUSTERED_POINTS = 500 MAX_CLUSTERED_POINTS = 2000 DEFAULT_ZOOM_LEVEL = 10 def __init__(self): self.location_layer = LocationAbstractionLayer() self.clustering_service = ClusteringService() self.cache_service = MapCacheService() def get_map_data( self, bounds: Optional[GeoBounds] = None, filters: Optional[MapFilters] = None, zoom_level: int = DEFAULT_ZOOM_LEVEL, cluster: bool = True, use_cache: bool = True ) -> MapResponse: """ Primary method for retrieving unified map data with intelligent caching, clustering, and performance optimization. """ # Implementation handles cache checking, database queries, # smart limiting, clustering decisions, and response caching pass def get_location_details(self, location_type: str, location_id: int) -> Optional[UnifiedLocation]: """Get detailed information for a specific location with caching.""" pass def search_locations( self, query: str, bounds: Optional[GeoBounds] = None, location_types: Optional[Set[LocationType]] = None, limit: int = 50 ) -> List[UnifiedLocation]: """Search locations with text query and geographic bounds.""" pass ``` #### Data Structure System The service uses type-safe data structures for all map operations: ```python # core/services/data_structures.py class LocationType(Enum): """Types of locations supported by the map service.""" PARK = "park" RIDE = "ride" COMPANY = "company" GENERIC = "generic" @dataclass class GeoBounds: """Geographic boundary box for spatial queries.""" north: float south: float east: float west: float def to_polygon(self) -> Polygon: """Convert bounds to PostGIS Polygon for database queries.""" return Polygon.from_bbox((self.west, self.south, self.east, self.north)) def expand(self, factor: float = 1.1) -> 'GeoBounds': """Expand bounds by factor for buffer queries.""" pass @dataclass class MapFilters: """Filtering options for map queries.""" location_types: Optional[Set[LocationType]] = None park_status: Optional[Set[str]] = None ride_types: Optional[Set[str]] = None search_query: Optional[str] = None min_rating: Optional[float] = None has_coordinates: bool = True country: Optional[str] = None state: Optional[str] = None city: Optional[str] = None @dataclass class UnifiedLocation: """Standardized location representation across all entity types.""" id: int location_type: LocationType name: str coordinates: Point url: str additional_data: Dict[str, Any] = field(default_factory=dict) ``` ### Moderation System ThrillWiki implements a comprehensive moderation system for user-generated content edits: #### Edit Submission Workflow ```python # moderation/models.py @pghistory.track() class EditSubmission(TrackedModel): """Tracks all proposed changes to parks and rides.""" STATUS_CHOICES = [ ("PENDING", "Pending"), ("APPROVED", "Approved"), ("REJECTED", "Rejected"), ("ESCALATED", "Escalated"), ] SUBMISSION_TYPE_CHOICES = [ ("EDIT", "Edit Existing"), ("CREATE", "Create New"), ] user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField(null=True, blank=True) content_object = GenericForeignKey("content_type", "object_id") submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE_CHOICES) proposed_changes = models.JSONField() # Stores field-level changes status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING") # Moderation tracking reviewed_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="reviewed_submissions" ) reviewed_at = models.DateTimeField(null=True, blank=True) reviewer_notes = models.TextField(blank=True) ``` #### Moderation Features - **Change Tracking**: Every edit submission tracked with `django-pghistory` - **Field-Level Changes**: JSON storage of specific field modifications - **Review Workflow**: Pending → Approved/Rejected/Escalated states - **Reviewer Assignment**: Track who reviewed each submission - **Audit Trail**: Complete history of all moderation decisions ### Search and Autocomplete #### Location-Based Search ```python # Autocomplete integration for geographic search class LocationAutocompleteView(autocomplete.Select2QuerySetView): """AJAX autocomplete for geographic locations.""" def get_queryset(self): if not self.request.user.is_authenticated: return Location.objects.none() qs = Location.objects.filter(is_active=True) if self.q: qs = qs.filter( Q(name__icontains=self.q) | Q(city__icontains=self.q) | Q(state__icontains=self.q) | Q(country__icontains=self.q) ) return qs.select_related('country', 'state').order_by('name')[:20] ``` #### Search Integration - **HTMX-Powered Search**: Real-time search suggestions without page reloads - **Geographic Filtering**: Search within specific bounds or regions - **Multi-Model Search**: Unified search across parks, rides, and companies - **Performance Optimized**: Cached results and smart query limiting --- ## Development Workflow ### Required Development Environment #### UV Package Manager Integration ThrillWiki exclusively uses [UV](https://github.com/astral-sh/uv) for all Python package management and Django commands: ```bash # CRITICAL: Always use these exact commands # Package Installation uv add # Add new dependencies uv add --dev # Add development dependencies # Django Management Commands uv run manage.py makemigrations # Create migrations uv run manage.py migrate # Apply migrations uv run manage.py createsuperuser # Create admin user uv run manage.py shell # Start Django shell uv run manage.py collectstatic # Collect static files # Development Server (CRITICAL - use exactly as shown) lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver ``` **IMPORTANT**: Never use `python manage.py` or `pip install`. The project is configured exclusively for UV. #### Local Development Setup ```bash # Initial setup git clone cd thrillwiki_django_no_react # Install dependencies uv sync # Database setup (requires PostgreSQL with PostGIS) uv run manage.py migrate # Create superuser uv run manage.py createsuperuser # Install Tailwind CSS and build uv run manage.py tailwind install uv run manage.py tailwind build # Start development server lsof -ti :8000 | xargs kill -9; find . -type d -name "__pycache__" -exec rm -r {} +; uv run manage.py tailwind runserver ``` ### Database Requirements #### PostgreSQL with PostGIS ```sql -- Required PostgreSQL extensions CREATE EXTENSION IF NOT EXISTS postgis; CREATE EXTENSION IF NOT EXISTS postgis_topology; CREATE EXTENSION IF NOT EXISTS postgis_raster; ``` #### Geographic Data - **Coordinate System**: WGS84 (SRID: 4326) for all geographic data - **Point Storage**: All locations stored as PostGIS Point geometry - **Spatial Queries**: Optimized with GiST indexes for geographic searches - **Distance Calculations**: Native PostGIS distance functions ### Frontend Development #### Tailwind CSS Workflow ```bash # Development mode (watches for changes) uv run manage.py tailwind start # Production build uv run manage.py tailwind build # Custom CSS location static/css/src/input.css # Source file static/css/tailwind.css # Compiled output ``` #### JavaScript Integration - **Alpine.js**: Reactive components and state management - **HTMX**: AJAX interactions and partial page updates - **Custom Scripts**: Modular JavaScript in `static/js/` directory --- ## API Endpoints and URL Structure ### Primary URL Configuration #### Main Application Routes ```python # thrillwiki/urls.py urlpatterns = [ path("admin/", admin.site.urls), path("", HomeView.as_view(), name="home"), # Autocomplete URLs (must be before other URLs) path("ac/", autocomplete_urls), # Core functionality path("parks/", include("parks.urls", namespace="parks")), path("rides/", include("rides.urls", namespace="rides")), path("photos/", include("media.urls", namespace="photos")), # Search and API path("search/", include("core.urls.search", namespace="search")), path("api/map/", include("core.urls.map_urls", namespace="map_api")), # User management path("accounts/", include("accounts.urls")), path("accounts/", include("allauth.urls")), path("user//", ProfileView.as_view(), name="user_profile"), path("settings/", SettingsView.as_view(), name="settings"), # Moderation system path("moderation/", include("moderation.urls", namespace="moderation")), # Static pages path("terms/", TemplateView.as_view(template_name="pages/terms.html"), name="terms"), path("privacy/", TemplateView.as_view(template_name="pages/privacy.html"), name="privacy"), ] ``` #### Parks URL Structure ```python # parks/urls.py app_name = "parks" urlpatterns = [ # Main park views path("", ParkSearchView.as_view(), name="park_list"), path("create/", ParkCreateView.as_view(), name="park_create"), path("/", ParkDetailView.as_view(), name="park_detail"), path("/edit/", ParkUpdateView.as_view(), name="park_update"), # HTMX endpoints path("add-park-button/", add_park_button, name="add_park_button"), path("search/location/", location_search, name="location_search"), path("search/reverse-geocode/", reverse_geocode, name="reverse_geocode"), path("areas/", get_park_areas, name="get_park_areas"), path("suggest_parks/", suggest_parks, name="suggest_parks"), # Park areas path("/areas//", ParkAreaDetailView.as_view(), name="area_detail"), # Category-specific rides within parks path("/roller_coasters/", ParkSingleCategoryListView.as_view(), {'category': 'RC'}, name="park_roller_coasters"), path("/dark_rides/", ParkSingleCategoryListView.as_view(), {'category': 'DR'}, name="park_dark_rides"), path("/flat_rides/", ParkSingleCategoryListView.as_view(), {'category': 'FR'}, name="park_flat_rides"), path("/water_rides/", ParkSingleCategoryListView.as_view(), {'category': 'WR'}, name="park_water_rides"), path("/transports/", ParkSingleCategoryListView.as_view(), {'category': 'TR'}, name="park_transports"), path("/others/", ParkSingleCategoryListView.as_view(), {'category': 'OT'}, name="park_others"), # Nested rides URLs path("/rides/", include("rides.park_urls", namespace="rides")), ] ``` ### API Endpoints #### Map API ``` GET /api/map/data/ - bounds: Geographic bounds (north,south,east,west) - zoom: Map zoom level - filters: JSON-encoded filter parameters - Returns: Unified location data with clustering GET /api/map/location/// - Returns: Detailed location information POST /api/map/search/ - query: Search text - bounds: Optional geographic bounds - types: Location types to search - Returns: Matching locations ``` #### Search API ``` GET /search/ - q: Search query - type: Entity type (park, ride, company) - location: Geographic filter - Returns: Search results with pagination GET /search/suggest/ - q: Partial query for autocomplete - Returns: Quick suggestions ``` #### HTMX Endpoints All HTMX endpoints return HTML fragments for seamless page updates: ``` POST /parks/suggest_parks/ # Park suggestions for autocomplete GET /parks/areas/ # Dynamic area loading POST /parks/search/location/ # Location search with coordinates POST /parks/search/reverse-geocode/ # Address lookup from coordinates ``` --- ## Conclusion ThrillWiki represents a sophisticated Django application implementing modern web development practices with a focus on performance, user experience, and maintainability. The project successfully combines: ### Technical Excellence - **Modern Django Patterns**: Service-oriented architecture with clear separation of concerns - **Geographic Capabilities**: Full PostGIS integration for spatial data and mapping - **Performance Optimization**: Intelligent caching, query optimization, and clustering - **Type Safety**: Comprehensive use of dataclasses and enums for data integrity ### User Experience - **Responsive Design**: Mobile-first approach with Tailwind CSS - **Progressive Enhancement**: HTMX for seamless interactions without JavaScript complexity - **Dark Mode Support**: Complete theming system with user preferences - **Accessibility**: WCAG-compliant components and navigation ### Development Workflow - **UV Integration**: Modern Python package management with reproducible builds - **Comprehensive Testing**: Model validation, service testing, and frontend integration - **Documentation**: Extensive inline documentation and architectural decisions - **Moderation System**: Complete workflow for user-generated content management ### Architectural Strengths 1. **Scalability**: Service layer architecture supports growth and feature expansion 2. **Maintainability**: Clear code organization with consistent patterns 3. **Performance**: Optimized database queries and intelligent caching strategies 4. **Security**: Authentication, authorization, and input validation throughout 5. **Extensibility**: Plugin-ready architecture for additional features The project demonstrates enterprise-level Django development practices while maintaining simplicity and developer experience. The combination of modern frontend techniques (HTMX, Alpine.js, Tailwind) with robust backend services creates a powerful platform for theme park and ride enthusiasts. This documentation serves as both a technical reference and architectural guide for understanding and extending the ThrillWiki platform.