mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:51:09 -05:00
Add management command to seed comprehensive sample data for ThrillWiki application
- Implemented cleanup of existing sample data to avoid conflicts. - Created functions to generate companies, parks, rides, park areas, and reviews. - Ensured proper relationships between models during data creation. - Added logging for better tracking of data seeding process. - Included checks for required database tables before seeding.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -387,4 +387,6 @@ Temporary Items
|
|||||||
***REMOVED***.webhook
|
***REMOVED***.webhook
|
||||||
.github-token
|
.github-token
|
||||||
logs/
|
logs/
|
||||||
profiles
|
profiles
|
||||||
|
.thrillwiki-github-token
|
||||||
|
.thrillwiki-template-config
|
||||||
317
parks/management/commands/create_sample_data.py
Normal file
317
parks/management/commands/create_sample_data.py
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db import transaction
|
||||||
|
from datetime import date, timedelta
|
||||||
|
import random
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# Import models from both apps
|
||||||
|
from parks.models import Company as ParkCompany, Park, ParkArea, ParkReview
|
||||||
|
from parks.models.location import ParkLocation
|
||||||
|
from rides.models import Company as RideCompany, Ride, RideModel, RideReview, RollerCoasterStats
|
||||||
|
from accounts.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Creates comprehensive sample data for the ThrillWiki theme park application'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.created_companies = {}
|
||||||
|
self.created_parks = {}
|
||||||
|
self.created_rides = {}
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.stdout.write('Starting sample data creation...')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
self.create_companies()
|
||||||
|
self.create_parks()
|
||||||
|
self.create_ride_models()
|
||||||
|
self.create_rides()
|
||||||
|
self.create_park_areas()
|
||||||
|
self.create_reviews()
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('Successfully created comprehensive sample data!'))
|
||||||
|
self.print_summary()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f'Error creating sample data: {e}'))
|
||||||
|
raise
|
||||||
|
|
||||||
|
def create_companies(self):
|
||||||
|
"""Create companies with different roles following entity relationship rules"""
|
||||||
|
self.stdout.write('Creating companies...')
|
||||||
|
|
||||||
|
# Park operators and property owners (using parks.models.Company)
|
||||||
|
park_operators_data = [
|
||||||
|
{
|
||||||
|
'name': 'The Walt Disney Company',
|
||||||
|
'slug': 'walt-disney-company',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'World\'s largest entertainment company and theme park operator.',
|
||||||
|
'website': 'https://www.disney.com/',
|
||||||
|
'founded_year': 1923,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Universal Parks & Resorts',
|
||||||
|
'slug': 'universal-parks-resorts',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'Division of Comcast NBCUniversal, operating major theme parks worldwide.',
|
||||||
|
'website': 'https://www.universalparks.com/',
|
||||||
|
'founded_year': 1964,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Six Flags Entertainment Corporation',
|
||||||
|
'slug': 'six-flags-entertainment',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'World\'s largest regional theme park company.',
|
||||||
|
'website': 'https://www.sixflags.com/',
|
||||||
|
'founded_year': 1961,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cedar Fair Entertainment Company',
|
||||||
|
'slug': 'cedar-fair-entertainment',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'One of North America\'s largest operators of regional amusement parks.',
|
||||||
|
'website': 'https://www.cedarfair.com/',
|
||||||
|
'founded_year': 1983,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Herschend Family Entertainment',
|
||||||
|
'slug': 'herschend-family-entertainment',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'Largest family-owned themed attractions corporation in the United States.',
|
||||||
|
'website': 'https://www.hfecorp.com/',
|
||||||
|
'founded_year': 1950,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'SeaWorld Parks & Entertainment',
|
||||||
|
'slug': 'seaworld-parks-entertainment',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'Theme park and entertainment company focusing on nature-based themes.',
|
||||||
|
'website': 'https://www.seaworldentertainment.com/',
|
||||||
|
'founded_year': 1959,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Merlin Entertainments',
|
||||||
|
'slug': 'merlin-entertainments',
|
||||||
|
'roles': ['OPERATOR', 'PROPERTY_OWNER'],
|
||||||
|
'description': 'European theme park operator with LEGOLAND and Madame Tussauds brands.',
|
||||||
|
'website': 'https://www.merlinentertainments.com/',
|
||||||
|
'founded_year': 1998,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for company_data in park_operators_data:
|
||||||
|
company, created = ParkCompany.objects.get_or_create(
|
||||||
|
slug=company_data['slug'],
|
||||||
|
defaults=company_data
|
||||||
|
)
|
||||||
|
self.created_companies[company.slug] = company
|
||||||
|
self.stdout.write(f' {"Created" if created else "Found"} park company: {company.name}')
|
||||||
|
|
||||||
|
# Ride manufacturers and designers (using rides.models.Company)
|
||||||
|
ride_companies_data = [
|
||||||
|
{
|
||||||
|
'name': 'Bolliger & Mabillard',
|
||||||
|
'slug': 'bolliger-mabillard',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'Swiss roller coaster manufacturer known for inverted and diving coasters.',
|
||||||
|
'website': 'https://www.bolliger-mabillard.com/',
|
||||||
|
'founded_date': '1988-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Intamin Amusement Rides',
|
||||||
|
'slug': 'intamin-amusement-rides',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'Liechtenstein-based manufacturer of roller coasters and thrill rides.',
|
||||||
|
'website': 'https://www.intamin.com/',
|
||||||
|
'founded_date': '1967-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Arrow Dynamics',
|
||||||
|
'slug': 'arrow-dynamics',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'American manufacturer known for corkscrew coasters and mine trains.',
|
||||||
|
'website': 'https://en.wikipedia.org/wiki/Arrow_Dynamics',
|
||||||
|
'founded_date': '1946-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Vekoma Rides Manufacturing',
|
||||||
|
'slug': 'vekoma-rides-manufacturing',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'Dutch manufacturer of roller coasters and family rides.',
|
||||||
|
'website': 'https://www.vekoma.com/',
|
||||||
|
'founded_date': '1926-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Rocky Mountain Construction',
|
||||||
|
'slug': 'rocky-mountain-construction',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'American manufacturer specializing in I-Box track and Raptor track coasters.',
|
||||||
|
'website': 'https://www.rockymtnconstruction.com/',
|
||||||
|
'founded_date': '2001-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Mack Rides',
|
||||||
|
'slug': 'mack-rides',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'German manufacturer known for water rides and powered coasters.',
|
||||||
|
'website': 'https://www.mack-rides.com/',
|
||||||
|
'founded_date': '1780-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Chance Rides',
|
||||||
|
'slug': 'chance-rides',
|
||||||
|
'roles': ['MANUFACTURER'],
|
||||||
|
'description': 'American manufacturer of thrill rides and amusement park equipment.',
|
||||||
|
'website': 'https://www.chancerides.com/',
|
||||||
|
'founded_date': '1961-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'S&S Worldwide',
|
||||||
|
'slug': 's-s-worldwide',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'American manufacturer known for drop towers and 4D free-fly coasters.',
|
||||||
|
'website': 'https://www.s-s.com/',
|
||||||
|
'founded_date': '1990-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Zierer Rides',
|
||||||
|
'slug': 'zierer-rides',
|
||||||
|
'roles': ['MANUFACTURER'],
|
||||||
|
'description': 'German manufacturer of kiddie rides and family coasters.',
|
||||||
|
'website': 'https://www.zierer.com/',
|
||||||
|
'founded_date': '1950-01-01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Gerstlauer',
|
||||||
|
'slug': 'gerstlauer',
|
||||||
|
'roles': ['MANUFACTURER', 'DESIGNER'],
|
||||||
|
'description': 'German manufacturer known for Euro-Fighter and spinning coasters.',
|
||||||
|
'website': 'https://www.gerstlauer-rides.de/',
|
||||||
|
'founded_date': '1982-01-01',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for company_data in ride_companies_data:
|
||||||
|
company, created = RideCompany.objects.get_or_create(
|
||||||
|
slug=company_data['slug'],
|
||||||
|
defaults=company_data
|
||||||
|
)
|
||||||
|
self.created_companies[company.slug] = company
|
||||||
|
self.stdout.write(f' {"Created" if created else "Found"} ride company: {company.name}')
|
||||||
|
|
||||||
|
def create_parks(self):
|
||||||
|
"""Create parks with proper operator relationships"""
|
||||||
|
self.stdout.write('Creating parks...')
|
||||||
|
|
||||||
|
parks_data = [
|
||||||
|
{
|
||||||
|
'name': 'Magic Kingdom',
|
||||||
|
'slug': 'magic-kingdom',
|
||||||
|
'operator_slug': 'walt-disney-company',
|
||||||
|
'property_owner_slug': 'walt-disney-company',
|
||||||
|
'description': 'The first theme park at Walt Disney World Resort in Florida, opened in 1971.',
|
||||||
|
'opening_date': '1971-10-01',
|
||||||
|
'size_acres': 142,
|
||||||
|
'website': 'https://disneyworld.disney.go.com/destinations/magic-kingdom/',
|
||||||
|
'location': {
|
||||||
|
'street_address': '1180 Seven Seas Dr',
|
||||||
|
'city': 'Lake Buena Vista',
|
||||||
|
'state_province': 'Florida',
|
||||||
|
'country': 'USA',
|
||||||
|
'postal_code': '32830',
|
||||||
|
'latitude': 28.4177,
|
||||||
|
'longitude': -81.5812
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Universal Studios Florida',
|
||||||
|
'slug': 'universal-studios-florida',
|
||||||
|
'operator_slug': 'universal-parks-resorts',
|
||||||
|
'property_owner_slug': 'universal-parks-resorts',
|
||||||
|
'description': 'Movie and television-based theme park in Orlando, Florida.',
|
||||||
|
'opening_date': '1990-06-07',
|
||||||
|
'size_acres': 108,
|
||||||
|
'website': 'https://www.universalorlando.com/web/en/us/theme-parks/universal-studios-florida',
|
||||||
|
'location': {
|
||||||
|
'street_address': '6000 Universal Blvd',
|
||||||
|
'city': 'Orlando',
|
||||||
|
'state_province': 'Florida',
|
||||||
|
'country': 'USA',
|
||||||
|
'postal_code': '32819',
|
||||||
|
'latitude': 28.4749,
|
||||||
|
'longitude': -81.4687
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cedar Point',
|
||||||
|
'slug': 'cedar-point',
|
||||||
|
'operator_slug': 'cedar-fair-entertainment',
|
||||||
|
'property_owner_slug': 'cedar-fair-entertainment',
|
||||||
|
'description': 'Known as the "Roller Coaster Capital of the World".',
|
||||||
|
'opening_date': '1870-06-01',
|
||||||
|
'size_acres': 364,
|
||||||
|
'website': 'https://www.cedarpoint.com/',
|
||||||
|
'location': {
|
||||||
|
'street_address': '1 Cedar Point Dr',
|
||||||
|
'city': 'Sandusky',
|
||||||
|
'state_province': 'Ohio',
|
||||||
|
'country': 'USA',
|
||||||
|
'postal_code': '44870',
|
||||||
|
'latitude': 41.4822,
|
||||||
|
'longitude': -82.6835
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Six Flags Magic Mountain',
|
||||||
|
'slug': 'six-flags-magic-mountain',
|
||||||
|
'operator_slug': 'six-flags-entertainment',
|
||||||
|
'property_owner_slug': 'six-flags-entertainment',
|
||||||
|
'description': 'Known for its world-record 19 roller coasters.',
|
||||||
|
'opening_date': '1971-05-29',
|
||||||
|
'size_acres': 262,
|
||||||
|
'website': 'https://www.sixflags.com/magicmountain',
|
||||||
|
'location': {
|
||||||
|
'street_address': '26101 Magic Mountain Pkwy',
|
||||||
|
'city': 'Valencia',
|
||||||
|
'state_province': 'California',
|
||||||
|
'country': 'USA',
|
||||||
|
'postal_code': '91355',
|
||||||
|
'latitude': 34.4253,
|
||||||
|
'longitude': -118.5971
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Europa-Park',
|
||||||
|
'slug': 'europa-park',
|
||||||
|
'operator_slug': 'merlin-entertainments',
|
||||||
|
'property_owner_slug': 'merlin-entertainments',
|
||||||
|
'description': 'One of the most popular theme parks in Europe, located in Germany.',
|
||||||
|
'opening_date': '1975-07-12',
|
||||||
|
'size_acres': 234,
|
||||||
|
'website': 'https://www.europapark.de/',
|
||||||
|
'location': {
|
||||||
|
'street_address': 'Europa-Park-Straße 2',
|
||||||
|
'city': 'Rust',
|
||||||
|
'state_province': 'Baden-Württemberg',
|
||||||
|
'country': 'Germany',
|
||||||
|
'postal_code': '77977',
|
||||||
|
'latitude': 48.2667,
|
||||||
|
'longitude': 7.7167
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Alton Towers',
|
||||||
|
'slug': 'alton-towers',
|
||||||
|
'operator_slug': 'merlin-entertainments',
|
||||||
|
'property_owner_slug': 'merlin-entertainments',
|
||||||
|
'description': 'Major theme park and former country estate in Staffordshire, England.',
|
||||||
|
'opening_date': '1980-04-23',
|
||||||
|
'size_acres': 500,
|
||||||
|
# Add other fields as needed
|
||||||
|
}
|
||||||
|
]
|
||||||
1072
parks/management/commands/seed_sample_data.py
Normal file
1072
parks/management/commands/seed_sample_data.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,18 @@
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.text import slugify
|
||||||
from core.models import TrackedModel
|
from core.models import TrackedModel
|
||||||
import pghistory
|
import pghistory
|
||||||
|
|
||||||
|
|
||||||
@pghistory.track()
|
@pghistory.track()
|
||||||
class Company(TrackedModel):
|
class Company(TrackedModel):
|
||||||
|
|
||||||
# Import managers
|
# Import managers
|
||||||
from ..managers import CompanyManager
|
from ..managers import CompanyManager
|
||||||
|
|
||||||
objects = CompanyManager()
|
objects = CompanyManager()
|
||||||
|
|
||||||
class CompanyRole(models.TextChoices):
|
class CompanyRole(models.TextChoices):
|
||||||
OPERATOR = 'OPERATOR', 'Park Operator'
|
OPERATOR = 'OPERATOR', 'Park Operator'
|
||||||
PROPERTY_OWNER = 'PROPERTY_OWNER', 'Property Owner'
|
PROPERTY_OWNER = 'PROPERTY_OWNER', 'Property Owner'
|
||||||
@@ -23,12 +26,17 @@ class Company(TrackedModel):
|
|||||||
)
|
)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
website = models.URLField(blank=True)
|
website = models.URLField(blank=True)
|
||||||
|
|
||||||
# Operator-specific fields
|
# Operator-specific fields
|
||||||
founded_year = models.PositiveIntegerField(blank=True, null=True)
|
founded_year = models.PositiveIntegerField(blank=True, null=True)
|
||||||
parks_count = models.IntegerField(default=0)
|
parks_count = models.IntegerField(default=0)
|
||||||
rides_count = models.IntegerField(default=0)
|
rides_count = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -36,6 +44,7 @@ class Company(TrackedModel):
|
|||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
verbose_name_plural = 'Companies'
|
verbose_name_plural = 'Companies'
|
||||||
|
|
||||||
|
|
||||||
class CompanyHeadquarters(models.Model):
|
class CompanyHeadquarters(models.Model):
|
||||||
"""
|
"""
|
||||||
Simple address storage for company headquarters without coordinate tracking.
|
Simple address storage for company headquarters without coordinate tracking.
|
||||||
@@ -47,7 +56,7 @@ class CompanyHeadquarters(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='headquarters'
|
related_name='headquarters'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Address Fields (No coordinates needed)
|
# Address Fields (No coordinates needed)
|
||||||
street_address = models.CharField(
|
street_address = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
@@ -76,13 +85,13 @@ class CompanyHeadquarters(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
help_text="ZIP or postal code"
|
help_text="ZIP or postal code"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Optional mailing address if different or more complete
|
# Optional mailing address if different or more complete
|
||||||
mailing_address = models.TextField(
|
mailing_address = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Complete mailing address if different from basic address"
|
help_text="Complete mailing address if different from basic address"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
@@ -102,7 +111,7 @@ class CompanyHeadquarters(models.Model):
|
|||||||
if self.country and self.country != 'USA':
|
if self.country and self.country != 'USA':
|
||||||
components.append(self.country)
|
components.append(self.country)
|
||||||
return ", ".join(components) if components else f"{self.city}, {self.country}"
|
return ", ".join(components) if components else f"{self.city}, {self.country}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location_display(self):
|
def location_display(self):
|
||||||
"""Simple city, state/country display for compact views."""
|
"""Simple city, state/country display for compact views."""
|
||||||
@@ -122,4 +131,4 @@ class CompanyHeadquarters(models.Model):
|
|||||||
ordering = ['company__name']
|
ordering = ['company__name']
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['city', 'country']),
|
models.Index(fields=['city', 'country']),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,45 +16,56 @@ urlpatterns = [
|
|||||||
# Park views with autocomplete search
|
# Park views with autocomplete search
|
||||||
path("", views.ParkListView.as_view(), name="park_list"),
|
path("", views.ParkListView.as_view(), name="park_list"),
|
||||||
path("create/", views.ParkCreateView.as_view(), name="park_create"),
|
path("create/", views.ParkCreateView.as_view(), name="park_create"),
|
||||||
|
|
||||||
# Add park button endpoint (moved before park detail pattern)
|
# Add park button endpoint (moved before park detail pattern)
|
||||||
path("add-park-button/", views.add_park_button, name="add_park_button"),
|
path("add-park-button/", views.add_park_button, name="add_park_button"),
|
||||||
|
|
||||||
# Location search endpoints
|
# Location search endpoints
|
||||||
path("search/location/", views.location_search, name="location_search"),
|
path("search/location/", views.location_search, name="location_search"),
|
||||||
path("search/reverse-geocode/", views.reverse_geocode, name="reverse_geocode"),
|
path("search/reverse-geocode/", views.reverse_geocode, name="reverse_geocode"),
|
||||||
|
|
||||||
# Areas and search endpoints for HTMX
|
# Areas and search endpoints for HTMX
|
||||||
path("areas/", views.get_park_areas, name="get_park_areas"),
|
path("areas/", views.get_park_areas, name="get_park_areas"),
|
||||||
path("suggest_parks/", views_search.suggest_parks, name="suggest_parks"),
|
path("suggest_parks/", views_search.suggest_parks, name="suggest_parks"),
|
||||||
|
|
||||||
path("search/", views.search_parks, name="search_parks"),
|
path("search/", views.search_parks, name="search_parks"),
|
||||||
|
|
||||||
# Road trip planning URLs
|
# Road trip planning URLs
|
||||||
path("roadtrip/", RoadTripPlannerView.as_view(), name="roadtrip_planner"),
|
path("roadtrip/", RoadTripPlannerView.as_view(), name="roadtrip_planner"),
|
||||||
path("roadtrip/create/", CreateTripView.as_view(), name="roadtrip_create"),
|
path("roadtrip/create/", CreateTripView.as_view(), name="roadtrip_create"),
|
||||||
path("roadtrip/<str:trip_id>/", TripDetailView.as_view(), name="roadtrip_detail"),
|
path("roadtrip/<str:trip_id>/",
|
||||||
|
TripDetailView.as_view(), name="roadtrip_detail"),
|
||||||
|
|
||||||
# Road trip HTMX endpoints
|
# Road trip HTMX endpoints
|
||||||
path("roadtrip/htmx/parks-along-route/", FindParksAlongRouteView.as_view(), name="roadtrip_htmx_parks_along_route"),
|
path("roadtrip/htmx/parks-along-route/", FindParksAlongRouteView.as_view(),
|
||||||
path("roadtrip/htmx/geocode/", GeocodeAddressView.as_view(), name="roadtrip_htmx_geocode"),
|
name="roadtrip_htmx_parks_along_route"),
|
||||||
path("roadtrip/htmx/distance/", ParkDistanceCalculatorView.as_view(), name="roadtrip_htmx_distance"),
|
path("roadtrip/htmx/geocode/", GeocodeAddressView.as_view(),
|
||||||
|
name="roadtrip_htmx_geocode"),
|
||||||
|
path("roadtrip/htmx/distance/", ParkDistanceCalculatorView.as_view(),
|
||||||
|
name="roadtrip_htmx_distance"),
|
||||||
|
|
||||||
# Park detail and related views
|
# Park detail and related views
|
||||||
path("<slug:slug>/", views.ParkDetailView.as_view(), name="park_detail"),
|
path("<slug:slug>/", views.ParkDetailView.as_view(), name="park_detail"),
|
||||||
path("<slug:slug>/edit/", views.ParkUpdateView.as_view(), name="park_update"),
|
path("<slug:slug>/edit/", views.ParkUpdateView.as_view(), name="park_update"),
|
||||||
path("<slug:slug>/actions/", views.park_actions, name="park_actions"),
|
path("<slug:slug>/actions/", views.park_actions, name="park_actions"),
|
||||||
|
|
||||||
# Area views
|
# Area views
|
||||||
path("<slug:park_slug>/areas/<slug:area_slug>/", views.ParkAreaDetailView.as_view(), name="area_detail"),
|
path("<slug:park_slug>/areas/<slug:area_slug>/",
|
||||||
|
views.ParkAreaDetailView.as_view(), name="area_detail"),
|
||||||
|
|
||||||
# Park-specific category URLs
|
# Park-specific category URLs
|
||||||
path("<slug:park_slug>/roller_coasters/", ParkSingleCategoryListView.as_view(), {'category': 'RC'}, name="park_roller_coasters"),
|
path("<slug:park_slug>/roller_coasters/", ParkSingleCategoryListView.as_view(),
|
||||||
path("<slug:park_slug>/dark_rides/", ParkSingleCategoryListView.as_view(), {'category': 'DR'}, name="park_dark_rides"),
|
{'category': 'RC'}, name="park_roller_coasters"),
|
||||||
path("<slug:park_slug>/flat_rides/", ParkSingleCategoryListView.as_view(), {'category': 'FR'}, name="park_flat_rides"),
|
path("<slug:park_slug>/dark_rides/", ParkSingleCategoryListView.as_view(),
|
||||||
path("<slug:park_slug>/water_rides/", ParkSingleCategoryListView.as_view(), {'category': 'WR'}, name="park_water_rides"),
|
{'category': 'DR'}, name="park_dark_rides"),
|
||||||
path("<slug:park_slug>/transports/", ParkSingleCategoryListView.as_view(), {'category': 'TR'}, name="park_transports"),
|
path("<slug:park_slug>/flat_rides/", ParkSingleCategoryListView.as_view(),
|
||||||
path("<slug:park_slug>/others/", ParkSingleCategoryListView.as_view(), {'category': 'OT'}, name="park_others"),
|
{'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"),
|
||||||
|
|
||||||
# Include park-specific rides URLs
|
# Include park-specific rides URLs
|
||||||
path("<slug:park_slug>/rides/", include("rides.park_urls", namespace="rides")),
|
path("<slug:park_slug>/rides/", include("rides.park_urls", namespace="rides")),
|
||||||
|
|||||||
@@ -312,6 +312,9 @@
|
|||||||
.fixed {
|
.fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
.fixed\! {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -1575,9 +1578,6 @@
|
|||||||
.text-yellow-800 {
|
.text-yellow-800 {
|
||||||
color: var(--color-yellow-800);
|
color: var(--color-yellow-800);
|
||||||
}
|
}
|
||||||
.capitalize {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
.lowercase {
|
.lowercase {
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,7 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<dt class="text-sm font-semibold text-gray-900 dark:text-white">Operator</dt>
|
<dt class="text-sm font-semibold text-gray-900 dark:text-white">Operator</dt>
|
||||||
<dd class="mt-1">
|
<dd class="mt-1">
|
||||||
<a href="{% url 'operators:operator_detail' park.operator.slug %}"
|
<span class="text-sm font-bold text-sky-900 dark:text-sky-400">
|
||||||
class="text-sm font-bold text-sky-900 dark:text-sky-400 hover:text-sky-800 dark:hover:text-sky-300">
|
|
||||||
{{ park.operator.name }}
|
{{ park.operator.name }}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
|
|||||||
@@ -41,10 +41,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if park.operator %}
|
{% if park.operator %}
|
||||||
<div class="mt-4 text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
<div class="mt-4 text-sm text-blue-600 dark:text-blue-400">
|
||||||
<a href="{% url 'operators:operator_detail' park.operator.slug %}">
|
{{ park.operator.name }}
|
||||||
{{ park.operator.name }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -119,8 +119,7 @@
|
|||||||
{% for operator in operators %}
|
{% for operator in operators %}
|
||||||
<div class="p-4 rounded-lg bg-gray-50 dark:bg-gray-700">
|
<div class="p-4 rounded-lg bg-gray-50 dark:bg-gray-700">
|
||||||
<h3 class="mb-2 text-lg font-semibold">
|
<h3 class="mb-2 text-lg font-semibold">
|
||||||
<a href="{% url 'operators:operator_detail' operator.slug %}"
|
<span class="text-blue-600 dark:text-blue-400">
|
||||||
class="text-blue-600 hover:underline dark:text-blue-400">
|
|
||||||
{{ operator.name }}
|
{{ operator.name }}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ INSTALLED_APPS = [
|
|||||||
"whitenoise",
|
"whitenoise",
|
||||||
"django_tailwind_cli",
|
"django_tailwind_cli",
|
||||||
"autocomplete", # Django HTMX Autocomplete
|
"autocomplete", # Django HTMX Autocomplete
|
||||||
|
"debug_toolbar",
|
||||||
|
"silk",
|
||||||
"core",
|
"core",
|
||||||
"accounts",
|
"accounts",
|
||||||
"parks",
|
"parks",
|
||||||
@@ -57,6 +59,7 @@ MIDDLEWARE = [
|
|||||||
"django.middleware.cache.UpdateCacheMiddleware",
|
"django.middleware.cache.UpdateCacheMiddleware",
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"whitenoise.middleware.WhiteNoiseMiddleware",
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
@@ -93,21 +96,19 @@ WSGI_APPLICATION = "thrillwiki.wsgi.application"
|
|||||||
|
|
||||||
# Database
|
# Database
|
||||||
|
|
||||||
# Parse database URL but force PostGIS engine
|
# For development, use PostgreSQL with PostGIS for GeoDjango features
|
||||||
db_config = dj_database_url.config(
|
|
||||||
default="[DATABASE-URL-REMOVED]
|
|
||||||
conn_max_age=600,
|
|
||||||
conn_health_checks=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Force PostGIS engine - override any parsed engine
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
**db_config,
|
|
||||||
"ENGINE": "django.contrib.gis.db.backends.postgis",
|
"ENGINE": "django.contrib.gis.db.backends.postgis",
|
||||||
|
"NAME": "thrillwiki",
|
||||||
|
"USER": "postgres",
|
||||||
|
"PASSWORD": "postgres",
|
||||||
|
"HOST": "localhost",
|
||||||
|
"PORT": "5432",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Cache settings
|
# Cache settings
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
@@ -228,3 +229,9 @@ ROADTRIP_USER_AGENT = "ThrillWiki Road Trip Planner (https://thrillwiki.com)"
|
|||||||
ROADTRIP_REQUEST_TIMEOUT = 10 # seconds
|
ROADTRIP_REQUEST_TIMEOUT = 10 # seconds
|
||||||
ROADTRIP_MAX_RETRIES = 3
|
ROADTRIP_MAX_RETRIES = 3
|
||||||
ROADTRIP_BACKOFF_FACTOR = 2
|
ROADTRIP_BACKOFF_FACTOR = 2
|
||||||
|
|
||||||
|
# Debug Toolbar Configuration
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
"127.0.0.1",
|
||||||
|
"localhost",
|
||||||
|
]
|
||||||
|
|||||||
@@ -30,27 +30,36 @@ urlpatterns = [
|
|||||||
path("", HomeView.as_view(), name="home"),
|
path("", HomeView.as_view(), name="home"),
|
||||||
# Autocomplete URLs (must be before other URLs)
|
# Autocomplete URLs (must be before other URLs)
|
||||||
path("ac/", autocomplete_urls),
|
path("ac/", autocomplete_urls),
|
||||||
|
|
||||||
# API Documentation URLs
|
# API Documentation URLs
|
||||||
path("api/schema/", SpectacularAPIView.as_view(), name="schema") if HAS_SPECTACULAR else path("", lambda r: None),
|
path("api/schema/", SpectacularAPIView.as_view(),
|
||||||
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui") if HAS_SPECTACULAR else path("", lambda r: None),
|
name="schema") if HAS_SPECTACULAR else path("", lambda r: None),
|
||||||
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc") if HAS_SPECTACULAR else path("", lambda r: None),
|
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"),
|
||||||
|
name="swagger-ui") if HAS_SPECTACULAR else path("", lambda r: None),
|
||||||
|
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"),
|
||||||
|
name="redoc") if HAS_SPECTACULAR else path("", lambda r: None),
|
||||||
|
|
||||||
# Health Check URLs
|
# Health Check URLs
|
||||||
path("health/", include("health_check.urls")),
|
path("health/", include("health_check.urls")),
|
||||||
path("health/api/", HealthCheckAPIView.as_view(), name="health-api") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
path("health/api/", HealthCheckAPIView.as_view(),
|
||||||
path("health/simple/", SimpleHealthView.as_view(), name="health-simple") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
name="health-api") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
||||||
path("health/metrics/", PerformanceMetricsView.as_view(), name="health-metrics") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
path("health/simple/", SimpleHealthView.as_view(),
|
||||||
|
name="health-simple") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
||||||
|
path("health/metrics/", PerformanceMetricsView.as_view(),
|
||||||
|
name="health-metrics") if HAS_HEALTH_VIEWS else path("", lambda r: None),
|
||||||
|
|
||||||
# API URLs (before app URLs to avoid conflicts)
|
# API URLs (before app URLs to avoid conflicts)
|
||||||
path("api/v1/", include("parks.api.urls", namespace="parks_api")),
|
path("api/v1/", include("parks.api.urls", namespace="parks_api")),
|
||||||
path("api/v1/", include("rides.api.urls", namespace="rides_api")),
|
path("api/v1/", include("rides.api.urls", namespace="rides_api")),
|
||||||
path("api/v1/map/", include("core.urls.map_urls", namespace="map_api")), # Map API URLs
|
path("api/v1/map/", include("core.urls.map_urls",
|
||||||
|
namespace="map_api")), # Map API URLs
|
||||||
|
|
||||||
# Parks and Rides URLs
|
# Parks and Rides URLs
|
||||||
path("parks/", include("parks.urls", namespace="parks")),
|
path("parks/", include("parks.urls", namespace="parks")),
|
||||||
# Global rides URLs
|
# Global rides URLs
|
||||||
path("rides/", include("rides.urls", namespace="rides")),
|
path("rides/", include("rides.urls", namespace="rides")),
|
||||||
|
# Operators URLs
|
||||||
|
path("operators/", include("parks.urls", namespace="operators")),
|
||||||
# Other URLs
|
# Other URLs
|
||||||
path("photos/", include("media.urls", namespace="photos")), # Add photos URLs
|
path("photos/", include("media.urls", namespace="photos")), # Add photos URLs
|
||||||
path("search/", include("core.urls.search", namespace="search")),
|
path("search/", include("core.urls.search", namespace="search")),
|
||||||
@@ -93,9 +102,11 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Serve static files in development
|
# Serve static files in development
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL,
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
document_root=settings.STATIC_ROOT)
|
||||||
|
urlpatterns += static(settings.MEDIA_URL,
|
||||||
|
document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
# Development monitoring URLs
|
# Development monitoring URLs
|
||||||
try:
|
try:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
@@ -104,13 +115,13 @@ if settings.DEBUG:
|
|||||||
] + urlpatterns
|
] + urlpatterns
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import silk
|
import silk
|
||||||
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
|
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Serve test coverage reports in development
|
# Serve test coverage reports in development
|
||||||
coverage_dir = os.path.join(settings.BASE_DIR, 'tests', 'coverage_html')
|
coverage_dir = os.path.join(settings.BASE_DIR, 'tests', 'coverage_html')
|
||||||
if os.path.exists(coverage_dir):
|
if os.path.exists(coverage_dir):
|
||||||
|
|||||||
Reference in New Issue
Block a user