mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:31:08 -05:00
- 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.
653 lines
22 KiB
Python
653 lines
22 KiB
Python
"""
|
|
Rides domain serializers for ThrillWiki API v1.
|
|
|
|
This module contains all serializers related to rides, roller coaster statistics,
|
|
ride locations, and ride reviews.
|
|
"""
|
|
|
|
from rest_framework import serializers
|
|
from drf_spectacular.utils import (
|
|
extend_schema_serializer,
|
|
extend_schema_field,
|
|
OpenApiExample,
|
|
)
|
|
|
|
from .shared import ModelChoices
|
|
|
|
|
|
# === RIDE SERIALIZERS ===
|
|
|
|
|
|
class RideParkOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride's park data."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
|
|
|
|
class RideModelOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride model data."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
description = serializers.CharField()
|
|
category = serializers.CharField()
|
|
manufacturer = serializers.SerializerMethodField()
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_manufacturer(self, obj) -> dict | None:
|
|
if obj.manufacturer:
|
|
return {
|
|
"id": obj.manufacturer.id,
|
|
"name": obj.manufacturer.name,
|
|
"slug": obj.manufacturer.slug,
|
|
}
|
|
return None
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Ride List Example",
|
|
summary="Example ride list response",
|
|
description="A typical ride in the list view",
|
|
value={
|
|
"id": 1,
|
|
"name": "Steel Vengeance",
|
|
"slug": "steel-vengeance",
|
|
"category": "ROLLER_COASTER",
|
|
"status": "OPERATING",
|
|
"description": "Hybrid roller coaster",
|
|
"park": {"id": 1, "name": "Cedar Point", "slug": "cedar-point"},
|
|
"average_rating": 4.8,
|
|
"capacity_per_hour": 1200,
|
|
"opening_date": "2018-05-05",
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RideListOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride list view."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
category = serializers.CharField()
|
|
status = serializers.CharField()
|
|
description = serializers.CharField()
|
|
|
|
# Park info
|
|
park = RideParkOutputSerializer()
|
|
|
|
# Statistics
|
|
average_rating = serializers.DecimalField(
|
|
max_digits=3, decimal_places=2, allow_null=True
|
|
)
|
|
capacity_per_hour = serializers.IntegerField(allow_null=True)
|
|
|
|
# Dates
|
|
opening_date = serializers.DateField(allow_null=True)
|
|
closing_date = serializers.DateField(allow_null=True)
|
|
|
|
# Metadata
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Ride Detail Example",
|
|
summary="Example ride detail response",
|
|
description="A complete ride detail response",
|
|
value={
|
|
"id": 1,
|
|
"name": "Steel Vengeance",
|
|
"slug": "steel-vengeance",
|
|
"category": "ROLLER_COASTER",
|
|
"status": "OPERATING",
|
|
"description": "Hybrid roller coaster featuring RMC I-Box track",
|
|
"park": {"id": 1, "name": "Cedar Point", "slug": "cedar-point"},
|
|
"opening_date": "2018-05-05",
|
|
"min_height_in": 48,
|
|
"capacity_per_hour": 1200,
|
|
"ride_duration_seconds": 150,
|
|
"average_rating": 4.8,
|
|
"manufacturer": {
|
|
"id": 1,
|
|
"name": "Rocky Mountain Construction",
|
|
"slug": "rocky-mountain-construction",
|
|
},
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RideDetailOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride detail view."""
|
|
|
|
id = serializers.IntegerField()
|
|
name = serializers.CharField()
|
|
slug = serializers.CharField()
|
|
category = serializers.CharField()
|
|
status = serializers.CharField()
|
|
post_closing_status = serializers.CharField(allow_null=True)
|
|
description = serializers.CharField()
|
|
|
|
# Park info
|
|
park = RideParkOutputSerializer()
|
|
park_area = serializers.SerializerMethodField()
|
|
|
|
# Dates
|
|
opening_date = serializers.DateField(allow_null=True)
|
|
closing_date = serializers.DateField(allow_null=True)
|
|
status_since = serializers.DateField(allow_null=True)
|
|
|
|
# Physical specs
|
|
min_height_in = serializers.IntegerField(allow_null=True)
|
|
max_height_in = serializers.IntegerField(allow_null=True)
|
|
capacity_per_hour = serializers.IntegerField(allow_null=True)
|
|
ride_duration_seconds = serializers.IntegerField(allow_null=True)
|
|
|
|
# Statistics
|
|
average_rating = serializers.DecimalField(
|
|
max_digits=3, decimal_places=2, allow_null=True
|
|
)
|
|
|
|
# Companies
|
|
manufacturer = serializers.SerializerMethodField()
|
|
designer = serializers.SerializerMethodField()
|
|
|
|
# Model
|
|
ride_model = RideModelOutputSerializer(allow_null=True)
|
|
|
|
# Metadata
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_park_area(self, obj) -> dict | None:
|
|
if obj.park_area:
|
|
return {
|
|
"id": obj.park_area.id,
|
|
"name": obj.park_area.name,
|
|
"slug": obj.park_area.slug,
|
|
}
|
|
return None
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_manufacturer(self, obj) -> dict | None:
|
|
if obj.manufacturer:
|
|
return {
|
|
"id": obj.manufacturer.id,
|
|
"name": obj.manufacturer.name,
|
|
"slug": obj.manufacturer.slug,
|
|
}
|
|
return None
|
|
|
|
@extend_schema_field(serializers.DictField(allow_null=True))
|
|
def get_designer(self, obj) -> dict | None:
|
|
if obj.designer:
|
|
return {
|
|
"id": obj.designer.id,
|
|
"name": obj.designer.name,
|
|
"slug": obj.designer.slug,
|
|
}
|
|
return None
|
|
|
|
|
|
class RideCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating rides."""
|
|
|
|
name = serializers.CharField(max_length=255)
|
|
description = serializers.CharField(allow_blank=True, default="")
|
|
category = serializers.ChoiceField(choices=[]) # Choices set dynamically
|
|
status = serializers.ChoiceField(
|
|
choices=[], default="OPERATING"
|
|
) # Choices set dynamically
|
|
|
|
# Required park
|
|
park_id = serializers.IntegerField()
|
|
|
|
# Optional area
|
|
park_area_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
# Optional dates
|
|
opening_date = serializers.DateField(required=False, allow_null=True)
|
|
closing_date = serializers.DateField(required=False, allow_null=True)
|
|
status_since = serializers.DateField(required=False, allow_null=True)
|
|
|
|
# Optional specs
|
|
min_height_in = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=30, max_value=90
|
|
)
|
|
max_height_in = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=30, max_value=90
|
|
)
|
|
capacity_per_hour = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
ride_duration_seconds = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
|
|
# Optional companies
|
|
manufacturer_id = serializers.IntegerField(required=False, allow_null=True)
|
|
designer_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
# Optional model
|
|
ride_model_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Date validation
|
|
opening_date = attrs.get("opening_date")
|
|
closing_date = attrs.get("closing_date")
|
|
|
|
if opening_date and closing_date and closing_date < opening_date:
|
|
raise serializers.ValidationError(
|
|
"Closing date cannot be before opening date"
|
|
)
|
|
|
|
# Height validation
|
|
min_height = attrs.get("min_height_in")
|
|
max_height = attrs.get("max_height_in")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class RideUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating rides."""
|
|
|
|
name = serializers.CharField(max_length=255, required=False)
|
|
description = serializers.CharField(allow_blank=True, required=False)
|
|
category = serializers.ChoiceField(
|
|
choices=[], required=False
|
|
) # Choices set dynamically
|
|
status = serializers.ChoiceField(
|
|
choices=[], required=False
|
|
) # Choices set dynamically
|
|
post_closing_status = serializers.ChoiceField(
|
|
choices=ModelChoices.get_ride_post_closing_choices(),
|
|
required=False,
|
|
allow_null=True,
|
|
)
|
|
|
|
# Park and area
|
|
park_id = serializers.IntegerField(required=False)
|
|
park_area_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
# Dates
|
|
opening_date = serializers.DateField(required=False, allow_null=True)
|
|
closing_date = serializers.DateField(required=False, allow_null=True)
|
|
status_since = serializers.DateField(required=False, allow_null=True)
|
|
|
|
# Specs
|
|
min_height_in = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=30, max_value=90
|
|
)
|
|
max_height_in = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=30, max_value=90
|
|
)
|
|
capacity_per_hour = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
ride_duration_seconds = serializers.IntegerField(
|
|
required=False, allow_null=True, min_value=1
|
|
)
|
|
|
|
# Companies
|
|
manufacturer_id = serializers.IntegerField(required=False, allow_null=True)
|
|
designer_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
# Model
|
|
ride_model_id = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
def validate(self, attrs):
|
|
"""Cross-field validation."""
|
|
# Date validation
|
|
opening_date = attrs.get("opening_date")
|
|
closing_date = attrs.get("closing_date")
|
|
|
|
if opening_date and closing_date and closing_date < opening_date:
|
|
raise serializers.ValidationError(
|
|
"Closing date cannot be before opening date"
|
|
)
|
|
|
|
# Height validation
|
|
min_height = attrs.get("min_height_in")
|
|
max_height = attrs.get("max_height_in")
|
|
|
|
if min_height and max_height and min_height > max_height:
|
|
raise serializers.ValidationError(
|
|
"Minimum height cannot be greater than maximum height"
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class RideFilterInputSerializer(serializers.Serializer):
|
|
"""Input serializer for ride filtering and search."""
|
|
|
|
# Search
|
|
search = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
# Category filter
|
|
category = serializers.MultipleChoiceField(
|
|
choices=[], required=False
|
|
) # Choices set dynamically
|
|
|
|
# Status filter
|
|
status = serializers.MultipleChoiceField(
|
|
choices=[],
|
|
required=False, # Choices set dynamically
|
|
)
|
|
|
|
# Park filter
|
|
park_id = serializers.IntegerField(required=False)
|
|
park_slug = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
# Company filters
|
|
manufacturer_id = serializers.IntegerField(required=False)
|
|
designer_id = serializers.IntegerField(required=False)
|
|
|
|
# Rating filter
|
|
min_rating = serializers.DecimalField(
|
|
max_digits=3,
|
|
decimal_places=2,
|
|
required=False,
|
|
min_value=1,
|
|
max_value=10,
|
|
)
|
|
|
|
# Height filters
|
|
min_height_requirement = serializers.IntegerField(required=False)
|
|
max_height_requirement = serializers.IntegerField(required=False)
|
|
|
|
# Capacity filter
|
|
min_capacity = serializers.IntegerField(required=False)
|
|
|
|
# Ordering
|
|
ordering = serializers.ChoiceField(
|
|
choices=[
|
|
"name",
|
|
"-name",
|
|
"opening_date",
|
|
"-opening_date",
|
|
"average_rating",
|
|
"-average_rating",
|
|
"capacity_per_hour",
|
|
"-capacity_per_hour",
|
|
"created_at",
|
|
"-created_at",
|
|
],
|
|
required=False,
|
|
default="name",
|
|
)
|
|
|
|
|
|
# === ROLLER COASTER STATS SERIALIZERS ===
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Roller Coaster Stats Example",
|
|
summary="Example roller coaster statistics",
|
|
description="Detailed statistics for a roller coaster",
|
|
value={
|
|
"id": 1,
|
|
"ride": {"id": 1, "name": "Steel Vengeance", "slug": "steel-vengeance"},
|
|
"height_ft": 205.0,
|
|
"length_ft": 5740.0,
|
|
"speed_mph": 74.0,
|
|
"inversions": 4,
|
|
"ride_time_seconds": 150,
|
|
"track_material": "HYBRID",
|
|
"roller_coaster_type": "SITDOWN",
|
|
"launch_type": "CHAIN",
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RollerCoasterStatsOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for roller coaster statistics."""
|
|
|
|
id = serializers.IntegerField()
|
|
height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
length_ft = serializers.DecimalField(
|
|
max_digits=7, decimal_places=2, allow_null=True
|
|
)
|
|
speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, allow_null=True
|
|
)
|
|
inversions = serializers.IntegerField()
|
|
ride_time_seconds = serializers.IntegerField(allow_null=True)
|
|
track_type = serializers.CharField()
|
|
track_material = serializers.CharField()
|
|
roller_coaster_type = serializers.CharField()
|
|
max_drop_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, allow_null=True
|
|
)
|
|
launch_type = serializers.CharField()
|
|
train_style = serializers.CharField()
|
|
trains_count = serializers.IntegerField(allow_null=True)
|
|
cars_per_train = serializers.IntegerField(allow_null=True)
|
|
seats_per_car = serializers.IntegerField(allow_null=True)
|
|
|
|
# Ride info
|
|
ride = serializers.SerializerMethodField()
|
|
|
|
@extend_schema_field(serializers.DictField())
|
|
def get_ride(self, obj) -> dict:
|
|
return {
|
|
"id": obj.ride.id,
|
|
"name": obj.ride.name,
|
|
"slug": obj.ride.slug,
|
|
}
|
|
|
|
|
|
class RollerCoasterStatsCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating roller coaster statistics."""
|
|
|
|
ride_id = serializers.IntegerField()
|
|
height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
length_ft = serializers.DecimalField(
|
|
max_digits=7, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
inversions = serializers.IntegerField(default=0)
|
|
ride_time_seconds = serializers.IntegerField(required=False, allow_null=True)
|
|
track_type = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
track_material = serializers.ChoiceField(
|
|
choices=ModelChoices.get_coaster_track_choices(), default="STEEL"
|
|
)
|
|
roller_coaster_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_coaster_type_choices(), default="SITDOWN"
|
|
)
|
|
max_drop_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
launch_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_launch_choices(), default="CHAIN"
|
|
)
|
|
train_style = serializers.CharField(max_length=255, allow_blank=True, default="")
|
|
trains_count = serializers.IntegerField(required=False, allow_null=True)
|
|
cars_per_train = serializers.IntegerField(required=False, allow_null=True)
|
|
seats_per_car = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
|
|
class RollerCoasterStatsUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating roller coaster statistics."""
|
|
|
|
height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
length_ft = serializers.DecimalField(
|
|
max_digits=7, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
speed_mph = serializers.DecimalField(
|
|
max_digits=5, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
inversions = serializers.IntegerField(required=False)
|
|
ride_time_seconds = serializers.IntegerField(required=False, allow_null=True)
|
|
track_type = serializers.CharField(max_length=255, allow_blank=True, required=False)
|
|
track_material = serializers.ChoiceField(
|
|
choices=ModelChoices.get_coaster_track_choices(), required=False
|
|
)
|
|
roller_coaster_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_coaster_type_choices(), required=False
|
|
)
|
|
max_drop_height_ft = serializers.DecimalField(
|
|
max_digits=6, decimal_places=2, required=False, allow_null=True
|
|
)
|
|
launch_type = serializers.ChoiceField(
|
|
choices=ModelChoices.get_launch_choices(), required=False
|
|
)
|
|
train_style = serializers.CharField(
|
|
max_length=255, allow_blank=True, required=False
|
|
)
|
|
trains_count = serializers.IntegerField(required=False, allow_null=True)
|
|
cars_per_train = serializers.IntegerField(required=False, allow_null=True)
|
|
seats_per_car = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
|
|
# === RIDE LOCATION SERIALIZERS ===
|
|
|
|
|
|
class RideLocationOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride locations."""
|
|
|
|
id = serializers.IntegerField()
|
|
latitude = serializers.FloatField(allow_null=True)
|
|
longitude = serializers.FloatField(allow_null=True)
|
|
coordinates = serializers.CharField()
|
|
|
|
# Ride info
|
|
ride = serializers.SerializerMethodField()
|
|
|
|
@extend_schema_field(serializers.DictField())
|
|
def get_ride(self, obj) -> dict:
|
|
return {
|
|
"id": obj.ride.id,
|
|
"name": obj.ride.name,
|
|
"slug": obj.ride.slug,
|
|
}
|
|
|
|
|
|
class RideLocationCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride locations."""
|
|
|
|
ride_id = serializers.IntegerField()
|
|
latitude = serializers.FloatField(required=False, allow_null=True)
|
|
longitude = serializers.FloatField(required=False, allow_null=True)
|
|
|
|
|
|
class RideLocationUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride locations."""
|
|
|
|
latitude = serializers.FloatField(required=False, allow_null=True)
|
|
longitude = serializers.FloatField(required=False, allow_null=True)
|
|
|
|
|
|
# === RIDE REVIEW SERIALIZERS ===
|
|
|
|
|
|
@extend_schema_serializer(
|
|
examples=[
|
|
OpenApiExample(
|
|
"Ride Review Example",
|
|
summary="Example ride review response",
|
|
description="A user review of a ride",
|
|
value={
|
|
"id": 1,
|
|
"rating": 9,
|
|
"title": "Amazing coaster!",
|
|
"content": "This ride was incredible, the airtime was fantastic.",
|
|
"visit_date": "2024-08-15",
|
|
"ride": {"id": 1, "name": "Steel Vengeance", "slug": "steel-vengeance"},
|
|
"user": {"username": "coaster_fan", "display_name": "Coaster Fan"},
|
|
"created_at": "2024-08-16T10:30:00Z",
|
|
"is_published": True,
|
|
},
|
|
)
|
|
]
|
|
)
|
|
class RideReviewOutputSerializer(serializers.Serializer):
|
|
"""Output serializer for ride reviews."""
|
|
|
|
id = serializers.IntegerField()
|
|
rating = serializers.IntegerField()
|
|
title = serializers.CharField()
|
|
content = serializers.CharField()
|
|
visit_date = serializers.DateField()
|
|
created_at = serializers.DateTimeField()
|
|
updated_at = serializers.DateTimeField()
|
|
is_published = serializers.BooleanField()
|
|
|
|
# Ride info
|
|
ride = serializers.SerializerMethodField()
|
|
# User info (limited for privacy)
|
|
user = serializers.SerializerMethodField()
|
|
|
|
@extend_schema_field(serializers.DictField())
|
|
def get_ride(self, obj) -> dict:
|
|
return {
|
|
"id": obj.ride.id,
|
|
"name": obj.ride.name,
|
|
"slug": obj.ride.slug,
|
|
}
|
|
|
|
@extend_schema_field(serializers.DictField())
|
|
def get_user(self, obj) -> dict:
|
|
return {
|
|
"username": obj.user.username,
|
|
"display_name": obj.user.get_display_name(),
|
|
}
|
|
|
|
|
|
class RideReviewCreateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for creating ride reviews."""
|
|
|
|
ride_id = serializers.IntegerField()
|
|
rating = serializers.IntegerField(min_value=1, max_value=10)
|
|
title = serializers.CharField(max_length=200)
|
|
content = serializers.CharField()
|
|
visit_date = serializers.DateField()
|
|
|
|
def validate_visit_date(self, value):
|
|
"""Validate visit date is not in the future."""
|
|
from django.utils import timezone
|
|
|
|
if value > timezone.now().date():
|
|
raise serializers.ValidationError("Visit date cannot be in the future")
|
|
return value
|
|
|
|
|
|
class RideReviewUpdateInputSerializer(serializers.Serializer):
|
|
"""Input serializer for updating ride reviews."""
|
|
|
|
rating = serializers.IntegerField(min_value=1, max_value=10, required=False)
|
|
title = serializers.CharField(max_length=200, required=False)
|
|
content = serializers.CharField(required=False)
|
|
visit_date = serializers.DateField(required=False)
|
|
|
|
def validate_visit_date(self, value):
|
|
"""Validate visit date is not in the future."""
|
|
from django.utils import timezone
|
|
|
|
if value and value > timezone.now().date():
|
|
raise serializers.ValidationError("Visit date cannot be in the future")
|
|
return value
|