mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-04-13 18:40:40 -04:00
feat: Add blog, media, and support apps, implement ride credits and image API, and remove toplist feature.
This commit is contained in:
@@ -90,12 +90,7 @@ _ACCOUNTS_SYMBOLS: List[str] = [
|
||||
"UserProfileOutputSerializer",
|
||||
"UserProfileCreateInputSerializer",
|
||||
"UserProfileUpdateInputSerializer",
|
||||
"TopListOutputSerializer",
|
||||
"TopListCreateInputSerializer",
|
||||
"TopListUpdateInputSerializer",
|
||||
"TopListItemOutputSerializer",
|
||||
"TopListItemCreateInputSerializer",
|
||||
"TopListItemUpdateInputSerializer",
|
||||
|
||||
"UserOutputSerializer",
|
||||
"LoginInputSerializer",
|
||||
"LoginOutputSerializer",
|
||||
|
||||
@@ -18,6 +18,7 @@ from apps.accounts.models import (
|
||||
NotificationPreference,
|
||||
)
|
||||
from apps.lists.models import UserList
|
||||
from apps.rides.models.credits import RideCredit
|
||||
from apps.core.choices.serializers import RichChoiceFieldSerializer
|
||||
|
||||
UserModel = get_user_model()
|
||||
@@ -66,6 +67,8 @@ class UserProfileSerializer(serializers.ModelSerializer):
|
||||
|
||||
avatar_url = serializers.SerializerMethodField()
|
||||
avatar_variants = serializers.SerializerMethodField()
|
||||
total_credits = serializers.SerializerMethodField()
|
||||
unique_parks = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
@@ -87,8 +90,19 @@ class UserProfileSerializer(serializers.ModelSerializer):
|
||||
"water_ride_credits",
|
||||
"unit_system",
|
||||
"location",
|
||||
"total_credits",
|
||||
"unique_parks",
|
||||
]
|
||||
read_only_fields = ["profile_id", "avatar_url", "avatar_variants"]
|
||||
read_only_fields = ["profile_id", "avatar_url", "avatar_variants", "total_credits", "unique_parks"]
|
||||
|
||||
def get_total_credits(self, obj):
|
||||
"""Get the total number of ride credits."""
|
||||
return RideCredit.objects.filter(user=obj.user).count()
|
||||
|
||||
def get_unique_parks(self, obj):
|
||||
"""Get the number of unique parks visited."""
|
||||
# This assumes RideCredit -> Ride -> Park relationship
|
||||
return RideCredit.objects.filter(user=obj.user).values("ride__park").distinct().count()
|
||||
|
||||
def get_avatar_url(self, obj):
|
||||
"""Get the avatar URL with fallback to default letter-based avatar."""
|
||||
@@ -167,6 +181,25 @@ class CompleteUserSerializer(serializers.ModelSerializer):
|
||||
read_only_fields = ["user_id", "date_joined", "role"]
|
||||
|
||||
|
||||
class PublicUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Public user serializer for viewing other users' profiles.
|
||||
Only exposes public information.
|
||||
"""
|
||||
profile = UserProfileSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"user_id",
|
||||
"username",
|
||||
"date_joined",
|
||||
"role",
|
||||
"profile",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
# === USER SETTINGS SERIALIZERS ===
|
||||
|
||||
|
||||
|
||||
171
backend/apps/api/v1/serializers/park_reviews.py
Normal file
171
backend/apps/api/v1/serializers/park_reviews.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Serializers for park review API endpoints.
|
||||
|
||||
This module contains serializers for park review CRUD operations.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field, extend_schema_serializer, OpenApiExample
|
||||
from apps.parks.models.reviews import ParkReview
|
||||
from apps.api.v1.serializers.reviews import ReviewUserSerializer
|
||||
|
||||
@extend_schema_serializer(
|
||||
examples=[
|
||||
OpenApiExample(
|
||||
name="Complete Park Review",
|
||||
summary="Full park review response",
|
||||
description="Example response showing all fields for a park review",
|
||||
value={
|
||||
"id": 123,
|
||||
"title": "Great family park!",
|
||||
"content": "We had a wonderful time. The atmosphere is charming.",
|
||||
"rating": 9,
|
||||
"visit_date": "2023-06-15",
|
||||
"created_at": "2023-06-16T10:30:00Z",
|
||||
"updated_at": "2023-06-16T10:30:00Z",
|
||||
"is_published": True,
|
||||
"user": {
|
||||
"username": "park_fan",
|
||||
"display_name": "Park Fan",
|
||||
"avatar_url": "https://example.com/avatar.jpg"
|
||||
},
|
||||
"park": {
|
||||
"id": 101,
|
||||
"name": "Cedar Point",
|
||||
"slug": "cedar-point"
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
class ParkReviewOutputSerializer(serializers.ModelSerializer):
|
||||
"""Output serializer for park reviews."""
|
||||
|
||||
user = ReviewUserSerializer(read_only=True)
|
||||
park = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ParkReview
|
||||
fields = [
|
||||
"id",
|
||||
"title",
|
||||
"content",
|
||||
"rating",
|
||||
"visit_date",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"is_published",
|
||||
"user",
|
||||
"park",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"user",
|
||||
"park",
|
||||
]
|
||||
|
||||
@extend_schema_field(serializers.DictField())
|
||||
def get_park(self, obj):
|
||||
"""Get park information."""
|
||||
return {
|
||||
"id": obj.park.id,
|
||||
"name": obj.park.name,
|
||||
"slug": obj.park.slug,
|
||||
}
|
||||
|
||||
|
||||
class ParkReviewCreateInputSerializer(serializers.ModelSerializer):
|
||||
"""Input serializer for creating park reviews."""
|
||||
|
||||
class Meta:
|
||||
model = ParkReview
|
||||
fields = [
|
||||
"title",
|
||||
"content",
|
||||
"rating",
|
||||
"visit_date",
|
||||
]
|
||||
|
||||
def validate_rating(self, value):
|
||||
"""Validate rating is between 1 and 10."""
|
||||
if not (1 <= value <= 10):
|
||||
raise serializers.ValidationError("Rating must be between 1 and 10.")
|
||||
return value
|
||||
|
||||
|
||||
class ParkReviewUpdateInputSerializer(serializers.ModelSerializer):
|
||||
"""Input serializer for updating park reviews."""
|
||||
|
||||
class Meta:
|
||||
model = ParkReview
|
||||
fields = [
|
||||
"title",
|
||||
"content",
|
||||
"rating",
|
||||
"visit_date",
|
||||
]
|
||||
|
||||
def validate_rating(self, value):
|
||||
"""Validate rating is between 1 and 10."""
|
||||
if not (1 <= value <= 10):
|
||||
raise serializers.ValidationError("Rating must be between 1 and 10.")
|
||||
return value
|
||||
|
||||
|
||||
class ParkReviewListOutputSerializer(serializers.ModelSerializer):
|
||||
"""Simplified output serializer for park review lists."""
|
||||
|
||||
user = ReviewUserSerializer(read_only=True)
|
||||
park_name = serializers.CharField(source="park.name", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ParkReview
|
||||
fields = [
|
||||
"id",
|
||||
"title",
|
||||
"rating",
|
||||
"visit_date",
|
||||
"created_at",
|
||||
"is_published",
|
||||
"user",
|
||||
"park_name",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class ParkReviewStatsOutputSerializer(serializers.Serializer):
|
||||
"""Output serializer for park review statistics."""
|
||||
|
||||
total_reviews = serializers.IntegerField()
|
||||
published_reviews = serializers.IntegerField()
|
||||
pending_reviews = serializers.IntegerField()
|
||||
average_rating = serializers.FloatField(allow_null=True)
|
||||
rating_distribution = serializers.DictField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="Count of reviews by rating (1-10)"
|
||||
)
|
||||
recent_reviews = serializers.IntegerField()
|
||||
|
||||
|
||||
class ParkReviewModerationInputSerializer(serializers.Serializer):
|
||||
"""Input serializer for review moderation operations."""
|
||||
|
||||
review_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
help_text="List of review IDs to moderate"
|
||||
)
|
||||
action = serializers.ChoiceField(
|
||||
choices=[
|
||||
("publish", "Publish"),
|
||||
("unpublish", "Unpublish"),
|
||||
("delete", "Delete"),
|
||||
],
|
||||
help_text="Moderation action to perform"
|
||||
)
|
||||
moderation_notes = serializers.CharField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
help_text="Optional notes about the moderation action"
|
||||
)
|
||||
47
backend/apps/api/v1/serializers/ride_credits.py
Normal file
47
backend/apps/api/v1/serializers/ride_credits.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from apps.rides.models.credits import RideCredit
|
||||
from apps.rides.models import Ride
|
||||
from apps.api.v1.serializers.rides import RideListOutputSerializer
|
||||
|
||||
class RideCreditSerializer(serializers.ModelSerializer):
|
||||
"""Serializer for user ride credits."""
|
||||
|
||||
ride_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Ride.objects.all(), source='ride', write_only=True
|
||||
)
|
||||
ride = RideListOutputSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RideCredit
|
||||
fields = [
|
||||
'id',
|
||||
'ride',
|
||||
'ride_id',
|
||||
'count',
|
||||
'rating',
|
||||
'first_ridden_at',
|
||||
'last_ridden_at',
|
||||
'notes',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
def validate(self, attrs):
|
||||
"""
|
||||
Validate data.
|
||||
"""
|
||||
# Ensure dates make sense
|
||||
first = attrs.get('first_ridden_at')
|
||||
last = attrs.get('last_ridden_at')
|
||||
if first and last and last < first:
|
||||
raise serializers.ValidationError("Last ridden date cannot be before first ridden date.")
|
||||
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Create a new ride credit."""
|
||||
user = self.context['request'].user
|
||||
validated_data['user'] = user
|
||||
return super().create(validated_data)
|
||||
Reference in New Issue
Block a user