feat: Implement Phase 1.5 entity models (Park, Ride, Company, RideModel, Photo)

- Created Company model with location tracking, date precision, and CloudFlare Images
- Created RideModel model for manufacturer's ride models with specifications
- Created Park model with location, dates, operator, and cached statistics
- Created Ride model with comprehensive stats, manufacturer, and park relationship
- Created Photo model with CloudFlare Images integration and generic relations
- Added lifecycle hooks for auto-slug generation and count updates
- Created migrations and applied to database
- Registered all models in Django admin with detailed fieldsets
- Fixed admin autocomplete_fields to use raw_id_fields where needed
- All models inherit from VersionedModel for automatic version tracking
- Models include date precision tracking for opening/closing dates
- Added comprehensive indexes for query performance

Phase 1.5 complete - Entity models ready for API development
This commit is contained in:
pacnpal
2025-11-08 11:43:27 -05:00
parent 543d7bc9dc
commit 9c46ef8b03
17 changed files with 2326 additions and 0 deletions

View File

@@ -0,0 +1,846 @@
# Generated by Django 4.2.8 on 2025-11-08 16:41
import dirtyfields.dirtyfields
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import django_lifecycle.mixins
import model_utils.fields
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Company",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"name",
models.CharField(
db_index=True,
help_text="Official company name",
max_length=255,
unique=True,
),
),
(
"slug",
models.SlugField(
help_text="URL-friendly identifier", max_length=255, unique=True
),
),
(
"description",
models.TextField(
blank=True, help_text="Company description and history"
),
),
(
"company_types",
models.JSONField(
default=list,
help_text="List of company types (manufacturer, operator, etc.)",
),
),
(
"founded_date",
models.DateField(
blank=True, help_text="Company founding date", null=True
),
),
(
"founded_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of founded date",
max_length=20,
),
),
(
"closed_date",
models.DateField(
blank=True,
help_text="Company closure date (if applicable)",
null=True,
),
),
(
"closed_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of closed date",
max_length=20,
),
),
(
"website",
models.URLField(blank=True, help_text="Official company website"),
),
(
"logo_image_id",
models.CharField(
blank=True,
help_text="CloudFlare image ID for company logo",
max_length=255,
),
),
(
"logo_image_url",
models.URLField(
blank=True, help_text="CloudFlare image URL for company logo"
),
),
(
"park_count",
models.IntegerField(
default=0, help_text="Number of parks operated (for operators)"
),
),
(
"ride_count",
models.IntegerField(
default=0,
help_text="Number of rides manufactured (for manufacturers)",
),
),
(
"location",
models.ForeignKey(
blank=True,
help_text="Company headquarters location",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="companies",
to="core.locality",
),
),
],
options={
"verbose_name": "Company",
"verbose_name_plural": "Companies",
"ordering": ["name"],
},
bases=(
dirtyfields.dirtyfields.DirtyFieldsMixin,
django_lifecycle.mixins.LifecycleModelMixin,
models.Model,
),
),
migrations.CreateModel(
name="Park",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"name",
models.CharField(
db_index=True, help_text="Official park name", max_length=255
),
),
(
"slug",
models.SlugField(
help_text="URL-friendly identifier", max_length=255, unique=True
),
),
(
"description",
models.TextField(
blank=True, help_text="Park description and history"
),
),
(
"park_type",
models.CharField(
choices=[
("theme_park", "Theme Park"),
("amusement_park", "Amusement Park"),
("water_park", "Water Park"),
(
"family_entertainment_center",
"Family Entertainment Center",
),
("traveling_park", "Traveling Park"),
("zoo", "Zoo"),
("aquarium", "Aquarium"),
],
db_index=True,
help_text="Type of park",
max_length=50,
),
),
(
"status",
models.CharField(
choices=[
("operating", "Operating"),
("closed", "Closed"),
("sbno", "Standing But Not Operating"),
("under_construction", "Under Construction"),
("planned", "Planned"),
],
db_index=True,
default="operating",
help_text="Current operational status",
max_length=50,
),
),
(
"opening_date",
models.DateField(
blank=True,
db_index=True,
help_text="Park opening date",
null=True,
),
),
(
"opening_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of opening date",
max_length=20,
),
),
(
"closing_date",
models.DateField(
blank=True, help_text="Park closing date (if closed)", null=True
),
),
(
"closing_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of closing date",
max_length=20,
),
),
(
"latitude",
models.DecimalField(
blank=True,
decimal_places=7,
help_text="Latitude coordinate",
max_digits=10,
null=True,
),
),
(
"longitude",
models.DecimalField(
blank=True,
decimal_places=7,
help_text="Longitude coordinate",
max_digits=10,
null=True,
),
),
(
"website",
models.URLField(blank=True, help_text="Official park website"),
),
(
"banner_image_id",
models.CharField(
blank=True,
help_text="CloudFlare image ID for park banner",
max_length=255,
),
),
(
"banner_image_url",
models.URLField(
blank=True, help_text="CloudFlare image URL for park banner"
),
),
(
"logo_image_id",
models.CharField(
blank=True,
help_text="CloudFlare image ID for park logo",
max_length=255,
),
),
(
"logo_image_url",
models.URLField(
blank=True, help_text="CloudFlare image URL for park logo"
),
),
(
"ride_count",
models.IntegerField(default=0, help_text="Total number of rides"),
),
(
"coaster_count",
models.IntegerField(
default=0, help_text="Number of roller coasters"
),
),
(
"custom_fields",
models.JSONField(
blank=True,
default=dict,
help_text="Additional park-specific data",
),
),
(
"location",
models.ForeignKey(
blank=True,
help_text="Park location",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="parks",
to="core.locality",
),
),
(
"operator",
models.ForeignKey(
blank=True,
help_text="Current park operator",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="operated_parks",
to="entities.company",
),
),
],
options={
"verbose_name": "Park",
"verbose_name_plural": "Parks",
"ordering": ["name"],
},
bases=(
dirtyfields.dirtyfields.DirtyFieldsMixin,
django_lifecycle.mixins.LifecycleModelMixin,
models.Model,
),
),
migrations.CreateModel(
name="RideModel",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"name",
models.CharField(
db_index=True,
help_text="Model name (e.g., 'Inverted Coaster', 'Boomerang')",
max_length=255,
),
),
(
"slug",
models.SlugField(
help_text="URL-friendly identifier", max_length=255, unique=True
),
),
(
"description",
models.TextField(
blank=True, help_text="Model description and technical details"
),
),
(
"model_type",
models.CharField(
choices=[
("coaster_model", "Roller Coaster Model"),
("flat_ride_model", "Flat Ride Model"),
("water_ride_model", "Water Ride Model"),
("dark_ride_model", "Dark Ride Model"),
("transport_ride_model", "Transport Ride Model"),
],
db_index=True,
help_text="Type of ride model",
max_length=50,
),
),
(
"typical_height",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="Typical height in feet",
max_digits=6,
null=True,
),
),
(
"typical_speed",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="Typical speed in mph",
max_digits=6,
null=True,
),
),
(
"typical_capacity",
models.IntegerField(
blank=True, help_text="Typical hourly capacity", null=True
),
),
(
"image_id",
models.CharField(
blank=True, help_text="CloudFlare image ID", max_length=255
),
),
(
"image_url",
models.URLField(blank=True, help_text="CloudFlare image URL"),
),
(
"installation_count",
models.IntegerField(
default=0, help_text="Number of installations worldwide"
),
),
(
"manufacturer",
models.ForeignKey(
help_text="Manufacturer of this ride model",
on_delete=django.db.models.deletion.CASCADE,
related_name="ride_models",
to="entities.company",
),
),
],
options={
"verbose_name": "Ride Model",
"verbose_name_plural": "Ride Models",
"ordering": ["manufacturer__name", "name"],
},
bases=(
dirtyfields.dirtyfields.DirtyFieldsMixin,
django_lifecycle.mixins.LifecycleModelMixin,
models.Model,
),
),
migrations.CreateModel(
name="Ride",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"name",
models.CharField(
db_index=True, help_text="Ride name", max_length=255
),
),
(
"slug",
models.SlugField(
help_text="URL-friendly identifier", max_length=255, unique=True
),
),
(
"description",
models.TextField(
blank=True, help_text="Ride description and history"
),
),
(
"ride_category",
models.CharField(
choices=[
("roller_coaster", "Roller Coaster"),
("flat_ride", "Flat Ride"),
("water_ride", "Water Ride"),
("dark_ride", "Dark Ride"),
("transport_ride", "Transport Ride"),
("other", "Other"),
],
db_index=True,
help_text="Broad ride category",
max_length=50,
),
),
(
"ride_type",
models.CharField(
blank=True,
db_index=True,
help_text="Specific ride type (e.g., 'Inverted Coaster', 'Drop Tower')",
max_length=100,
),
),
(
"is_coaster",
models.BooleanField(
db_index=True,
default=False,
help_text="Is this ride a roller coaster?",
),
),
(
"status",
models.CharField(
choices=[
("operating", "Operating"),
("closed", "Closed"),
("sbno", "Standing But Not Operating"),
("relocated", "Relocated"),
("under_construction", "Under Construction"),
("planned", "Planned"),
],
db_index=True,
default="operating",
help_text="Current operational status",
max_length=50,
),
),
(
"opening_date",
models.DateField(
blank=True,
db_index=True,
help_text="Ride opening date",
null=True,
),
),
(
"opening_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of opening date",
max_length=20,
),
),
(
"closing_date",
models.DateField(
blank=True, help_text="Ride closing date (if closed)", null=True
),
),
(
"closing_date_precision",
models.CharField(
choices=[("year", "Year"), ("month", "Month"), ("day", "Day")],
default="day",
help_text="Precision of closing date",
max_length=20,
),
),
(
"height",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="Height in feet",
max_digits=6,
null=True,
),
),
(
"speed",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="Top speed in mph",
max_digits=6,
null=True,
),
),
(
"length",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="Track/ride length in feet",
max_digits=8,
null=True,
),
),
(
"duration",
models.IntegerField(
blank=True, help_text="Ride duration in seconds", null=True
),
),
(
"inversions",
models.IntegerField(
blank=True,
help_text="Number of inversions (for coasters)",
null=True,
),
),
(
"capacity",
models.IntegerField(
blank=True,
help_text="Hourly capacity (riders per hour)",
null=True,
),
),
(
"image_id",
models.CharField(
blank=True,
help_text="CloudFlare image ID for main photo",
max_length=255,
),
),
(
"image_url",
models.URLField(
blank=True, help_text="CloudFlare image URL for main photo"
),
),
(
"custom_fields",
models.JSONField(
blank=True,
default=dict,
help_text="Additional ride-specific data",
),
),
(
"manufacturer",
models.ForeignKey(
blank=True,
help_text="Ride manufacturer",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="manufactured_rides",
to="entities.company",
),
),
(
"model",
models.ForeignKey(
blank=True,
help_text="Specific ride model",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="rides",
to="entities.ridemodel",
),
),
(
"park",
models.ForeignKey(
help_text="Park where ride is located",
on_delete=django.db.models.deletion.CASCADE,
related_name="rides",
to="entities.park",
),
),
],
options={
"verbose_name": "Ride",
"verbose_name_plural": "Rides",
"ordering": ["park__name", "name"],
},
bases=(
dirtyfields.dirtyfields.DirtyFieldsMixin,
django_lifecycle.mixins.LifecycleModelMixin,
models.Model,
),
),
migrations.AddIndex(
model_name="ridemodel",
index=models.Index(
fields=["manufacturer", "name"], name="entities_ri_manufac_1fe3c1_idx"
),
),
migrations.AddIndex(
model_name="ridemodel",
index=models.Index(
fields=["model_type"], name="entities_ri_model_t_610d23_idx"
),
),
migrations.AlterUniqueTogether(
name="ridemodel",
unique_together={("manufacturer", "name")},
),
migrations.AddIndex(
model_name="ride",
index=models.Index(
fields=["park", "name"], name="entities_ri_park_id_e73e3b_idx"
),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(fields=["slug"], name="entities_ri_slug_d2d6bb_idx"),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(fields=["status"], name="entities_ri_status_b69114_idx"),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(
fields=["is_coaster"], name="entities_ri_is_coas_912a4d_idx"
),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(
fields=["ride_category"], name="entities_ri_ride_ca_bc4554_idx"
),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(
fields=["opening_date"], name="entities_ri_opening_c4fc53_idx"
),
),
migrations.AddIndex(
model_name="ride",
index=models.Index(
fields=["manufacturer"], name="entities_ri_manufac_0d9a25_idx"
),
),
migrations.AddIndex(
model_name="park",
index=models.Index(fields=["name"], name="entities_pa_name_f8a746_idx"),
),
migrations.AddIndex(
model_name="park",
index=models.Index(fields=["slug"], name="entities_pa_slug_a21c73_idx"),
),
migrations.AddIndex(
model_name="park",
index=models.Index(fields=["status"], name="entities_pa_status_805296_idx"),
),
migrations.AddIndex(
model_name="park",
index=models.Index(
fields=["park_type"], name="entities_pa_park_ty_8eba41_idx"
),
),
migrations.AddIndex(
model_name="park",
index=models.Index(
fields=["opening_date"], name="entities_pa_opening_102a60_idx"
),
),
migrations.AddIndex(
model_name="park",
index=models.Index(
fields=["location"], name="entities_pa_locatio_20a884_idx"
),
),
migrations.AddIndex(
model_name="company",
index=models.Index(fields=["name"], name="entities_co_name_d061e8_idx"),
),
migrations.AddIndex(
model_name="company",
index=models.Index(fields=["slug"], name="entities_co_slug_00ae5c_idx"),
),
]