From c3c13ef21e7a41c8b2705cea315ebeb84f54b108 Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Wed, 28 May 2025 14:56:59 +0330 Subject: [PATCH] Tag Assignment - organization OTP --- apps/authentication/api/v1/api.py | 92 +++++++++++++++++++++++++++++- apps/authentication/api/v1/urls.py | 4 +- apps/tag/models.py | 2 + apps/tag/web/api/v1/api.py | 33 ++++++++++- common/sms.py | 21 +++++++ 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 common/sms.py diff --git a/apps/authentication/api/v1/api.py b/apps/authentication/api/v1/api.py index 9ffe500..33f004f 100644 --- a/apps/authentication/api/v1/api.py +++ b/apps/authentication/api/v1/api.py @@ -1,5 +1,5 @@ import typing - +from rest_framework.permissions import AllowAny from apps.authentication.api.v1.serializers.jwt import CustomizedTokenObtainPairSerializer from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework.decorators import action, permission_classes @@ -26,7 +26,10 @@ from apps.authentication.models import ( from django.db import transaction from rest_framework.response import Response from common.tools import CustomOperations +from django.core.cache import cache from rest_framework import status +from common.sms import send_sms +import random class CustomizedTokenObtainPairView(TokenObtainPairView): @@ -138,6 +141,20 @@ class UserViewSet(ModelViewSet): else: return Response(serializer.errors, status=status.HTTP_403_FORBIDDEN) + @action( + methods=['get'], + detail=False, + url_name='profile', + url_path='profile', + name='profile', + permission_classes=[AllowAny] + ) + def profile(self, request): + serializer = authorize_view.UserRelationSerializer( + authorize_view.UserRelations.objects.get(user=request.user) + ) + return Response(serializer.data, status.HTTP_200_OK) + class CityViewSet(ModelViewSet): """ Crud operations for city model """ # @@ -195,3 +212,76 @@ class BankAccountViewSet(ModelViewSet): """ Crud operations for bank account model """ # queryset = BankAccountInformation.objects.all() serializer_class = BankAccountSerializer + + +class GeneralOTPViewSet(ModelViewSet): + """ general OTP user authorization """ + + user_relations_queryset = authorize_view.UserRelations.objects.all() + organization_queryset = Organization.objects.all() + user_queryset = User.objects.all() + user_serializer = UserSerializer + organization_serializer = OrganizationSerializer + user_relations_serializer = authorize_view.UserRelationSerializer + + @classmethod + def get_user_mobile(cls, data: dict) -> typing.Any: + """ find user mobile in multiple modes like from organization """ + + if data['get_mobile_type'] == 'organization': + # get user mobile by his/her organization + user_mobile = cls.user_relations_queryset.filter( + organization_id=int(data['object_id']), + role__role_name='Management').first().user.mobile + return user_mobile + + if data['get_mobile_type'] == 'general': + return data['mobile'] + + @action( + methods=['post'], + detail=False, + url_path='send_otp', + url_name='send_otp', + name='send_otp' + ) + @transaction.atomic + def send_otp(self, request): + """ + This module is for sending otp in whole project and different parts + like send otp code to user by organization or by general user mobile + """ + + mobile = self.get_user_mobile( + data=request.data + ) + + # generate random integer and message for otp code + random_number = random.randint(10000, 99999) + message = f'کد احراز شما : {random_number}' # noqa + + # caching code + if 'timeout' in request.data.keys(): + cache.set(f"{random_number}", str(random_number), timeout=60 * int(request.data['timeout'])) + else: + cache.set(f"{random_number}", str(random_number), timeout=60 * 3) + + sms_response = send_sms(mobile=mobile, message=message) + return Response(status=status.HTTP_200_OK) + + @action( + methods=['post'], + detail=False, + url_name='check_otp', + url_path='check_otp', + name='check_otp' + ) + def check_otp(self, request): + """ Check sent otp code to user """ + + entered_code = request.data['code'] + cached_code = cache.get(entered_code) + + if entered_code == cached_code: + return Response(status=status.HTTP_200_OK) + return Response(status=status.HTTP_403_FORBIDDEN) diff --git a/apps/authentication/api/v1/urls.py b/apps/authentication/api/v1/urls.py index 06dc114..ecf0d2c 100644 --- a/apps/authentication/api/v1/urls.py +++ b/apps/authentication/api/v1/urls.py @@ -12,7 +12,8 @@ from .api import ( CityViewSet, ProvinceViewSet, OrganizationViewSet, - OrganizationTypeViewSet + OrganizationTypeViewSet, + GeneralOTPViewSet ) router = DefaultRouter() @@ -21,6 +22,7 @@ router.register(r'city', CityViewSet, basename='city') router.register(r'province', ProvinceViewSet, basename='province') router.register(r'organization', OrganizationViewSet, basename='organization') router.register(r'organization-type', OrganizationTypeViewSet, basename='organization_type') +router.register(r'otp', GeneralOTPViewSet, basename='otp') urlpatterns = [ path('login/', CustomizedTokenObtainPairView.as_view(), name='token_obtain_pair'), diff --git a/apps/tag/models.py b/apps/tag/models.py index bae4f4c..e29bc51 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -6,6 +6,7 @@ from apps.core.models import BaseModel from crum import get_current_user from django.db import models from jdatetime import datetime +import random class Tag(BaseModel): @@ -85,6 +86,7 @@ class TagAssignment(BaseModel): )[3] + str( datetime.now().month ) + self.serial_random_part = random.randint(1000, 9999) if not self.serial: self.serial = f"" \ f"{self.serial_sender_part}" \ diff --git a/apps/tag/web/api/v1/api.py b/apps/tag/web/api/v1/api.py index 4be8eac..7c5f7b6 100644 --- a/apps/tag/web/api/v1/api.py +++ b/apps/tag/web/api/v1/api.py @@ -12,6 +12,7 @@ from django.db import transaction from rest_framework.exceptions import APIException from apps.tag import permissions as tag_permissions from apps.authorization import models as authorize_models +from apps.authentication.api.v1.api import GeneralOTPViewSet from apps.tag.tools import tag_code_serial_scanning from apps.tag import exceptions as tag_exceptions from common.helpers import detect_file_extension @@ -110,6 +111,7 @@ class TagViewSet(viewsets.ModelViewSet): class TagAssignmentViewSet(viewsets.ModelViewSet): """ assignment of tags """ queryset = tag_models.TagAssignment.objects.all() + user_relations_queryset = authorize_models.UserRelations.objects.all() serializer_class = TagAssignmentSerializer @transaction.atomic @@ -120,6 +122,12 @@ class TagAssignmentViewSet(viewsets.ModelViewSet): if serializer.is_valid(raise_exception=True): tag_assignment = serializer.save() + # get assigner organization code + assigner_organization_code = self.user_relations_queryset.get( + user=request.user).organization.type.code # noqa + tag_assignment.serial_sender_part = assigner_organization_code # noqa + tag_assignment.save() + # get tags by species number like: 2 tags of species code 4 tags_to_allocate = request.data['allocated_tags'] for tag in tags_to_allocate: @@ -140,7 +148,7 @@ class TagAssignmentViewSet(viewsets.ModelViewSet): status='W', species_code=tag['species_code'] ).save() # noqa - tag_to_allocate.status = 'W' # change tag status from free to waiting + tag_to_allocate.status = ' ' # change tag status from free to waiting tag_to_allocate.save() return Response(serializer.data, status.HTTP_201_CREATED) @@ -207,6 +215,29 @@ class TagAssignmentViewSet(viewsets.ModelViewSet): allocated_tag.delete() return Response(status.HTTP_200_OK) + @action( + methods=['post'], + detail=False, + url_path='otp_verification', + url_name='otp_verification', + name='otp_verification' + ) + def otp_verification(self, request): + """ OTP verification for organization """ + + if request.data['type'] == 'send': + otp_response = GeneralOTPViewSet().send_otp(request) + if otp_response.status_code == 200: + return Response(otp_response.status_code, status=status.HTTP_200_OK) + else: + return Response(otp_response.status_code, status=status.HTTP_403_FORBIDDEN) + if request.data['type'] == 'check': + check_response = GeneralOTPViewSet().check_otp(request) + if check_response.status_code == 200: + return Response(check_response.status_code, status=status.HTTP_200_OK) + else: + return Response(check_response.status_code, status=status.HTTP_403_FORBIDDEN) + @action( methods=['put'], detail=True, diff --git a/common/sms.py b/common/sms.py new file mode 100644 index 0000000..a838454 --- /dev/null +++ b/common/sms.py @@ -0,0 +1,21 @@ +import typing +import requests + +USERNAME_SMS = 'hamedan' # noqa # Sahand Sms username +PASSWORD_SMS = 'hamedan12345' # noqa # Sahand sms password + + +def send_sms(mobile: str = None, message: str = None) -> typing.Any: + """ send sms to a number with custom message """ + + url = f"http://webservice.sahandsms.com/newsmswebservice.asmx/SendPostUrl?" \ + f"username={USERNAME_SMS}&password={PASSWORD_SMS}" \ + f"&from=30002501&to={mobile}&message={message}" + + headers = {"Content-Type": "application/x-www-form-urlencoded"} + response = requests.request('GET', url, headers=headers) + print(response) + + +if __name__ == "__main__": + send_sms(mobile="09389657326", message="12345") \ No newline at end of file