import os import json import random import uuid from datetime import datetime from django.core.management.base import BaseCommand from django.contrib.auth.hashers import make_password from django.core.files import File from django.utils.text import slugify from django.contrib.contenttypes.models import ContentType from django.db import connection from faker import Faker import requests from io import BytesIO from PIL import Image from cities_light.models import City, Country 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): try: response = requests.get(url) img = Image.open(BytesIO(response.content)) img_io = BytesIO() img.save(img_io, format='JPEG') img_io.seek(0) filename = url.split('/')[-1] return filename, File(img_io) except Exception as e: self.stdout.write(self.style.WARNING(f'Failed to download image {url}: {str(e)}')) return None, None def create_users(self, count): self.stdout.write('Creating users...') users = [] try: # Get existing admin user admin_user = User.objects.get(username='admin') users.append(admin_user) self.stdout.write('Added existing admin user') except User.DoesNotExist: self.stdout.write(self.style.WARNING('Admin user not found, skipping...')) # Create regular users using raw SQL roles = ['USER'] * 20 + ['MODERATOR'] * 3 + ['ADMIN'] * 2 with connection.cursor() as cursor: for _ in range(count): # Create user username = fake.user_name() while User.objects.filter(username=username).exists(): username = fake.user_name() user_id = str(uuid.uuid4())[:10] cursor.execute(""" INSERT INTO accounts_user ( username, password, email, is_superuser, is_staff, is_active, date_joined, user_id, first_name, last_name, role, is_banned, ban_reason, theme_preference ) VALUES ( %s, %s, %s, false, false, true, NOW(), %s, '', '', %s, false, '', 'light' ) RETURNING id; """, [username, make_password('password123'), fake.email(), user_id, random.choice(roles)]) user_db_id = cursor.fetchone()[0] # Create profile profile_id = str(uuid.uuid4())[:10] display_name = f"{fake.first_name()}_{fake.last_name()}_{fake.random_number(digits=4)}" cursor.execute(""" INSERT INTO accounts_userprofile ( profile_id, display_name, pronouns, bio, twitter, instagram, youtube, discord, coaster_credits, dark_ride_credits, flat_ride_credits, water_ride_credits, user_id, avatar ) VALUES ( %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, '' ); """, [ profile_id, display_name, random.choice(['he/him', 'she/her', 'they/them', '']), fake.text(max_nb_chars=200), fake.url() if random.choice([True, False]) else '', fake.url() if random.choice([True, False]) else '', fake.url() if random.choice([True, False]) else '', fake.user_name() if random.choice([True, False]) else '', random.randint(0, 500), random.randint(0, 200), random.randint(0, 300), random.randint(0, 100), user_db_id ]) users.append(User.objects.get(id=user_db_id)) 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']: try: # Get country from cities_light country = Country.objects.get(code2=park_data['country']) # Try to find city, but don't require it city = None try: city_name = park_data['location'].split(',')[0].strip() city = City.objects.filter(name__iexact=city_name, country=country).first() except: self.stdout.write(self.style.WARNING(f'City not found for {park_data["name"]}, using location text')) # Create park park = Park.objects.create( name=park_data['name'], slug=slugify(park_data['name']), location=park_data['location'], country=country, city=city, 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 photo_url in park_data.get('photos', []): filename, file = self.download_and_save_image(photo_url) if filename and file: Photo.objects.create( content_object=park, image=file, uploaded_by=random.choice(users), caption=f"Photo of {park.name}", 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 photo_url in ride_data.get('photos', []): filename, file = self.download_and_save_image(photo_url) if filename and file: Photo.objects.create( content_object=ride, image=file, uploaded_by=random.choice(users), caption=f"Photo of {ride.name}", is_approved=True ) parks.append(park) self.stdout.write(f'Created park and rides: {park.name}') except Exception as e: self.stdout.write(self.style.ERROR(f'Failed to create park {park_data["name"]}: {str(e)}')) continue 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'))