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