mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 23:51:09 -05:00
Last with the old frontend
This commit is contained in:
362
backend/apps/api/v1/parks/company_views.py
Normal file
362
backend/apps/api/v1/parks/company_views.py
Normal 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)
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user