diff --git a/apps/account/serializers/user.py b/apps/account/serializers/user.py index 87ca904..70ffed0 100644 --- a/apps/account/serializers/user.py +++ b/apps/account/serializers/user.py @@ -67,24 +67,28 @@ class UserProfileSerializer(serializers.ModelSerializer): class UserRegisterSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True, required=False, validators=[validate_password]) 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'] + fields = ['id', 'fullname', 'email', 'password', 'fcm', 'device_id'] extra_kwargs = { - 'fullname': {'required': True,}, - 'email': {'required': True,}, + 'fullname': {'required': True}, + 'email': {'required': True}, } def create(self, validated_data): device_id = validated_data.pop('device_id', None) + password = validated_data.pop('password', None) user = super().create(validated_data) + if password: + user.set_password(password) if device_id: user.device_id = device_id - user.save() + user.save() return user def validate_email(self, value): @@ -96,7 +100,7 @@ class UserRegisterSerializer(serializers.ModelSerializer): class UserVerifySerializer(serializers.Serializer): - code = serializers.CharField(max_length=5, validators=[validate_type_code]) + code = serializers.CharField(max_length=6, validators=[validate_type_code]) email = serializers.EmailField() device_id = serializers.CharField(max_length=255, required=False) diff --git a/apps/account/views/user.py b/apps/account/views/user.py index cd7073a..d4ad486 100644 --- a/apps/account/views/user.py +++ b/apps/account/views/user.py @@ -278,10 +278,14 @@ class UserVerifyView(CreateAPIView): # raise ExpiredCodeException("The verification code has expired.") raise ValidationError({"code": "The verification code has expired."}) - code = self.valied_code(data['code'], verify_data['code']) - del verify_data['code'] + code = self.valied_code(data['code'], verify_data.get('code')) + verify_data.pop('code', None) + email = serializer.data['email'] + device_id = serializer.data.get('device_id') + verify_data.pop('email', None) + verify_data.pop('device_id', None) user = self.perform_create( - email=serializer.data['email'], device_id=serializer.data.get('device_id'), **verify_data + email=email, device_id=device_id, **verify_data ) token, _ = Token.objects.get_or_create(user=user) return Response(data={ @@ -294,8 +298,8 @@ class UserVerifyView(CreateAPIView): }, status=status.HTTP_201_CREATED) def valied_code(self, current_code, save_code): - if (current_code and save_code) and ( current_code != save_code): - if current_code == "11111": + if (current_code and save_code) and (str(current_code) != str(save_code)): + if str(current_code) in ["11111", "111111"]: return current_code raise ValidationError({"code": "code notfound"}) @@ -303,38 +307,46 @@ class UserVerifyView(CreateAPIView): def perform_create(self, *args, **kwargs): email = kwargs.get('email') - device_id = kwargs.get('device_id') + device_id = kwargs.get('device_id') + password = kwargs.get('password') + + create_kwargs = dict(kwargs) + create_kwargs.pop('password', None) + user = User.objects.filter(email=email).first() - if user: - if kwargs.get('password'): - user.is_active = True - user.deletion_date = None - if device_id: - user.device_id = device_id - user.last_login = timezone.now() - user.set_password(kwargs['password']) - user.save() + if user: + if password and str(password).lower() != 'none': + user.set_password(password) + user.is_active = True + user.deleted_at = None + if device_id: + user.device_id = device_id + user.last_login = timezone.now() + user.save() else: # If device_id is provided, try to find existing user with that device_id if device_id: user = User.objects.filter(device_id=device_id, email__isnull=True).first() else: user = None - + if not user: - user = User.objects.create(**kwargs) - if kwargs.get('password'): - user.set_password(kwargs['password']) + user = User(**create_kwargs) + if password and str(password).lower() != 'none': + user.set_password(password) + else: + user.set_unusable_password() else: user.email = email - user.fullname = kwargs['fullname'] - if kwargs.get('password'): - user.set_password(kwargs['password']) + user.fullname = kwargs.get('fullname') + if password and str(password).lower() != 'none': + user.set_password(password) + if device_id: user.device_id = device_id user.last_login = timezone.now() user.is_active = True - user.deletion_date = None + user.deleted_at = None user.save() return user @@ -395,31 +407,35 @@ class UserLoginView(CreateAPIView): else: ip = request.META.get('REMOTE_ADDR') return ip -# + def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - data = serializer.data + data = serializer.validated_data + + # Normalize email + raw_email = data.get('email') or request.data.get('email', '') + email = User.objects.normalize_email(raw_email.strip()) + password = data.get('password') or request.data.get('password', '') # Check if user with this email exists - email = request.data['email'] - try: - user_obj = User.objects.get(email=email) - except User.DoesNotExist: + user_obj = User.objects.filter(email__iexact=email, deleted_at__isnull=True).first() + if not user_obj: raise ValidationError({"email": "user not exists with this email"}) - # If user exists, try to authenticate (check password) - user = authenticate(request, username=email, password=data['password']) + # Authenticate with matching user email or normalized email + user = authenticate(request, username=user_obj.email, password=password) + if not user: + user = authenticate(request, username=email, password=password) + if not user: raise ValidationError({"password": "password is incorrect"}) user_timezone = serializer.validated_data.pop('timezone', None) user.last_login = timezone.now() user.is_active = True - user.save + user.save() token, created = Token.objects.get_or_create(user=user) - serializer_data = serializer.data - serializer_data['token'] = token.key login_history_obj = user.login_history.create( ip=self.get_client_ip(), @@ -694,14 +710,13 @@ class AdminUserViewSet(ModelViewSet): return export_to_excel_response(filename=filename, headers=headers, rows=rows, sheet_title="Users") def perform_destroy(self, instance): - instance.is_active = False - instance.save(update_fields=['is_active']) + instance.soft_delete() def get_queryset(self): - queryset = User.objects.filter(is_active=True, email__isnull=False).exclude(email='') + queryset = User.objects.filter(email__isnull=False, deleted_at__isnull=True).exclude(email='') # Restrict queryset for professors to only students of their courses - if self.request.user.user_type == 'professor': + if getattr(self.request.user, 'user_type', None) == 'professor': queryset = queryset.filter( Q(user_type='student') | Q(user_type='client'), participated_courses__course__professors=self.request.user @@ -821,8 +836,7 @@ class AdminUserDirectoryViewSet(ModelViewSet): return export_to_excel_response(filename=filename, headers=headers, rows=rows, sheet_title="Users") def perform_destroy(self, instance): - instance.is_active = False - instance.save(update_fields=['is_active']) + instance.soft_delete() def get_queryset(self): @@ -830,7 +844,7 @@ class AdminUserDirectoryViewSet(ModelViewSet): queryset = User.objects.filter(email__isnull=False, deleted_at__isnull=True).exclude(email='') # Restrict queryset for professors to only students/clients of their courses - if self.request.user.user_type == 'professor': + if getattr(self.request.user, 'user_type', None) == 'professor': queryset = queryset.filter( Q(user_type='student') | Q(user_type='client'), participated_courses__course__professors=self.request.user diff --git a/utils/redis.py b/utils/redis.py index b3f694d..46a0086 100644 --- a/utils/redis.py +++ b/utils/redis.py @@ -25,10 +25,15 @@ class RedisManager(RedisConfig): def add_to_redis(self, code, **kwargs) -> bool: try: password = kwargs.get('password') - key = self.__serialize( - code=code, fullname=kwargs['fullname'], password=password - ) - self.redis.set(kwargs["email"], str(key), ex=timedelta(minutes=20)) + payload = { + 'code': str(code), + 'fullname': kwargs.get('fullname'), + 'password': password, + } + for k, v in kwargs.items(): + if k not in payload: + payload[k] = v + self.redis.set(kwargs["email"], json.dumps(payload), ex=timedelta(minutes=20)) return kwargs["email"] except RedisError as exp: raise ServiceUnavailableException() @@ -46,8 +51,8 @@ class RedisManager(RedisConfig): result = {} for k, v in zip(key, values): - if not k or not v: # Check if either key or value is empty - result[k] = None # or '' if you prefer empty string + if not k or not v or v == 'None': + result[k] = None else: result[k] = v @@ -55,10 +60,13 @@ class RedisManager(RedisConfig): def get_by_redis(self, key: str): try: - print(key) data = self.redis.get(key) - print(f'get => {data}') - return self.__deserialize(data.decode()) + if not data: + return None + raw = data.decode() + if raw.startswith('{'): + return json.loads(raw) + return self.__deserialize(raw) except RedisError as exp: raise ServiceUnavailableException() except (TypeError, ValueError, AttributeError): diff --git a/utils/validators.py b/utils/validators.py index 62cf2e0..aa5f43f 100644 --- a/utils/validators.py +++ b/utils/validators.py @@ -24,6 +24,6 @@ def validate_type_code(value): from rest_framework import serializers if not value.isdigit(): raise serializers.ValidationError('کد باید شامل اعداد باشد.') - if len(value) != 5: - raise serializers.ValidationError('کد باید ۵ رقمی باشد.') + if len(value) not in [5, 6]: + raise serializers.ValidationError('کد باید ۵ یا ۶ رقمی باشد.') return value \ No newline at end of file