from rest_framework import generics, viewsets, status from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response from config.pagination import StandardResultsSetPagination from .models import Skill, UserSkill from .serializers import SkillSerializer, UserSkillSerializer, UserSkillCreateUpdateSerializer from .utils import proficiency_to_level class SkillListView(generics.ListAPIView): serializer_class = SkillSerializer permission_classes = [IsAuthenticated] pagination_class = StandardResultsSetPagination def get_queryset(self): queryset = Skill.objects.all().order_by('skill_name') category = self.request.query_params.get('category', None) if category: queryset = queryset.filter(category__iexact=category) return queryset class UserSkillViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] # `queryset` is required so drf-spectacular can resolve the PK type during # schema generation (the real filter lives in get_queryset). None() keeps # the runtime filter the sole source of truth — nothing leaks. queryset = UserSkill.objects.none() def get_queryset(self): return self.request.user.user_skills.select_related('skill') def get_serializer_class(self): if self.action in ['create', 'update', 'partial_update']: return UserSkillCreateUpdateSerializer return UserSkillSerializer def create(self, request, *args, **kwargs): serializer = UserSkillCreateUpdateSerializer( data=request.data, context={'request': request} ) serializer.is_valid(raise_exception=True) user_skill = serializer.save() return Response( UserSkillSerializer(user_skill).data, status=status.HTTP_201_CREATED ) def update(self, request, *args, **kwargs): instance = self.get_object() serializer = UserSkillCreateUpdateSerializer( instance, data=request.data, partial=kwargs.pop('partial', False), context={'request': request} ) serializer.is_valid(raise_exception=True) user_skill = serializer.save() return Response(UserSkillSerializer(user_skill).data) def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.update(request, *args, **kwargs) class SkillAdminViewSet(viewsets.ModelViewSet): """Admin-only CRUD on the Skill catalog. Delete is blocked with 409 when UserSkill / RoleSkill / SkillResource rows still reference the skill — avoids orphaning user progress or role requirements. """ queryset = Skill.objects.all().order_by('skill_name') serializer_class = SkillSerializer permission_classes = [IsAuthenticated, IsAdminUser] pagination_class = StandardResultsSetPagination def destroy(self, request, *args, **kwargs): skill = self.get_object() blockers = [] if UserSkill.objects.filter(skill=skill).exists(): blockers.append('student skill profiles') # Reverse lookups on Skill for RoleSkill and SkillResource (neither # declares a related_name, so they land on the default *_set name). if skill.roleskill_set.exists(): blockers.append('role requirements') from apps.resources.models import SkillResource if SkillResource.objects.filter(skill=skill).exists(): blockers.append('learning resources') if blockers: return Response( {'detail': ( "Cannot delete this skill: still referenced by " + ', '.join(blockers) + '.' )}, status=status.HTTP_409_CONFLICT, ) return super().destroy(request, *args, **kwargs)