from django.contrib.auth import authenticate from django.contrib.auth.password_validation import validate_password as django_validate_password from django.core.exceptions import ValidationError as DjangoValidationError from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from .models import User, UserProfile from apps.skills.models import UserSkill from apps.skills.serializers import UserSkillSerializer class RegisterSerializer(serializers.ModelSerializer): password_confirm = serializers.CharField(write_only=True) class Meta: model = User fields = ['name', 'email', 'password', 'password_confirm'] extra_kwargs = { 'password': {'write_only': True}, } def validate(self, attrs): if attrs['password'] != attrs['password_confirm']: raise serializers.ValidationError({"password": "Passwords do not match."}) return attrs def validate_email(self, value): # Case-insensitive uniqueness: store lowercase so `Foo@X.com` and # `foo@x.com` cannot both register, and login is case-insensitive too. normalized = value.strip().lower() if User.objects.filter(email__iexact=normalized).exists(): raise serializers.ValidationError("A user with this email already exists.") return normalized def validate_password(self, value): # Run Django's configured AUTH_PASSWORD_VALIDATORS (min-length, common, # numeric, user-similarity). Without this call the settings list is dead # config. try: django_validate_password(value) except DjangoValidationError as exc: raise serializers.ValidationError(list(exc.messages)) return value def create(self, validated_data): validated_data.pop('password_confirm') email = validated_data['email'] user = User.objects.create_user( username=email, email=email, name=validated_data.get('name', ''), password=validated_data['password'], role_type='STUDENT' ) return user class LoginSerializer(serializers.Serializer): email = serializers.EmailField(write_only=True) password = serializers.CharField(write_only=True) def validate(self, attrs): # Normalize to match the lowercase username stored at registration. email = (attrs.get('email') or '').strip().lower() user = authenticate( request=self.context.get('request'), username=email, password=attrs.get('password') ) if not user: raise serializers.ValidationError("Invalid credentials.") if not user.is_active: raise serializers.ValidationError("User account is disabled.") return user class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'name', 'email', 'role_type', 'created_at'] read_only_fields = ['id', 'name', 'email', 'role_type', 'created_at'] class UserProfileSerializer(serializers.ModelSerializer): class Meta: model = UserProfile fields = ['degree', 'semester', 'interests', 'bio', 'updated_at'] read_only_fields = ['updated_at'] class ProfileWithSkillsSerializer(serializers.Serializer): user = serializers.SerializerMethodField() profile = serializers.SerializerMethodField() skills = serializers.SerializerMethodField() @extend_schema_field(UserSerializer) def get_user(self, obj): return UserSerializer(obj).data @extend_schema_field(UserProfileSerializer) def get_profile(self, obj): try: return UserProfileSerializer(obj.profile).data except UserProfile.DoesNotExist: return {} @extend_schema_field(UserSkillSerializer(many=True)) def get_skills(self, obj): user_skills = UserSkill.objects.filter(user=obj).select_related('skill') return UserSkillSerializer(user_skills, many=True).data