merge from master
This commit is contained in:
commit
f711ee34a1
17 changed files with 173 additions and 74 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,4 +6,5 @@ delme*.py
|
|||
.env
|
||||
/venv/
|
||||
oidc.key
|
||||
/log/
|
||||
/logs/*.log
|
||||
|
|
|
|||
12
Dockerfile
12
Dockerfile
|
|
@ -1,9 +1,9 @@
|
|||
FROM debian:13
|
||||
FROM base:debian13
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
WORKDIR /app
|
||||
RUN apt update
|
||||
RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip3 install --break-system-packages -r requirements.txt
|
||||
RUN pip3 install --break-system-packages setuptools
|
||||
#RUN apt update
|
||||
#RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
||||
#COPY requirements.txt /app/requirements.txt
|
||||
#RUN pip3 install --break-system-packages -r requirements.txt
|
||||
#RUN pip3 install --break-system-packages setuptools
|
||||
#ENTRYPOINT ["./run.sh"]
|
||||
|
|
|
|||
|
|
@ -8,19 +8,12 @@ from unittest.mock import patch
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from oauth2_provider.models import get_access_token_model, get_application_model
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient, APITestCase
|
||||
|
||||
|
||||
from apps.core.models import Config
|
||||
from apps.gooyal_oauth2.models import Scope
|
||||
from apps.users.models import User
|
||||
|
||||
AccessToken = get_access_token_model()
|
||||
Application = get_application_model()
|
||||
|
||||
|
||||
|
||||
def mock_notifications_push_user_success(user_uuid, title, message, priority=5, extras=None):
|
||||
import uuid as sys_uuid
|
||||
|
|
@ -31,6 +24,12 @@ def mock_notifications_push_user_success(user_uuid, title, message, priority=5,
|
|||
return data
|
||||
|
||||
|
||||
from oauth2_provider.models import get_access_token_model, get_application_model
|
||||
|
||||
AccessToken = get_access_token_model()
|
||||
Application = get_application_model()
|
||||
|
||||
|
||||
class GooyalOAuth2Tests(APITestCase):
|
||||
user_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||
access_token_1 = 'au4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||
|
|
@ -46,6 +45,7 @@ class GooyalOAuth2Tests(APITestCase):
|
|||
client = APIClient()
|
||||
|
||||
def setUp(self):
|
||||
|
||||
from django.conf import settings
|
||||
settings.SMS_SEND = False
|
||||
|
||||
|
|
@ -203,10 +203,3 @@ class GooyalOAuth2Tests(APITestCase):
|
|||
result = self.client.post(reverse("gooyal_oauth2:revoke-token"), data=revoke_data)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(AccessToken.objects.count(), 0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import django_filters
|
||||
from .models import User
|
||||
|
||||
class ListUUIDFilter(django_filters.BaseInFilter, django_filters.UUIDFilter):
|
||||
pass
|
||||
|
||||
|
||||
class UserFilter(django_filters.FilterSet):
|
||||
uuid_in = ListUUIDFilter(field_name='uuid', required=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = {
|
||||
'uuid': ['in'],
|
||||
}
|
||||
|
||||
|
||||
# 'uuid': ['in'],
|
||||
}
|
||||
60
apps/users/management/commands/send_sms_by_uuid_list.py
Normal file
60
apps/users/management/commands/send_sms_by_uuid_list.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from apps.users.models import *
|
||||
import os
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "send sms by uuid list"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('message_file', type=str, help='Message as a file')
|
||||
parser.add_argument('uuids_file', type=str, help='File with one UUID per line')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
print(options['message_file'])
|
||||
print(options['uuids_file'])
|
||||
message_file_path = options['message_file']
|
||||
uuids_file_path = options['uuids_file']
|
||||
|
||||
if not os.path.exists(message_file_path):
|
||||
raise CommandError(f'File "{message_file_path}" does not exist')
|
||||
|
||||
if not os.path.exists(uuids_file_path):
|
||||
raise CommandError(f'File "{uuids_file_path}" does not exist')
|
||||
|
||||
uuids = []
|
||||
invalid_lines = []
|
||||
message: str = ''''''
|
||||
|
||||
with open(message_file_path, 'r', encoding='utf-8') as f:
|
||||
message = f.read()
|
||||
|
||||
with open(uuids_file_path, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line: # Skip empty lines
|
||||
continue
|
||||
try:
|
||||
# Validate and normalize UUID
|
||||
valid_uuid = str(uuid.UUID(line))
|
||||
uuids.append(valid_uuid)
|
||||
self.stdout.write(f'Line {line_num}: ✓ {valid_uuid}')
|
||||
except (ValueError, AttributeError, TypeError):
|
||||
invalid_lines.append((line_num, line))
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'Line {line_num}: ✗ Invalid UUID "{line}"')
|
||||
)
|
||||
|
||||
sms_client = SMSPolicy.get_client()
|
||||
users = User.objects.filter(uuid__in=uuids)
|
||||
empty_phone_numbers = list(filter(lambda u: not u.phone_number, users))
|
||||
if empty_phone_numbers:
|
||||
logger.error(f'not exist phone numbers for following uuids')
|
||||
print(empty_phone_numbers)
|
||||
logger.error(f'end of not exist phone numbers')
|
||||
return
|
||||
for user in users:
|
||||
logger.info(f'try to send message for: {user.uuid} and {user.phone_number}')
|
||||
sms_client.send_sms(user.phone_number.strip('+'), message)
|
||||
logger.info(f'sent message for: {user.uuid} and {user.phone_number}')
|
||||
|
|
@ -160,9 +160,6 @@ class User(AbstractUser):
|
|||
return state.get_province_by_id(self.province)
|
||||
|
||||
def set_otp(self):
|
||||
if self.otp_expire and (self.otp_expire + timedelta(seconds=MAX_OTP_VALID_DURATION)) > timezone.now():
|
||||
raise Exception(_('otp expire time not reached.'))
|
||||
|
||||
if not settings.SMS_SEND:
|
||||
self._otp = '77501'
|
||||
elif self.phone_number in settings.TEST_PHONENUMBERS:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from rest_framework import serializers
|
|||
from apps.users.models import User
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from unidecode import unidecode
|
||||
|
||||
from utils.exceptions import UnprocessableEntity
|
||||
|
||||
|
|
@ -62,6 +63,10 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
|||
|
||||
read_only_fields = ['otp_expire', 'ttl']
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs['phone_number'] = unidecode(attrs['phone_number'])
|
||||
return attrs
|
||||
|
||||
def save(self, **kwargs):
|
||||
# TODO: move it to query set
|
||||
validated_data = self.validated_data
|
||||
|
|
@ -71,16 +76,9 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
|||
user = User.objects.create_user(**validated_data)
|
||||
|
||||
if not user.otp_is_valid():
|
||||
try:
|
||||
user.set_otp()
|
||||
except Exception as e:
|
||||
raise UnprocessableEntity(_('otp expire time not reached'))
|
||||
|
||||
user.set_otp()
|
||||
user.send_otp()
|
||||
|
||||
else:
|
||||
raise UnprocessableEntity(_('otp expire time not reached yet'))
|
||||
|
||||
self.instance = user
|
||||
return user
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ from django.urls import path
|
|||
from django.contrib.auth.views import LogoutView
|
||||
from .views import UserListView, UserPublicRetrieveView, AccountView, RequestOTPView, ChangePasswordView, \
|
||||
OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView, UserCurrentAvatarUrlView, UserInquiryView, \
|
||||
UserDetailedRetrieveView, UserSessionListView
|
||||
UserDetailedRetrieveView, UserSessionListView, UserProfileView
|
||||
|
||||
app_name = "users"
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', OTPLoginView.as_view(), name='login'),
|
||||
path('logout/', LogoutView.as_view(), name='logout'),
|
||||
path('account/', ProfileDetailView.as_view(), name='account_detail'),
|
||||
path('account/update/', ProfileUpdateView.as_view(), name='account_update'),
|
||||
path('api/account/', AccountView.as_view(), name='account_api'),
|
||||
path('api/users/', UserListView.as_view(), name='user_list_api'),
|
||||
path('api/users/all', UserListView.as_view(), name='user_list_api'),
|
||||
path('api/users/', UserProfileView.as_view(), name='user_profile_list_api'),
|
||||
path('api/sessions/', UserSessionListView.as_view(), name='user_sessions_api'),
|
||||
path('api/users/<uuid>/', UserPublicRetrieveView.as_view(), name='user_public_retrieve_api'),
|
||||
path('api/users/<uuid>/avatar', UserCurrentAvatarUrlView.as_view(), name='user_avatar_api'),
|
||||
|
|
@ -22,3 +22,4 @@ urlpatterns = [
|
|||
path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'),
|
||||
path('api/inquiry/', UserInquiryView.as_view(), name='user_inquiry_api'),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from apps.users.models import User
|
|||
from apps.users.provinces_and_cities import State
|
||||
from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer, \
|
||||
ChangePasswordSerializer, UserInquirySerializer, SessionSerializer, ReferralSetSerializer
|
||||
from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle, NumberedRequestOTPDayRateThrottle, NumberedRequestOTPMinRateThrottle
|
||||
from utils.clients.promotions_client import promote_user
|
||||
from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle
|
||||
from utils import exceptions
|
||||
|
|
@ -33,17 +34,27 @@ from utils import exceptions
|
|||
UserModel = get_user_model()
|
||||
|
||||
|
||||
class UserListView(generics.ListAPIView):
|
||||
class BaseUserListView(generics.ListAPIView):
|
||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||
queryset = User.objects.all()
|
||||
serializer_class = PublicUserSerializer
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filterset_class = UserFilter
|
||||
|
||||
# Make filterset_class abstract - must be defined in subclasses
|
||||
filterset_class = None
|
||||
|
||||
class UserProfileView(BaseUserListView):
|
||||
filterset_class = UserFilter
|
||||
required_alternate_scopes = {
|
||||
"GET": [["accounts.profile:list"]],
|
||||
}
|
||||
|
||||
class UserListView(BaseUserListView):
|
||||
# filterset_class = UserFilter
|
||||
required_alternate_scopes = {
|
||||
"GET": [["accounts.users:list"]],
|
||||
}
|
||||
|
||||
|
||||
class UserPublicRetrieveView(generics.RetrieveAPIView):
|
||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||
|
|
@ -99,7 +110,10 @@ class RequestOTPView(generics.CreateAPIView):
|
|||
permission_classes = []
|
||||
serializer_class = RequestOTPSerializer
|
||||
required_scopes = []
|
||||
throttle_classes = [RequestOTPMinRateThrottle, RequestOTPDayRateThrottle]
|
||||
throttle_classes = [RequestOTPMinRateThrottle,
|
||||
RequestOTPDayRateThrottle,
|
||||
NumberedRequestOTPDayRateThrottle,
|
||||
NumberedRequestOTPMinRateThrottle]
|
||||
|
||||
|
||||
class RequestOTTView(generics.CreateAPIView):
|
||||
|
|
|
|||
32
client.py
32
client.py
|
|
@ -3,10 +3,10 @@ import time
|
|||
|
||||
import requests
|
||||
|
||||
OAUTH_CLIENT_ID = 'xrFXKf53jrxVOygbLEoqrtOlUwBP00jIQ4zVpzc6'
|
||||
OAUTH_CLIENT_SECRET = 'qelUdRCdEEalVdko5aHRojxsC3CaL29yjwxmc6FeWs39SeVSb6aM9mNrYQixMJNTYQK7s2wffwPW94JPPbP6jTdd9dSf1mlqYcr6hW2COuayFUk4jP33OWu4taUYP2F5'
|
||||
OAUTH_CLIENT_ID = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
||||
OAUTH_CLIENT_SECRET = 'KPc4dMSztNwAIB3vii3geXrzC1mKUIsAztz3t2ylC3HlrgJudJWhdrtoY6XeRJIuadTAREYYsXk9XtFHUDfPVcJvyfHMpNkz2VvghwrijhVprok0VYV7XrOirJ5nFUxD'
|
||||
|
||||
API_URI = 'https://accounts.gooyal.com'
|
||||
API_URI = 'https://accounts.gooyal.ir'
|
||||
|
||||
|
||||
# API_URI = 'http://127.0.0.1:8000'
|
||||
|
|
@ -27,7 +27,7 @@ class ApiClient():
|
|||
"grant_type": "password",
|
||||
"username": phone_number,
|
||||
"password": password,
|
||||
"scope": 'accounts.account:request_ott accounts.account:change_password accounts.profile:inquiry accounts.account:update accounts.account:retrieve accounts.profile:retrieve accounts.profile:list introspection wallet.wallet:get_balance',
|
||||
"scope": 'introspection',
|
||||
"auth_fields": 'phone_number:otp'
|
||||
}
|
||||
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||
|
|
@ -51,7 +51,8 @@ class ApiClient():
|
|||
"grant_type": "password",
|
||||
"username": phone_number,
|
||||
"password": token,
|
||||
"scope": 'introspection education.course:retrieve education.course:submit superapp.applications:list accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||
# "scope": 'introspection education.course:retrieve education.course:submit superapp.applications:list accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||
"scope": 'introspection',
|
||||
"auth_fields": 'phone_number:ott'
|
||||
}
|
||||
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||
|
|
@ -156,6 +157,7 @@ class ApiClient():
|
|||
}
|
||||
url = f'{API_URI}/{path}'
|
||||
response = self._request(url, method='PUT', data=data)
|
||||
print(response)
|
||||
return response and 'code' in response, response
|
||||
|
||||
def introspect_account(self, token):
|
||||
|
|
@ -185,19 +187,29 @@ class ApiClient():
|
|||
|
||||
return self._request(url=url)
|
||||
|
||||
def get_wallet_balance(self):
|
||||
url = 'http://127.0.0.1:8000/wallet/api/wallet/default/balance'
|
||||
def get_application_preferences(self):
|
||||
url = 'https://preferences.gooyal.com/data/application/'
|
||||
|
||||
return self._request(url=url)
|
||||
|
||||
|
||||
auth_data = {'access_token': 'DJ1mycH9r5FCwaNgDA9nB9uU3KWt9m', 'expires_in': 36000, 'token_type': 'Bearer', 'scope': 'accounts.account:change_password accounts.account:update accounts.account:retrieve wallet.wallet:get_balance billboard_merchant.billboard:retrieve billboard_merchant.billboard:update billboard_merchant.billboard:create wallet.user:transaction_list data_crud.data:retrieve data_crud.data:update data_crud.data:delete', 'refresh_token': 'VF4P11tfcnGv9tc2u0qH6035OW5qU4'}
|
||||
|
||||
client = ApiClient('+989106853582')
|
||||
# client.auth_data = auth_data
|
||||
# print(client.login_as_client_credentials())
|
||||
print(client.request_otp())
|
||||
print(client.otp_login('12345'))
|
||||
print(client.introspect_account('V3LUQ9OryHOy6q5iWwLBRAtRBm80ex'))
|
||||
print(client.get_wallet_balance())
|
||||
import time
|
||||
start = time.time()
|
||||
print(client.otp_login('77502'))
|
||||
# end = time.time()
|
||||
# print(end - start)
|
||||
# print(client.get_application_preferences())
|
||||
# exit()
|
||||
# print(client.introspect_account('V3LUQ9OryHOy6q5iWwLBRAtRBm80ex'))
|
||||
# print(client.get_wallet_balance())
|
||||
# ott = client.get_application_token()[1]['ott']
|
||||
# print(client.ott_login(ott))
|
||||
# print(ott)
|
||||
client.update_account('test')
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# from django.conf import settings
|
||||
#
|
||||
# # set the default Django settings module for the 'celery' program.
|
||||
# # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'accounts.settings')
|
||||
# # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
||||
#
|
||||
# app = Celery('notification_service')
|
||||
#
|
||||
|
|
|
|||
|
|
@ -126,7 +126,9 @@ REST_FRAMEWORK = {
|
|||
],
|
||||
'DEFAULT_THROTTLE_RATES': {
|
||||
'otp_min': '2/min',
|
||||
'otp_day': '200/day',
|
||||
'otp_day': '50/day',
|
||||
'numbered_otp_min': '1/min',
|
||||
'numbered_otp_day': '10/day',
|
||||
},
|
||||
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
|
||||
}
|
||||
|
|
@ -293,7 +295,7 @@ CACHES = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
||||
|
||||
from main.other_settings.logging import LOGGING
|
||||
|
||||
|
|
@ -304,10 +306,10 @@ STORAGES = {
|
|||
"default": {
|
||||
"BACKEND": "django_minio_backend.models.MinioBackend",
|
||||
"OPTIONS": {
|
||||
"MINIO_ENDPOINT": config('MINIO_ENDPOINT', default='drive.gooyal.com'),
|
||||
"MINIO_ENDPOINT": config('MINIO_ENDPOINT', default='drive.gooyal.ir'),
|
||||
"MINIO_USE_HTTPS": config('MINIO_USE_HTTPS', default=True, cast=bool),
|
||||
|
||||
"MINIO_EXTERNAL_ENDPOINT": config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.com'),
|
||||
"MINIO_EXTERNAL_ENDPOINT": config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.ir'),
|
||||
# Default is same as MINIO_ENDPOINT
|
||||
"MINIO_EXTERNAL_ENDPOINT_USE_HTTPS": config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True, cast=bool),
|
||||
# Default is same as MINIO_USE_HTTPS
|
||||
|
|
@ -315,7 +317,7 @@ STORAGES = {
|
|||
"MINIO_ACCESS_KEY": config('MINIO_ACCESS_KEY'),
|
||||
"MINIO_SECRET_KEY": config('MINIO_SECRET_KEY'),
|
||||
"MINIO_URL_EXPIRY_HOURS": timedelta(days=1), # Default is 7 days (longest) if not defined
|
||||
"MINIO_CONSISTENCY_CHECK_ON_START": False,
|
||||
"MINIO_CONSISTENCY_CHECK_ON_START": True,
|
||||
"MINIO_DEFAULT_BUCKET": config('MINIO_MEDIA_FILES_BUCKET'), # replacement for MEDIA_ROOT
|
||||
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
||||
|
||||
|
|
@ -360,7 +362,7 @@ MINIO_ENDPOINT = config('MINIO_ENDPOINT', default='drive.gooyal.ir')
|
|||
MINIO_USE_HTTPS = config('MINIO_USE_HTTPS', default=True, cast=bool)
|
||||
|
||||
MINIO_EXTERNAL_ENDPOINT = config('MINIO_EXTERNAL_ENDPOINT',
|
||||
default='drive.gooyal.com') # Default is same as MINIO_ENDPOINT
|
||||
default='drive.gooyal.ir') # Default is same as MINIO_ENDPOINT
|
||||
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS = config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True,
|
||||
cast=bool) # Default is same as MINIO_USE_HTTPS
|
||||
MINIO_REGION = None # Default is set to None
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ django_redis
|
|||
django_minio_backend
|
||||
daphne
|
||||
Twisted[tls,http2]
|
||||
packaging==25.0
|
||||
packaging==25.0
|
||||
unidecode
|
||||
|
|
@ -246,3 +246,4 @@ zope-interface==8.2
|
|||
# via
|
||||
# gevent
|
||||
# twisted
|
||||
unidecode==1.4.0
|
||||
|
|
|
|||
4
run.sh
4
run.sh
|
|
@ -4,6 +4,6 @@
|
|||
# sleep 3
|
||||
#done
|
||||
#python3 manage.py collectstatic --noinput
|
||||
#python3 manage.py migrate
|
||||
python3 manage.py migrate
|
||||
gunicorn main.wsgi:application --bind 0.0.0.0:8000 -w 4
|
||||
#daphne -b 0.0.0.0 -p 8000 accounts.asgi:application
|
||||
#daphne -b 0.0.0.0 -p 8000 main.asgi:application
|
||||
|
|
@ -23,14 +23,9 @@ def exception_handler(exc, context):
|
|||
|
||||
if isinstance(exc, APIException):
|
||||
try:
|
||||
if isinstance(exc.detail, dict):
|
||||
if 'code' in exc.detail:
|
||||
error = exc.detail['code']
|
||||
else:
|
||||
attr = next(iter(exc.detail))
|
||||
error = exc.detail[attr].code
|
||||
else:
|
||||
error = exc.detail.code or exc.code
|
||||
error = getattr(getattr(exc, 'detail', None), 'code', None) or getattr(exc, 'code', None)
|
||||
if not error:
|
||||
error = getattr(exc, 'default_code')
|
||||
except:
|
||||
error = ''
|
||||
|
||||
|
|
@ -46,10 +41,7 @@ def exception_handler(exc, context):
|
|||
# message = error
|
||||
#
|
||||
# response_data['message'] = message
|
||||
if isinstance(exc.detail, dict):
|
||||
response_data['details'] = exc.detail
|
||||
else:
|
||||
response_data['details'] = {"message": exc.detail}
|
||||
response_data['details'] = {"message": exc.detail}
|
||||
response_data['details']['error'] = error
|
||||
response_data['details']['timestamp'] = timezone.now().isoformat()
|
||||
|
||||
|
|
@ -89,7 +81,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
|
||||
class UnprocessableEntity(APIException):
|
||||
status_code = 422
|
||||
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
|
||||
default_code = 'unprocessable_entity'
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,33 @@ class RequestOTPDayRateThrottle(SimpleRateThrottle):
|
|||
}
|
||||
|
||||
|
||||
|
||||
class RequestOTPMinRateThrottle(RequestOTPDayRateThrottle):
|
||||
scope = 'otp_min'
|
||||
|
||||
|
||||
class NumberedRequestOTPDayRateThrottle(SimpleRateThrottle):
|
||||
scope = 'numbered_otp_day'
|
||||
|
||||
def get_ident(self, request):
|
||||
return request.data.get('phone_number')
|
||||
|
||||
def get_cache_key(self, request, view):
|
||||
if request.user and request.user.is_authenticated:
|
||||
ident = request.user.pk
|
||||
else:
|
||||
ident = self.get_ident(request)
|
||||
|
||||
return self.cache_format % {
|
||||
'scope': self.scope,
|
||||
'ident': ident
|
||||
}
|
||||
|
||||
|
||||
|
||||
class NumberedRequestOTPMinRateThrottle(NumberedRequestOTPDayRateThrottle):
|
||||
scope = 'numbered_otp_min'
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue