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.
 
 
 
 

339 lines
13 KiB

from rest_framework import serializers
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password
from django.utils.translation import gettext_lazy as _
from apps.account.models import User
from utils import FileFieldSerializer, absolute_url
from utils.validators import validate_type_code
class UserProfileSerializer(serializers.ModelSerializer):
avatar = FileFieldSerializer(required=False, allow_null=True)
password = serializers.CharField(write_only=True, required=False, validators=[validate_password])
fullname = serializers.CharField(required=False)
gender = serializers.ChoiceField(
choices=User.GenderChoices.choices,
required=False,
help_text="Select the user's gender."
)
fcm = serializers.CharField(required=False, help_text="Firebase Cloud Messaging token.")
saved_location = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'device_id', 'fcm', 'fullname', 'slug', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill', 'city', 'country', 'birthdate', 'gender', 'saved_location']
read_only_fields = ['email', 'info', 'skill', 'device_id', 'slug', 'saved_location']
def get_saved_location(self, obj):
# Check if user is authenticated and has location_history attribute
if not obj.is_authenticated or not hasattr(obj, 'location_history'):
return None
last_location = obj.location_history.order_by('-at_time').first()
if last_location:
return {
'lat': last_location.lat,
'lon': last_location.lon,
'city': last_location.city,
'country': last_location.country,
'timezone': last_location.timezone,
'selected_manually': last_location.selected_manually,
'at_time': last_location.at_time,
}
return None
# def validate_email(self, value):
# if User.objects.filter(email=value).exists():
# raise serializers.ValidationError("This email is already registered.")
# return value
def update(self, instance, validated_data):
# Pop the password from the data to handle it separately
password = validated_data.pop('password', None)
# Use the default update logic for all other fields
for attr, value in validated_data.items():
if value is not None:
setattr(instance, attr, value)
# If a new password was provided, hash and set it correctly
if password:
instance.set_password(password)
instance.save()
return instance
class UserRegisterSerializer(serializers.ModelSerializer):
fcm = serializers.CharField(required=False, allow_blank=True, allow_null=True)
device_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, write_only=True)
email = serializers.EmailField()
class Meta:
model = User
fields = ['id','fullname', 'email', 'fcm', 'device_id']
extra_kwargs = {
'fullname': {'required': True,},
'email': {'required': True,},
}
def create(self, validated_data):
device_id = validated_data.pop('device_id', None)
user = super().create(validated_data)
if device_id:
user.device_id = device_id
user.save()
return user
def validate_email(self, value):
normalized_email = User.objects.normalize_email(value)
if User.objects.filter(email=normalized_email).exists():
raise serializers.ValidationError("This email is already registered.")
return normalized_email
class UserVerifySerializer(serializers.Serializer):
code = serializers.CharField(max_length=5, validators=[validate_type_code])
email = serializers.EmailField()
device_id = serializers.CharField(max_length=255, required=False)
def validate_email(self, value):
"""
Normalize the email to ensure the Redis key matches correctly.
"""
return User.objects.normalize_email(value)
class UserLoginSerializer(serializers.Serializer):
password = serializers.CharField(write_only=True)
token = serializers.CharField(allow_null=True, read_only=True, required=False)
fullname = serializers.CharField(allow_null=True, read_only=True, required=False)
avatar = serializers.CharField(allow_null=True, read_only=True, required=False)
email = serializers.EmailField(write_only=True)
password = serializers.CharField(style={'input_type': 'password'}, trim_whitespace=False)
fcm = serializers.CharField(required=False)
device_id = serializers.CharField(required=False)
timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
def validate(self, data):
# Custom validation logic can be added here if needed
# data.pop('fcm', None)
# data.pop('device_id', None)
return data
def validate_email(self, value):
"""
Normalize email for case-insensitive login.
"""
return User.objects.normalize_email(value)
# class UserLoginSerializer(serializers.Serializer):
# password = serializers.CharField(write_only=True)
# token = serializers.CharField(allow_null=True, read_only=True, required=False)
# fullname = serializers.CharField(allow_null=True, read_only=True, required=False)
# avatar = serializers.CharField(allow_null=True, read_only=True, required=False)
# email = serializers.EmailField(write_only=True)
# password = serializers.CharField(style={'input_type': 'password'}, trim_whitespace=False)
# fcm = serializers.CharField(required=False)
# device_id = serializers.CharField(required=False)
# timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
# class UserRecoverPasswordSerializer(serializers.ModelSerializer):
# email = serializers.EmailField()
# class Meta:
# model = User
# fields = ['email',]
# extra_kwargs = {
# 'email': {'required': True,},
# }
class UserRecoverPasswordSerializer(serializers.Serializer):
"""
Validates that an email is provided and is in a valid format
without checking for database uniqueness.
"""
email = serializers.EmailField(required=True)
def validate_email(self, value):
"""
Normalize the email address to ensure case-insensitive lookups.
"""
return User.objects.normalize_email(value)
class UserResetPasswordSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['password', ]
extra_kwargs = {
'password': {'required': True,},
}
class UserGuestSerializer(serializers.ModelSerializer):
lat = serializers.CharField(max_length=255, allow_null=True, allow_blank=True, required=False)
lon = serializers.CharField(max_length=255, allow_null=True, allow_blank=True, required=False)
fcm = serializers.CharField(required=False)
device_id = serializers.CharField(required=False)
device_os = serializers.ChoiceField(choices=User.DeviceOs.choices, required=False)
timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
class Meta:
model = User
fields = ['device_id', 'fcm', 'device_os', 'lat', 'lon', 'timezone']
def validate(self, data):
# Make sure at least device_id is provided
if not data.get('device_id'):
raise serializers.ValidationError({"device_id": "Device ID is required for guest users."})
return data
class WebUserGuestSerializer(serializers.ModelSerializer):
user_agent = serializers.CharField(required=False, allow_null=True, allow_blank=True)
client_ip = serializers.CharField(required=False, allow_null=True, allow_blank=True)
timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
class Meta:
model = User
fields = ['user_agent', 'client_ip', 'timezone', 'device_id', 'device_os']
def validate(self, data):
# Ensure device_id is provided (generated by view)
if not data.get('device_id'):
raise serializers.ValidationError({"device_id": "Device ID is required for web guest users."})
return data
class UserFCMSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['fcm']
class AdminUserSerializer(serializers.ModelSerializer):
avatar = FileFieldSerializer(required=False, allow_null=True)
password = serializers.CharField(write_only=True, required=False, validators=[validate_password])
email = serializers.EmailField(required=True)
auth_token = serializers.SerializerMethodField()
plain_password = serializers.SerializerMethodField()
def get_auth_token(self, obj):
token = Token.objects.filter(user=obj).first()
return token.key if token else None
def get_plain_password(self, obj):
request = self.context.get('request')
if request and request.user and (request.user.is_superuser or request.user.user_type in ['super_admin', 'admin']):
return obj.get_plain_password()
return None
class Meta:
model = User
fields = [
'id', 'fullname', 'email', 'phone_number', 'password', 'avatar',
'gender', 'birthdate', 'info', 'skill', 'city', 'country',
'device_id', 'device_os', 'user_agent', 'client_ip', 'fcm',
'user_type', 'is_active', 'is_staff', 'is_superuser',
'date_joined', 'last_login', 'auth_token', 'plain_password'
]
read_only_fields = ['id', 'date_joined', 'last_login', 'auth_token', 'device_id', 'device_os', 'user_agent', 'client_ip']
ROLE_GROUP_MAP = {
User.UserType.STUDENT: "Student Group",
User.UserType.PROFESSOR: "Professor Group",
User.UserType.ADMIN: "Admin Group",
User.UserType.SUPER_ADMIN: "Super Admin Group",
User.UserType.CONSULTANT: "Consultant Group",
}
PANEL_GROUPS = {"Professor Group", "Admin Group", "Super admin Group", "Super Admin Group"}
def _ensure_group(self, instance, group_name):
group, _ = Group.objects.get_or_create(name=group_name)
instance.groups.add(group)
def _remove_groups(self, instance, group_names):
if not group_names:
return
instance.groups.remove(*Group.objects.filter(name__in=group_names))
def _normalize_role_access(self, instance, requested_user_type):
if not requested_user_type:
return
requested_user_type = str(requested_user_type)
if requested_user_type == User.UserType.SUPER_ADMIN:
instance.is_staff = True
instance.is_superuser = True
self._ensure_group(instance, "Super Admin Group")
self._remove_groups(instance, {"Professor Group", "Admin Group", "Super admin Group"})
return
if requested_user_type == User.UserType.PROFESSOR:
instance.is_staff = False
instance.is_superuser = False
self._ensure_group(instance, "Professor Group")
self._remove_groups(instance, {"Admin Group", "Super admin Group", "Super Admin Group"})
return
if requested_user_type == User.UserType.ADMIN:
instance.is_staff = False
instance.is_superuser = False
self._ensure_group(instance, "Admin Group")
self._remove_groups(instance, {"Professor Group", "Super admin Group", "Super Admin Group"})
return
if requested_user_type == User.UserType.STUDENT:
instance.is_staff = False
instance.is_superuser = False
self._ensure_group(instance, "Student Group")
self._remove_groups(instance, self.PANEL_GROUPS)
return
if requested_user_type == User.UserType.CONSULTANT:
instance.is_staff = False
instance.is_superuser = False
self._ensure_group(instance, "Consultant Group")
self._remove_groups(instance, self.PANEL_GROUPS)
return
if requested_user_type == User.UserType.CLIENT:
instance.is_staff = False
instance.is_superuser = False
self._remove_groups(instance, self.PANEL_GROUPS)
def create(self, validated_data):
password = validated_data.pop('password', None)
user = User(**validated_data)
if password:
user.set_password(password)
user.set_plain_password(password)
else:
user.set_unusable_password()
user.save()
self._normalize_role_access(user, validated_data.get('user_type', user.user_type))
user.save()
return user
def update(self, instance, validated_data):
password = validated_data.pop('password', None)
user = super().update(instance, validated_data)
self._normalize_role_access(user, validated_data.get('user_type', user.user_type))
if password:
user.set_password(password)
user.set_plain_password(password)
user.save()
return user