mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
feat: Add PrimeProgress, PrimeSelect, and PrimeSkeleton components with customizable styles and props
- Implemented PrimeProgress component with support for labels, helper text, and various styles (size, variant, color). - Created PrimeSelect component with dropdown functionality, custom templates, and validation states. - Developed PrimeSkeleton component for loading placeholders with different shapes and animations. - Updated index.ts to export new components for easy import. - Enhanced PrimeVueTest.vue to include tests for new components and their functionalities. - Introduced a custom ThrillWiki theme for PrimeVue with tailored color schemes and component styles. - Added ambient type declarations for various components to improve TypeScript support.
This commit is contained in:
86
backend/apps/api/v1/accounts/serializers.py
Normal file
86
backend/apps/api/v1/accounts/serializers.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from apps.accounts.models import UserProfile, TopList, TopListItem
|
||||
from apps.accounts.serializers import UserSerializer # existing shared user serializer
|
||||
|
||||
|
||||
class UserProfileCreateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class UserProfileUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = "__all__"
|
||||
extra_kwargs = {"user": {"read_only": True}}
|
||||
|
||||
|
||||
class UserProfileOutputSerializer(serializers.ModelSerializer):
|
||||
user = UserSerializer(read_only=True)
|
||||
avatar_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = "__all__"
|
||||
|
||||
@extend_schema_field(serializers.URLField(allow_null=True))
|
||||
def get_avatar_url(self, obj) -> str | None:
|
||||
"""Get user avatar URL"""
|
||||
# Safely try to return an avatar url if present
|
||||
avatar = getattr(obj, "avatar", None)
|
||||
if avatar:
|
||||
return getattr(avatar, "url", None)
|
||||
user_profile = getattr(obj, "user", None)
|
||||
if user_profile and getattr(user_profile, "profile", None):
|
||||
avatar = getattr(user_profile.profile, "avatar", None)
|
||||
if avatar:
|
||||
return getattr(avatar, "url", None)
|
||||
return None
|
||||
|
||||
|
||||
class TopListItemCreateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TopListItem
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class TopListItemUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TopListItem
|
||||
fields = "__all__"
|
||||
# allow updates, adjust as needed
|
||||
extra_kwargs = {"top_list": {"read_only": False}}
|
||||
|
||||
|
||||
class TopListItemOutputSerializer(serializers.ModelSerializer):
|
||||
# Remove the ride field since it doesn't exist on the model
|
||||
# The model likely uses a generic foreign key or different field name
|
||||
|
||||
class Meta:
|
||||
model = TopListItem
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class TopListCreateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TopList
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class TopListUpdateInputSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TopList
|
||||
fields = "__all__"
|
||||
# user is set by view's perform_create
|
||||
extra_kwargs = {"user": {"read_only": True}}
|
||||
|
||||
|
||||
class TopListOutputSerializer(serializers.ModelSerializer):
|
||||
user = UserSerializer(read_only=True)
|
||||
items = TopListItemOutputSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TopList
|
||||
fields = "__all__"
|
||||
@@ -7,11 +7,13 @@ from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Q
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from apps.accounts.models import UserProfile, TopList, TopListItem
|
||||
from ..serializers import (
|
||||
from .serializers import (
|
||||
UserProfileCreateInputSerializer,
|
||||
UserProfileUpdateInputSerializer,
|
||||
UserProfileOutputSerializer,
|
||||
@@ -26,13 +28,60 @@ from ..serializers import (
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="List user profiles",
|
||||
description="Retrieve a list of user profiles.",
|
||||
responses={200: UserProfileOutputSerializer(many=True)},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
create=extend_schema(
|
||||
summary="Create user profile",
|
||||
description="Create a new user profile.",
|
||||
request=UserProfileCreateInputSerializer,
|
||||
responses={201: UserProfileOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
summary="Get user profile",
|
||||
description="Retrieve a specific user profile by ID.",
|
||||
responses={200: UserProfileOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
update=extend_schema(
|
||||
summary="Update user profile",
|
||||
description="Update a user profile.",
|
||||
request=UserProfileUpdateInputSerializer,
|
||||
responses={200: UserProfileOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
summary="Partially update user profile",
|
||||
description="Partially update a user profile.",
|
||||
request=UserProfileUpdateInputSerializer,
|
||||
responses={200: UserProfileOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
destroy=extend_schema(
|
||||
summary="Delete user profile",
|
||||
description="Delete a user profile.",
|
||||
responses={204: None},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
me=extend_schema(
|
||||
summary="Get current user's profile",
|
||||
description="Retrieve the current authenticated user's profile.",
|
||||
responses={200: UserProfileOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
)
|
||||
class UserProfileViewSet(ModelViewSet):
|
||||
"""ViewSet for managing user profiles."""
|
||||
|
||||
queryset = UserProfile.objects.select_related("user").all()
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_serializer_class(self):
|
||||
def get_serializer_class(self): # type: ignore[override]
|
||||
"""Return appropriate serializer based on action."""
|
||||
if self.action == "create":
|
||||
return UserProfileCreateInputSerializer
|
||||
@@ -40,9 +89,9 @@ class UserProfileViewSet(ModelViewSet):
|
||||
return UserProfileUpdateInputSerializer
|
||||
return UserProfileOutputSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # type: ignore[override]
|
||||
"""Filter profiles based on user permissions."""
|
||||
if self.request.user.is_staff:
|
||||
if getattr(self.request.user, "is_staff", False):
|
||||
return self.queryset
|
||||
return self.queryset.filter(user=self.request.user)
|
||||
|
||||
@@ -59,6 +108,59 @@ class UserProfileViewSet(ModelViewSet):
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="List top lists",
|
||||
description="Retrieve a list of top lists.",
|
||||
responses={200: TopListOutputSerializer(many=True)},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
create=extend_schema(
|
||||
summary="Create top list",
|
||||
description="Create a new top list.",
|
||||
request=TopListCreateInputSerializer,
|
||||
responses={201: TopListOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
summary="Get top list",
|
||||
description="Retrieve a specific top list by ID.",
|
||||
responses={200: TopListOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
update=extend_schema(
|
||||
summary="Update top list",
|
||||
description="Update a top list.",
|
||||
request=TopListUpdateInputSerializer,
|
||||
responses={200: TopListOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
summary="Partially update top list",
|
||||
description="Partially update a top list.",
|
||||
request=TopListUpdateInputSerializer,
|
||||
responses={200: TopListOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
destroy=extend_schema(
|
||||
summary="Delete top list",
|
||||
description="Delete a top list.",
|
||||
responses={204: None},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
my_lists=extend_schema(
|
||||
summary="Get current user's top lists",
|
||||
description="Retrieve all top lists belonging to the current user.",
|
||||
responses={200: TopListOutputSerializer(many=True)},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
duplicate=extend_schema(
|
||||
summary="Duplicate top list",
|
||||
description="Create a copy of an existing top list for the current user.",
|
||||
responses={201: TopListOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
)
|
||||
class TopListViewSet(ModelViewSet):
|
||||
"""ViewSet for managing user top lists."""
|
||||
|
||||
@@ -67,7 +169,7 @@ class TopListViewSet(ModelViewSet):
|
||||
)
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_serializer_class(self):
|
||||
def get_serializer_class(self): # type: ignore[override]
|
||||
"""Return appropriate serializer based on action."""
|
||||
if self.action == "create":
|
||||
return TopListCreateInputSerializer
|
||||
@@ -75,11 +177,11 @@ class TopListViewSet(ModelViewSet):
|
||||
return TopListUpdateInputSerializer
|
||||
return TopListOutputSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # type: ignore[override]
|
||||
"""Filter lists based on user permissions and visibility."""
|
||||
queryset = self.queryset
|
||||
|
||||
if not self.request.user.is_staff:
|
||||
if not getattr(self.request.user, "is_staff", False):
|
||||
# Non-staff users can only see their own lists and public lists
|
||||
queryset = queryset.filter(Q(user=self.request.user) | Q(is_public=True))
|
||||
|
||||
@@ -99,6 +201,7 @@ class TopListViewSet(ModelViewSet):
|
||||
@action(detail=True, methods=["post"])
|
||||
def duplicate(self, request, pk=None):
|
||||
"""Duplicate a top list for the current user."""
|
||||
_ = pk # reference pk to avoid unused-variable warnings
|
||||
original_list = self.get_object()
|
||||
|
||||
# Create new list
|
||||
@@ -122,13 +225,62 @@ class TopListViewSet(ModelViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
summary="List top list items",
|
||||
description="Retrieve a list of top list items.",
|
||||
responses={200: TopListItemOutputSerializer(many=True)},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
create=extend_schema(
|
||||
summary="Create top list item",
|
||||
description="Add a new item to a top list.",
|
||||
request=TopListItemCreateInputSerializer,
|
||||
responses={201: TopListItemOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
summary="Get top list item",
|
||||
description="Retrieve a specific top list item by ID.",
|
||||
responses={200: TopListItemOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
update=extend_schema(
|
||||
summary="Update top list item",
|
||||
description="Update a top list item.",
|
||||
request=TopListItemUpdateInputSerializer,
|
||||
responses={200: TopListItemOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
summary="Partially update top list item",
|
||||
description="Partially update a top list item.",
|
||||
request=TopListItemUpdateInputSerializer,
|
||||
responses={200: TopListItemOutputSerializer},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
destroy=extend_schema(
|
||||
summary="Delete top list item",
|
||||
description="Remove an item from a top list.",
|
||||
responses={204: None},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
reorder=extend_schema(
|
||||
summary="Reorder top list items",
|
||||
description="Reorder items within a top list.",
|
||||
responses={
|
||||
200: {"type": "object", "properties": {"success": {"type": "boolean"}}}
|
||||
},
|
||||
tags=["Accounts"],
|
||||
),
|
||||
)
|
||||
class TopListItemViewSet(ModelViewSet):
|
||||
"""ViewSet for managing top list items."""
|
||||
|
||||
queryset = TopListItem.objects.select_related("top_list__user", "ride").all()
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_serializer_class(self):
|
||||
def get_serializer_class(self): # type: ignore[override]
|
||||
"""Return appropriate serializer based on action."""
|
||||
if self.action == "create":
|
||||
return TopListItemCreateInputSerializer
|
||||
@@ -136,11 +288,11 @@ class TopListItemViewSet(ModelViewSet):
|
||||
return TopListItemUpdateInputSerializer
|
||||
return TopListItemOutputSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
def get_queryset(self): # type: ignore[override]
|
||||
"""Filter items based on user permissions."""
|
||||
queryset = self.queryset
|
||||
|
||||
if not self.request.user.is_staff:
|
||||
if not getattr(self.request.user, "is_staff", False):
|
||||
# Non-staff users can only see items from their own lists or public lists
|
||||
queryset = queryset.filter(
|
||||
Q(top_list__user=self.request.user) | Q(top_list__is_public=True)
|
||||
@@ -151,24 +303,27 @@ class TopListItemViewSet(ModelViewSet):
|
||||
def perform_create(self, serializer):
|
||||
"""Validate user can add items to the list."""
|
||||
top_list = serializer.validated_data["top_list"]
|
||||
if top_list.user != self.request.user and not self.request.user.is_staff:
|
||||
raise PermissionError("You can only add items to your own lists")
|
||||
if top_list.user != self.request.user and not getattr(
|
||||
self.request.user, "is_staff", False
|
||||
):
|
||||
raise PermissionDenied("You can only add items to your own lists")
|
||||
serializer.save()
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""Validate user can update items in the list."""
|
||||
top_list = serializer.instance.top_list
|
||||
if top_list.user != self.request.user and not self.request.user.is_staff:
|
||||
raise PermissionError("You can only update items in your own lists")
|
||||
if top_list.user != self.request.user and not getattr(
|
||||
self.request.user, "is_staff", False
|
||||
):
|
||||
raise PermissionDenied("You can only update items in your own lists")
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
"""Validate user can delete items from the list."""
|
||||
if (
|
||||
instance.top_list.user != self.request.user
|
||||
and not self.request.user.is_staff
|
||||
if instance.top_list.user != self.request.user and not getattr(
|
||||
self.request.user, "is_staff", False
|
||||
):
|
||||
raise PermissionError("You can only delete items from your own lists")
|
||||
raise PermissionDenied("You can only delete items from your own lists")
|
||||
instance.delete()
|
||||
|
||||
@action(detail=False, methods=["post"])
|
||||
@@ -185,7 +340,9 @@ class TopListItemViewSet(ModelViewSet):
|
||||
|
||||
try:
|
||||
top_list = TopList.objects.get(id=top_list_id)
|
||||
if top_list.user != request.user and not request.user.is_staff:
|
||||
if top_list.user != request.user and not getattr(
|
||||
request.user, "is_staff", False
|
||||
):
|
||||
return Response(
|
||||
{"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user