mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 13:31:08 -05:00
- 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.
362 lines
13 KiB
Python
362 lines
13 KiB
Python
"""
|
|
Accounts API ViewSets for user profiles and top lists.
|
|
"""
|
|
|
|
from rest_framework.viewsets import ModelViewSet
|
|
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 (
|
|
UserProfileCreateInputSerializer,
|
|
UserProfileUpdateInputSerializer,
|
|
UserProfileOutputSerializer,
|
|
TopListCreateInputSerializer,
|
|
TopListUpdateInputSerializer,
|
|
TopListOutputSerializer,
|
|
TopListItemCreateInputSerializer,
|
|
TopListItemUpdateInputSerializer,
|
|
TopListItemOutputSerializer,
|
|
)
|
|
|
|
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): # type: ignore[override]
|
|
"""Return appropriate serializer based on action."""
|
|
if self.action == "create":
|
|
return UserProfileCreateInputSerializer
|
|
elif self.action in ["update", "partial_update"]:
|
|
return UserProfileUpdateInputSerializer
|
|
return UserProfileOutputSerializer
|
|
|
|
def get_queryset(self): # type: ignore[override]
|
|
"""Filter profiles based on user permissions."""
|
|
if getattr(self.request.user, "is_staff", False):
|
|
return self.queryset
|
|
return self.queryset.filter(user=self.request.user)
|
|
|
|
@action(detail=False, methods=["get"])
|
|
def me(self, request):
|
|
"""Get current user's profile."""
|
|
try:
|
|
profile = UserProfile.objects.get(user=request.user)
|
|
serializer = self.get_serializer(profile)
|
|
return Response(serializer.data)
|
|
except UserProfile.DoesNotExist:
|
|
return Response(
|
|
{"error": "Profile not found"}, status=status.HTTP_404_NOT_FOUND
|
|
)
|
|
|
|
|
|
@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."""
|
|
|
|
queryset = (
|
|
TopList.objects.select_related("user").prefetch_related("items__ride").all()
|
|
)
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
def get_serializer_class(self): # type: ignore[override]
|
|
"""Return appropriate serializer based on action."""
|
|
if self.action == "create":
|
|
return TopListCreateInputSerializer
|
|
elif self.action in ["update", "partial_update"]:
|
|
return TopListUpdateInputSerializer
|
|
return TopListOutputSerializer
|
|
|
|
def get_queryset(self): # type: ignore[override]
|
|
"""Filter lists based on user permissions and visibility."""
|
|
queryset = self.queryset
|
|
|
|
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))
|
|
|
|
return queryset.order_by("-created_at")
|
|
|
|
def perform_create(self, serializer):
|
|
"""Set the user when creating a top list."""
|
|
serializer.save(user=self.request.user)
|
|
|
|
@action(detail=False, methods=["get"])
|
|
def my_lists(self, request):
|
|
"""Get current user's top lists."""
|
|
lists = self.get_queryset().filter(user=request.user)
|
|
serializer = self.get_serializer(lists, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@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
|
|
new_list = TopList.objects.create(
|
|
user=request.user,
|
|
name=f"Copy of {original_list.name}",
|
|
description=original_list.description,
|
|
is_public=False, # Duplicated lists are private by default
|
|
)
|
|
|
|
# Copy all items
|
|
for item in original_list.items.all():
|
|
TopListItem.objects.create(
|
|
top_list=new_list,
|
|
ride=item.ride,
|
|
position=item.position,
|
|
notes=item.notes,
|
|
)
|
|
|
|
serializer = self.get_serializer(new_list)
|
|
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): # type: ignore[override]
|
|
"""Return appropriate serializer based on action."""
|
|
if self.action == "create":
|
|
return TopListItemCreateInputSerializer
|
|
elif self.action in ["update", "partial_update"]:
|
|
return TopListItemUpdateInputSerializer
|
|
return TopListItemOutputSerializer
|
|
|
|
def get_queryset(self): # type: ignore[override]
|
|
"""Filter items based on user permissions."""
|
|
queryset = self.queryset
|
|
|
|
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)
|
|
)
|
|
|
|
return queryset.order_by("top_list_id", "position")
|
|
|
|
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 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 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 getattr(
|
|
self.request.user, "is_staff", False
|
|
):
|
|
raise PermissionDenied("You can only delete items from your own lists")
|
|
instance.delete()
|
|
|
|
@action(detail=False, methods=["post"])
|
|
def reorder(self, request):
|
|
"""Reorder items in a top list."""
|
|
top_list_id = request.data.get("top_list_id")
|
|
item_ids = request.data.get("item_ids", [])
|
|
|
|
if not top_list_id or not item_ids:
|
|
return Response(
|
|
{"error": "top_list_id and item_ids are required"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
try:
|
|
top_list = TopList.objects.get(id=top_list_id)
|
|
if top_list.user != request.user and not getattr(
|
|
request.user, "is_staff", False
|
|
):
|
|
return Response(
|
|
{"error": "Permission denied"}, status=status.HTTP_403_FORBIDDEN
|
|
)
|
|
|
|
# Update positions
|
|
for position, item_id in enumerate(item_ids, 1):
|
|
TopListItem.objects.filter(id=item_id, top_list=top_list).update(
|
|
position=position
|
|
)
|
|
|
|
return Response({"success": True})
|
|
|
|
except TopList.DoesNotExist:
|
|
return Response(
|
|
{"error": "Top list not found"}, status=status.HTTP_404_NOT_FOUND
|
|
)
|