mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
Add migrations for ParkPhoto and RidePhoto models with associated events
- Created ParkPhoto and ParkPhotoEvent models in the parks app, including fields for image, caption, alt text, and relationships to the Park model. - Implemented triggers for insert and update operations on ParkPhoto to log changes in ParkPhotoEvent. - Created RidePhoto and RidePhotoEvent models in the rides app, with similar structure and functionality as ParkPhoto. - Added fields for photo type in RidePhoto and implemented corresponding triggers for logging changes. - Established necessary indexes and unique constraints for both models to ensure data integrity and optimize queries.
This commit is contained in:
@@ -5,12 +5,10 @@ This module provides ViewSets for accessing historical data and change tracking
|
||||
across all models in the ThrillWiki system using django-pghistory.
|
||||
"""
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
@@ -3,18 +3,9 @@ Centralized map API views.
|
||||
Migrated from apps.core.views.map_views
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from django.http import JsonResponse, HttpRequest
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
@@ -30,15 +21,54 @@ logger = logging.getLogger(__name__)
|
||||
summary="Get map locations",
|
||||
description="Get map locations with optional clustering and filtering.",
|
||||
parameters=[
|
||||
{"name": "north", "in": "query", "required": False, "schema": {"type": "number"}},
|
||||
{"name": "south", "in": "query", "required": False, "schema": {"type": "number"}},
|
||||
{"name": "east", "in": "query", "required": False, "schema": {"type": "number"}},
|
||||
{"name": "west", "in": "query", "required": False, "schema": {"type": "number"}},
|
||||
{"name": "zoom", "in": "query", "required": False, "schema": {"type": "integer"}},
|
||||
{"name": "types", "in": "query", "required": False, "schema": {"type": "string"}},
|
||||
{"name": "cluster", "in": "query", "required": False,
|
||||
"schema": {"type": "boolean"}},
|
||||
{"name": "q", "in": "query", "required": False, "schema": {"type": "string"}},
|
||||
{
|
||||
"name": "north",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "south",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "east",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "west",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "zoom",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "integer"},
|
||||
},
|
||||
{
|
||||
"name": "types",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
{
|
||||
"name": "cluster",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "boolean"},
|
||||
},
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": False,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT},
|
||||
tags=["Maps"],
|
||||
@@ -54,18 +84,20 @@ class MapLocationsAPIView(APIView):
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
# TODO: Implement full functionality
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": "Map locations endpoint - implementation needed",
|
||||
"data": []
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Map locations endpoint - implementation needed",
|
||||
"data": [],
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in MapLocationsAPIView: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
"status": "error",
|
||||
"message": "Failed to retrieve map locations"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
return Response(
|
||||
{"status": "error", "message": "Failed to retrieve map locations"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
@@ -73,10 +105,18 @@ class MapLocationsAPIView(APIView):
|
||||
summary="Get location details",
|
||||
description="Get detailed information about a specific location.",
|
||||
parameters=[
|
||||
{"name": "location_type", "in": "path",
|
||||
"required": True, "schema": {"type": "string"}},
|
||||
{"name": "location_id", "in": "path",
|
||||
"required": True, "schema": {"type": "integer"}},
|
||||
{
|
||||
"name": "location_type",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
{
|
||||
"name": "location_id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"},
|
||||
},
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT, 404: OpenApiTypes.OBJECT},
|
||||
tags=["Maps"],
|
||||
@@ -87,25 +127,29 @@ class MapLocationDetailAPIView(APIView):
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
def get(self, request: HttpRequest, location_type: str, location_id: int) -> Response:
|
||||
def get(
|
||||
self, request: HttpRequest, location_type: str, location_id: int
|
||||
) -> Response:
|
||||
"""Get detailed information for a specific location."""
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": f"Location detail for {location_type}/{location_id} - implementation needed",
|
||||
"data": {
|
||||
"location_type": location_type,
|
||||
"location_id": location_id
|
||||
return Response(
|
||||
{
|
||||
"status": "success",
|
||||
"message": f"Location detail for {location_type}/{location_id} - implementation needed",
|
||||
"data": {
|
||||
"location_type": location_type,
|
||||
"location_id": location_id,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in MapLocationDetailAPIView: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
"status": "error",
|
||||
"message": "Failed to retrieve location details"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
return Response(
|
||||
{"status": "error", "message": "Failed to retrieve location details"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
@@ -113,7 +157,12 @@ class MapLocationDetailAPIView(APIView):
|
||||
summary="Search map locations",
|
||||
description="Search locations by text query with optional bounds filtering.",
|
||||
parameters=[
|
||||
{"name": "q", "in": "query", "required": True, "schema": {"type": "string"}},
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT},
|
||||
tags=["Maps"],
|
||||
@@ -129,24 +178,29 @@ class MapSearchAPIView(APIView):
|
||||
try:
|
||||
query = request.GET.get("q", "").strip()
|
||||
if not query:
|
||||
return Response({
|
||||
"status": "error",
|
||||
"message": "Search query 'q' parameter is required"
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Search query 'q' parameter is required",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": f"Search for '{query}' - implementation needed",
|
||||
"data": []
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
"status": "success",
|
||||
"message": f"Search for '{query}' - implementation needed",
|
||||
"data": [],
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in MapSearchAPIView: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
"status": "error",
|
||||
"message": "Search failed due to internal error"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
return Response(
|
||||
{"status": "error", "message": "Search failed due to internal error"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
@@ -154,10 +208,30 @@ class MapSearchAPIView(APIView):
|
||||
summary="Get locations within bounds",
|
||||
description="Get locations within specific geographic bounds.",
|
||||
parameters=[
|
||||
{"name": "north", "in": "query", "required": True, "schema": {"type": "number"}},
|
||||
{"name": "south", "in": "query", "required": True, "schema": {"type": "number"}},
|
||||
{"name": "east", "in": "query", "required": True, "schema": {"type": "number"}},
|
||||
{"name": "west", "in": "query", "required": True, "schema": {"type": "number"}},
|
||||
{
|
||||
"name": "north",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "south",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "east",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
{
|
||||
"name": "west",
|
||||
"in": "query",
|
||||
"required": True,
|
||||
"schema": {"type": "number"},
|
||||
},
|
||||
],
|
||||
responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT},
|
||||
tags=["Maps"],
|
||||
@@ -172,18 +246,23 @@ class MapBoundsAPIView(APIView):
|
||||
"""Get locations within specific geographic bounds."""
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": "Bounds query - implementation needed",
|
||||
"data": []
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Bounds query - implementation needed",
|
||||
"data": [],
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in MapBoundsAPIView: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
"status": "error",
|
||||
"message": "Failed to retrieve locations within bounds"
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
return Response(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Failed to retrieve locations within bounds",
|
||||
},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
@@ -203,14 +282,12 @@ class MapStatsAPIView(APIView):
|
||||
"""Get map service statistics and performance metrics."""
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"data": {
|
||||
"total_locations": 0,
|
||||
"cache_hits": 0,
|
||||
"cache_misses": 0
|
||||
return Response(
|
||||
{
|
||||
"status": "success",
|
||||
"data": {"total_locations": 0, "cache_hits": 0, "cache_misses": 0},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
@@ -242,10 +319,9 @@ class MapCacheAPIView(APIView):
|
||||
"""Clear all map cache (admin only)."""
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": "Map cache cleared successfully"
|
||||
})
|
||||
return Response(
|
||||
{"status": "success", "message": "Map cache cleared successfully"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
@@ -257,10 +333,9 @@ class MapCacheAPIView(APIView):
|
||||
"""Invalidate specific cache entries."""
|
||||
try:
|
||||
# Simple implementation to fix import error
|
||||
return Response({
|
||||
"status": "success",
|
||||
"message": "Cache invalidated successfully"
|
||||
})
|
||||
return Response(
|
||||
{"status": "success", "message": "Cache invalidated successfully"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Park API URLs for ThrillWiki API v1.
|
||||
"""
|
||||
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Park API views for ThrillWiki API v1.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Ride API URLs for ThrillWiki API v1.
|
||||
"""
|
||||
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Ride API views for ThrillWiki API v1.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
@@ -6,8 +6,6 @@ for the ThrillWiki API, including better documentation and examples.
|
||||
"""
|
||||
|
||||
from drf_spectacular.openapi import AutoSchema
|
||||
from drf_spectacular.utils import OpenApiExample
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
|
||||
# Custom examples for common serializers
|
||||
|
||||
@@ -12,12 +12,7 @@ from drf_spectacular.utils import (
|
||||
OpenApiExample,
|
||||
)
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from .shared import UserModel, ModelChoices
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ using django-pghistory.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_serializer, extend_schema_field
|
||||
import pghistory.models
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
|
||||
|
||||
class ParkHistoryEventSerializer(serializers.Serializer):
|
||||
|
||||
@@ -7,9 +7,7 @@ miscellaneous functionality.
|
||||
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema_serializer,
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -239,7 +239,8 @@ class ParkFilterInputSerializer(serializers.Serializer):
|
||||
|
||||
# Status filter
|
||||
status = serializers.MultipleChoiceField(
|
||||
choices=[], required=False # Choices set dynamically
|
||||
choices=[],
|
||||
required=False, # Choices set dynamically
|
||||
)
|
||||
|
||||
# Location filters
|
||||
|
||||
@@ -12,39 +12,40 @@ class ParkPhotoOutputSerializer(serializers.ModelSerializer):
|
||||
"""Output serializer for park photos."""
|
||||
|
||||
uploaded_by_username = serializers.CharField(
|
||||
source='uploaded_by.username', read_only=True)
|
||||
source="uploaded_by.username", read_only=True
|
||||
)
|
||||
file_size = serializers.ReadOnlyField()
|
||||
dimensions = serializers.ReadOnlyField()
|
||||
park_slug = serializers.CharField(source='park.slug', read_only=True)
|
||||
park_name = serializers.CharField(source='park.name', read_only=True)
|
||||
park_slug = serializers.CharField(source="park.slug", read_only=True)
|
||||
park_name = serializers.CharField(source="park.name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ParkPhoto
|
||||
fields = [
|
||||
'id',
|
||||
'image',
|
||||
'caption',
|
||||
'alt_text',
|
||||
'is_primary',
|
||||
'is_approved',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'date_taken',
|
||||
'uploaded_by_username',
|
||||
'file_size',
|
||||
'dimensions',
|
||||
'park_slug',
|
||||
'park_name',
|
||||
"id",
|
||||
"image",
|
||||
"caption",
|
||||
"alt_text",
|
||||
"is_primary",
|
||||
"is_approved",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"date_taken",
|
||||
"uploaded_by_username",
|
||||
"file_size",
|
||||
"dimensions",
|
||||
"park_slug",
|
||||
"park_name",
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'uploaded_by_username',
|
||||
'file_size',
|
||||
'dimensions',
|
||||
'park_slug',
|
||||
'park_name',
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"uploaded_by_username",
|
||||
"file_size",
|
||||
"dimensions",
|
||||
"park_slug",
|
||||
"park_name",
|
||||
]
|
||||
|
||||
|
||||
@@ -54,10 +55,10 @@ class ParkPhotoCreateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ParkPhoto
|
||||
fields = [
|
||||
'image',
|
||||
'caption',
|
||||
'alt_text',
|
||||
'is_primary',
|
||||
"image",
|
||||
"caption",
|
||||
"alt_text",
|
||||
"is_primary",
|
||||
]
|
||||
|
||||
|
||||
@@ -67,9 +68,9 @@ class ParkPhotoUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ParkPhoto
|
||||
fields = [
|
||||
'caption',
|
||||
'alt_text',
|
||||
'is_primary',
|
||||
"caption",
|
||||
"alt_text",
|
||||
"is_primary",
|
||||
]
|
||||
|
||||
|
||||
@@ -77,18 +78,19 @@ class ParkPhotoListOutputSerializer(serializers.ModelSerializer):
|
||||
"""Simplified output serializer for park photo lists."""
|
||||
|
||||
uploaded_by_username = serializers.CharField(
|
||||
source='uploaded_by.username', read_only=True)
|
||||
source="uploaded_by.username", read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ParkPhoto
|
||||
fields = [
|
||||
'id',
|
||||
'image',
|
||||
'caption',
|
||||
'is_primary',
|
||||
'is_approved',
|
||||
'created_at',
|
||||
'uploaded_by_username',
|
||||
"id",
|
||||
"image",
|
||||
"caption",
|
||||
"is_primary",
|
||||
"is_approved",
|
||||
"created_at",
|
||||
"uploaded_by_username",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
@@ -97,12 +99,10 @@ class ParkPhotoApprovalInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for photo approval operations."""
|
||||
|
||||
photo_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="List of photo IDs to approve"
|
||||
child=serializers.IntegerField(), help_text="List of photo IDs to approve"
|
||||
)
|
||||
approve = serializers.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether to approve (True) or reject (False) the photos"
|
||||
default=True, help_text="Whether to approve (True) or reject (False) the photos"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -344,7 +344,8 @@ class RideFilterInputSerializer(serializers.Serializer):
|
||||
|
||||
# Status filter
|
||||
status = serializers.MultipleChoiceField(
|
||||
choices=[], required=False # Choices set dynamically
|
||||
choices=[],
|
||||
required=False, # Choices set dynamically
|
||||
)
|
||||
|
||||
# Park filter
|
||||
|
||||
@@ -12,46 +12,47 @@ class RidePhotoOutputSerializer(serializers.ModelSerializer):
|
||||
"""Output serializer for ride photos."""
|
||||
|
||||
uploaded_by_username = serializers.CharField(
|
||||
source='uploaded_by.username', read_only=True)
|
||||
source="uploaded_by.username", read_only=True
|
||||
)
|
||||
file_size = serializers.ReadOnlyField()
|
||||
dimensions = serializers.ReadOnlyField()
|
||||
ride_slug = serializers.CharField(source='ride.slug', read_only=True)
|
||||
ride_name = serializers.CharField(source='ride.name', read_only=True)
|
||||
park_slug = serializers.CharField(source='ride.park.slug', read_only=True)
|
||||
park_name = serializers.CharField(source='ride.park.name', read_only=True)
|
||||
ride_slug = serializers.CharField(source="ride.slug", read_only=True)
|
||||
ride_name = serializers.CharField(source="ride.name", read_only=True)
|
||||
park_slug = serializers.CharField(source="ride.park.slug", read_only=True)
|
||||
park_name = serializers.CharField(source="ride.park.name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RidePhoto
|
||||
fields = [
|
||||
'id',
|
||||
'image',
|
||||
'caption',
|
||||
'alt_text',
|
||||
'is_primary',
|
||||
'is_approved',
|
||||
'photo_type',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'date_taken',
|
||||
'uploaded_by_username',
|
||||
'file_size',
|
||||
'dimensions',
|
||||
'ride_slug',
|
||||
'ride_name',
|
||||
'park_slug',
|
||||
'park_name',
|
||||
"id",
|
||||
"image",
|
||||
"caption",
|
||||
"alt_text",
|
||||
"is_primary",
|
||||
"is_approved",
|
||||
"photo_type",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"date_taken",
|
||||
"uploaded_by_username",
|
||||
"file_size",
|
||||
"dimensions",
|
||||
"ride_slug",
|
||||
"ride_name",
|
||||
"park_slug",
|
||||
"park_name",
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'uploaded_by_username',
|
||||
'file_size',
|
||||
'dimensions',
|
||||
'ride_slug',
|
||||
'ride_name',
|
||||
'park_slug',
|
||||
'park_name',
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"uploaded_by_username",
|
||||
"file_size",
|
||||
"dimensions",
|
||||
"ride_slug",
|
||||
"ride_name",
|
||||
"park_slug",
|
||||
"park_name",
|
||||
]
|
||||
|
||||
|
||||
@@ -61,11 +62,11 @@ class RidePhotoCreateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RidePhoto
|
||||
fields = [
|
||||
'image',
|
||||
'caption',
|
||||
'alt_text',
|
||||
'photo_type',
|
||||
'is_primary',
|
||||
"image",
|
||||
"caption",
|
||||
"alt_text",
|
||||
"photo_type",
|
||||
"is_primary",
|
||||
]
|
||||
|
||||
|
||||
@@ -75,10 +76,10 @@ class RidePhotoUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RidePhoto
|
||||
fields = [
|
||||
'caption',
|
||||
'alt_text',
|
||||
'photo_type',
|
||||
'is_primary',
|
||||
"caption",
|
||||
"alt_text",
|
||||
"photo_type",
|
||||
"is_primary",
|
||||
]
|
||||
|
||||
|
||||
@@ -86,19 +87,20 @@ class RidePhotoListOutputSerializer(serializers.ModelSerializer):
|
||||
"""Simplified output serializer for ride photo lists."""
|
||||
|
||||
uploaded_by_username = serializers.CharField(
|
||||
source='uploaded_by.username', read_only=True)
|
||||
source="uploaded_by.username", read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RidePhoto
|
||||
fields = [
|
||||
'id',
|
||||
'image',
|
||||
'caption',
|
||||
'photo_type',
|
||||
'is_primary',
|
||||
'is_approved',
|
||||
'created_at',
|
||||
'uploaded_by_username',
|
||||
"id",
|
||||
"image",
|
||||
"caption",
|
||||
"photo_type",
|
||||
"is_primary",
|
||||
"is_approved",
|
||||
"created_at",
|
||||
"uploaded_by_username",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
@@ -107,12 +109,10 @@ class RidePhotoApprovalInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for photo approval operations."""
|
||||
|
||||
photo_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="List of photo IDs to approve"
|
||||
child=serializers.IntegerField(), help_text="List of photo IDs to approve"
|
||||
)
|
||||
approve = serializers.BooleanField(
|
||||
default=True,
|
||||
help_text="Whether to approve (True) or reject (False) the photos"
|
||||
default=True, help_text="Whether to approve (True) or reject (False) the photos"
|
||||
)
|
||||
|
||||
|
||||
@@ -125,8 +125,7 @@ class RidePhotoStatsOutputSerializer(serializers.Serializer):
|
||||
has_primary = serializers.BooleanField()
|
||||
recent_uploads = serializers.IntegerField()
|
||||
by_type = serializers.DictField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="Photo counts by type"
|
||||
child=serializers.IntegerField(), help_text="Photo counts by type"
|
||||
)
|
||||
|
||||
|
||||
@@ -135,13 +134,13 @@ class RidePhotoTypeFilterSerializer(serializers.Serializer):
|
||||
|
||||
photo_type = serializers.ChoiceField(
|
||||
choices=[
|
||||
('exterior', 'Exterior View'),
|
||||
('queue', 'Queue Area'),
|
||||
('station', 'Station'),
|
||||
('onride', 'On-Ride'),
|
||||
('construction', 'Construction'),
|
||||
('other', 'Other'),
|
||||
("exterior", "Exterior View"),
|
||||
("queue", "Queue Area"),
|
||||
("station", "Station"),
|
||||
("onride", "On-Ride"),
|
||||
("construction", "Construction"),
|
||||
("other", "Other"),
|
||||
],
|
||||
required=False,
|
||||
help_text="Filter photos by type"
|
||||
help_text="Filter photos by type",
|
||||
)
|
||||
|
||||
@@ -6,11 +6,6 @@ and other search functionality.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema_serializer,
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
|
||||
|
||||
# === CORE ENTITY SEARCH SERIALIZERS ===
|
||||
|
||||
@@ -7,9 +7,7 @@ history tracking, moderation, and roadtrip planning.
|
||||
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema_serializer,
|
||||
extend_schema_field,
|
||||
OpenApiExample,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ from drf_spectacular.utils import (
|
||||
)
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
@@ -400,7 +399,8 @@ class ParkFilterInputSerializer(serializers.Serializer):
|
||||
|
||||
# Status filter
|
||||
status = serializers.MultipleChoiceField(
|
||||
choices=[], required=False # Choices set dynamically
|
||||
choices=[],
|
||||
required=False, # Choices set dynamically
|
||||
)
|
||||
|
||||
# Location filters
|
||||
@@ -777,7 +777,8 @@ class RideFilterInputSerializer(serializers.Serializer):
|
||||
|
||||
# Status filter
|
||||
status = serializers.MultipleChoiceField(
|
||||
choices=[], required=False # Choices set dynamically
|
||||
choices=[],
|
||||
required=False, # Choices set dynamically
|
||||
)
|
||||
|
||||
# Park filter
|
||||
|
||||
@@ -5,18 +5,14 @@ This module contains all authentication-related API endpoints including
|
||||
login, signup, logout, password management, and social authentication.
|
||||
"""
|
||||
|
||||
import time
|
||||
from django.contrib.auth import authenticate, login, logout, get_user_model
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from allauth.socialaccount import providers
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
# Import serializers inside methods to avoid Django initialization issues
|
||||
@@ -274,7 +270,7 @@ class LogoutAPIView(APIView):
|
||||
{"message": "Logout successful"}
|
||||
)
|
||||
return Response(response_serializer.data)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return Response(
|
||||
{"error": "Logout failed"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
@@ -385,7 +381,6 @@ class SocialProvidersAPIView(APIView):
|
||||
|
||||
def get(self, request: Request) -> Response:
|
||||
from django.core.cache import cache
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
|
||||
site = get_current_site(request._request) # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ performance metrics, and database analysis.
|
||||
import time
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
@@ -6,8 +6,6 @@ including trending parks, rides, and recently added content.
|
||||
"""
|
||||
|
||||
from datetime import datetime, date
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
API viewsets for the ride ranking system.
|
||||
"""
|
||||
|
||||
from django.db.models import Q, Count, Max
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
||||
@@ -147,9 +147,7 @@ class RideRankingViewSet(ReadOnlyModelViewSet):
|
||||
ranking = self.get_object()
|
||||
history = RankingSnapshot.objects.filter(ride=ranking.ride).order_by(
|
||||
"-snapshot_date"
|
||||
)[
|
||||
:90
|
||||
] # Last 3 months
|
||||
)[:90] # Last 3 months
|
||||
|
||||
serializer = self.get_serializer(history, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
Reference in New Issue
Block a user