Last with the old frontend

This commit is contained in:
pacnpal
2025-08-28 11:37:24 -04:00
parent 08a4a2d034
commit c4702559fb
10 changed files with 3434 additions and 6 deletions

View File

@@ -0,0 +1,362 @@
"""
Parks Company API views for ThrillWiki API v1.
This module implements comprehensive Company endpoints for the Parks domain,
handling companies with OPERATOR and PROPERTY_OWNER roles.
Endpoints:
- List / Create: GET /parks/companies/ POST /parks/companies/
- Retrieve / Update / Delete: GET /parks/companies/{pk}/ PATCH/PUT/DELETE
- Search: GET /parks/companies/search/?q=...
"""
from typing import Any
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework.exceptions import NotFound
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
# Import serializers
from apps.api.v1.serializers.companies import (
CompanyDetailOutputSerializer,
CompanyCreateInputSerializer,
CompanyUpdateInputSerializer,
)
# Attempt to import model-level helpers; fall back gracefully if not present.
try:
from apps.parks.models import Company as ParkCompany # type: ignore
MODELS_AVAILABLE = True
except Exception:
ParkCompany = None # type: ignore
MODELS_AVAILABLE = False
class StandardResultsSetPagination(PageNumberPagination):
page_size = 20
page_size_query_param = "page_size"
max_page_size = 1000
# --- Company list & create -------------------------------------------------
class ParkCompanyListCreateAPIView(APIView):
"""
Parks Company endpoints for OPERATOR and PROPERTY_OWNER companies.
"""
permission_classes = [permissions.AllowAny]
@extend_schema(
summary="List park companies (operators/property owners)",
description=(
"List companies with OPERATOR and PROPERTY_OWNER roles "
"with filtering and pagination."
),
parameters=[
OpenApiParameter(
name="page", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="page_size", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="search", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
),
OpenApiParameter(
name="roles",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description=(
"Filter by roles: OPERATOR, PROPERTY_OWNER (comma-separated)"
),
),
],
responses={200: CompanyDetailOutputSerializer(many=True)},
tags=["Parks", "Companies"],
)
def get(self, request: Request) -> Response:
"""List park companies with filtering and pagination."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company listing is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"to enable listing."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
# Filter to only park-related roles
qs = ParkCompany.objects.filter(
roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).distinct() # type: ignore
# Basic filters
q = request.query_params.get("search")
if q:
qs = qs.filter(name__icontains=q)
roles = request.query_params.get("roles")
if roles:
role_list = [role.strip().upper() for role in roles.split(",")]
# Filter to companies that have any of the specified roles
valid_roles = [r for r in role_list if r in ["OPERATOR", "PROPERTY_OWNER"]]
if valid_roles:
qs = qs.filter(roles__overlap=valid_roles)
paginator = StandardResultsSetPagination()
page = paginator.paginate_queryset(qs, request)
serializer = CompanyDetailOutputSerializer(
page, many=True, context={"request": request}
)
return paginator.get_paginated_response(serializer.data)
@extend_schema(
summary="Create a new park company",
description="Create a new company with OPERATOR and/or PROPERTY_OWNER roles.",
request=CompanyCreateInputSerializer,
responses={201: CompanyDetailOutputSerializer()},
tags=["Parks", "Companies"],
)
def post(self, request: Request) -> Response:
"""Create a new park company."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company creation is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"and necessary create logic."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
serializer_in = CompanyCreateInputSerializer(data=request.data)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# Validate that roles are appropriate for parks domain
roles = validated.get("roles", [])
valid_park_roles = [r for r in roles if r in ["OPERATOR", "PROPERTY_OWNER"]]
if not valid_park_roles:
return Response(
{
"detail": (
"Park companies must have at least one of: "
"OPERATOR, PROPERTY_OWNER"
)
},
status=status.HTTP_400_BAD_REQUEST,
)
# Create the company
company = ParkCompany.objects.create( # type: ignore
name=validated["name"],
roles=valid_park_roles,
description=validated.get("description", ""),
website=validated.get("website", ""),
founded_date=validated.get("founded_date"),
)
out_serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(out_serializer.data, status=status.HTTP_201_CREATED)
# --- Company retrieve / update / delete ------------------------------------
@extend_schema(
summary="Retrieve, update or delete a park company",
responses={200: CompanyDetailOutputSerializer()},
tags=["Parks", "Companies"],
)
class ParkCompanyDetailAPIView(APIView):
"""
Park Company detail endpoints for OPERATOR and PROPERTY_OWNER companies.
"""
permission_classes = [permissions.AllowAny]
def _get_company_or_404(self, pk: int) -> Any:
if not MODELS_AVAILABLE:
raise NotFound(
(
"Park company detail is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"to enable detail endpoints."
)
)
try:
# Only allow access to companies with park-related roles
return ParkCompany.objects.filter(
roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).get(
pk=pk
) # type: ignore
except ParkCompany.DoesNotExist: # type: ignore
raise NotFound("Park company not found")
def get(self, request: Request, pk: int) -> Response:
"""Retrieve a park company."""
company = self._get_company_or_404(pk)
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
@extend_schema(
request=CompanyUpdateInputSerializer,
responses={200: CompanyDetailOutputSerializer()},
)
def patch(self, request: Request, pk: int) -> Response:
"""Update a park company."""
company = self._get_company_or_404(pk)
serializer_in = CompanyUpdateInputSerializer(data=request.data, partial=True)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# If roles are being updated, validate they're appropriate for parks domain
if "roles" in validated:
roles = validated["roles"]
valid_park_roles = [r for r in roles if r in ["OPERATOR", "PROPERTY_OWNER"]]
if not valid_park_roles:
return Response(
{
"detail": (
"Park companies must have at least one of: "
"OPERATOR, PROPERTY_OWNER"
)
},
status=status.HTTP_400_BAD_REQUEST,
)
validated["roles"] = valid_park_roles
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company update is not available because domain models "
"are not imported."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
for key, value in validated.items():
setattr(company, key, value)
company.save()
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
def put(self, request: Request, pk: int) -> Response:
"""Full replace - reuse patch behavior for simplicity."""
return self.patch(request, pk)
def delete(self, request: Request, pk: int) -> Response:
"""Delete a park company."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company delete is not available because domain models "
"are not imported."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
company = self._get_company_or_404(pk)
company.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# --- Company search (enhanced) ---------------------------------------------
@extend_schema(
summary="Search park companies (operators/property owners) for autocomplete",
parameters=[
OpenApiParameter(
name="q", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
),
OpenApiParameter(
name="roles",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Filter by roles: OPERATOR, PROPERTY_OWNER (comma-separated)",
),
],
responses={200: OpenApiTypes.OBJECT},
tags=["Parks", "Companies"],
)
class ParkCompanySearchAPIView(APIView):
"""
Enhanced park company search with role filtering.
"""
permission_classes = [permissions.AllowAny]
def get(self, request: Request) -> Response:
q = request.query_params.get("q", "")
if not q:
return Response([], status=status.HTTP_200_OK)
if ParkCompany is None:
# Provide helpful placeholder structure
return Response(
[
{
"id": 1,
"name": "Six Flags Entertainment",
"slug": "six-flags",
"roles": ["OPERATOR"],
},
{
"id": 2,
"name": "Cedar Fair",
"slug": "cedar-fair",
"roles": ["OPERATOR", "PROPERTY_OWNER"],
},
{
"id": 3,
"name": "Disney Parks",
"slug": "disney",
"roles": ["OPERATOR", "PROPERTY_OWNER"],
},
]
)
# Filter to only park-related roles
qs = ParkCompany.objects.filter(
name__icontains=q, roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).distinct() # type: ignore
# Additional role filtering
roles = request.query_params.get("roles")
if roles:
role_list = [role.strip().upper() for role in roles.split(",")]
valid_roles = [r for r in role_list if r in ["OPERATOR", "PROPERTY_OWNER"]]
if valid_roles:
qs = qs.filter(roles__overlap=valid_roles)
qs = qs[:20] # Limit results
results = [
{
"id": c.id,
"name": c.name,
"slug": getattr(c, "slug", ""),
"roles": c.roles if hasattr(c, "roles") else [],
}
for c in qs
]
return Response(results)

View File

@@ -13,9 +13,13 @@ from .park_views import (
ParkListCreateAPIView,
ParkDetailAPIView,
FilterOptionsAPIView,
CompanySearchAPIView,
ParkSearchSuggestionsAPIView,
)
from .company_views import (
ParkCompanyListCreateAPIView,
ParkCompanyDetailAPIView,
ParkCompanySearchAPIView,
)
from .views import ParkPhotoViewSet
# Create router for nested photo endpoints
@@ -29,10 +33,13 @@ urlpatterns = [
path("", ParkListCreateAPIView.as_view(), name="park-list-create"),
# Filter options
path("filter-options/", FilterOptionsAPIView.as_view(), name="park-filter-options"),
# Company endpoints - domain-specific CRUD for OPERATOR/PROPERTY_OWNER companies
path("companies/", ParkCompanyListCreateAPIView.as_view(), name="park-companies-list-create"),
path("companies/<int:pk>/", ParkCompanyDetailAPIView.as_view(), name="park-company-detail"),
# Autocomplete / suggestion endpoints
path(
"search/companies/",
CompanySearchAPIView.as_view(),
ParkCompanySearchAPIView.as_view(),
name="park-search-companies",
),
path(