mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 14:31:07 -05:00
first commit
This commit is contained in:
0
parks/__init__.py
Normal file
0
parks/__init__.py
Normal file
BIN
parks/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
parks/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/admin.cpython-311.pyc
Normal file
BIN
parks/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/apps.cpython-311.pyc
Normal file
BIN
parks/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/models.cpython-311.pyc
Normal file
BIN
parks/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/signals.cpython-311.pyc
Normal file
BIN
parks/__pycache__/signals.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/urls.cpython-311.pyc
Normal file
BIN
parks/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
parks/__pycache__/views.cpython-311.pyc
Normal file
BIN
parks/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
19
parks/admin.py
Normal file
19
parks/admin.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.contrib import admin
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import Park, ParkArea
|
||||
|
||||
@admin.register(Park)
|
||||
class ParkAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'location', 'owner', 'status', 'opening_date')
|
||||
list_filter = ('status', 'owner')
|
||||
search_fields = ('name', 'location', 'description')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
@admin.register(ParkArea)
|
||||
class ParkAreaAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'park', 'opening_date')
|
||||
list_filter = ('park',)
|
||||
search_fields = ('name', 'description')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
8
parks/apps.py
Normal file
8
parks/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class ParksConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'parks'
|
||||
|
||||
def ready(self):
|
||||
import parks.signals # noqa
|
||||
288
parks/management/commands/seed_data.json
Normal file
288
parks/management/commands/seed_data.json
Normal file
@@ -0,0 +1,288 @@
|
||||
{
|
||||
"parks": [
|
||||
{
|
||||
"name": "Walt Disney World Magic Kingdom",
|
||||
"location": "Orlando, Florida",
|
||||
"country": "US",
|
||||
"opening_date": "1971-10-01",
|
||||
"status": "OPERATING",
|
||||
"description": "The most visited theme park in the world, Magic Kingdom is Walt Disney World's first theme park.",
|
||||
"website": "https://disneyworld.disney.go.com/destinations/magic-kingdom/",
|
||||
"owner": "The Walt Disney Company",
|
||||
"size_acres": 142,
|
||||
"rides": [
|
||||
{
|
||||
"name": "Space Mountain",
|
||||
"category": "RC",
|
||||
"opening_date": "1975-01-15",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A high-speed roller coaster in the dark through space.",
|
||||
"stats": {
|
||||
"height_ft": 183,
|
||||
"length_ft": 3196,
|
||||
"speed_mph": 27,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Big Thunder Mountain Railroad",
|
||||
"category": "RC",
|
||||
"opening_date": "1980-09-23",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A mine train roller coaster through the Old West.",
|
||||
"stats": {
|
||||
"height_ft": 104,
|
||||
"length_ft": 2671,
|
||||
"speed_mph": 30,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 197
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Seven Dwarfs Mine Train",
|
||||
"category": "RC",
|
||||
"opening_date": "2014-05-28",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Vekoma",
|
||||
"description": "A family roller coaster featuring unique swinging cars.",
|
||||
"stats": {
|
||||
"height_ft": 112,
|
||||
"length_ft": 2000,
|
||||
"speed_mph": 34,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Haunted Mansion",
|
||||
"category": "DR",
|
||||
"opening_date": "1971-10-01",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A dark ride through a haunted estate."
|
||||
},
|
||||
{
|
||||
"name": "Pirates of the Caribbean",
|
||||
"category": "DR",
|
||||
"opening_date": "1973-12-15",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A boat ride through pirate-filled Caribbean waters."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Cedar Point",
|
||||
"location": "Sandusky, Ohio",
|
||||
"country": "US",
|
||||
"opening_date": "1870-06-01",
|
||||
"status": "OPERATING",
|
||||
"description": "Known as the Roller Coaster Capital of the World.",
|
||||
"website": "https://www.cedarpoint.com",
|
||||
"owner": "Cedar Fair",
|
||||
"size_acres": 364,
|
||||
"rides": [
|
||||
{
|
||||
"name": "Steel Vengeance",
|
||||
"category": "RC",
|
||||
"opening_date": "2018-05-05",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Rocky Mountain Construction",
|
||||
"description": "A hybrid roller coaster featuring multiple inversions.",
|
||||
"stats": {
|
||||
"height_ft": 205,
|
||||
"length_ft": 5740,
|
||||
"speed_mph": 74,
|
||||
"inversions": 4,
|
||||
"ride_time_seconds": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Millennium Force",
|
||||
"category": "RC",
|
||||
"opening_date": "2000-05-13",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A giga coaster with stunning views of Lake Erie.",
|
||||
"stats": {
|
||||
"height_ft": 310,
|
||||
"length_ft": 6595,
|
||||
"speed_mph": 93,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 120
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Top Thrill Dragster",
|
||||
"category": "RC",
|
||||
"opening_date": "2003-05-04",
|
||||
"status": "SBNO",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A strata coaster featuring a 420-foot top hat element.",
|
||||
"stats": {
|
||||
"height_ft": 420,
|
||||
"length_ft": 2800,
|
||||
"speed_mph": 120,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Maverick",
|
||||
"category": "RC",
|
||||
"opening_date": "2007-05-26",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A launched roller coaster with multiple inversions.",
|
||||
"stats": {
|
||||
"height_ft": 105,
|
||||
"length_ft": 4450,
|
||||
"speed_mph": 70,
|
||||
"inversions": 2,
|
||||
"ride_time_seconds": 150
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Universal's Islands of Adventure",
|
||||
"location": "Orlando, Florida",
|
||||
"country": "US",
|
||||
"opening_date": "1999-05-28",
|
||||
"status": "OPERATING",
|
||||
"description": "A theme park featuring cutting-edge technology and thrilling attractions.",
|
||||
"website": "https://www.universalorlando.com/web/en/us/theme-parks/islands-of-adventure",
|
||||
"owner": "NBCUniversal",
|
||||
"size_acres": 110,
|
||||
"rides": [
|
||||
{
|
||||
"name": "Jurassic World VelociCoaster",
|
||||
"category": "RC",
|
||||
"opening_date": "2021-06-10",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A high-speed launch coaster featuring velociraptors.",
|
||||
"stats": {
|
||||
"height_ft": 155,
|
||||
"length_ft": 4700,
|
||||
"speed_mph": 70,
|
||||
"inversions": 4,
|
||||
"ride_time_seconds": 145
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hagrid's Magical Creatures Motorbike Adventure",
|
||||
"category": "RC",
|
||||
"opening_date": "2019-06-13",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A story coaster through the Forbidden Forest.",
|
||||
"stats": {
|
||||
"height_ft": 65,
|
||||
"length_ft": 5053,
|
||||
"speed_mph": 50,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "The Amazing Adventures of Spider-Man",
|
||||
"category": "DR",
|
||||
"opening_date": "1999-05-28",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Oceaneering International",
|
||||
"description": "A 3D dark ride featuring Spider-Man."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Alton Towers",
|
||||
"location": "Staffordshire, England",
|
||||
"country": "GB",
|
||||
"opening_date": "1980-04-04",
|
||||
"status": "OPERATING",
|
||||
"description": "The UK's largest theme park, built around a historic stately home.",
|
||||
"website": "https://www.altontowers.com",
|
||||
"owner": "Merlin Entertainments",
|
||||
"size_acres": 910,
|
||||
"rides": [
|
||||
{
|
||||
"name": "Nemesis",
|
||||
"category": "RC",
|
||||
"opening_date": "1994-03-19",
|
||||
"status": "CLOSED",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "An inverted roller coaster through ravines.",
|
||||
"stats": {
|
||||
"height_ft": 43,
|
||||
"length_ft": 2349,
|
||||
"speed_mph": 50,
|
||||
"inversions": 4,
|
||||
"ride_time_seconds": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Oblivion",
|
||||
"category": "RC",
|
||||
"opening_date": "1998-03-14",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "The world's first vertical drop roller coaster.",
|
||||
"stats": {
|
||||
"height_ft": 65,
|
||||
"length_ft": 1804,
|
||||
"speed_mph": 68,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Europa-Park",
|
||||
"location": "Rust, Germany",
|
||||
"country": "DE",
|
||||
"opening_date": "1975-07-12",
|
||||
"status": "OPERATING",
|
||||
"description": "Germany's largest theme park, featuring European-themed areas.",
|
||||
"website": "https://www.europapark.de",
|
||||
"owner": "Mack Rides",
|
||||
"size_acres": 235,
|
||||
"rides": [
|
||||
{
|
||||
"name": "Silver Star",
|
||||
"category": "RC",
|
||||
"opening_date": "2002-03-23",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "A hypercoaster with stunning views.",
|
||||
"stats": {
|
||||
"height_ft": 239,
|
||||
"length_ft": 4003,
|
||||
"speed_mph": 79,
|
||||
"inversions": 0,
|
||||
"ride_time_seconds": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Blue Fire",
|
||||
"category": "RC",
|
||||
"opening_date": "2009-04-04",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Mack Rides",
|
||||
"description": "A launched roller coaster with multiple inversions.",
|
||||
"stats": {
|
||||
"height_ft": 125,
|
||||
"length_ft": 3465,
|
||||
"speed_mph": 62,
|
||||
"inversions": 4,
|
||||
"ride_time_seconds": 150
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
396
parks/management/commands/seed_data.py
Normal file
396
parks/management/commands/seed_data.py
Normal file
@@ -0,0 +1,396 @@
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
from datetime import datetime
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files import File
|
||||
from django.utils.text import slugify
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from faker import Faker
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
|
||||
from parks.models import Park
|
||||
from rides.models import Ride, RollerCoasterStats
|
||||
from reviews.models import Review
|
||||
from media.models import Photo
|
||||
from accounts.models import User, UserProfile, TopList, TopListItem
|
||||
from companies.models import Company, Manufacturer
|
||||
|
||||
fake = Faker()
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Seeds the database with sample data'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--users', type=int, default=50)
|
||||
parser.add_argument('--reviews-per-item', type=int, default=10)
|
||||
|
||||
def download_and_save_image(self, url, prefix):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
img = Image.open(BytesIO(response.content))
|
||||
img_io = BytesIO()
|
||||
img.save(img_io, format='JPEG')
|
||||
img_io.seek(0)
|
||||
return f'{prefix}_{fake.uuid4()}.jpg', File(img_io)
|
||||
except:
|
||||
return None, None
|
||||
|
||||
def create_users(self, count):
|
||||
self.stdout.write('Creating users...')
|
||||
users = []
|
||||
roles = ['USER'] * 20 + ['MODERATOR'] * 3 + ['ADMIN'] * 2
|
||||
|
||||
# Create or get superuser
|
||||
try:
|
||||
superuser = User.objects.get(username='admin')
|
||||
self.stdout.write('Superuser already exists')
|
||||
except User.DoesNotExist:
|
||||
superuser = User.objects.create_superuser(
|
||||
username='admin',
|
||||
email='admin@thrillwiki.com',
|
||||
password='admin',
|
||||
role='SUPERUSER'
|
||||
)
|
||||
UserProfile.objects.create(
|
||||
user=superuser,
|
||||
display_name='Admin',
|
||||
pronouns='they/them',
|
||||
bio='ThrillWiki Administrator'
|
||||
)
|
||||
self.stdout.write('Created superuser')
|
||||
|
||||
users.append(superuser)
|
||||
|
||||
# Delete existing non-superuser users if any
|
||||
User.objects.exclude(username='admin').delete()
|
||||
self.stdout.write('Deleted existing users')
|
||||
|
||||
for _ in range(count):
|
||||
username = fake.user_name()
|
||||
while User.objects.filter(username=username).exists():
|
||||
username = fake.user_name()
|
||||
|
||||
user = User.objects.create_user(
|
||||
username=username,
|
||||
email=fake.email(),
|
||||
password='password123',
|
||||
role=random.choice(roles)
|
||||
)
|
||||
|
||||
# Create user profile
|
||||
profile = UserProfile.objects.create(
|
||||
user=user,
|
||||
display_name=fake.name(),
|
||||
pronouns=random.choice(['he/him', 'she/her', 'they/them', '']),
|
||||
bio=fake.text(max_nb_chars=200),
|
||||
twitter=fake.url() if random.choice([True, False]) else '',
|
||||
instagram=fake.url() if random.choice([True, False]) else '',
|
||||
youtube=fake.url() if random.choice([True, False]) else '',
|
||||
discord=fake.user_name() if random.choice([True, False]) else '',
|
||||
coaster_credits=random.randint(0, 500),
|
||||
dark_ride_credits=random.randint(0, 200),
|
||||
flat_ride_credits=random.randint(0, 300),
|
||||
water_ride_credits=random.randint(0, 100)
|
||||
)
|
||||
|
||||
# Add avatar
|
||||
img_url = f'https://picsum.photos/200/200?random={fake.random_number(5)}'
|
||||
filename, file = self.download_and_save_image(img_url, 'avatar')
|
||||
if filename and file:
|
||||
profile.avatar.save(filename, file, save=True)
|
||||
|
||||
users.append(user)
|
||||
self.stdout.write(f'Created user: {username}')
|
||||
|
||||
return users
|
||||
|
||||
def create_companies(self):
|
||||
self.stdout.write('Creating companies...')
|
||||
# Delete existing companies
|
||||
Company.objects.all().delete()
|
||||
self.stdout.write('Deleted existing companies')
|
||||
|
||||
companies = {
|
||||
'The Walt Disney Company': {
|
||||
'headquarters': 'Burbank, California',
|
||||
'founded_date': '1923-10-16',
|
||||
'website': 'https://www.disney.com',
|
||||
},
|
||||
'Cedar Fair': {
|
||||
'headquarters': 'Sandusky, Ohio',
|
||||
'founded_date': '1983-05-01',
|
||||
'website': 'https://www.cedarfair.com',
|
||||
},
|
||||
'NBCUniversal': {
|
||||
'headquarters': 'New York City, New York',
|
||||
'founded_date': '1912-04-30',
|
||||
'website': 'https://www.nbcuniversal.com',
|
||||
},
|
||||
'Merlin Entertainments': {
|
||||
'headquarters': 'Poole, England',
|
||||
'founded_date': '1999-05-19',
|
||||
'website': 'https://www.merlinentertainments.biz',
|
||||
},
|
||||
'Mack Rides': {
|
||||
'headquarters': 'Waldkirch, Germany',
|
||||
'founded_date': '1780-01-01',
|
||||
'website': 'https://mack-rides.com',
|
||||
},
|
||||
}
|
||||
|
||||
company_instances = {}
|
||||
for name, details in companies.items():
|
||||
company = Company.objects.create(
|
||||
name=name,
|
||||
slug=slugify(name),
|
||||
headquarters=details['headquarters'],
|
||||
founded_date=datetime.strptime(details['founded_date'], '%Y-%m-%d').date(),
|
||||
website=details['website'],
|
||||
)
|
||||
company_instances[name] = company
|
||||
self.stdout.write(f'Created company: {name}')
|
||||
|
||||
return company_instances
|
||||
|
||||
def create_manufacturers(self):
|
||||
self.stdout.write('Creating manufacturers...')
|
||||
# Delete existing manufacturers
|
||||
Manufacturer.objects.all().delete()
|
||||
self.stdout.write('Deleted existing manufacturers')
|
||||
|
||||
manufacturers = {
|
||||
'Walt Disney Imagineering': {
|
||||
'headquarters': 'Glendale, California',
|
||||
'founded_date': '1952-12-16',
|
||||
'website': 'https://sites.disney.com/waltdisneyimagineering/',
|
||||
},
|
||||
'Bolliger & Mabillard': {
|
||||
'headquarters': 'Monthey, Switzerland',
|
||||
'founded_date': '1988-01-01',
|
||||
'website': 'https://www.bolliger-mabillard.com',
|
||||
},
|
||||
'Intamin': {
|
||||
'headquarters': 'Schaan, Liechtenstein',
|
||||
'founded_date': '1967-01-01',
|
||||
'website': 'https://www.intamin.com',
|
||||
},
|
||||
'Rocky Mountain Construction': {
|
||||
'headquarters': 'Hayden, Idaho',
|
||||
'founded_date': '2001-01-01',
|
||||
'website': 'https://www.rockymountainconstruction.com',
|
||||
},
|
||||
'Vekoma': {
|
||||
'headquarters': 'Vlodrop, Netherlands',
|
||||
'founded_date': '1926-01-01',
|
||||
'website': 'https://www.vekoma.com',
|
||||
},
|
||||
'Mack Rides': {
|
||||
'headquarters': 'Waldkirch, Germany',
|
||||
'founded_date': '1780-01-01',
|
||||
'website': 'https://mack-rides.com',
|
||||
},
|
||||
'Oceaneering International': {
|
||||
'headquarters': 'Houston, Texas',
|
||||
'founded_date': '1964-01-01',
|
||||
'website': 'https://www.oceaneering.com',
|
||||
},
|
||||
}
|
||||
|
||||
manufacturer_instances = {}
|
||||
for name, details in manufacturers.items():
|
||||
manufacturer = Manufacturer.objects.create(
|
||||
name=name,
|
||||
slug=slugify(name),
|
||||
headquarters=details['headquarters'],
|
||||
founded_date=datetime.strptime(details['founded_date'], '%Y-%m-%d').date(),
|
||||
website=details['website'],
|
||||
)
|
||||
manufacturer_instances[name] = manufacturer
|
||||
self.stdout.write(f'Created manufacturer: {name}')
|
||||
|
||||
return manufacturer_instances
|
||||
|
||||
def create_parks_and_rides(self, users):
|
||||
self.stdout.write('Creating parks and rides from seed data...')
|
||||
|
||||
# Create companies and manufacturers first
|
||||
companies = self.create_companies()
|
||||
manufacturers = self.create_manufacturers()
|
||||
|
||||
# Load seed data
|
||||
seed_data_path = os.path.join(os.path.dirname(__file__), 'seed_data.json')
|
||||
with open(seed_data_path, 'r') as f:
|
||||
seed_data = json.load(f)
|
||||
|
||||
# Delete existing parks (this will cascade delete rides)
|
||||
Park.objects.all().delete()
|
||||
self.stdout.write('Deleted existing parks and rides')
|
||||
|
||||
parks = []
|
||||
for park_data in seed_data['parks']:
|
||||
# Create park with company instance
|
||||
park = Park.objects.create(
|
||||
name=park_data['name'],
|
||||
slug=slugify(park_data['name']),
|
||||
location=park_data['location'],
|
||||
country=park_data['country'],
|
||||
opening_date=datetime.strptime(park_data['opening_date'], '%Y-%m-%d').date(),
|
||||
status=park_data['status'],
|
||||
description=park_data['description'],
|
||||
website=park_data['website'],
|
||||
owner=companies[park_data['owner']],
|
||||
size_acres=park_data['size_acres']
|
||||
)
|
||||
|
||||
# Add park photos
|
||||
for _ in range(random.randint(2, 5)):
|
||||
img_url = f'https://picsum.photos/800/600?random={fake.random_number(5)}'
|
||||
filename, file = self.download_and_save_image(img_url, 'park')
|
||||
if filename and file:
|
||||
Photo.objects.create(
|
||||
content_object=park,
|
||||
image=file,
|
||||
uploaded_by=random.choice(users),
|
||||
caption=fake.sentence(),
|
||||
is_approved=True
|
||||
)
|
||||
|
||||
# Create rides for this park
|
||||
for ride_data in park_data['rides']:
|
||||
ride = Ride.objects.create(
|
||||
name=ride_data['name'],
|
||||
slug=slugify(ride_data['name']),
|
||||
category=ride_data['category'],
|
||||
park=park,
|
||||
status=ride_data['status'],
|
||||
opening_date=datetime.strptime(ride_data['opening_date'], '%Y-%m-%d').date(),
|
||||
manufacturer=manufacturers[ride_data['manufacturer']],
|
||||
description=ride_data['description']
|
||||
)
|
||||
|
||||
# Add roller coaster stats if applicable
|
||||
if ride_data['category'] == 'RC' and 'stats' in ride_data:
|
||||
RollerCoasterStats.objects.create(
|
||||
ride=ride,
|
||||
height_ft=ride_data['stats']['height_ft'],
|
||||
length_ft=ride_data['stats']['length_ft'],
|
||||
speed_mph=ride_data['stats']['speed_mph'],
|
||||
inversions=ride_data['stats']['inversions'],
|
||||
ride_time_seconds=ride_data['stats']['ride_time_seconds']
|
||||
)
|
||||
|
||||
# Add ride photos
|
||||
for _ in range(random.randint(2, 5)):
|
||||
img_url = f'https://picsum.photos/800/600?random={fake.random_number(5)}'
|
||||
filename, file = self.download_and_save_image(img_url, 'ride')
|
||||
if filename and file:
|
||||
Photo.objects.create(
|
||||
content_object=ride,
|
||||
image=file,
|
||||
uploaded_by=random.choice(users),
|
||||
caption=fake.sentence(),
|
||||
is_approved=True
|
||||
)
|
||||
|
||||
parks.append(park)
|
||||
self.stdout.write(f'Created park and rides: {park.name}')
|
||||
|
||||
return parks
|
||||
|
||||
def create_reviews(self, users, reviews_per_item):
|
||||
self.stdout.write('Creating reviews...')
|
||||
# Delete existing reviews
|
||||
Review.objects.all().delete()
|
||||
self.stdout.write('Deleted existing reviews')
|
||||
|
||||
# Park reviews
|
||||
total_parks = Park.objects.count()
|
||||
for i, park in enumerate(Park.objects.all(), 1):
|
||||
for _ in range(random.randint(reviews_per_item - 5, reviews_per_item + 5)):
|
||||
Review.objects.create(
|
||||
user=random.choice(users),
|
||||
content_object=park,
|
||||
title=fake.sentence(),
|
||||
content=fake.text(max_nb_chars=500),
|
||||
rating=random.randint(1, 10),
|
||||
visit_date=fake.date_between(start_date=park.opening_date, end_date='today'),
|
||||
is_published=True
|
||||
)
|
||||
if i % 5 == 0:
|
||||
self.stdout.write(f'Created reviews for {i}/{total_parks} parks')
|
||||
|
||||
# Ride reviews
|
||||
total_rides = Ride.objects.count()
|
||||
for i, ride in enumerate(Ride.objects.all(), 1):
|
||||
for _ in range(random.randint(reviews_per_item - 5, reviews_per_item + 5)):
|
||||
Review.objects.create(
|
||||
user=random.choice(users),
|
||||
content_object=ride,
|
||||
title=fake.sentence(),
|
||||
content=fake.text(max_nb_chars=500),
|
||||
rating=random.randint(1, 10),
|
||||
visit_date=fake.date_between(start_date=ride.opening_date, end_date='today'),
|
||||
is_published=True
|
||||
)
|
||||
if i % 20 == 0:
|
||||
self.stdout.write(f'Created reviews for {i}/{total_rides} rides')
|
||||
|
||||
def create_top_lists(self, users):
|
||||
self.stdout.write('Creating top lists...')
|
||||
# Delete existing top lists
|
||||
TopList.objects.all().delete()
|
||||
self.stdout.write('Deleted existing top lists')
|
||||
|
||||
categories = ['RC', 'DR', 'FR', 'WR', 'PK']
|
||||
total_users = len(users)
|
||||
|
||||
# Get content types
|
||||
park_ct = ContentType.objects.get_for_model(Park)
|
||||
ride_ct = ContentType.objects.get_for_model(Ride)
|
||||
|
||||
for i, user in enumerate(users, 1):
|
||||
for category in categories:
|
||||
if random.choice([True, False]): # 50% chance to create a list
|
||||
top_list = TopList.objects.create(
|
||||
user=user,
|
||||
title=f"My Top {random.randint(5, 20)} {dict(TopList.Categories.choices)[category]}s",
|
||||
category=category,
|
||||
description=fake.text(max_nb_chars=200)
|
||||
)
|
||||
|
||||
# Add items to the list
|
||||
items = []
|
||||
if category == 'PK':
|
||||
items = list(Park.objects.all())
|
||||
content_type = park_ct
|
||||
else:
|
||||
items = list(Ride.objects.filter(category=category))
|
||||
content_type = ride_ct
|
||||
|
||||
if items:
|
||||
selected_items = random.sample(items, min(len(items), random.randint(5, 20)))
|
||||
for rank, item in enumerate(selected_items, 1):
|
||||
TopListItem.objects.create(
|
||||
top_list=top_list,
|
||||
content_type=content_type,
|
||||
object_id=item.id,
|
||||
rank=rank,
|
||||
notes=fake.sentence() if random.choice([True, False]) else ''
|
||||
)
|
||||
|
||||
if i % 10 == 0:
|
||||
self.stdout.write(f'Created top lists for {i}/{total_users} users')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write('Starting database seed...')
|
||||
|
||||
users = self.create_users(options['users'])
|
||||
parks = self.create_parks_and_rides(users)
|
||||
self.create_reviews(users, options['reviews_per_item'])
|
||||
self.create_top_lists(users)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Successfully seeded database'))
|
||||
118
parks/migrations/0001_initial.py
Normal file
118
parks/migrations/0001_initial.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-28 20:17
|
||||
|
||||
import django.db.models.deletion
|
||||
import simple_history.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('companies', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HistoricalPark',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('location', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished')], default='OPERATING', max_length=20)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('operating_season', models.CharField(blank=True, max_length=255)),
|
||||
('size_acres', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('average_rating', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='companies.company')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park',
|
||||
'verbose_name_plural': 'historical parks',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Park',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255, unique=True)),
|
||||
('location', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished')], default='OPERATING', max_length=20)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('operating_season', models.CharField(blank=True, max_length=255)),
|
||||
('size_acres', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('average_rating', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parks', to='companies.company')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalParkArea',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('park', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='parks.park')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park area',
|
||||
'verbose_name_plural': 'historical park areas',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ParkArea',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('park', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areas', to='parks.park')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('park', 'slug')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
parks/migrations/__init__.py
Normal file
0
parks/migrations/__init__.py
Normal file
BIN
parks/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
parks/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
parks/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
parks/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
114
parks/models.py
Normal file
114
parks/models.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.utils.text import slugify
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
class Park(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('OPERATING', 'Operating'),
|
||||
('CLOSED_TEMP', 'Temporarily Closed'),
|
||||
('CLOSED_PERM', 'Permanently Closed'),
|
||||
('UNDER_CONSTRUCTION', 'Under Construction'),
|
||||
('DEMOLISHED', 'Demolished'),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
location = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
owner = models.ForeignKey(
|
||||
'companies.Company',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='parks',
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='OPERATING'
|
||||
)
|
||||
opening_date = models.DateField(null=True, blank=True)
|
||||
closing_date = models.DateField(null=True, blank=True)
|
||||
operating_season = models.CharField(max_length=255, blank=True)
|
||||
size_acres = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
website = models.URLField(blank=True)
|
||||
average_rating = models.DecimalField(
|
||||
max_digits=3,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
photos = GenericRelation('media.Photo')
|
||||
reviews = GenericRelation('reviews.Review')
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug):
|
||||
"""Get park by current or historical slug"""
|
||||
try:
|
||||
return cls.objects.get(slug=slug), False
|
||||
except cls.DoesNotExist:
|
||||
# Check historical slugs
|
||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
||||
if history:
|
||||
return cls.objects.get(id=history.id), True
|
||||
raise cls.DoesNotExist("No park found with this slug")
|
||||
|
||||
class ParkArea(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
park = models.ForeignKey(
|
||||
Park,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='areas'
|
||||
)
|
||||
opening_date = models.DateField(null=True, blank=True)
|
||||
closing_date = models.DateField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
photos = GenericRelation('media.Photo')
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = ['park', 'slug']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.park.name} - {self.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug):
|
||||
"""Get area by current or historical slug"""
|
||||
try:
|
||||
return cls.objects.get(slug=slug), False
|
||||
except cls.DoesNotExist:
|
||||
# Check historical slugs
|
||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
||||
if history:
|
||||
return cls.objects.get(id=history.id), True
|
||||
raise cls.DoesNotExist("No area found with this slug")
|
||||
0
parks/signals.py
Normal file
0
parks/signals.py
Normal file
3
parks/tests.py
Normal file
3
parks/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
11
parks/urls.py
Normal file
11
parks/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
from rides.views import RideDetailView
|
||||
|
||||
app_name = 'parks'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.ParkListView.as_view(), name='park_list'),
|
||||
path('<slug:slug>/', views.ParkDetailView.as_view(), name='park_detail'),
|
||||
path('<slug:park_slug>/<slug:ride_slug>/', RideDetailView.as_view(), name='ride_detail'),
|
||||
]
|
||||
91
parks/views.py
Normal file
91
parks/views.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.shortcuts import get_object_or_404
|
||||
from .models import Park, ParkArea
|
||||
from rides.models import Ride
|
||||
from core.views import SlugRedirectMixin
|
||||
|
||||
class ParkDetailView(SlugRedirectMixin, DetailView):
|
||||
model = Park
|
||||
template_name = 'parks/park_detail.html'
|
||||
context_object_name = 'park'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
slug = self.kwargs.get(self.slug_url_kwarg)
|
||||
# Try to get by current or historical slug
|
||||
return self.model.get_by_slug(slug)[0]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['rides'] = Ride.objects.filter(
|
||||
park=self.object
|
||||
).select_related('coaster_stats')
|
||||
context['areas'] = ParkArea.objects.filter(park=self.object)
|
||||
return context
|
||||
|
||||
def get_redirect_url_pattern(self):
|
||||
return 'park_detail'
|
||||
|
||||
class ParkAreaDetailView(SlugRedirectMixin, DetailView):
|
||||
model = ParkArea
|
||||
template_name = 'parks/area_detail.html'
|
||||
context_object_name = 'area'
|
||||
slug_url_kwarg = 'area_slug'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
park_slug = self.kwargs.get('park_slug')
|
||||
area_slug = self.kwargs.get('area_slug')
|
||||
# Try to get by current or historical slug
|
||||
obj, is_old_slug = self.model.get_by_slug(area_slug)
|
||||
if obj.park.slug != park_slug:
|
||||
raise self.model.DoesNotExist("Park slug doesn't match")
|
||||
return obj
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['rides'] = Ride.objects.filter(
|
||||
area=self.object
|
||||
).select_related('coaster_stats')
|
||||
return context
|
||||
|
||||
def get_redirect_url_pattern(self):
|
||||
return 'area_detail'
|
||||
|
||||
def get_redirect_url_kwargs(self):
|
||||
return {
|
||||
'park_slug': self.object.park.slug,
|
||||
'area_slug': self.object.slug
|
||||
}
|
||||
|
||||
class ParkListView(ListView):
|
||||
model = Park
|
||||
template_name = 'parks/park_list.html'
|
||||
context_object_name = 'parks'
|
||||
paginate_by = 12
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Park.objects.select_related('owner')
|
||||
|
||||
# Filter by location if specified
|
||||
location = self.request.GET.get('location')
|
||||
if location:
|
||||
queryset = queryset.filter(location__icontains=location)
|
||||
|
||||
# Filter by status if specified
|
||||
status = self.request.GET.get('status')
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
return queryset.order_by('name')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Add list of locations for the filter dropdown
|
||||
context['locations'] = Park.objects.values_list(
|
||||
'location', flat=True
|
||||
).distinct().order_by('location')
|
||||
context['selected_location'] = self.request.GET.get('location', '')
|
||||
return context
|
||||
Reference in New Issue
Block a user