Files
thrillwiki_django_no_react/docs/THRILLWIKI_PROJECT_DOCUMENTATION.md
2025-08-15 12:24:20 -04:00

1753 lines
55 KiB
Markdown

# 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 <package>
# Django management (NEVER use python manage.py)
uv run manage.py <command>
# 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
<!-- Search with live results -->
<form hx-get="{% url 'parks:search' %}"
hx-target="#search-results"
hx-trigger="input changed delay:300ms">
<input type="text" name="q" placeholder="Search parks and rides...">
</form>
<div id="search-results"
hx-indicator="#loading-spinner">
<!-- Results loaded dynamically -->
</div>
```
#### Modal Interactions
```html
<!-- HTMX-powered modals -->
<div hx-get="{% url 'account_login' %}"
hx-target="body"
hx-swap="beforeend"
class="cursor-pointer menu-item">
<span>Login</span>
</div>
```
#### 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
<div x-data="{ open: false }"
@click.outside="open = false">
<!-- Profile dropdown -->
<img @click="open = !open"
src="{{ user.profile.avatar.url }}"
class="cursor-pointer">
<div x-cloak
x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100">
<!-- Dropdown content -->
</div>
</div>
```
#### Modal Management
```html
<div x-data="{
show: false,
editingPhoto: null,
init() { this.editingPhoto = { caption: '' }; }
}"
@show-photo-upload.window="show = true; init()">
<!-- Modal implementation -->
</div>
```
### 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
<!-- base/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}ThrillWiki{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body class="bg-gradient-to-br from-white via-blue-50 to-indigo-50
dark:from-gray-950 dark:via-indigo-950 dark:to-purple-950">
{% include "base/navigation.html" %}
<main class="container flex-grow px-6 py-8 mx-auto">
{% block content %}{% endblock %}
</main>
{% block extra_js %}{% endblock %}
</body>
</html>
```
#### HTMX Template Patterns
```html
<!-- Responsive template selection -->
{% extends "base/base.html" %}
{% block content %}
<div hx-get="{% url 'parks:park_list' %}"
hx-target="#park-grid"
hx-swap="innerHTML"
hx-trigger="load, view-change from:body">
<div id="park-grid" class="grid-adaptive">
{% include "parks/partials/park_list_item.html" %}
</div>
</div>
{% 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
<!-- Search results with context -->
<div class="search-result-item">
<div class="result-type-badge">Park</div>
<h3 class="result-title">{{ park.name }}</h3>
<p class="result-location">{{ park.formatted_location }}</p>
<div class="result-stats">
<span>{{ park.ride_count }} rides</span>
<span>{{ park.get_status_display }}</span>
</div>
</div>
```
#### 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
<!-- Modal photo upload -->
<div x-data="photoUploadModal()"
@show-photo-upload.window="show = true">
<form hx-post="{% url 'media:upload' %}"
hx-encoding="multipart/form-data">
{% csrf_token %}
<input type="file" multiple accept="image/*">
<input type="text" placeholder="Caption (optional)">
</form>
</div>
```
#### 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
<!-- User profile stats -->
<div class="profile-stats grid-stats">
<div class="stat-card">
<span class="stat-value">{{ profile.coaster_credits }}</span>
<span class="stat-label">Coaster Credits</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ profile.dark_ride_credits }}</span>
<span class="stat-label">Dark Rides</span>
</div>
<!-- Additional stat types... -->
</div>
```
#### 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
<!-- templates/base/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token }}">
<title>{% block title %}ThrillWiki{% endblock %}</title>
<!-- Theme prevention script (prevents flash) -->
<script>
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");
}
</script>
<!-- External dependencies -->
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<script defer src="{% static 'js/alpine.min.js' %}"></script>
<!-- Styling -->
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
{% block extra_head %}{% endblock %}
</head>
<body class="flex flex-col min-h-screen text-gray-900
bg-gradient-to-br from-white via-blue-50 to-indigo-50
dark:from-gray-950 dark:via-indigo-950 dark:to-purple-950
dark:text-white">
{% include "base/header.html" %}
{% include "base/flash_messages.html" %}
<main class="container flex-grow px-6 py-8 mx-auto">
{% block content %}{% endblock %}
</main>
{% include "base/footer.html" %}
<script src="{% static 'js/main.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
```
#### Component-Based Template System
##### Navigation Component (`templates/base/header.html`)
```html
<header class="sticky top-0 z-40 border-b shadow-lg
bg-white/90 dark:bg-gray-800/90 backdrop-blur-lg
border-gray-200/50 dark:border-gray-700/50">
<nav class="container mx-auto nav-container">
<div class="flex items-center justify-between">
<!-- Logo -->
<div class="flex items-center">
<a href="{% url 'home' %}"
class="font-bold text-transparent transition-transform site-logo
bg-gradient-to-r from-primary to-secondary bg-clip-text
hover:scale-105">
ThrillWiki
</a>
</div>
<!-- Main Navigation Links -->
<div class="flex items-center space-x-2 sm:space-x-4">
<a href="{% url 'parks:park_list' %}" class="nav-link">
<i class="fas fa-map-marker-alt"></i>
<span>Parks</span>
</a>
<a href="{% url 'rides:global_ride_list' %}" class="nav-link">
<i class="fas fa-rocket"></i>
<span>Rides</span>
</a>
</div>
<!-- Search Bar (Desktop) -->
<div class="flex-1 hidden max-w-md mx-8 lg:flex">
<form action="{% url 'search:search' %}" method="get" class="w-full">
<div class="relative">
<input type="text" name="q"
placeholder="Search parks and rides..."
class="form-input">
</div>
</form>
</div>
<!-- User Menu and Theme Toggle -->
<div class="flex items-center space-x-2 sm:space-x-6">
{% include "base/theme_toggle.html" %}
{% include "base/user_menu.html" %}
{% include "base/mobile_menu_button.html" %}
</div>
</div>
<!-- Mobile Menu -->
{% include "base/mobile_menu.html" %}
</nav>
</header>
```
##### Theme Toggle Component
```html
<!-- templates/base/theme_toggle.html -->
<label for="theme-toggle" class="cursor-pointer">
<input type="checkbox" id="theme-toggle" class="hidden">
<div class="inline-flex items-center justify-center p-2
text-gray-500 transition-colors hover:text-primary
dark:text-gray-400 dark:hover:text-primary theme-toggle-btn"
role="button" aria-label="Toggle dark mode">
<i class="text-xl fas"></i>
</div>
</label>
```
### Page-Specific Template Patterns
#### Park Detail Page Structure
```html
<!-- templates/parks/park_detail.html -->
{% extends "base/base.html" %}
{% load static park_tags %}
{% block title %}{{ park.name }} - ThrillWiki{% endblock %}
{% block extra_head %}
{% if park.location.exists %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
{% endif %}
{% endblock %}
{% block content %}
<div class="container px-4 mx-auto sm:px-6 lg:px-8">
<!-- Dynamic Action Buttons -->
<div hx-get="{% url 'parks:park_actions' park.slug %}"
hx-trigger="load, auth-changed from:body"
hx-swap="innerHTML">
</div>
<!-- Park Header -->
<div class="p-compact mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
<div class="text-center">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white lg:text-4xl">
{{ park.name }}
</h1>
{% if park.formatted_location %}
<div class="flex items-center justify-center mt-2 text-sm text-gray-600 dark:text-gray-400">
<i class="mr-1 fas fa-map-marker-alt"></i>
<p>{{ park.formatted_location }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Statistics Grid -->
<div class="grid-stats mb-6">
{% include "parks/partials/park_stats.html" %}
</div>
<!-- Photo Gallery -->
{% if park.photos.exists %}
<div class="p-optimized mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
{% include "media/partials/photo_display.html" %}
</div>
{% endif %}
<!-- Main Content Grid -->
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
<!-- Left Column: Description and Rides -->
<div class="lg:col-span-2">
{% include "parks/partials/park_description.html" %}
{% include "parks/partials/park_rides.html" %}
</div>
<!-- Right Column: Map and Additional Info -->
<div class="lg:col-span-1">
{% include "parks/partials/park_map.html" %}
{% include "parks/partials/park_history.html" %}
</div>
</div>
</div>
<!-- Photo Upload Modal -->
{% include "media/partials/photo_upload_modal.html" %}
{% endblock %}
{% block extra_js %}
<script src="{% static 'js/photo-gallery.js' %}"></script>
{% if park.location.exists %}
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="{% static 'js/park-map.js' %}"></script>
{% endif %}
{% endblock %}
```
#### Reusable Partial Templates
##### Park Statistics Component
```html
<!-- templates/parks/partials/park_stats.html -->
<!-- Operator - Priority Card (First Position) -->
{% if park.operator %}
<div class="bg-white rounded-lg shadow-lg dark:bg-gray-800 p-compact card-stats card-stats-priority">
<div class="text-center">
<dt class="text-sm font-semibold text-gray-900 dark:text-white">Operator</dt>
<dd class="mt-1">
<a href="{% url 'operators:operator_detail' park.operator.slug %}"
class="text-sm font-bold text-sky-900 dark:text-sky-400 hover:text-sky-800 dark:hover:text-sky-300">
{{ park.operator.name }}
</a>
</dd>
</div>
</div>
{% endif %}
<!-- Additional stats cards... -->
<a href="{% url 'parks:rides:ride_list' park.slug %}"
class="bg-white rounded-lg shadow-lg dark:bg-gray-800 p-compact card-stats transition-transform hover:scale-[1.02]">
<div class="text-center">
<dt class="text-sm font-semibold text-gray-900 dark:text-white">Total Rides</dt>
<dd class="mt-1 text-2xl font-bold text-sky-900 dark:text-sky-400">
{{ park.ride_count|default:"N/A" }}
</dd>
</div>
</a>
```
##### Photo Display Component
```html
<!-- templates/media/partials/photo_display.html -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
x-data="photoGallery()">
{% for photo in photos %}
<div class="relative group">
<img src="{{ photo.image.url }}"
alt="{{ photo.alt_text|default:photo.caption }}"
class="w-full h-48 object-cover rounded-lg shadow-md
transition-transform group-hover:scale-105 cursor-pointer"
@click="openModal('{{ photo.image.url }}', '{{ photo.caption }}')">
{% if photo.caption %}
<div class="absolute bottom-0 left-0 right-0 p-2
bg-gradient-to-t from-black/70 to-transparent rounded-b-lg">
<p class="text-white text-sm">{{ photo.caption }}</p>
</div>
{% endif %}
</div>
{% endfor %}
</div>
```
### Form Templates and User Input
#### Dynamic Form Rendering
```html
<!-- templates/parks/park_form.html -->
{% extends "base/base.html" %}
{% block content %}
<div class="max-w-4xl mx-auto">
<div class="auth-card">
<h1 class="auth-title">
{% if is_edit %}Edit Park{% else %}Add New Park{% endif %}
</h1>
<form method="post" enctype="multipart/form-data"
class="space-y-6"
hx-post="{% if is_edit %}{% url 'parks:park_edit' object.slug %}{% else %}{% url 'parks:park_create' %}{% endif %}"
hx-target="#form-container"
hx-swap="innerHTML">
{% csrf_token %}
<!-- Dynamic field rendering -->
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
{% if field.field.required %}<span class="text-red-500">*</span>{% endif %}
</label>
{{ field|add_class:"form-input" }}
{% if field.help_text %}
<div class="form-hint">{{ field.help_text }}</div>
{% endif %}
{% if field.errors %}
<div class="form-error">{{ field.errors }}</div>
{% endif %}
</div>
{% endfor %}
<div class="flex justify-end space-x-4">
<a href="{% if is_edit %}{{ object.get_absolute_url }}{% else %}{% url 'parks:park_list' %}{% endif %}"
class="btn-secondary">Cancel</a>
<button type="submit" class="btn-primary">
{% if is_edit %}Update Park{% else %}Add Park{% endif %}
</button>
</div>
</form>
</div>
</div>
{% 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
<!-- Using custom park tags -->
{% load park_tags %}
<!-- Status badge with automatic styling -->
<span class="{% park_status_class park.status %}">
{{ park.get_status_display }}
</span>
<!-- Rating display with stars -->
{% 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 <package> # Add new dependencies
uv add --dev <package> # 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 <repository-url>
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/<str:username>/", 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("<slug:slug>/", ParkDetailView.as_view(), name="park_detail"),
path("<slug:slug>/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("<slug:park_slug>/areas/<slug:area_slug>/", ParkAreaDetailView.as_view(), name="area_detail"),
# Category-specific rides within parks
path("<slug:park_slug>/roller_coasters/", ParkSingleCategoryListView.as_view(),
{'category': 'RC'}, name="park_roller_coasters"),
path("<slug:park_slug>/dark_rides/", ParkSingleCategoryListView.as_view(),
{'category': 'DR'}, name="park_dark_rides"),
path("<slug:park_slug>/flat_rides/", ParkSingleCategoryListView.as_view(),
{'category': 'FR'}, name="park_flat_rides"),
path("<slug:park_slug>/water_rides/", ParkSingleCategoryListView.as_view(),
{'category': 'WR'}, name="park_water_rides"),
path("<slug:park_slug>/transports/", ParkSingleCategoryListView.as_view(),
{'category': 'TR'}, name="park_transports"),
path("<slug:park_slug>/others/", ParkSingleCategoryListView.as_view(),
{'category': 'OT'}, name="park_others"),
# Nested rides URLs
path("<slug:park_slug>/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/<type>/<id>/
- 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.