mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 07:31:08 -05:00
Add operators and property owners functionality
- Implemented OperatorListView and OperatorDetailView for managing operators. - Created corresponding templates for operator listing and detail views. - Added PropertyOwnerListView and PropertyOwnerDetailView for managing property owners. - Developed templates for property owner listing and detail views. - Established relationships between parks and operators, and parks and property owners in the models. - Created migrations to reflect the new relationships and fields in the database. - Added admin interfaces for PropertyOwner management. - Implemented tests for operators and property owners.
This commit is contained in:
@@ -3,7 +3,7 @@ from django.utils.html import format_html
|
||||
from .models import Park, ParkArea
|
||||
|
||||
class ParkAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'formatted_location', 'status', 'owner', 'created_at', 'updated_at')
|
||||
list_display = ('name', 'formatted_location', 'status', 'operator', 'property_owner', 'created_at', 'updated_at')
|
||||
list_filter = ('status',)
|
||||
search_fields = ('name', 'description', 'location__name', 'location__city', 'location__country')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
@@ -13,7 +13,7 @@ from django_filters import (
|
||||
)
|
||||
from .models import Park
|
||||
from .querysets import get_base_park_queryset
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
|
||||
def validate_positive_integer(value):
|
||||
"""Validate that a value is a positive integer"""
|
||||
@@ -47,17 +47,17 @@ class ParkFilter(LocationFilterMixin, RatingFilterMixin, DateRangeFilterMixin, F
|
||||
help_text=_("Filter parks by their current operating status")
|
||||
)
|
||||
|
||||
# Owner filters with helpful descriptions
|
||||
owner = ModelChoiceFilter(
|
||||
field_name='owner',
|
||||
queryset=Company.objects.all(),
|
||||
empty_label=_('Any company'),
|
||||
# Operator filters with helpful descriptions
|
||||
operator = ModelChoiceFilter(
|
||||
field_name='operator',
|
||||
queryset=Operator.objects.all(),
|
||||
empty_label=_('Any operator'),
|
||||
label=_("Operating Company"),
|
||||
help_text=_("Filter parks by their operating company")
|
||||
)
|
||||
has_owner = BooleanFilter(
|
||||
method='filter_has_owner',
|
||||
label=_("Company Status"),
|
||||
has_operator = BooleanFilter(
|
||||
method='filter_has_operator',
|
||||
label=_("Operator Status"),
|
||||
help_text=_("Show parks with or without an operating company")
|
||||
)
|
||||
|
||||
@@ -113,9 +113,9 @@ class ParkFilter(LocationFilterMixin, RatingFilterMixin, DateRangeFilterMixin, F
|
||||
|
||||
return queryset.filter(query).distinct()
|
||||
|
||||
def filter_has_owner(self, queryset, name, value):
|
||||
"""Filter parks based on whether they have an owner"""
|
||||
return queryset.filter(owner__isnull=not value)
|
||||
def filter_has_operator(self, queryset, name, value):
|
||||
"""Filter parks based on whether they have an operator"""
|
||||
return queryset.filter(operator__isnull=not value)
|
||||
|
||||
@property
|
||||
def qs(self):
|
||||
|
||||
@@ -24,7 +24,7 @@ class ParkAutocomplete(BaseAutocomplete):
|
||||
"""Return search results with related data."""
|
||||
return (get_base_park_queryset()
|
||||
.filter(name__icontains=search)
|
||||
.select_related('owner')
|
||||
.select_related('operator', 'property_owner')
|
||||
.order_by('name'))
|
||||
|
||||
def format_result(self, park):
|
||||
@@ -117,7 +117,8 @@ class ParkForm(forms.ModelForm):
|
||||
fields = [
|
||||
"name",
|
||||
"description",
|
||||
"owner",
|
||||
"operator",
|
||||
"property_owner",
|
||||
"status",
|
||||
"opening_date",
|
||||
"closing_date",
|
||||
@@ -145,7 +146,12 @@ class ParkForm(forms.ModelForm):
|
||||
"rows": 2,
|
||||
}
|
||||
),
|
||||
"owner": forms.Select(
|
||||
"operator": forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
}
|
||||
),
|
||||
"property_owner": forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ from django.core.files import File
|
||||
import requests
|
||||
from parks.models import Park
|
||||
from rides.models import Ride, RollerCoasterStats
|
||||
from companies.models import Company, Manufacturer
|
||||
from operators.models import Operator
|
||||
from manufacturers.models import Manufacturer
|
||||
from reviews.models import Review
|
||||
from media.models import Photo
|
||||
from django.contrib.auth.models import Permission
|
||||
@@ -85,7 +86,7 @@ class Command(BaseCommand):
|
||||
User.objects.exclude(username='admin').delete() # Delete all users except admin
|
||||
Park.objects.all().delete()
|
||||
Ride.objects.all().delete()
|
||||
Company.objects.all().delete()
|
||||
Operator.objects.all().delete()
|
||||
Manufacturer.objects.all().delete()
|
||||
Review.objects.all().delete()
|
||||
Photo.objects.all().delete()
|
||||
@@ -167,7 +168,7 @@ class Command(BaseCommand):
|
||||
]
|
||||
|
||||
for name in companies:
|
||||
Company.objects.create(name=name)
|
||||
Operator.objects.create(name=name)
|
||||
self.stdout.write(f"Created company: {name}")
|
||||
|
||||
def create_manufacturers(self):
|
||||
@@ -213,7 +214,7 @@ class Command(BaseCommand):
|
||||
status=park_data["status"],
|
||||
description=park_data["description"],
|
||||
website=park_data["website"],
|
||||
owner=Company.objects.get(name=park_data["owner"]),
|
||||
operator=Operator.objects.get(name=park_data["owner"]),
|
||||
size_acres=park_data["size_acres"],
|
||||
# Add location fields
|
||||
latitude=park_coords["latitude"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
from parks.models import Park, ParkArea
|
||||
from location.models import Location
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -51,12 +51,12 @@ class Command(BaseCommand):
|
||||
|
||||
companies = {}
|
||||
for company_data in companies_data:
|
||||
company, created = Company.objects.get_or_create(
|
||||
operator, created = Operator.objects.get_or_create(
|
||||
name=company_data['name'],
|
||||
defaults=company_data
|
||||
)
|
||||
companies[company.name] = company
|
||||
self.stdout.write(f'{"Created" if created else "Found"} company: {company.name}')
|
||||
companies[operator.name] = operator
|
||||
self.stdout.write(f'{"Created" if created else "Found"} company: {operator.name}')
|
||||
|
||||
# Create parks with their locations
|
||||
parks_data = [
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Generated by Django 5.1.4 on 2025-07-04 15:26
|
||||
|
||||
import django.db.models.deletion
|
||||
import pgtrigger.compiler
|
||||
import pgtrigger.migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("operators", "0001_initial"),
|
||||
("parks", "0003_alter_park_id_alter_parkarea_id_and_more"),
|
||||
("property_owners", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
pgtrigger.migrations.RemoveTrigger(
|
||||
model_name="park",
|
||||
name="insert_insert",
|
||||
),
|
||||
pgtrigger.migrations.RemoveTrigger(
|
||||
model_name="park",
|
||||
name="update_update",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="park",
|
||||
name="owner",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="parkevent",
|
||||
name="owner",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="park",
|
||||
name="operator",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="parks",
|
||||
to="operators.operator",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="park",
|
||||
name="property_owner",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="owned_parks",
|
||||
to="property_owners.propertyowner",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="parkevent",
|
||||
name="operator",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to="operators.operator",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="parkevent",
|
||||
name="property_owner",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
related_query_name="+",
|
||||
to="property_owners.propertyowner",
|
||||
),
|
||||
),
|
||||
pgtrigger.migrations.AddTrigger(
|
||||
model_name="park",
|
||||
trigger=pgtrigger.compiler.Trigger(
|
||||
name="insert_insert",
|
||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||
func='INSERT INTO "parks_parkevent" ("average_rating", "closing_date", "coaster_count", "created_at", "description", "id", "name", "opening_date", "operating_season", "operator_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "property_owner_id", "ride_count", "size_acres", "slug", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."closing_date", NEW."coaster_count", NEW."created_at", NEW."description", NEW."id", NEW."name", NEW."opening_date", NEW."operating_season", NEW."operator_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."property_owner_id", NEW."ride_count", NEW."size_acres", NEW."slug", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;',
|
||||
hash="[AWS-SECRET-REMOVED]",
|
||||
operation="INSERT",
|
||||
pgid="pgtrigger_insert_insert_66883",
|
||||
table="parks_park",
|
||||
when="AFTER",
|
||||
),
|
||||
),
|
||||
),
|
||||
pgtrigger.migrations.AddTrigger(
|
||||
model_name="park",
|
||||
trigger=pgtrigger.compiler.Trigger(
|
||||
name="update_update",
|
||||
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
|
||||
func='INSERT INTO "parks_parkevent" ("average_rating", "closing_date", "coaster_count", "created_at", "description", "id", "name", "opening_date", "operating_season", "operator_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "property_owner_id", "ride_count", "size_acres", "slug", "status", "updated_at", "website") VALUES (NEW."average_rating", NEW."closing_date", NEW."coaster_count", NEW."created_at", NEW."description", NEW."id", NEW."name", NEW."opening_date", NEW."operating_season", NEW."operator_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."property_owner_id", NEW."ride_count", NEW."size_acres", NEW."slug", NEW."status", NEW."updated_at", NEW."website"); RETURN NULL;',
|
||||
hash="[AWS-SECRET-REMOVED]",
|
||||
operation="UPDATE",
|
||||
pgid="pgtrigger_update_update_19f56",
|
||||
table="parks_park",
|
||||
when="AFTER",
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -7,7 +7,8 @@ from decimal import Decimal, ROUND_DOWN, InvalidOperation
|
||||
from typing import Tuple, Optional, Any, TYPE_CHECKING
|
||||
import pghistory
|
||||
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
from property_owners.models import PropertyOwner
|
||||
from media.models import Photo
|
||||
from history_tracking.models import TrackedModel
|
||||
from location.models import Location
|
||||
@@ -54,8 +55,11 @@ class Park(TrackedModel):
|
||||
coaster_count = models.IntegerField(null=True, blank=True)
|
||||
|
||||
# Relationships
|
||||
owner = models.ForeignKey(
|
||||
Company, on_delete=models.SET_NULL, null=True, blank=True, related_name="parks"
|
||||
operator = models.ForeignKey(
|
||||
Operator, on_delete=models.SET_NULL, null=True, blank=True, related_name="parks"
|
||||
)
|
||||
property_owner = models.ForeignKey(
|
||||
PropertyOwner, on_delete=models.SET_NULL, null=True, blank=True, related_name="owned_parks"
|
||||
)
|
||||
photos = GenericRelation(Photo, related_query_name="park")
|
||||
areas: models.Manager['ParkArea'] # Type hint for reverse relation
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.contrib.gis.geos import Point
|
||||
from django.http import HttpResponse
|
||||
from typing import cast, Optional, Tuple
|
||||
from .models import Park, ParkArea
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
from location.models import Location
|
||||
|
||||
User = get_user_model()
|
||||
@@ -38,7 +38,7 @@ class ParkModelTests(TestCase):
|
||||
)
|
||||
|
||||
# Create test company
|
||||
cls.company = Company.objects.create(
|
||||
cls.operator = Operator.objects.create(
|
||||
name='Test Company',
|
||||
website='http://example.com'
|
||||
)
|
||||
@@ -46,7 +46,7 @@ class ParkModelTests(TestCase):
|
||||
# Create test park
|
||||
cls.park = Park.objects.create(
|
||||
name='Test Park',
|
||||
owner=cls.company,
|
||||
owner=cls.operator,
|
||||
status='OPERATING',
|
||||
website='http://testpark.com'
|
||||
)
|
||||
@@ -57,7 +57,7 @@ class ParkModelTests(TestCase):
|
||||
def test_park_creation(self) -> None:
|
||||
"""Test park instance creation and field values"""
|
||||
self.assertEqual(self.park.name, 'Test Park')
|
||||
self.assertEqual(self.park.owner, self.company)
|
||||
self.assertEqual(self.park.operator, self.operator)
|
||||
self.assertEqual(self.park.status, 'OPERATING')
|
||||
self.assertEqual(self.park.website, 'http://testpark.com')
|
||||
self.assertTrue(self.park.slug)
|
||||
@@ -92,7 +92,7 @@ class ParkModelTests(TestCase):
|
||||
class ParkAreaTests(TestCase):
|
||||
def setUp(self) -> None:
|
||||
# Create test company
|
||||
self.company = Company.objects.create(
|
||||
self.operator = Operator.objects.create(
|
||||
name='Test Company',
|
||||
website='http://example.com'
|
||||
)
|
||||
@@ -100,7 +100,7 @@ class ParkAreaTests(TestCase):
|
||||
# Create test park
|
||||
self.park = Park.objects.create(
|
||||
name='Test Park',
|
||||
owner=self.company,
|
||||
owner=self.operator,
|
||||
status='OPERATING'
|
||||
)
|
||||
|
||||
@@ -139,13 +139,13 @@ class ParkViewTests(TestCase):
|
||||
email='test@example.com',
|
||||
password='testpass123'
|
||||
)
|
||||
self.company = Company.objects.create(
|
||||
self.operator = Operator.objects.create(
|
||||
name='Test Company',
|
||||
website='http://example.com'
|
||||
)
|
||||
self.park = Park.objects.create(
|
||||
name='Test Park',
|
||||
owner=self.company,
|
||||
owner=self.operator,
|
||||
status='OPERATING'
|
||||
)
|
||||
self.location = create_test_location(self.park)
|
||||
|
||||
@@ -9,19 +9,19 @@ from datetime import date, timedelta
|
||||
|
||||
from parks.models import Park
|
||||
from parks.filters import ParkFilter
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
from location.models import Location
|
||||
|
||||
class ParkFilterTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Set up test data for all filter tests"""
|
||||
# Create companies
|
||||
cls.company1 = Company.objects.create(
|
||||
# Create operators
|
||||
cls.operator1 = Operator.objects.create(
|
||||
name="Thrilling Adventures Inc",
|
||||
slug="thrilling-adventures"
|
||||
)
|
||||
cls.company2 = Company.objects.create(
|
||||
cls.operator2 = Operator.objects.create(
|
||||
name="Family Fun Corp",
|
||||
slug="family-fun"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ class ParkFilterTests(TestCase):
|
||||
name="Thrilling Adventures Park",
|
||||
description="A thrilling park with lots of roller coasters",
|
||||
status="OPERATING",
|
||||
owner=cls.company1,
|
||||
operator=cls.operator1,
|
||||
opening_date=date(2020, 1, 1),
|
||||
size_acres=100,
|
||||
ride_count=20,
|
||||
@@ -55,7 +55,7 @@ class ParkFilterTests(TestCase):
|
||||
name="Family Fun Park",
|
||||
description="Family-friendly entertainment and attractions",
|
||||
status="CLOSED_TEMP",
|
||||
owner=cls.company2,
|
||||
owner=cls.operator2,
|
||||
opening_date=date(2015, 6, 15),
|
||||
size_acres=50,
|
||||
ride_count=15,
|
||||
@@ -193,12 +193,12 @@ class ParkFilterTests(TestCase):
|
||||
def test_company_filtering(self):
|
||||
"""Test company/owner filtering"""
|
||||
# Test specific company
|
||||
queryset = ParkFilter(data={"owner": str(self.company1.id)}).qs
|
||||
queryset = ParkFilter(data={"operator": str(self.operator1.id)}).qs
|
||||
self.assertEqual(queryset.count(), 1)
|
||||
self.assertIn(self.park1, queryset)
|
||||
|
||||
# Test other company
|
||||
queryset = ParkFilter(data={"owner": str(self.company2.id)}).qs
|
||||
queryset = ParkFilter(data={"operator": str(self.operator2.id)}).qs
|
||||
self.assertEqual(queryset.count(), 1)
|
||||
self.assertIn(self.park2, queryset)
|
||||
|
||||
@@ -218,7 +218,7 @@ class ParkFilterTests(TestCase):
|
||||
self.assertEqual(queryset.count(), 3)
|
||||
|
||||
# Test invalid company ID
|
||||
queryset = ParkFilter(data={"owner": "99999"}).qs
|
||||
queryset = ParkFilter(data={"operator": "99999"}).qs
|
||||
self.assertEqual(queryset.count(), 0)
|
||||
|
||||
def test_numeric_filtering(self):
|
||||
|
||||
@@ -9,13 +9,13 @@ from django.utils import timezone
|
||||
from datetime import date
|
||||
|
||||
from parks.models import Park, ParkArea
|
||||
from companies.models import Company
|
||||
from operators.models import Operator
|
||||
from location.models import Location
|
||||
|
||||
class ParkModelTests(TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
self.company = Company.objects.create(
|
||||
self.operator = Operator.objects.create(
|
||||
name="Test Company",
|
||||
slug="test-company"
|
||||
)
|
||||
@@ -25,7 +25,7 @@ class ParkModelTests(TestCase):
|
||||
name="Test Park",
|
||||
description="A test park",
|
||||
status="OPERATING",
|
||||
owner=self.company
|
||||
owner=self.operator
|
||||
)
|
||||
|
||||
# Create location for the park
|
||||
@@ -47,7 +47,7 @@ class ParkModelTests(TestCase):
|
||||
self.assertEqual(self.park.name, "Test Park")
|
||||
self.assertEqual(self.park.slug, "test-park")
|
||||
self.assertEqual(self.park.status, "OPERATING")
|
||||
self.assertEqual(self.park.owner, self.company)
|
||||
self.assertEqual(self.park.operator, self.operator)
|
||||
|
||||
def test_slug_generation(self):
|
||||
"""Test automatic slug generation"""
|
||||
|
||||
Reference in New Issue
Block a user