diff --git a/core/apps/accounts/admin/user.py b/core/apps/accounts/admin/user.py index 9aed37d..5da1088 100644 --- a/core/apps/accounts/admin/user.py +++ b/core/apps/accounts/admin/user.py @@ -29,6 +29,7 @@ class CustomUserAdmin(admin.UserAdmin, ModelAdmin): "groups", "user_permissions", "role", + "validated_at", ), }, ), diff --git a/core/apps/accounts/serializers/auth.py b/core/apps/accounts/serializers/auth.py index 325eff6..b777163 100644 --- a/core/apps/accounts/serializers/auth.py +++ b/core/apps/accounts/serializers/auth.py @@ -4,9 +4,16 @@ from django.utils.translation import gettext as _ from rest_framework import exceptions, serializers OTP_SIZE = env.int("OTP_SIZE", 4) + + class LoginSerializer(serializers.Serializer): - username = serializers.CharField(max_length=255) - password = serializers.CharField(max_length=255) + phone = serializers.CharField(max_length=255) + + def validate_phone(self, value): + user = get_user_model().objects.filter(phone=value, validated_at__isnull=False).exists() + if not user: + raise exceptions.ValidationError(_("Phone Not Found")) + return value class RegisterSerializer(serializers.ModelSerializer): @@ -20,13 +27,7 @@ class RegisterSerializer(serializers.ModelSerializer): class Meta: model = get_user_model() - fields = ["first_name", "last_name", "phone", "password"] - extra_kwargs = { - "first_name": { - "required": True, - }, - "last_name": {"required": True}, - } + fields = ["phone"] class ConfirmSerializer(serializers.Serializer): diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index d701a07..7cc506c 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -4,7 +4,7 @@ Accounts app urls from django.urls import path, include from rest_framework_simplejwt import views as jwt_views -from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView +from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView, LoginView from rest_framework.routers import DefaultRouter router = DefaultRouter() @@ -12,7 +12,7 @@ router.register("auth", RegisterView, basename="auth") router.register("auth", ResetPasswordView, basename="reset-password") router.register("auth", MeView, basename="me") router.register("auth", ChangePasswordView, basename="change-password") - +router.register("login", LoginView, basename="login") urlpatterns = [ path("", include(router.urls)), diff --git a/core/apps/accounts/views/__init__.py b/core/apps/accounts/views/__init__.py index 1e88b4e..f6012ff 100644 --- a/core/apps/accounts/views/__init__.py +++ b/core/apps/accounts/views/__init__.py @@ -1 +1,2 @@ from .auth import * # noqa +from .login import * # noqa diff --git a/core/apps/accounts/views/auth.py b/core/apps/accounts/views/auth.py index 750dfc5..cdd7089 100644 --- a/core/apps/accounts/views/auth.py +++ b/core/apps/accounts/views/auth.py @@ -54,7 +54,7 @@ class RegisterView(BaseViewSetMixin, GenericViewSet, UserService): data = ser.data phone = data.get("phone") # Create pending user - self.create_user(phone, data.get("first_name"), data.get("last_name"), data.get("password")) + self.create_user(phone) self.send_confirmation(phone) # Send confirmation code for sms eskiz.uz return Response( {"detail": _("Sms %(phone)s raqamiga yuborildi") % {"phone": phone}}, diff --git a/core/apps/accounts/views/login.py b/core/apps/accounts/views/login.py new file mode 100644 index 0000000..380b4a1 --- /dev/null +++ b/core/apps/accounts/views/login.py @@ -0,0 +1,61 @@ +from drf_spectacular.utils import extend_schema +from rest_framework.permissions import AllowAny +from rest_framework.decorators import action +from django_core.mixins.base import BaseViewSetMixin +from rest_framework.viewsets import GenericViewSet +from core.services import UserService, SmsService +from ..serializers import LoginSerializer, ConfirmSerializer +from django.utils.translation import gettext_lazy as _ +from rest_framework.response import Response +from rest_framework import status +from django.contrib.auth import get_user_model +from rest_framework.exceptions import PermissionDenied +from django_core import exceptions + + +@extend_schema(tags=["Login"]) +class LoginView(BaseViewSetMixin, GenericViewSet, UserService): + permission_classes = [AllowAny] + + def get_serializer_class(self): + match self.action: + case "send_code": + return LoginSerializer + case "send_confirm": + return ConfirmSerializer + case _: + return LoginSerializer + + @action(detail=False, methods=["post"], url_path="send_code") + def send_code(self, request): + ser = self.get_serializer(data=request.data) + ser.is_valid(raise_exception=True) + data = ser.validated_data + phone = data.get('phone') + self.send_confirmation(phone) + return Response( + {"detail": _("Sms %(phone)s raqamiga yuborildi") % {"phone": phone}}, + status=status.HTTP_202_ACCEPTED, + ) + + @action(detail=False, methods=["post"], url_path="send_confirm") + def send_confirm(self, request): + ser = self.get_serializer(data=request.data) + ser.is_valid(raise_exception=True) + data = ser.validated_data + phone = data.get("phone") + code = data.get("code") + try: + if SmsService.check_confirm(phone, code=code): + token = self.get_token(get_user_model().objects.filter(phone=phone).first()) + return Response( + data={ + "detail": _("Tasdiqlash ko'di qabul qilindi"), + "token": token, + }, + status=status.HTTP_202_ACCEPTED, + ) + except exceptions.SmsException as e: + raise PermissionDenied(e) # Response exception for APIException + except Exception as e: + raise PermissionDenied(e) # Api exception for APIException diff --git a/core/services/sms.py b/core/services/sms.py index 4dc80cc..a407180 100644 --- a/core/services/sms.py +++ b/core/services/sms.py @@ -1,4 +1,4 @@ -#type: ignore +# type: ignore import random from datetime import datetime, timedelta @@ -22,16 +22,18 @@ class SmsService: if env.bool("OTP_PROD", False): code = "".join(str(random.randint(0, 9)) for _ in range(env.int("OTP_SIZE", 4))) else: - code = env.int("OTP_DEFAULT", 1111) - + code = env.int("OTP_DEFAULT", 1111) + print("ishlayapti01") sms_confirm, status = models.SmsConfirm.objects.get_or_create(phone=phone, defaults={"code": code}) - + print(sms_confirm) + print(status) sms_confirm.sync_limits() - + print("ishlayapti") if sms_confirm.resend_unlock_time is not None: expired = sms_confirm.interval(sms_confirm.resend_unlock_time) exception = exceptions.SmsException(f"Resend blocked, try again in {expired}", expired=expired) raise exception + print("ishlayapti2") sms_confirm.code = code sms_confirm.try_count = 0 @@ -41,9 +43,11 @@ class SmsService: sms_confirm.resend_unlock_time = datetime.now() + timedelta( seconds=models.SmsConfirm.SMS_EXPIRY_SECONDS ) # noqa + print("ishlayapti3") sms_confirm.save() - SendConfirm.delay(phone, code) + print("ishlayapti4") + return True @staticmethod diff --git a/core/services/user.py b/core/services/user.py index 766b7bd..a8d6aa4 100644 --- a/core/services/user.py +++ b/core/services/user.py @@ -1,7 +1,7 @@ from datetime import datetime from core.services import sms -from django.contrib.auth import get_user_model, hashers +from django.contrib.auth import get_user_model from django.utils.translation import gettext as _ from django_core import exceptions from rest_framework.exceptions import PermissionDenied @@ -17,20 +17,19 @@ class UserService(sms.SmsService): "access": str(refresh.access_token), } - def create_user(self, phone, first_name, last_name, password): - get_user_model().objects.update_or_create( + def create_user(self, phone): + user, created = get_user_model().objects.update_or_create( phone=phone, defaults={ "phone": phone, - "first_name": first_name, - "last_name": last_name, - "password": hashers.make_password(password), }, ) + user.set_unusable_password() + user.save() def send_confirmation(self, phone) -> bool: try: - self.send_confirm(phone) + sms.SmsService.send_confirm(phone) return True except exceptions.SmsException as e: raise PermissionDenied(_("Qayta sms yuborish uchun kuting: {}").format(e.kwargs.get("expired"))) diff --git a/docker-compose.yml b/docker-compose.yml index ce01250..fd57a29 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,6 @@ volumes: services: nginx: - env_file: - - .env networks: - uzxarid ports: @@ -23,6 +21,8 @@ services: depends_on: - web web: + env_file: + - .env networks: - uzxarid build: