From 70fa849840a791a58be732113f20eb1f70c2c43b Mon Sep 17 00:00:00 2001 From: Mojtaba-z Date: Sun, 4 May 2025 15:24:28 +0330 Subject: [PATCH] deploy login & reCaptcha --- Rasaddam_Backend/settings.py | 79 +++++++++++++++++- Rasaddam_Backend/urls.py | 4 +- apps/__init__.py | 0 apps/authentication/api/__init__.py | 0 apps/authentication/api/v1/__init__.py | 0 apps/authentication/api/v1/api.py | 27 ++++++ .../api/v1/serializers/__init__.py | 0 apps/authentication/api/v1/serializers/jwt.py | 55 ++++++++++++ .../api/v1/serializers/serializer.py | 0 apps/authentication/api/v1/urls.py | 14 ++++ ...ovince_city_organization_city_user_city.py | 45 ++++++++++ .../migrations/0004_user_otp_status.py | 18 ++++ apps/authentication/models.py | 29 +++++++ apps/authentication/urls.py | 10 +++ .../0002_organizationrole_otp_status.py | 18 ++++ ...0003_remove_organizationrole_otp_status.py | 17 ++++ apps/captcha_app/__init__.py | 0 apps/captcha_app/admin.py | 3 + apps/captcha_app/api/v1/api.py | 0 apps/captcha_app/api/v1/serializers.py | 15 ++++ apps/captcha_app/api/v1/urls.py | 8 ++ apps/captcha_app/api/v1/utils.py | 71 ++++++++++++++++ apps/captcha_app/api/v1/views.py | 43 ++++++++++ apps/captcha_app/apps.py | 6 ++ apps/captcha_app/exceptions.py | 12 +++ apps/pos/__init__.py | 0 apps/pos/admin.py | 3 + apps/pos/api/v1/api.py | 0 apps/pos/api/v1/serializers.py | 0 apps/pos/api/v1/urls.py | 0 apps/pos/apps.py | 6 ++ apps/pos/models.py | 3 + apps/pos/tests.py | 3 + apps/pos/views.py | 3 + requirements.txt | 7 +- ss.png | Bin 0 -> 496 bytes 36 files changed, 494 insertions(+), 5 deletions(-) create mode 100644 apps/__init__.py create mode 100644 apps/authentication/api/__init__.py create mode 100644 apps/authentication/api/v1/__init__.py create mode 100644 apps/authentication/api/v1/api.py create mode 100644 apps/authentication/api/v1/serializers/__init__.py create mode 100644 apps/authentication/api/v1/serializers/jwt.py create mode 100644 apps/authentication/api/v1/serializers/serializer.py create mode 100644 apps/authentication/api/v1/urls.py create mode 100644 apps/authentication/migrations/0003_user_province_city_organization_city_user_city.py create mode 100644 apps/authentication/migrations/0004_user_otp_status.py create mode 100644 apps/authentication/urls.py create mode 100644 apps/authorization/migrations/0002_organizationrole_otp_status.py create mode 100644 apps/authorization/migrations/0003_remove_organizationrole_otp_status.py create mode 100644 apps/captcha_app/__init__.py create mode 100644 apps/captcha_app/admin.py create mode 100644 apps/captcha_app/api/v1/api.py create mode 100644 apps/captcha_app/api/v1/serializers.py create mode 100644 apps/captcha_app/api/v1/urls.py create mode 100644 apps/captcha_app/api/v1/utils.py create mode 100644 apps/captcha_app/api/v1/views.py create mode 100644 apps/captcha_app/apps.py create mode 100644 apps/captcha_app/exceptions.py create mode 100644 apps/pos/__init__.py create mode 100644 apps/pos/admin.py create mode 100644 apps/pos/api/v1/api.py create mode 100644 apps/pos/api/v1/serializers.py create mode 100644 apps/pos/api/v1/urls.py create mode 100644 apps/pos/apps.py create mode 100644 apps/pos/models.py create mode 100644 apps/pos/tests.py create mode 100644 apps/pos/views.py create mode 100644 ss.png diff --git a/Rasaddam_Backend/settings.py b/Rasaddam_Backend/settings.py index 670fc45..6efe577 100644 --- a/Rasaddam_Backend/settings.py +++ b/Rasaddam_Backend/settings.py @@ -9,8 +9,10 @@ https://docs.djangoproject.com/en/5.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.2/ref/settings/ """ - +import os.path from pathlib import Path +from datetime import timedelta +from django.conf import settings # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -39,8 +41,12 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'rest_framework', "corsheaders", + 'rest_framework_simplejwt', 'apps.authentication.apps.AuthenticationConfig', 'apps.authorization.apps.AuthorizationConfig', + 'rest_captcha', + 'captcha', + 'apps.captcha_app.apps.CaptchaAppConfig' ] MIDDLEWARE = [ @@ -94,12 +100,81 @@ REST_FRAMEWORK = { 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "ROTATE_REFRESH_TOKENS": False, + "BLACKLIST_AFTER_ROTATION": False, + "UPDATE_LAST_LOGIN": False, + + "ALGORITHM": "HS256", + "SIGNING_KEY": settings.SECRET_KEY, + "VERIFYING_KEY": "", + "AUDIENCE": None, + "ISSUER": None, + "JSON_ENCODER": None, + "JWK_URL": None, + "LEEWAY": 0, + + "AUTH_HEADER_TYPES": ("Bearer",), + "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", + "USER_ID_FIELD": "id", + "USER_ID_CLAIM": "user_id", + "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", + + "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), + "TOKEN_TYPE_CLAIM": "token_type", + "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", + + "JTI_CLAIM": "jti", + + "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", + "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), + "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1), + + "TOKEN_OBTAIN_SERIALIZER": 'apps.authentication.api.v1.jwt_serializer.CustomizedTokenObtainPairSerializer', + "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer", + "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer", + "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer", + "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer", + "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", +} + +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6379/1', # Use the appropriate Redis server URL + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + }, + 'memcache': { + "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", + "LOCATION": "127.0.0.1:11211", + } +} + +REST_CAPTCHA = { + 'CAPTCHA_CACHE': 'default', + 'CAPTCHA_TIMEOUT': 300, # 5 minutes + 'CAPTCHA_LENGTH': 6, + 'CAPTCHA_FONT_SIZE': 35, + 'CAPTCHA_IMAGE_SIZE': (90, 20), + 'CAPTCHA_LETTER_ROTATION': (-35, 35), + 'CAPTCHA_FOREGROUND_COLOR': '#000000', + 'CAPTCHA_BACKGROUND_COLOR': '#ffffff', + # 'CAPTCHA_FONT_PATH': 'apps.authentication.api.v1.serializers.captcha.FONT_PATH', + 'CAPTCHA_CACHE_KEY': 'rest_captcha_{key}.{version}', + 'FILTER_FUNCTION': 'rest_captcha.captcha.filter_default', + 'NOISE_FUNCTION': 'apps.captcha_app.api.v1.serializers.noise_default' +} + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators diff --git a/Rasaddam_Backend/urls.py b/Rasaddam_Backend/urls.py index a0f721a..84ddeda 100644 --- a/Rasaddam_Backend/urls.py +++ b/Rasaddam_Backend/urls.py @@ -15,8 +15,10 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('auth/', include('apps.authentication.urls')), + path('', include('apps.captcha_app.api.v1.urls')), ] diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/api/__init__.py b/apps/authentication/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/api/v1/__init__.py b/apps/authentication/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/api/v1/api.py b/apps/authentication/api/v1/api.py new file mode 100644 index 0000000..2b0a2b7 --- /dev/null +++ b/apps/authentication/api/v1/api.py @@ -0,0 +1,27 @@ +from apps.authentication.api.v1.serializers.jwt import CustomizedTokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework.viewsets import ModelViewSet +from rest_framework.decorators import action +from apps.authentication.models import User +from django.db import transaction + + +class CustomizedTokenObtainPairView(TokenObtainPairView): + serializer_class = CustomizedTokenObtainPairSerializer + + +class Authentication(ModelViewSet): + queryset = User + serializer_class = '' + permission_classes = '' + + @action( + methods=['post', ], + detail=False, + name='login', + url_name='login', + url_path='login' + ) + @transaction.atomic + def login(self, request): + pass diff --git a/apps/authentication/api/v1/serializers/__init__.py b/apps/authentication/api/v1/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/api/v1/serializers/jwt.py b/apps/authentication/api/v1/serializers/jwt.py new file mode 100644 index 0000000..ad7801f --- /dev/null +++ b/apps/authentication/api/v1/serializers/jwt.py @@ -0,0 +1,55 @@ +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from apps.captcha_app import exceptions as captcha_exception +from rest_framework_simplejwt.settings import api_settings +from django.contrib.auth.models import update_last_login +from rest_framework import exceptions +from django.core.cache import cache +from typing import Any + + +class CustomizedTokenObtainPairSerializer(TokenObtainPairSerializer): # noqa + """ + customize jwt token + 'set new variables in generated token' + """ + + def validate(self, attrs: dict[str, Any]) -> dict[str, str]: + """ + override validate method to add more conditions + """ + captcha_code, captcha_key = attrs['captcha_code'], attrs['captcha_key'] + + if captcha_code != cache.get(captcha_key) or captcha_code not in attrs.keys(): + raise captcha_exception.CaptchaFailed() + + data = super().validate(attrs) + + refresh = self.get_token(self.user) + + data["refresh"] = str(refresh) + data["access"] = str(refresh.access_token) + data["otp_status"] = self.user.otp_status + + if not self.user.is_active: + raise exceptions.AuthenticationFailed( + self.error_messages["no_active_account"], + "no_active_account", + ) + + if api_settings.UPDATE_LAST_LOGIN: + update_last_login(None, self.user) + + return data + + @classmethod + def get_token(cls, user): + """ + set variables in encoded jwt token + """ + + token = super().get_token(user) + + # Add custom claims + token['name'] = user.username + + return token diff --git a/apps/authentication/api/v1/serializers/serializer.py b/apps/authentication/api/v1/serializers/serializer.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/api/v1/urls.py b/apps/authentication/api/v1/urls.py new file mode 100644 index 0000000..15d42ea --- /dev/null +++ b/apps/authentication/api/v1/urls.py @@ -0,0 +1,14 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, + TokenVerifyView +) +from .api import CustomizedTokenObtainPairView + +urlpatterns = [ + path('login/', CustomizedTokenObtainPairView.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), +] diff --git a/apps/authentication/migrations/0003_user_province_city_organization_city_user_city.py b/apps/authentication/migrations/0003_user_province_city_organization_city_user_city.py new file mode 100644 index 0000000..882c31c --- /dev/null +++ b/apps/authentication/migrations/0003_user_province_city_organization_city_user_city.py @@ -0,0 +1,45 @@ +# Generated by Django 5.0 on 2025-05-03 07:35 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0002_alter_user_id'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='province', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_province', to='authentication.province'), + ), + migrations.CreateModel( + name='City', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_date', models.DateTimeField(auto_now_add=True)), + ('modify_date', models.DateTimeField(auto_now=True)), + ('trash', models.BooleanField(default=False)), + ('name', models.CharField(max_length=50)), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_createdby', to=settings.AUTH_USER_MODEL)), + ('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modifiedby', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='organization', + name='city', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='city_organization', to='authentication.city'), + ), + migrations.AddField( + model_name='user', + name='city', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_city', to='authentication.city'), + ), + ] diff --git a/apps/authentication/migrations/0004_user_otp_status.py b/apps/authentication/migrations/0004_user_otp_status.py new file mode 100644 index 0000000..1cb0425 --- /dev/null +++ b/apps/authentication/migrations/0004_user_otp_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-05-03 08:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0003_user_province_city_organization_city_user_city'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='otp_status', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 502673d..d3fc21e 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -9,6 +9,19 @@ class User(AbstractUser, BaseModel): mobile = models.CharField(max_length=18) national_code = models.CharField(max_length=16) photo = models.CharField(max_length=50) + province = models.ForeignKey( + 'Province', + on_delete=models.CASCADE, + related_name='user_province', + null=True + ) + city = models.ForeignKey( + 'City', + on_delete=models.CASCADE, + related_name='user_city', + null=True + ) + otp_status = models.BooleanField(default=False) def __str__(self): return f'{self.username} {self.last_name}-{self.last_login}' @@ -27,6 +40,16 @@ class Province(BaseModel): super(Province, self).save(*args, **kwargs) +class City(BaseModel): + name = models.CharField(max_length=50) + + def __str__(self): + return f'{self.name}' + + def save(self, *args, **kwargs): + super(City, self).save(*args, **kwargs) + + class Organization(BaseModel): name = models.CharField(max_length=50) type = models.CharField(max_length=50) @@ -36,6 +59,12 @@ class Organization(BaseModel): related_name="province_organization", null=True ) + city = models.ForeignKey( + 'City', + on_delete=models.CASCADE, + related_name='city_organization', + null=True + ) parent_organization = models.ForeignKey( 'Organization', on_delete=models.CASCADE, diff --git a/apps/authentication/urls.py b/apps/authentication/urls.py new file mode 100644 index 0000000..5c71b0f --- /dev/null +++ b/apps/authentication/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register('', '', basename='') + +app_name = "authentication" +urlpatterns = [ + path('api/v1/', include('apps.authentication.api.v1.urls')), +] diff --git a/apps/authorization/migrations/0002_organizationrole_otp_status.py b/apps/authorization/migrations/0002_organizationrole_otp_status.py new file mode 100644 index 0000000..6b5e035 --- /dev/null +++ b/apps/authorization/migrations/0002_organizationrole_otp_status.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0 on 2025-05-03 07:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authorization', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='organizationrole', + name='otp_status', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/authorization/migrations/0003_remove_organizationrole_otp_status.py b/apps/authorization/migrations/0003_remove_organizationrole_otp_status.py new file mode 100644 index 0000000..9f61cd7 --- /dev/null +++ b/apps/authorization/migrations/0003_remove_organizationrole_otp_status.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0 on 2025-05-03 08:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authorization', '0002_organizationrole_otp_status'), + ] + + operations = [ + migrations.RemoveField( + model_name='organizationrole', + name='otp_status', + ), + ] diff --git a/apps/captcha_app/__init__.py b/apps/captcha_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/captcha_app/admin.py b/apps/captcha_app/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/captcha_app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/captcha_app/api/v1/api.py b/apps/captcha_app/api/v1/api.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/captcha_app/api/v1/serializers.py b/apps/captcha_app/api/v1/serializers.py new file mode 100644 index 0000000..b8a467e --- /dev/null +++ b/apps/captcha_app/api/v1/serializers.py @@ -0,0 +1,15 @@ +from rest_captcha.serializers import RestCaptchaSerializer +from rest_captcha import utils +from rest_captcha.settings import api_settings +from django.core.cache import caches + +cache = caches[api_settings.CAPTCHA_CACHE] + + +def noise_default(image, draw): + draw = utils.noise_dots(draw, image, api_settings.CAPTCHA_FOREGROUND_COLOR) + # draw = utils.noise_arcs(draw, image, api_settings.CAPTCHA_FOREGROUND_COLOR) + + +class HumanOnlyDataSerializer(RestCaptchaSerializer): # noqa + pass diff --git a/apps/captcha_app/api/v1/urls.py b/apps/captcha_app/api/v1/urls.py new file mode 100644 index 0000000..306fc4e --- /dev/null +++ b/apps/captcha_app/api/v1/urls.py @@ -0,0 +1,8 @@ +from django.urls import path, include +from .views import CustomizeRestCaptchaView + +app_name = 'captcha_app' + +urlpatterns = [ + path('captcha/', CustomizeRestCaptchaView.as_view(), name='captcha') +] diff --git a/apps/captcha_app/api/v1/utils.py b/apps/captcha_app/api/v1/utils.py new file mode 100644 index 0000000..c641ccc --- /dev/null +++ b/apps/captcha_app/api/v1/utils.py @@ -0,0 +1,71 @@ +import os.path +import random + +from rest_captcha.settings import api_settings as settings +from django.conf import settings as django_setting +from django.core.cache import caches +from rest_captcha import captcha +from PIL import ImageFont, ImageDraw, Image +from io import BytesIO as StringIO + +cache = caches[settings.CAPTCHA_CACHE] + +path = os.path.dirname(__file__) + '/' # noqa + + +def random_char_challenge(length): + chars = '123456789' + ret = '' + for i in range(length): + ret += random.choice(chars) + return ret.upper() + + +def generate_image(word): + font = ImageFont.load_default() + size = settings.CAPTCHA_IMAGE_SIZE + + xpos = 2 + from_top = 4 + + image = captcha.makeimg(size) + + for char in word: + fgimage = Image.new('RGB', size, settings.CAPTCHA_FOREGROUND_COLOR) + charimage = Image.new('L', captcha.getsize(font, ' %s ' % char), '#000000') + chardraw = ImageDraw.Draw(charimage) + chardraw.text((0, 0), char, font=font, fill='#ffffff') + + charimage = charimage.crop(charimage.getbbox()) + maskimage = Image.new('L', size) + + xpos2 = xpos + charimage.size[0] + from_top2 = from_top + charimage.size[1] + maskimage.paste(charimage, (xpos, from_top, xpos2, from_top2)) + size = maskimage.size + image = Image.composite(fgimage, image, maskimage) + xpos = xpos + 2 + charimage.size[0] + + if settings.CAPTCHA_IMAGE_SIZE: + # centering captcha on the image + tmpimg = captcha.makeimg(size) + xpos2 = int((size[0] - xpos) / 2) + from_top2 = int((size[1] - charimage.size[1]) / 2 - from_top) + tmpimg.paste(image, (xpos2, from_top2)) + image = tmpimg.crop((0, 0, size[0], size[1])) + else: + image = image.crop((0, 0, xpos + 1, size[1])) + + draw = ImageDraw.Draw(image) + + # settings.FILTER_FUNCTION(image) + settings.NOISE_FUNCTION(image, draw) + + out = StringIO() + image.save(out, 'PNG') + # image.save('ss.png', 'PNG') + content = out.getvalue() + out.seek(0) + out.close() + + return content diff --git a/apps/captcha_app/api/v1/views.py b/apps/captcha_app/api/v1/views.py new file mode 100644 index 0000000..3631088 --- /dev/null +++ b/apps/captcha_app/api/v1/views.py @@ -0,0 +1,43 @@ +from rest_captcha.settings import api_settings as settings +from rest_framework import views +import uuid +from rest_captcha import utils +import base64 +from rest_framework import response +from .utils import ( + random_char_challenge, + generate_image +) +from django.core.cache import cache + + +class CustomizeRestCaptchaView(views.APIView): + """ + overriding RestCaptchaView to generate captcha image + """ + authentication_classes = () # noqa + permission_classes = () + + def post(self, request): + key = str(uuid.uuid4()) + value = random_char_challenge(settings.CAPTCHA_LENGTH) + cache_key = utils.get_cache_key(key) + print(cache_key) + cache.set(cache_key, value, settings.CAPTCHA_TIMEOUT) + print(cache.get(cache_key)) + + # generate image + image_bytes = generate_image(value) + image_b64 = base64.b64encode(image_bytes) + + data = { + settings.CAPTCHA_KEY: key, + settings.CAPTCHA_IMAGE: image_b64, + 'image_type': 'image/png', + 'image_decode': 'base64' + } + return response.Response(data) + + def get(self, request): + key = cache.get("rest_captcha_9e3ca166-c2f8-41e8-8f19-aa6f520fc123.0") + return response.Response(key) diff --git a/apps/captcha_app/apps.py b/apps/captcha_app/apps.py new file mode 100644 index 0000000..17f3389 --- /dev/null +++ b/apps/captcha_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CaptchaAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.captcha_app' diff --git a/apps/captcha_app/exceptions.py b/apps/captcha_app/exceptions.py new file mode 100644 index 0000000..ee0d76e --- /dev/null +++ b/apps/captcha_app/exceptions.py @@ -0,0 +1,12 @@ +from django.utils.translation import gettext_lazy as _ +from rest_framework.exceptions import APIException +from rest_framework import status + + +class CaptchaFailed(APIException): + """ + raised exception when user entered wrong captcha code + """ + status_code = status.HTTP_403_FORBIDDEN + default_detail = _('Wrong Captcha') + default_code = 'wrong captcha' diff --git a/apps/pos/__init__.py b/apps/pos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pos/admin.py b/apps/pos/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/pos/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/pos/api/v1/api.py b/apps/pos/api/v1/api.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pos/api/v1/serializers.py b/apps/pos/api/v1/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pos/api/v1/urls.py b/apps/pos/api/v1/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/pos/apps.py b/apps/pos/apps.py new file mode 100644 index 0000000..63b4843 --- /dev/null +++ b/apps/pos/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PosConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'pos' diff --git a/apps/pos/models.py b/apps/pos/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/pos/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/pos/tests.py b/apps/pos/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/pos/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/pos/views.py b/apps/pos/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/pos/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements.txt b/requirements.txt index 8682d48..985f673 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ oauth2-provider oauthlib openpyxl packaging -Pillow +pillow==9.5.0 prompt-toolkit psycopg2 psycopg2-binary @@ -63,4 +63,7 @@ tablib typing-extensions urllib3 XlsxWriter -zipp \ No newline at end of file +zipp +django-simple-captcha +django-rest-captcha +pymemcache \ No newline at end of file diff --git a/ss.png b/ss.png new file mode 100644 index 0000000000000000000000000000000000000000..657692191852107d7be1009071ce03516b615d59 GIT binary patch literal 496 zcmVK*1LqwFg292yA1Fc0smnixHLzo?6my301=B02I67Prd_K z?F##Oj4urtT!Pp`Fr1&BcYA(ZNro_vYbNS4WEr&6rEyNCyS_j6h=7t!Yzf->khfZ5 z?tFgo=9^@SIyV1S5R^tfEu{p?*3fc=bC(3|>HAV5bXR+OLT+*Kx!pIX-IgmiHm_yQ zvV}VL5KhO!)<_3vrX{IZmhm4R44_HEL$FrkUt*J`#Fy&qShC@UWKpJ0a)@}2;-Z@z z*tJxKkY&1+-a9f=ZU-M$(nw7Kf9>pUf6!oL5jkKZ{H_7{J8>Xi3Gs0A0_c zH?13`Cs}1p75*+CNEgt`EY6S$=nIm!1ZM=>N=uzGQ1e)Hhs~*% zCBj9)HS8UkX_ww#dVKMoS?NoNC9!egTwSk`Dnj>` zzlS(^!^n=00j|~^)s8HJpTh^^Y2RfZqso90000+e|r literal 0 HcmV?d00001