Spaces:
Sleeping
Sleeping
| 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) | |