from rest_framework import serializers from apps.skills.models import Skill from apps.skills.serializers import SkillSerializer from .models import Role, RoleSkill, UserTargetRole class RoleListSerializer(serializers.ModelSerializer): class Meta: model = Role fields = ['id', 'role_name', 'industry'] class RoleSkillSerializer(serializers.ModelSerializer): skill = SkillSerializer(read_only=True) class Meta: model = RoleSkill fields = ['id', 'skill', 'required_level', 'weight', 'is_mandatory'] class RoleDetailSerializer(serializers.ModelSerializer): role_skills = RoleSkillSerializer(many=True, read_only=True) class Meta: model = Role fields = ['id', 'role_name', 'description', 'industry', 'onet_soc_code', 'role_skills'] class UserTargetRoleSerializer(serializers.ModelSerializer): role = RoleListSerializer(read_only=True) role_id = serializers.IntegerField(write_only=True) class Meta: model = UserTargetRole fields = ['id', 'role', 'role_id', 'selected_at', 'is_active'] def validate_role_id(self, value): if not Role.objects.filter(id=value, is_active=True).exists(): raise serializers.ValidationError('Role not found or inactive.') return value class RoleAdminSerializer(serializers.ModelSerializer): """Admin view of a Role — exposes is_active + onet_soc_code for write.""" role_skills = RoleSkillSerializer(many=True, read_only=True) class Meta: model = Role fields = ['id', 'role_name', 'description', 'industry', 'is_active', 'onet_soc_code', 'role_skills'] class RoleSkillAdminSerializer(serializers.ModelSerializer): """Admin CRUD on a single RoleSkill junction row.""" skill = SkillSerializer(read_only=True) skill_id = serializers.IntegerField(write_only=True) role_id = serializers.IntegerField(write_only=True) class Meta: model = RoleSkill fields = ['id', 'role_id', 'skill', 'skill_id', 'required_level', 'weight', 'is_mandatory'] def validate_skill_id(self, value): if not Skill.objects.filter(id=value).exists(): raise serializers.ValidationError('Skill not found.') return value def validate_role_id(self, value): if not Role.objects.filter(id=value).exists(): raise serializers.ValidationError('Role not found.') return value def validate(self, attrs): # F16/F17 cross-field rule: a mandatory skill must carry positive weight. # (The non-negative bound is enforced by the model's MinValueValidator, # which DRF auto-propagates to the `weight` field as min_value=0.0.) # On create, `weight` may be omitted (model default 1.0); on PATCH, fall # back to the persisted value — never to None, which would falsely reject # a mandatory create that doesn't resend weight. instance = self.instance is_mandatory = attrs.get( 'is_mandatory', getattr(instance, 'is_mandatory', False) ) if 'weight' in attrs: weight = attrs['weight'] elif instance is not None: weight = instance.weight else: weight = RoleSkill._meta.get_field('weight').default if is_mandatory and (weight is None or weight <= 0): raise serializers.ValidationError( {'weight': 'A mandatory skill must have weight greater than 0.'} ) return attrs def create(self, validated_data): return RoleSkill.objects.create(**validated_data) def update(self, instance, validated_data): # Skill and role are fixed once the junction row exists — renaming # either would effectively be a different row. validated_data.pop('skill_id', None) validated_data.pop('role_id', None) return super().update(instance, validated_data)