mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
chore(api): remove duplicate serializers/accounts.py after consolidation into auth/serializers.py
This commit is contained in:
@@ -1,491 +0,0 @@
|
|||||||
"""
|
|
||||||
Accounts domain serializers for ThrillWiki API v1.
|
|
||||||
|
|
||||||
This module contains all serializers related to user accounts, profiles,
|
|
||||||
authentication, top lists, and user statistics.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from drf_spectacular.utils import (
|
|
||||||
extend_schema_serializer,
|
|
||||||
extend_schema_field,
|
|
||||||
OpenApiExample,
|
|
||||||
)
|
|
||||||
from django.contrib.auth.password_validation import validate_password
|
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
|
|
||||||
from .shared import UserModel, ModelChoices
|
|
||||||
|
|
||||||
|
|
||||||
# === USER PROFILE SERIALIZERS ===
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_serializer(
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"User Profile Example",
|
|
||||||
summary="Example user profile response",
|
|
||||||
description="A user's profile information",
|
|
||||||
value={
|
|
||||||
"id": 1,
|
|
||||||
"profile_id": "1234",
|
|
||||||
"display_name": "Coaster Enthusiast",
|
|
||||||
"bio": "Love visiting theme parks around the world!",
|
|
||||||
"pronouns": "they/them",
|
|
||||||
"avatar_url": "/media/avatars/user1.jpg",
|
|
||||||
"coaster_credits": 150,
|
|
||||||
"dark_ride_credits": 45,
|
|
||||||
"flat_ride_credits": 80,
|
|
||||||
"water_ride_credits": 25,
|
|
||||||
"user": {
|
|
||||||
"username": "coaster_fan",
|
|
||||||
"date_joined": "2024-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
class UserProfileOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for user profiles."""
|
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
|
||||||
profile_id = serializers.CharField()
|
|
||||||
display_name = serializers.CharField()
|
|
||||||
bio = serializers.CharField()
|
|
||||||
pronouns = serializers.CharField()
|
|
||||||
avatar_url = serializers.SerializerMethodField()
|
|
||||||
twitter = serializers.URLField()
|
|
||||||
instagram = serializers.URLField()
|
|
||||||
youtube = serializers.URLField()
|
|
||||||
discord = serializers.CharField()
|
|
||||||
|
|
||||||
# Ride statistics
|
|
||||||
coaster_credits = serializers.IntegerField()
|
|
||||||
dark_ride_credits = serializers.IntegerField()
|
|
||||||
flat_ride_credits = serializers.IntegerField()
|
|
||||||
water_ride_credits = serializers.IntegerField()
|
|
||||||
|
|
||||||
# User info (limited)
|
|
||||||
user = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.URLField(allow_null=True))
|
|
||||||
def get_avatar_url(self, obj) -> str | None:
|
|
||||||
return obj.get_avatar()
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.DictField())
|
|
||||||
def get_user(self, obj) -> dict:
|
|
||||||
return {
|
|
||||||
"username": obj.user.username,
|
|
||||||
"date_joined": obj.user.date_joined,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileCreateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for creating user profiles."""
|
|
||||||
|
|
||||||
display_name = serializers.CharField(max_length=50)
|
|
||||||
bio = serializers.CharField(max_length=500, allow_blank=True, default="")
|
|
||||||
pronouns = serializers.CharField(max_length=50, allow_blank=True, default="")
|
|
||||||
twitter = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
instagram = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
youtube = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
discord = serializers.CharField(max_length=100, allow_blank=True, default="")
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileUpdateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for updating user profiles."""
|
|
||||||
|
|
||||||
display_name = serializers.CharField(max_length=50, required=False)
|
|
||||||
bio = serializers.CharField(max_length=500, allow_blank=True, required=False)
|
|
||||||
pronouns = serializers.CharField(max_length=50, allow_blank=True, required=False)
|
|
||||||
twitter = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
instagram = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
youtube = serializers.URLField(required=False, allow_blank=True)
|
|
||||||
discord = serializers.CharField(max_length=100, allow_blank=True, required=False)
|
|
||||||
coaster_credits = serializers.IntegerField(required=False)
|
|
||||||
dark_ride_credits = serializers.IntegerField(required=False)
|
|
||||||
flat_ride_credits = serializers.IntegerField(required=False)
|
|
||||||
water_ride_credits = serializers.IntegerField(required=False)
|
|
||||||
|
|
||||||
|
|
||||||
# === TOP LIST SERIALIZERS ===
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_serializer(
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"Top List Example",
|
|
||||||
summary="Example top list response",
|
|
||||||
description="A user's top list of rides or parks",
|
|
||||||
value={
|
|
||||||
"id": 1,
|
|
||||||
"title": "My Top 10 Roller Coasters",
|
|
||||||
"category": "RC",
|
|
||||||
"description": "My favorite roller coasters ranked",
|
|
||||||
"user": {"username": "coaster_fan", "display_name": "Coaster Fan"},
|
|
||||||
"created_at": "2024-01-01T00:00:00Z",
|
|
||||||
"updated_at": "2024-08-15T12:00:00Z",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
class TopListOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for top lists."""
|
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
|
||||||
title = serializers.CharField()
|
|
||||||
category = serializers.CharField()
|
|
||||||
description = serializers.CharField()
|
|
||||||
created_at = serializers.DateTimeField()
|
|
||||||
updated_at = serializers.DateTimeField()
|
|
||||||
|
|
||||||
# User info
|
|
||||||
user = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.DictField())
|
|
||||||
def get_user(self, obj) -> dict:
|
|
||||||
return {
|
|
||||||
"username": obj.user.username,
|
|
||||||
"display_name": obj.user.get_display_name(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TopListCreateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for creating top lists."""
|
|
||||||
|
|
||||||
title = serializers.CharField(max_length=100)
|
|
||||||
category = serializers.ChoiceField(choices=ModelChoices.get_top_list_categories())
|
|
||||||
description = serializers.CharField(allow_blank=True, default="")
|
|
||||||
|
|
||||||
|
|
||||||
class TopListUpdateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for updating top lists."""
|
|
||||||
|
|
||||||
title = serializers.CharField(max_length=100, required=False)
|
|
||||||
category = serializers.ChoiceField(
|
|
||||||
choices=ModelChoices.get_top_list_categories(), required=False
|
|
||||||
)
|
|
||||||
description = serializers.CharField(allow_blank=True, required=False)
|
|
||||||
|
|
||||||
|
|
||||||
# === TOP LIST ITEM SERIALIZERS ===
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_serializer(
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"Top List Item Example",
|
|
||||||
summary="Example top list item response",
|
|
||||||
description="An item in a user's top list",
|
|
||||||
value={
|
|
||||||
"id": 1,
|
|
||||||
"rank": 1,
|
|
||||||
"notes": "Amazing airtime and smooth ride",
|
|
||||||
"object_name": "Steel Vengeance",
|
|
||||||
"object_type": "Ride",
|
|
||||||
"top_list": {"id": 1, "title": "My Top 10 Roller Coasters"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
class TopListItemOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for top list items."""
|
|
||||||
|
|
||||||
id = serializers.IntegerField()
|
|
||||||
rank = serializers.IntegerField()
|
|
||||||
notes = serializers.CharField()
|
|
||||||
object_name = serializers.SerializerMethodField()
|
|
||||||
object_type = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
# Top list info
|
|
||||||
top_list = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.CharField())
|
|
||||||
def get_object_name(self, obj) -> str:
|
|
||||||
"""Get the name of the referenced object."""
|
|
||||||
# This would need to be implemented based on the generic foreign key
|
|
||||||
return "Object Name" # Placeholder
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.CharField())
|
|
||||||
def get_object_type(self, obj) -> str:
|
|
||||||
"""Get the type of the referenced object."""
|
|
||||||
return obj.content_type.model_class().__name__
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.DictField())
|
|
||||||
def get_top_list(self, obj) -> dict:
|
|
||||||
return {
|
|
||||||
"id": obj.top_list.id,
|
|
||||||
"title": obj.top_list.title,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TopListItemCreateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for creating top list items."""
|
|
||||||
|
|
||||||
top_list_id = serializers.IntegerField()
|
|
||||||
content_type_id = serializers.IntegerField()
|
|
||||||
object_id = serializers.IntegerField()
|
|
||||||
rank = serializers.IntegerField(min_value=1)
|
|
||||||
notes = serializers.CharField(allow_blank=True, default="")
|
|
||||||
|
|
||||||
|
|
||||||
class TopListItemUpdateInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for updating top list items."""
|
|
||||||
|
|
||||||
rank = serializers.IntegerField(min_value=1, required=False)
|
|
||||||
notes = serializers.CharField(allow_blank=True, required=False)
|
|
||||||
|
|
||||||
|
|
||||||
# === ACCOUNTS SERIALIZERS ===
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_serializer(
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"User Example",
|
|
||||||
summary="Example user response",
|
|
||||||
description="A typical user object",
|
|
||||||
value={
|
|
||||||
"id": 1,
|
|
||||||
"username": "john_doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"first_name": "John",
|
|
||||||
"last_name": "Doe",
|
|
||||||
"date_joined": "2024-01-01T12:00:00Z",
|
|
||||||
"is_active": True,
|
|
||||||
"avatar_url": "https://example.com/avatars/john.jpg",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
class UserOutputSerializer(serializers.ModelSerializer):
|
|
||||||
"""User serializer for API responses."""
|
|
||||||
|
|
||||||
avatar_url = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = UserModel
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"username",
|
|
||||||
"email",
|
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
"date_joined",
|
|
||||||
"is_active",
|
|
||||||
"avatar_url",
|
|
||||||
]
|
|
||||||
read_only_fields = ["id", "date_joined", "is_active"]
|
|
||||||
|
|
||||||
@extend_schema_field(serializers.URLField(allow_null=True))
|
|
||||||
def get_avatar_url(self, obj) -> str | None:
|
|
||||||
"""Get user avatar URL."""
|
|
||||||
if hasattr(obj, "profile") and obj.profile.avatar:
|
|
||||||
return obj.profile.avatar.url
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class LoginInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for user login."""
|
|
||||||
|
|
||||||
username = serializers.CharField(
|
|
||||||
max_length=254, help_text="Username or email address"
|
|
||||||
)
|
|
||||||
password = serializers.CharField(
|
|
||||||
max_length=128, style={"input_type": "password"}, trim_whitespace=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
username = attrs.get("username")
|
|
||||||
password = attrs.get("password")
|
|
||||||
|
|
||||||
if username and password:
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
raise serializers.ValidationError("Must include username/email and password.")
|
|
||||||
|
|
||||||
|
|
||||||
class LoginOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for successful login."""
|
|
||||||
|
|
||||||
token = serializers.CharField()
|
|
||||||
user = UserOutputSerializer()
|
|
||||||
message = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class SignupInputSerializer(serializers.ModelSerializer):
|
|
||||||
"""Input serializer for user registration."""
|
|
||||||
|
|
||||||
password = serializers.CharField(
|
|
||||||
write_only=True,
|
|
||||||
validators=[validate_password],
|
|
||||||
style={"input_type": "password"},
|
|
||||||
)
|
|
||||||
password_confirm = serializers.CharField(
|
|
||||||
write_only=True, style={"input_type": "password"}
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = UserModel
|
|
||||||
fields = [
|
|
||||||
"username",
|
|
||||||
"email",
|
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
"password",
|
|
||||||
"password_confirm",
|
|
||||||
]
|
|
||||||
extra_kwargs = {
|
|
||||||
"password": {"write_only": True},
|
|
||||||
"email": {"required": True},
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate_email(self, value):
|
|
||||||
"""Validate email is unique."""
|
|
||||||
if UserModel.objects.filter(email=value).exists():
|
|
||||||
raise serializers.ValidationError("A user with this email already exists.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate_username(self, value):
|
|
||||||
"""Validate username is unique."""
|
|
||||||
if UserModel.objects.filter(username=value).exists():
|
|
||||||
raise serializers.ValidationError(
|
|
||||||
"A user with this username already exists."
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Validate passwords match."""
|
|
||||||
password = attrs.get("password")
|
|
||||||
password_confirm = attrs.get("password_confirm")
|
|
||||||
|
|
||||||
if password != password_confirm:
|
|
||||||
raise serializers.ValidationError(
|
|
||||||
{"password_confirm": "Passwords do not match."}
|
|
||||||
)
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
"""Create user with validated data."""
|
|
||||||
validated_data.pop("password_confirm", None)
|
|
||||||
password = validated_data.pop("password")
|
|
||||||
|
|
||||||
# Use type: ignore for Django's create_user method which isn't properly typed
|
|
||||||
user = UserModel.objects.create_user( # type: ignore[attr-defined]
|
|
||||||
password=password, **validated_data
|
|
||||||
)
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
class SignupOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for successful signup."""
|
|
||||||
|
|
||||||
token = serializers.CharField()
|
|
||||||
user = UserOutputSerializer()
|
|
||||||
message = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for password reset request."""
|
|
||||||
|
|
||||||
email = serializers.EmailField()
|
|
||||||
|
|
||||||
def validate_email(self, value):
|
|
||||||
"""Validate email exists."""
|
|
||||||
try:
|
|
||||||
user = UserModel.objects.get(email=value)
|
|
||||||
self.user = user
|
|
||||||
return value
|
|
||||||
except UserModel.DoesNotExist:
|
|
||||||
# Don't reveal if email exists or not for security
|
|
||||||
return value
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
"""Send password reset email if user exists."""
|
|
||||||
if hasattr(self, "user"):
|
|
||||||
# Create password reset token
|
|
||||||
token = get_random_string(64)
|
|
||||||
# Note: PasswordReset model would need to be imported
|
|
||||||
# PasswordReset.objects.update_or_create(...)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for password reset request."""
|
|
||||||
|
|
||||||
detail = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeInputSerializer(serializers.Serializer):
|
|
||||||
"""Input serializer for password change."""
|
|
||||||
|
|
||||||
old_password = serializers.CharField(
|
|
||||||
max_length=128, style={"input_type": "password"}
|
|
||||||
)
|
|
||||||
new_password = serializers.CharField(
|
|
||||||
max_length=128,
|
|
||||||
validators=[validate_password],
|
|
||||||
style={"input_type": "password"},
|
|
||||||
)
|
|
||||||
new_password_confirm = serializers.CharField(
|
|
||||||
max_length=128, style={"input_type": "password"}
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_old_password(self, value):
|
|
||||||
"""Validate old password is correct."""
|
|
||||||
user = self.context["request"].user
|
|
||||||
if not user.check_password(value):
|
|
||||||
raise serializers.ValidationError("Old password is incorrect.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Validate new passwords match."""
|
|
||||||
new_password = attrs.get("new_password")
|
|
||||||
new_password_confirm = attrs.get("new_password_confirm")
|
|
||||||
|
|
||||||
if new_password != new_password_confirm:
|
|
||||||
raise serializers.ValidationError(
|
|
||||||
{"new_password_confirm": "New passwords do not match."}
|
|
||||||
)
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def save(self, **kwargs):
|
|
||||||
"""Change user password."""
|
|
||||||
user = self.context["request"].user
|
|
||||||
# validated_data is guaranteed to exist after is_valid() is called
|
|
||||||
new_password = self.validated_data["new_password"] # type: ignore[index]
|
|
||||||
|
|
||||||
user.set_password(new_password)
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordChangeOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for password change."""
|
|
||||||
|
|
||||||
detail = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for logout."""
|
|
||||||
|
|
||||||
message = serializers.CharField()
|
|
||||||
|
|
||||||
|
|
||||||
class SocialProviderOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for social authentication providers."""
|
|
||||||
|
|
||||||
id = serializers.CharField()
|
|
||||||
name = serializers.CharField()
|
|
||||||
authUrl = serializers.URLField()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthStatusOutputSerializer(serializers.Serializer):
|
|
||||||
"""Output serializer for authentication status check."""
|
|
||||||
|
|
||||||
authenticated = serializers.BooleanField()
|
|
||||||
user = UserOutputSerializer(allow_null=True)
|
|
||||||
Reference in New Issue
Block a user