""" 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 )