diff --git a/apps/users/migrations/0007_alter_user_otp.py b/apps/users/migrations/0007_alter_user_otp.py new file mode 100644 index 0000000..1d5a7b9 --- /dev/null +++ b/apps/users/migrations/0007_alter_user_otp.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-11-18 11:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0006_alter_user_avatar'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='otp', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='otp'), + ), + ] diff --git a/apps/users/models.py b/apps/users/models.py index e4b4e46..00fa21e 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -5,6 +5,7 @@ from datetime import timedelta # import service_clients from django.conf import settings from django.contrib.auth.base_user import BaseUserManager +from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser from django.contrib.auth.validators import UnicodeUsernameValidator from django.db import models @@ -34,7 +35,7 @@ class UserManager(BaseUserManager): # TODO: validate phone number user = self.model(phone_number=phone_number, username=username, email=email, **extra_fields) user.set_password(password) - user.set_otp() + # user.set_otp() user.date_joined = timezone.now() user.save(using=self._db) @@ -113,7 +114,7 @@ class User(AbstractUser): null=True ) phone_number = models.CharField(_('phone number'), max_length=30, blank=True, null=True, unique=True) - otp = models.CharField(_('otp'), max_length=6, blank=True, null=True) + otp = models.CharField(_('otp'), max_length=128, blank=True, null=True) otp_expire = models.DateTimeField(_('otp expire'), blank=True, null=True) otp_try = models.IntegerField(_('otp try'), blank=True, null=True, default=0) @@ -151,7 +152,11 @@ class User(AbstractUser): return state.get_province_by_id(self.province) def set_otp(self): - self.otp = '77501' + if self.otp_expire and (self.otp_expire + timedelta(seconds=MAX_OTP_VALID_DURATION)) > timezone.now(): + raise Exception('otp expire time not reached.') + + self._otp = '77501' + self.otp = make_password(self._otp) # if settings.DEBUG or self.phone_number in DEVELOPMENT_PHONE_NUMBERS: # self.otp = '77501' # @@ -168,10 +173,10 @@ class User(AbstractUser): self.ott_expire = timezone.now() + timedelta(seconds=MAX_OTP_VALID_DURATION) def otp_is_valid(self): - return bool(self.otp and timezone.now() <= self.otp_expire) + return bool(self.otp and self.otp_expire and timezone.now() <= self.otp_expire) def ott_is_valid(self): - return bool(self.ott and timezone.now() <= self.ott_expire) + return bool(self.ott and self.ott_expire and timezone.now() <= self.ott_expire) def check_otp(self, otp): if self.otp_try <= MAX_OTP_TRY: @@ -221,8 +226,8 @@ class User(AbstractUser): return result def send_otp(self): - print(self.otp) - self.notify(body=str(self.otp), title='OTP') + print(self._otp) + self.notify(body=str(self._otp), title='OTP') def notify(self, body, title=None, notification_type='sms'): message = ("وینسو" diff --git a/apps/users/serializers.py b/apps/users/serializers.py index 4e07bb7..a0ec8b9 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -5,6 +5,8 @@ from apps.users.models import User from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from utils.exceptions import UnprocessableEntity + phone_number_validator = RegexValidator(regex=r'^\+98[1-9]\d{9}$', message=_( "Phone number must be entered in the format: '+989999999999'." ),) @@ -70,8 +72,10 @@ class RequestOTPSerializer(serializers.ModelSerializer): if not user.otp_is_valid(): user.set_otp() user.save() + user.send_otp() - user.send_otp() + else: + raise UnprocessableEntity() self.instance = user return user diff --git a/utils/exceptions.py b/utils/exceptions.py index 28a4f95..756d9a4 100644 --- a/utils/exceptions.py +++ b/utils/exceptions.py @@ -47,4 +47,10 @@ class ErrorMiddleware: "details": str(exception) # فقط در حالت توسعه! در تولید بهتر است لاگ شود. }, status=500 - ) \ No newline at end of file + ) + + +class UnprocessableEntity(APIException): + status_code = 422 + default_detail = 'The request was well-formed but cannot be processed due to semantic errors.' + default_code = 'unprocessable_entity' \ No newline at end of file