mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 16:11:08 -05:00
feat: Add continent and park type fields to Park and ParkLocation models; update API filters and documentation
This commit is contained in:
@@ -240,10 +240,15 @@ class ParkListCreateAPIView(APIView):
|
|||||||
if city:
|
if city:
|
||||||
qs = qs.filter(location__city__iexact=city)
|
qs = qs.filter(location__city__iexact=city)
|
||||||
|
|
||||||
# NOTE: continent and park_type filters are not implemented because
|
# Continent filter (now available field)
|
||||||
# these fields don't exist in the current Django models:
|
continent = params.get("continent")
|
||||||
# - ParkLocation model has no 'continent' field
|
if continent:
|
||||||
# - Park model has no 'park_type' field
|
qs = qs.filter(location__continent__iexact=continent)
|
||||||
|
|
||||||
|
# Park type filter (now available field)
|
||||||
|
park_type = params.get("park_type")
|
||||||
|
if park_type:
|
||||||
|
qs = qs.filter(park_type=park_type)
|
||||||
|
|
||||||
# Status filter (available field)
|
# Status filter (available field)
|
||||||
status_filter = params.get("status")
|
status_filter = params.get("status")
|
||||||
@@ -559,7 +564,15 @@ class FilterOptionsAPIView(APIView):
|
|||||||
|
|
||||||
# Try to get dynamic options from database
|
# Try to get dynamic options from database
|
||||||
try:
|
try:
|
||||||
# NOTE: continent field doesn't exist in ParkLocation model, so we use static list
|
# Get continents from database (now available field)
|
||||||
|
continents = list(Park.objects.exclude(
|
||||||
|
location__continent__isnull=True
|
||||||
|
).exclude(
|
||||||
|
location__continent__exact=''
|
||||||
|
).values_list('location__continent', flat=True).distinct().order_by('location__continent'))
|
||||||
|
|
||||||
|
# Fallback to static list if no continents in database
|
||||||
|
if not continents:
|
||||||
continents = [
|
continents = [
|
||||||
"North America",
|
"North America",
|
||||||
"South America",
|
"South America",
|
||||||
@@ -582,21 +595,10 @@ class FilterOptionsAPIView(APIView):
|
|||||||
location__state__exact=''
|
location__state__exact=''
|
||||||
).values_list('location__state', flat=True).distinct().order_by('location__state'))
|
).values_list('location__state', flat=True).distinct().order_by('location__state'))
|
||||||
|
|
||||||
# Try to use ModelChoices if available
|
# Get park types from model choices (now available field)
|
||||||
if HAVE_MODELCHOICES and ModelChoices is not None:
|
|
||||||
try:
|
|
||||||
park_types = ModelChoices.get_park_type_choices()
|
|
||||||
except Exception:
|
|
||||||
park_types = [
|
park_types = [
|
||||||
{"value": "THEME_PARK", "label": "Theme Park"},
|
{"value": choice[0], "label": choice[1]}
|
||||||
{"value": "AMUSEMENT_PARK", "label": "Amusement Park"},
|
for choice in Park.PARK_TYPE_CHOICES
|
||||||
{"value": "WATER_PARK", "label": "Water Park"},
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
park_types = [
|
|
||||||
{"value": "THEME_PARK", "label": "Theme Park"},
|
|
||||||
{"value": "AMUSEMENT_PARK", "label": "Amusement Park"},
|
|
||||||
{"value": "WATER_PARK", "label": "Water Park"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Generated by Django 5.2.5 on 2025-08-31 01:46
|
||||||
|
|
||||||
|
import pgtrigger.compiler
|
||||||
|
import pgtrigger.migrations
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("parks", "0012_remove_parkphoto_insert_insert_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name="park",
|
||||||
|
name="insert_insert",
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name="park",
|
||||||
|
name="update_update",
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name="parklocation",
|
||||||
|
name="insert_insert",
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.RemoveTrigger(
|
||||||
|
model_name="parklocation",
|
||||||
|
name="update_update",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="park",
|
||||||
|
name="park_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("THEME_PARK", "Theme Park"),
|
||||||
|
("AMUSEMENT_PARK", "Amusement Park"),
|
||||||
|
("WATER_PARK", "Water Park"),
|
||||||
|
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
|
||||||
|
("CARNIVAL", "Carnival"),
|
||||||
|
("FAIR", "Fair"),
|
||||||
|
("PIER", "Pier"),
|
||||||
|
("BOARDWALK", "Boardwalk"),
|
||||||
|
("SAFARI_PARK", "Safari Park"),
|
||||||
|
("ZOO", "Zoo"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
db_index=True,
|
||||||
|
default="THEME_PARK",
|
||||||
|
help_text="Type/category of the park",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="parkevent",
|
||||||
|
name="park_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("THEME_PARK", "Theme Park"),
|
||||||
|
("AMUSEMENT_PARK", "Amusement Park"),
|
||||||
|
("WATER_PARK", "Water Park"),
|
||||||
|
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
|
||||||
|
("CARNIVAL", "Carnival"),
|
||||||
|
("FAIR", "Fair"),
|
||||||
|
("PIER", "Pier"),
|
||||||
|
("BOARDWALK", "Boardwalk"),
|
||||||
|
("SAFARI_PARK", "Safari Park"),
|
||||||
|
("ZOO", "Zoo"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
default="THEME_PARK",
|
||||||
|
help_text="Type/category of the park",
|
||||||
|
max_length=30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="parklocation",
|
||||||
|
name="continent",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text="Continent where the park is located",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="parklocationevent",
|
||||||
|
name="continent",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Continent where the park is located",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name="park",
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name="insert_insert",
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
func='INSERT INTO "parks_parkevent" ("average_rating", "banner_image_id", "card_image_id", "closing_date", "coaster_count", "created_at", "description", "id", "name", "opening_date", "operating_season", "operator_id", "park_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "property_owner_id", "ride_count", "size_acres", "slug", "status", "updated_at", "url", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."card_image_id", NEW."closing_date", NEW."coaster_count", NEW."created_at", NEW."description", NEW."id", NEW."name", NEW."opening_date", NEW."operating_season", NEW."operator_id", NEW."park_type", _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."url", NEW."website"); RETURN NULL;',
|
||||||
|
hash="406615e99a0bd58eadc2ad3023c988364c61f65a",
|
||||||
|
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", "banner_image_id", "card_image_id", "closing_date", "coaster_count", "created_at", "description", "id", "name", "opening_date", "operating_season", "operator_id", "park_type", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "property_owner_id", "ride_count", "size_acres", "slug", "status", "updated_at", "url", "website") VALUES (NEW."average_rating", NEW."banner_image_id", NEW."card_image_id", NEW."closing_date", NEW."coaster_count", NEW."created_at", NEW."description", NEW."id", NEW."name", NEW."opening_date", NEW."operating_season", NEW."operator_id", NEW."park_type", _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."url", NEW."website"); RETURN NULL;',
|
||||||
|
hash="ca8b337b9b1f1a937a4dd88cde8b31231d0fdff5",
|
||||||
|
operation="UPDATE",
|
||||||
|
pgid="pgtrigger_update_update_19f56",
|
||||||
|
table="parks_park",
|
||||||
|
when="AFTER",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name="parklocation",
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name="insert_insert",
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
func='INSERT INTO "parks_parklocationevent" ("best_arrival_time", "city", "continent", "country", "highway_exit", "id", "osm_id", "osm_type", "park_id", "parking_notes", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "point", "postal_code", "seasonal_notes", "state", "street_address") VALUES (NEW."best_arrival_time", NEW."city", NEW."continent", NEW."country", NEW."highway_exit", NEW."id", NEW."osm_id", NEW."osm_type", NEW."park_id", NEW."parking_notes", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."point", NEW."postal_code", NEW."seasonal_notes", NEW."state", NEW."street_address"); RETURN NULL;',
|
||||||
|
hash="aecd083c917cea3170e944c73c4906a78eccd676",
|
||||||
|
operation="INSERT",
|
||||||
|
pgid="pgtrigger_insert_insert_f8c53",
|
||||||
|
table="parks_parklocation",
|
||||||
|
when="AFTER",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pgtrigger.migrations.AddTrigger(
|
||||||
|
model_name="parklocation",
|
||||||
|
trigger=pgtrigger.compiler.Trigger(
|
||||||
|
name="update_update",
|
||||||
|
sql=pgtrigger.compiler.UpsertTriggerSql(
|
||||||
|
condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)",
|
||||||
|
func='INSERT INTO "parks_parklocationevent" ("best_arrival_time", "city", "continent", "country", "highway_exit", "id", "osm_id", "osm_type", "park_id", "parking_notes", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "point", "postal_code", "seasonal_notes", "state", "street_address") VALUES (NEW."best_arrival_time", NEW."city", NEW."continent", NEW."country", NEW."highway_exit", NEW."id", NEW."osm_id", NEW."osm_type", NEW."park_id", NEW."parking_notes", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."point", NEW."postal_code", NEW."seasonal_notes", NEW."state", NEW."street_address"); RETURN NULL;',
|
||||||
|
hash="a70bc26b34235fe4342009d491d80b990ee3ed7e",
|
||||||
|
operation="UPDATE",
|
||||||
|
pgid="pgtrigger_update_update_6dd0d",
|
||||||
|
table="parks_parklocation",
|
||||||
|
when="AFTER",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -26,6 +26,12 @@ class ParkLocation(models.Model):
|
|||||||
city = models.CharField(max_length=100, db_index=True)
|
city = models.CharField(max_length=100, db_index=True)
|
||||||
state = models.CharField(max_length=100, db_index=True)
|
state = models.CharField(max_length=100, db_index=True)
|
||||||
country = models.CharField(max_length=100, default="USA")
|
country = models.CharField(max_length=100, default="USA")
|
||||||
|
continent = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text="Continent where the park is located"
|
||||||
|
)
|
||||||
postal_code = models.CharField(max_length=20, blank=True)
|
postal_code = models.CharField(max_length=20, blank=True)
|
||||||
|
|
||||||
# Road Trip Metadata
|
# Road Trip Metadata
|
||||||
|
|||||||
@@ -36,6 +36,28 @@ class Park(TrackedModel):
|
|||||||
max_length=20, choices=STATUS_CHOICES, default="OPERATING"
|
max_length=20, choices=STATUS_CHOICES, default="OPERATING"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PARK_TYPE_CHOICES = [
|
||||||
|
("THEME_PARK", "Theme Park"),
|
||||||
|
("AMUSEMENT_PARK", "Amusement Park"),
|
||||||
|
("WATER_PARK", "Water Park"),
|
||||||
|
("FAMILY_ENTERTAINMENT_CENTER", "Family Entertainment Center"),
|
||||||
|
("CARNIVAL", "Carnival"),
|
||||||
|
("FAIR", "Fair"),
|
||||||
|
("PIER", "Pier"),
|
||||||
|
("BOARDWALK", "Boardwalk"),
|
||||||
|
("SAFARI_PARK", "Safari Park"),
|
||||||
|
("ZOO", "Zoo"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
]
|
||||||
|
|
||||||
|
park_type = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=PARK_TYPE_CHOICES,
|
||||||
|
default="THEME_PARK",
|
||||||
|
db_index=True,
|
||||||
|
help_text="Type/category of the park"
|
||||||
|
)
|
||||||
|
|
||||||
# Location relationship - reverse relation from ParkLocation
|
# Location relationship - reverse relation from ParkLocation
|
||||||
# location will be available via the 'location' related_name on
|
# location will be available via the 'location' related_name on
|
||||||
# ParkLocation
|
# ParkLocation
|
||||||
|
|||||||
@@ -212,13 +212,15 @@ The moderation system provides comprehensive content moderation, user management
|
|||||||
|
|
||||||
### Parks Listing
|
### Parks Listing
|
||||||
- **GET** `/api/v1/parks/`
|
- **GET** `/api/v1/parks/`
|
||||||
- **Query Parameters** (22 filtering parameters supported by Django backend):
|
- **Query Parameters** (24 filtering parameters fully supported by Django backend):
|
||||||
- `page` (int): Page number for pagination
|
- `page` (int): Page number for pagination
|
||||||
- `page_size` (int): Number of results per page
|
- `page_size` (int): Number of results per page
|
||||||
- `search` (string): Search in park names and descriptions
|
- `search` (string): Search in park names and descriptions
|
||||||
|
- `continent` (string): Filter by continent
|
||||||
- `country` (string): Filter by country
|
- `country` (string): Filter by country
|
||||||
- `state` (string): Filter by state/province
|
- `state` (string): Filter by state/province
|
||||||
- `city` (string): Filter by city
|
- `city` (string): Filter by city
|
||||||
|
- `park_type` (string): Filter by park type (THEME_PARK, AMUSEMENT_PARK, WATER_PARK, etc.)
|
||||||
- `status` (string): Filter by operational status
|
- `status` (string): Filter by operational status
|
||||||
- `operator_id` (int): Filter by operator company ID
|
- `operator_id` (int): Filter by operator company ID
|
||||||
- `operator_slug` (string): Filter by operator company slug
|
- `operator_slug` (string): Filter by operator company slug
|
||||||
@@ -236,12 +238,6 @@ The moderation system provides comprehensive content moderation, user management
|
|||||||
- `max_roller_coaster_count` (int): Maximum roller coaster count
|
- `max_roller_coaster_count` (int): Maximum roller coaster count
|
||||||
- `ordering` (string): Order by fields (name, opening_date, ride_count, average_rating, coaster_count, etc.)
|
- `ordering` (string): Order by fields (name, opening_date, ride_count, average_rating, coaster_count, etc.)
|
||||||
|
|
||||||
**⚠️ Note**: The following parameters are documented in the API schema but not currently implemented in the Django backend due to missing model fields:
|
|
||||||
- `continent` (string): ParkLocation model has no continent field
|
|
||||||
- `park_type` (string): Park model has no park_type field
|
|
||||||
|
|
||||||
These parameters are accepted by the API but will be ignored until the corresponding model fields are added.
|
|
||||||
|
|
||||||
### Filter Options
|
### Filter Options
|
||||||
- **GET** `/api/v1/parks/filter-options/`
|
- **GET** `/api/v1/parks/filter-options/`
|
||||||
- **Returns**: Comprehensive filter options including continents, countries, states, park types, and ordering options
|
- **Returns**: Comprehensive filter options including continents, countries, states, park types, and ordering options
|
||||||
|
|||||||
@@ -745,7 +745,6 @@ export const parksApi = {
|
|||||||
Object.entries(params).forEach(([key, value]) => {
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
// Note: continent and park_type parameters are accepted but ignored by backend
|
// Note: continent and park_type parameters are accepted but ignored by backend
|
||||||
// due to missing model fields (ParkLocation has no continent, Park has no park_type)
|
|
||||||
searchParams.append(key, value.toString());
|
searchParams.append(key, value.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -978,11 +978,8 @@ export interface ParkSearchFilters {
|
|||||||
min_roller_coaster_count?: number;
|
min_roller_coaster_count?: number;
|
||||||
max_roller_coaster_count?: number;
|
max_roller_coaster_count?: number;
|
||||||
ordering?: string;
|
ordering?: string;
|
||||||
|
continent?: string;
|
||||||
// Note: The following parameters are not currently supported by the backend
|
park_type?: string;
|
||||||
// due to missing model fields, but are kept for future compatibility:
|
|
||||||
continent?: string; // ParkLocation model has no continent field
|
|
||||||
park_type?: string; // Park model has no park_type field
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParkCompanySearchResult {
|
export interface ParkCompanySearchResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user