Integrate parks app with site-wide search system; add filter configuration, error handling, and search interfaces

This commit is contained in:
pacnpal
2025-02-12 16:59:20 -05:00
parent af57592496
commit 1fe299fb4b
13 changed files with 1267 additions and 72 deletions

1
parks/tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Parks app test suite

250
parks/tests/test_filters.py Normal file
View File

@@ -0,0 +1,250 @@
"""
Tests for park filtering functionality including search, status filtering,
date ranges, and numeric validations.
"""
from django.test import TestCase
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from datetime import date, timedelta
from parks.models import Park
from parks.filters import ParkFilter
from companies.models import Company
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(
name="Thrilling Adventures Inc",
slug="thrilling-adventures"
)
cls.company2 = Company.objects.create(
name="Family Fun Corp",
slug="family-fun"
)
# Create parks with various attributes for testing all filters
cls.park1 = Park.objects.create(
name="Thrilling Adventures Park",
description="A thrilling park with lots of roller coasters",
status="OPERATING",
owner=cls.company1,
opening_date=date(2020, 1, 1),
size_acres=100,
ride_count=20,
coaster_count=5,
average_rating=4.5
)
Location.objects.create(
name="Thrilling Adventures Location",
location_type="park",
street_address="123 Thrill St",
city="Thrill City",
state="Thrill State",
country="USA",
postal_code="12345",
latitude=40.7128,
longitude=-74.0060,
content_object=cls.park1
)
cls.park2 = Park.objects.create(
name="Family Fun Park",
description="Family-friendly entertainment and attractions",
status="CLOSED_TEMP",
owner=cls.company2,
opening_date=date(2015, 6, 15),
size_acres=50,
ride_count=15,
coaster_count=2,
average_rating=4.0
)
Location.objects.create(
name="Family Fun Location",
location_type="park",
street_address="456 Fun St",
city="Fun City",
state="Fun State",
country="Canada",
postal_code="54321",
latitude=43.6532,
longitude=-79.3832,
content_object=cls.park2
)
# Park with minimal data for edge case testing
cls.park3 = Park.objects.create(
name="Incomplete Park",
status="UNDER_CONSTRUCTION"
)
def test_text_search(self):
"""Test search functionality across different fields"""
# Test name search
queryset = ParkFilter(data={"search": "Thrilling"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park1, queryset)
# Test description search
queryset = ParkFilter(data={"search": "family-friendly"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park2, queryset)
# Test location search
queryset = ParkFilter(data={"search": "Thrill City"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park1, queryset)
# Test combined field search
queryset = ParkFilter(data={"search": "Park"}).qs
self.assertEqual(queryset.count(), 3)
# Test empty search
queryset = ParkFilter(data={}).qs
self.assertEqual(queryset.count(), 3)
def test_status_filtering(self):
"""Test status filter with various values"""
# Test each status
status_tests = {
"OPERATING": [self.park1],
"CLOSED_TEMP": [self.park2],
"UNDER_CONSTRUCTION": [self.park3]
}
for status, expected_parks in status_tests.items():
queryset = ParkFilter(data={"status": status}).qs
self.assertEqual(queryset.count(), len(expected_parks))
for park in expected_parks:
self.assertIn(park, queryset)
# Test empty status (should return all)
queryset = ParkFilter(data={}).qs
self.assertEqual(queryset.count(), 3)
# Test invalid status
queryset = ParkFilter(data={"status": "INVALID"}).qs
self.assertEqual(queryset.count(), 0)
self.assertEqual(queryset.count(), 3)
def test_date_range_filtering(self):
"""Test date range filter functionality"""
# Test various date range scenarios
test_cases = [
# Start date only
({
"opening_date_after": "2019-01-01"
}, [self.park1]),
# End date only
({
"opening_date_before": "2016-01-01"
}, [self.park2]),
# Date range including one park
({
"opening_date_after": "2014-01-01",
"opening_date_before": "2016-01-01"
}, [self.park2]),
# Date range including multiple parks
({
"opening_date_after": "2014-01-01",
"opening_date_before": "2022-01-01"
}, [self.park1, self.park2]),
# Empty filter (should return all)
({}, [self.park1, self.park2, self.park3]),
# Future date (should return none)
({
"opening_date_after": "2030-01-01"
}, []),
]
for filter_data, expected_parks in test_cases:
queryset = ParkFilter(data=filter_data).qs
self.assertEqual(
set(queryset),
set(expected_parks),
f"Failed for filter: {filter_data}"
)
# Test invalid date formats
invalid_dates = [
{"opening_date_after": "invalid-date"},
{"opening_date_before": "2023-13-01"}, # Invalid month
{"opening_date_after": "2023-01-32"}, # Invalid day
{"opening_date_before": "not-a-date"},
]
for invalid_data in invalid_dates:
filter_instance = ParkFilter(data=invalid_data)
self.assertFalse(
filter_instance.is_valid(),
f"Filter should be invalid for data: {invalid_data}"
)
def test_company_filtering(self):
"""Test company/owner filtering"""
# Test specific company
queryset = ParkFilter(data={"owner": str(self.company1.id)}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park1, queryset)
# Test other company
queryset = ParkFilter(data={"owner": str(self.company2.id)}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park2, queryset)
# Test null owner (park3 has no owner)
queryset = ParkFilter(data={"owner": "null"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park3, queryset)
# Test empty filter (should return all)
queryset = ParkFilter(data={}).qs
self.assertEqual(queryset.count(), 3)
# Test invalid company ID
queryset = ParkFilter(data={"owner": "99999"}).qs
self.assertEqual(queryset.count(), 0)
def test_numeric_filtering(self):
"""Test numeric filters with validation"""
# Test minimum rides filter
test_cases = [
({"min_rides": "18"}, [self.park1]), # Only park1 has >= 18 rides
({"min_rides": "10"}, [self.park1, self.park2]), # Both park1 and park2 have >= 10 rides
({"min_rides": "0"}, [self.park1, self.park2, self.park3]), # All parks have >= 0 rides
({}, [self.park1, self.park2, self.park3]), # No filter should return all
]
for filter_data, expected_parks in test_cases:
queryset = ParkFilter(data=filter_data).qs
self.assertEqual(
set(queryset),
set(expected_parks),
f"Failed for filter: {filter_data}"
)
# Test coaster count filter
queryset = ParkFilter(data={"min_coasters": "3"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park1, queryset)
# Test size filter
queryset = ParkFilter(data={"min_size": "75"}).qs
self.assertEqual(queryset.count(), 1)
self.assertIn(self.park1, queryset)
# Test validation
invalid_values = ["-1", "invalid", "0.5"]
for value in invalid_values:
filter_instance = ParkFilter(data={"min_rides": value})
self.assertFalse(
filter_instance.is_valid(),
f"Filter should be invalid for value: {value}"
)

213
parks/tests/test_models.py Normal file
View File

@@ -0,0 +1,213 @@
"""
Tests for park models functionality including CRUD operations,
slug handling, status management, and location integration.
"""
from django.test import TestCase
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import date
from parks.models import Park, ParkArea
from companies.models import Company
from location.models import Location
class ParkModelTests(TestCase):
def setUp(self):
"""Set up test data"""
self.company = Company.objects.create(
name="Test Company",
slug="test-company"
)
# Create a basic park
self.park = Park.objects.create(
name="Test Park",
description="A test park",
status="OPERATING",
owner=self.company
)
# Create location for the park
self.location = Location.objects.create(
name="Test Park Location",
location_type="park",
street_address="123 Test St",
city="Test City",
state="Test State",
country="Test Country",
postal_code="12345",
latitude=40.7128,
longitude=-74.0060,
content_object=self.park
)
def test_park_creation(self):
"""Test basic park creation and fields"""
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)
def test_slug_generation(self):
"""Test automatic slug generation"""
park = Park.objects.create(
name="Another Test Park",
status="OPERATING"
)
self.assertEqual(park.slug, "another-test-park")
def test_historical_slug_lookup(self):
"""Test finding park by historical slug"""
from django.db import transaction
from django.contrib.contenttypes.models import ContentType
from history_tracking.models import HistoricalSlug
with transaction.atomic():
# Create initial park with a specific name/slug
park = Park.objects.create(
name="Original Park Name",
description="Test description",
status="OPERATING"
)
original_slug = park.slug
print(f"\nInitial park created with slug: {original_slug}")
# Ensure we have a save to trigger history
park.save()
# Modify name to trigger slug change
park.name = "Updated Park Name"
park.save()
new_slug = park.slug
print(f"Park updated with new slug: {new_slug}")
# Check HistoricalSlug records
historical_slugs = HistoricalSlug.objects.filter(
content_type=ContentType.objects.get_for_model(Park),
object_id=park.id
)
print(f"Historical slug records: {[h.slug for h in historical_slugs]}")
# Check pghistory records
event_model = getattr(Park, 'event_model', None)
if event_model:
historical_records = event_model.objects.filter(
pgh_obj_id=park.id
).order_by('-pgh_created_at')
print(f"\nPG History records:")
for record in historical_records:
print(f"- Event ID: {record.pgh_id}")
print(f" Name: {record.name}")
print(f" Slug: {record.slug}")
print(f" Created At: {record.pgh_created_at}")
else:
print("\nNo pghistory event model available")
# Try to find by old slug
found_park, is_historical = Park.get_by_slug(original_slug)
self.assertEqual(found_park.id, park.id)
print(f"Found park by old slug: {found_park.slug}, is_historical: {is_historical}")
self.assertTrue(is_historical)
# Try current slug
found_park, is_historical = Park.get_by_slug(new_slug)
self.assertEqual(found_park.id, park.id)
print(f"Found park by new slug: {found_park.slug}, is_historical: {is_historical}")
self.assertFalse(is_historical)
def test_status_color_mapping(self):
"""Test status color class mapping"""
status_tests = {
'OPERATING': 'bg-green-100 text-green-800',
'CLOSED_TEMP': 'bg-yellow-100 text-yellow-800',
'CLOSED_PERM': 'bg-red-100 text-red-800',
'UNDER_CONSTRUCTION': 'bg-blue-100 text-blue-800',
'DEMOLISHED': 'bg-gray-100 text-gray-800',
'RELOCATED': 'bg-purple-100 text-purple-800'
}
for status, expected_color in status_tests.items():
self.park.status = status
self.assertEqual(self.park.get_status_color(), expected_color)
def test_location_integration(self):
"""Test location-related functionality"""
# Test formatted location - compare individual components
location = self.park.location.first()
self.assertIsNotNone(location)
formatted_address = location.get_formatted_address()
self.assertIn("123 Test St", formatted_address)
self.assertIn("Test City", formatted_address)
self.assertIn("Test State", formatted_address)
self.assertIn("12345", formatted_address)
self.assertIn("Test Country", formatted_address)
# Test coordinates
self.assertEqual(self.park.coordinates, (40.7128, -74.0060))
# Test park without location
park = Park.objects.create(name="No Location Park")
self.assertEqual(park.formatted_location, "")
self.assertIsNone(park.coordinates)
def test_absolute_url(self):
"""Test get_absolute_url method"""
expected_url = f"/parks/{self.park.slug}/"
self.assertEqual(self.park.get_absolute_url(), expected_url)
class ParkAreaModelTests(TestCase):
def setUp(self):
"""Set up test data"""
self.park = Park.objects.create(
name="Test Park",
status="OPERATING"
)
self.area = ParkArea.objects.create(
park=self.park,
name="Test Area",
description="A test area"
)
def test_area_creation(self):
"""Test basic area creation and fields"""
self.assertEqual(self.area.name, "Test Area")
self.assertEqual(self.area.slug, "test-area")
self.assertEqual(self.area.park, self.park)
def test_historical_slug_lookup(self):
"""Test finding area by historical slug"""
# Change area name/slug
self.area.name = "Updated Area Name"
self.area.save()
# Try to find by old slug
area, is_historical = ParkArea.get_by_slug("test-area")
self.assertEqual(area.id, self.area.id)
self.assertTrue(is_historical)
# Try current slug
area, is_historical = ParkArea.get_by_slug("updated-area-name")
self.assertEqual(area.id, self.area.id)
self.assertFalse(is_historical)
def test_unique_together_constraint(self):
"""Test unique_together constraint for park and slug"""
# Try to create area with same slug in same park
with self.assertRaises(ValidationError):
ParkArea.objects.create(
park=self.park,
name="Test Area" # Will generate same slug
)
# Should be able to use same name in different park
other_park = Park.objects.create(name="Other Park")
area = ParkArea.objects.create(
park=other_park,
name="Test Area"
)
self.assertEqual(area.slug, "test-area")
def test_absolute_url(self):
"""Test get_absolute_url method"""
expected_url = f"/parks/{self.park.slug}/areas/{self.area.slug}/"
self.assertEqual(self.area.get_absolute_url(), expected_url)