You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
9.2 KiB
272 lines
9.2 KiB
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.password_validation import validate_password
|
|
from django.utils.translation import gettext_lazy as _
|
|
from rest_framework import serializers
|
|
from rest_framework_simplejwt.tokens import RefreshToken
|
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
|
from phonenumber_field.serializerfields import PhoneNumberField
|
|
from apps.account.models.verification import VerificationDocument
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
|
|
class RegisterRequestSerializer(serializers.Serializer):
|
|
email = serializers.EmailField(required=True)
|
|
password = serializers.CharField(write_only=True, required=True, min_length=6)
|
|
fullname = serializers.CharField(required=True, max_length=255)
|
|
phone_number = serializers.CharField(required=False, allow_blank=True, allow_null=True)
|
|
user_type = serializers.ChoiceField(
|
|
choices=User.UserType.choices,
|
|
default=User.UserType.CLIENT,
|
|
required=False
|
|
)
|
|
languages = serializers.ListField(
|
|
child=serializers.CharField(max_length=10),
|
|
required=False,
|
|
default=list
|
|
)
|
|
skills = serializers.ListField(
|
|
child=serializers.CharField(max_length=100),
|
|
required=False,
|
|
default=list
|
|
)
|
|
country = serializers.CharField(required=False, allow_blank=True, max_length=255)
|
|
city = serializers.CharField(required=False, allow_blank=True, max_length=255)
|
|
|
|
def validate_email(self, value):
|
|
normalized = value.strip().lower()
|
|
if User.objects.filter(email__iexact=normalized).exists():
|
|
raise serializers.ValidationError("A user with this email address already exists.")
|
|
return normalized
|
|
|
|
def validate_password(self, value):
|
|
validate_password(value)
|
|
return value
|
|
|
|
def create(self, validated_data):
|
|
email = validated_data.pop('email')
|
|
password = validated_data.pop('password')
|
|
fullname = validated_data.pop('fullname')
|
|
user_type = validated_data.pop('user_type', User.UserType.CLIENT)
|
|
|
|
user = User.objects.create_user(
|
|
email=email,
|
|
password=password,
|
|
fullname=fullname,
|
|
username=email,
|
|
user_type=user_type,
|
|
**validated_data
|
|
)
|
|
return user
|
|
|
|
|
|
|
|
class UserMeSerializer(serializers.ModelSerializer):
|
|
bio = serializers.CharField(source='info', allow_blank=True, required=False, allow_null=True)
|
|
roles = serializers.SerializerMethodField()
|
|
permissions = serializers.SerializerMethodField()
|
|
verification_status = serializers.SerializerMethodField()
|
|
is_verified = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = [
|
|
'id',
|
|
'email',
|
|
'username',
|
|
'fullname',
|
|
'phone_number',
|
|
'user_type',
|
|
'avatar',
|
|
'bio',
|
|
'languages',
|
|
'skills',
|
|
'country',
|
|
'city',
|
|
'gender',
|
|
'birthdate',
|
|
'date_joined',
|
|
'is_active',
|
|
'is_staff',
|
|
'roles',
|
|
'permissions',
|
|
'verification_status',
|
|
'is_verified',
|
|
]
|
|
read_only_fields = [
|
|
'id',
|
|
'email',
|
|
'username',
|
|
'user_type',
|
|
'date_joined',
|
|
'is_active',
|
|
'is_staff',
|
|
'roles',
|
|
'permissions',
|
|
'verification_status',
|
|
'is_verified',
|
|
]
|
|
|
|
def get_roles(self, obj) -> list:
|
|
roles = [obj.user_type] if obj.user_type else []
|
|
for group in obj.groups.all():
|
|
roles.append(group.name)
|
|
return list(set(roles))
|
|
|
|
def get_permissions(self, obj) -> list:
|
|
perms = []
|
|
if obj.is_super_admin:
|
|
perms.extend(['super_admin', 'manage_all', 'manage_institutions', 'manage_users', 'manage_content', 'approve_verifications'])
|
|
elif obj.is_regional_admin:
|
|
perms.extend(['regional_admin', 'manage_regional_institutions', 'manage_content', 'approve_verifications'])
|
|
elif obj.is_institution_admin:
|
|
perms.extend(['institution_admin', 'manage_institution_profile', 'manage_team', 'manage_events', 'manage_projects'])
|
|
elif obj.is_editor:
|
|
perms.extend(['editor', 'create_posts', 'manage_events', 'edit_projects'])
|
|
else:
|
|
perms.extend(['client_access', 'view_courses', 'submit_tickets', 'attend_events'])
|
|
return perms
|
|
|
|
def get_verification_status(self, obj) -> str:
|
|
return obj.verification_status
|
|
|
|
def get_is_verified(self, obj) -> bool:
|
|
return obj.verification_status == VerificationDocument.Status.VERIFIED
|
|
|
|
|
|
class UserMeUpdateSerializer(serializers.ModelSerializer):
|
|
bio = serializers.CharField(source='info', allow_blank=True, required=False, allow_null=True)
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = [
|
|
'fullname',
|
|
'phone_number',
|
|
'avatar',
|
|
'bio',
|
|
'languages',
|
|
'skills',
|
|
'country',
|
|
'city',
|
|
'gender',
|
|
'birthdate',
|
|
]
|
|
|
|
def to_internal_value(self, data):
|
|
if hasattr(data, 'copy'):
|
|
mutable_data = data.copy()
|
|
else:
|
|
mutable_data = dict(data)
|
|
|
|
# Handle avatar: if avatar is a string (e.g. existing URL or empty string), do not treat as file
|
|
if 'avatar' in mutable_data:
|
|
avatar_val = mutable_data['avatar']
|
|
if isinstance(avatar_val, str):
|
|
if not avatar_val.strip() or avatar_val.startswith('http') or avatar_val.startswith('/'):
|
|
mutable_data.pop('avatar')
|
|
elif avatar_val is None:
|
|
mutable_data['avatar'] = None
|
|
|
|
return super().to_internal_value(mutable_data)
|
|
|
|
def update(self, instance, validated_data):
|
|
for attr, value in validated_data.items():
|
|
setattr(instance, attr, value)
|
|
instance.save()
|
|
return instance
|
|
|
|
|
|
class RegisterResponseSerializer(serializers.Serializer):
|
|
user = UserMeSerializer()
|
|
access = serializers.CharField()
|
|
refresh = serializers.CharField()
|
|
message = serializers.CharField()
|
|
|
|
|
|
class VerificationDocumentSerializer(serializers.ModelSerializer):
|
|
document_type_display = serializers.CharField(source='get_document_type_display', read_only=True)
|
|
status_display = serializers.CharField(source='get_status_display', read_only=True)
|
|
reviewed_by_email = serializers.EmailField(source='reviewed_by.email', read_only=True, allow_null=True)
|
|
|
|
class Meta:
|
|
model = VerificationDocument
|
|
fields = [
|
|
'id',
|
|
'user',
|
|
'document_type',
|
|
'document_type_display',
|
|
'title',
|
|
'document_file',
|
|
'status',
|
|
'status_display',
|
|
'admin_notes',
|
|
'reviewed_by_email',
|
|
'uploaded_at',
|
|
'reviewed_at',
|
|
]
|
|
read_only_fields = [
|
|
'id',
|
|
'user',
|
|
'document_type_display',
|
|
'status',
|
|
'status_display',
|
|
'admin_notes',
|
|
'reviewed_by_email',
|
|
'uploaded_at',
|
|
'reviewed_at',
|
|
]
|
|
|
|
|
|
class VerificationDocumentUploadSerializer(serializers.Serializer):
|
|
document_type = serializers.ChoiceField(
|
|
choices=VerificationDocument.DocumentType.choices,
|
|
default=VerificationDocument.DocumentType.NATIONAL_ID
|
|
)
|
|
title = serializers.CharField(required=False, allow_blank=True, max_length=255)
|
|
document_file = serializers.FileField(required=True)
|
|
|
|
|
|
class PasswordRecoverRequestSerializer(serializers.Serializer):
|
|
email = serializers.EmailField(required=True)
|
|
|
|
|
|
class PasswordResetRequestSerializer(serializers.Serializer):
|
|
email = serializers.EmailField(required=True)
|
|
token = serializers.CharField(required=True)
|
|
new_password = serializers.CharField(required=True, min_length=6)
|
|
|
|
def validate_new_password(self, value):
|
|
validate_password(value)
|
|
return value
|
|
|
|
|
|
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
|
"""
|
|
JWT token obtain serializer that accepts either 'username' or 'email' as the credential.
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if 'email' in self.fields:
|
|
self.fields['email'].required = False
|
|
if 'username' not in self.fields:
|
|
self.fields['username'] = serializers.CharField(required=False, write_only=True)
|
|
|
|
def to_internal_value(self, data):
|
|
if isinstance(data, dict):
|
|
mutable_data = data.copy()
|
|
identifier = mutable_data.get('email') or mutable_data.get('username')
|
|
if identifier:
|
|
mutable_data['email'] = identifier
|
|
mutable_data['username'] = identifier
|
|
return super().to_internal_value(mutable_data)
|
|
return super().to_internal_value(data)
|
|
|
|
def validate(self, attrs):
|
|
identifier = attrs.get('email') or attrs.get('username')
|
|
if not identifier:
|
|
raise serializers.ValidationError({"email": _("This field is required.")})
|
|
attrs['email'] = identifier
|
|
attrs['username'] = identifier
|
|
return super().validate(attrs)
|
|
|