From 8bc3a307c03b879c26acfad66bd1bcc8e61df615 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 23 Jun 2023 19:13:49 +0530 Subject: [PATCH] Initial Commit --- zod_bank/account/__init__.py | 0 zod_bank/account/admin.py | 25 +++ zod_bank/account/apps.py | 6 + zod_bank/account/migrations/0001_initial.py | 65 +++++++ zod_bank/account/migrations/__init__.py | 0 zod_bank/account/models.py | 75 ++++++++ zod_bank/account/serializers.py | 104 +++++++++++ zod_bank/account/tests.py | 3 + zod_bank/account/urls.py | 20 +++ zod_bank/account/utils.py | 31 ++++ zod_bank/account/views.py | 111 ++++++++++++ zod_bank/base/__init__.py | 3 + zod_bank/base/common_email.py | 30 ++++ zod_bank/base/constants.py | 99 +++++++++++ zod_bank/base/custom_exceptions.py | 16 ++ zod_bank/base/image_constants.py | 16 ++ zod_bank/base/messages.py | 92 ++++++++++ zod_bank/base/routers.py | 17 ++ zod_bank/base/search_and_filters.py | 49 +++++ zod_bank/base/upload_file.py | 177 +++++++++++++++++++ zod_bank/guardian/__init__.py | 0 zod_bank/guardian/admin.py | 9 + zod_bank/guardian/apps.py | 6 + zod_bank/guardian/migrations/0001_initial.py | 43 +++++ zod_bank/guardian/migrations/__init__.py | 0 zod_bank/guardian/models.py | 31 ++++ zod_bank/guardian/serializers.py | 91 ++++++++++ zod_bank/guardian/tasks.py | 6 + zod_bank/guardian/tests.py | 3 + zod_bank/guardian/urls.py | 17 ++ zod_bank/guardian/views.py | 44 +++++ zod_bank/junior/__init__.py | 0 zod_bank/junior/admin.py | 9 + zod_bank/junior/apps.py | 6 + zod_bank/junior/migrations/0001_initial.py | 42 +++++ zod_bank/junior/migrations/__init__.py | 0 zod_bank/junior/models.py | 32 ++++ zod_bank/junior/serializers.py | 73 ++++++++ zod_bank/junior/tests.py | 3 + zod_bank/junior/urls.py | 17 ++ zod_bank/junior/views.py | 40 +++++ zod_bank/manage.py | 21 +++ zod_bank/nginx/django.conf | 24 +++ zod_bank/requirements.txt | 55 ++++++ zod_bank/zod_bank/__init__.py | 0 zod_bank/zod_bank/asgi.py | 16 ++ zod_bank/zod_bank/settings.py | 176 ++++++++++++++++++ zod_bank/zod_bank/urls.py | 33 ++++ zod_bank/zod_bank/wsgi.py | 16 ++ 49 files changed, 1752 insertions(+) create mode 100644 zod_bank/account/__init__.py create mode 100644 zod_bank/account/admin.py create mode 100644 zod_bank/account/apps.py create mode 100644 zod_bank/account/migrations/0001_initial.py create mode 100644 zod_bank/account/migrations/__init__.py create mode 100644 zod_bank/account/models.py create mode 100644 zod_bank/account/serializers.py create mode 100644 zod_bank/account/tests.py create mode 100644 zod_bank/account/urls.py create mode 100644 zod_bank/account/utils.py create mode 100644 zod_bank/account/views.py create mode 100644 zod_bank/base/__init__.py create mode 100644 zod_bank/base/common_email.py create mode 100644 zod_bank/base/constants.py create mode 100644 zod_bank/base/custom_exceptions.py create mode 100644 zod_bank/base/image_constants.py create mode 100644 zod_bank/base/messages.py create mode 100644 zod_bank/base/routers.py create mode 100644 zod_bank/base/search_and_filters.py create mode 100644 zod_bank/base/upload_file.py create mode 100644 zod_bank/guardian/__init__.py create mode 100644 zod_bank/guardian/admin.py create mode 100644 zod_bank/guardian/apps.py create mode 100644 zod_bank/guardian/migrations/0001_initial.py create mode 100644 zod_bank/guardian/migrations/__init__.py create mode 100644 zod_bank/guardian/models.py create mode 100644 zod_bank/guardian/serializers.py create mode 100644 zod_bank/guardian/tasks.py create mode 100644 zod_bank/guardian/tests.py create mode 100644 zod_bank/guardian/urls.py create mode 100644 zod_bank/guardian/views.py create mode 100644 zod_bank/junior/__init__.py create mode 100644 zod_bank/junior/admin.py create mode 100644 zod_bank/junior/apps.py create mode 100644 zod_bank/junior/migrations/0001_initial.py create mode 100644 zod_bank/junior/migrations/__init__.py create mode 100644 zod_bank/junior/models.py create mode 100644 zod_bank/junior/serializers.py create mode 100644 zod_bank/junior/tests.py create mode 100644 zod_bank/junior/urls.py create mode 100644 zod_bank/junior/views.py create mode 100755 zod_bank/manage.py create mode 100644 zod_bank/nginx/django.conf create mode 100644 zod_bank/requirements.txt create mode 100644 zod_bank/zod_bank/__init__.py create mode 100644 zod_bank/zod_bank/asgi.py create mode 100644 zod_bank/zod_bank/settings.py create mode 100644 zod_bank/zod_bank/urls.py create mode 100644 zod_bank/zod_bank/wsgi.py diff --git a/zod_bank/account/__init__.py b/zod_bank/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/account/admin.py b/zod_bank/account/admin.py new file mode 100644 index 0000000..fce7bde --- /dev/null +++ b/zod_bank/account/admin.py @@ -0,0 +1,25 @@ +from django.contrib import admin + +# Register your models here. +from .models import UserProfile, UserEmailOtp, UserPhoneOtp +# Register your models here. +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + list_display = ['user'] + + def __str__(self): + return self.user__email + +@admin.register(UserEmailOtp) +class UserEmailOtpAdmin(admin.ModelAdmin): + list_display = ['email'] + + def __str__(self): + return self.email + '-' + self.otp + +@admin.register(UserPhoneOtp) +class UserPhoneOtpAdmin(admin.ModelAdmin): + list_display = ['phone'] + + def __str__(self): + return self.phone + '-' + self.otp diff --git a/zod_bank/account/apps.py b/zod_bank/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/zod_bank/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'account' diff --git a/zod_bank/account/migrations/0001_initial.py b/zod_bank/account/migrations/0001_initial.py new file mode 100644 index 0000000..3a50122 --- /dev/null +++ b/zod_bank/account/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserEmailOtp', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(max_length=254)), + ('otp', models.CharField(max_length=10)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('expired_at', models.DateTimeField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + options={ + 'db_table': 'user_email_otp', + }, + ), + migrations.CreateModel( + name='UserPhoneOtp', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('country_code', models.IntegerField()), + ('phone', models.CharField(max_length=17)), + ('otp', models.CharField(max_length=10)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('expired_at', models.DateTimeField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + options={ + 'db_table': 'user_phone_otp', + }, + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_type', models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_active', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'user_profile', + }, + ), + ] diff --git a/zod_bank/account/migrations/__init__.py b/zod_bank/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/account/models.py b/zod_bank/account/models.py new file mode 100644 index 0000000..ff8bb5d --- /dev/null +++ b/zod_bank/account/models.py @@ -0,0 +1,75 @@ +from django.db import models +import random +from django.contrib.auth.models import User +from base.constants import USER_TYPE +# Create your models here. + +class UserProfile(models.Model): + """ + User details + """ + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile') + user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_active = models.BooleanField(default=False) + + class Meta(object): + """ Meta information """ + db_table = 'user_profile' + + def __str__(self): + """return phone as an object""" + return f'{self.user}' + +class UserPhoneOtp(models.Model): + """ + This class is used to verify user email and their contact no. + """ + """user details""" + country_code = models.IntegerField() + phone = models.CharField(max_length=17) + """otp details""" + otp = models.CharField(max_length=10) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + expired_at = models.DateTimeField(blank=True, null=True) + is_active = models.BooleanField(default=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_phone_otp' + + def __str__(self): + """return phone as an object""" + return self.phone + +class UserEmailOtp(models.Model): + """ + This class is used to verify user email and their contact no. + """ + """user details""" + email = models.EmailField() + """otp details""" + otp = models.CharField(max_length=10) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + expired_at = models.DateTimeField(blank=True, null=True) + is_active = models.BooleanField(default=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_email_otp' + + def __str__(self): + """return phone as an object""" + return self.email diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py new file mode 100644 index 0000000..03b1012 --- /dev/null +++ b/zod_bank/account/serializers.py @@ -0,0 +1,104 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from guardian.models import Guardian +from junior.models import Junior +from account.models import UserProfile, UserEmailOtp, UserPhoneOtp +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from django.db import transaction +from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR +from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth import authenticate +from rest_framework import viewsets, status +from rest_framework.decorators import action +from django.contrib.auth import authenticate, login +from rest_framework_simplejwt.tokens import RefreshToken + +class SuperUserSerializer(serializers.ModelSerializer): + user_type = serializers.SerializerMethodField('get_user_type') + + def get_user_type(self, obj): + """user type""" + return SUPERUSER + + class Meta(object): + """Meta info""" + model = User + fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type'] + + +class GuardianSerializer(serializers.ModelSerializer): + """guardian serializer""" + user_type = serializers.SerializerMethodField('get_user_type') + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj.user) + access_token = str(refresh.access_token) + return access_token + + def get_user_type(self, obj): + """user type""" + return GUARDIAN + + def get_auth(self, obj): + """user email address""" + return obj.user.username + + def get_first_name(self, obj): + """user first name""" + return obj.user.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.user.last_name + + class Meta(object): + """Meta info""" + model = Guardian + fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', + 'referral_code', 'is_active', 'is_complete_profile', 'passcode', + 'created_at', 'updated_at', 'user_type'] + + +class JuniorSerializer(serializers.ModelSerializer): + """junior serializer""" + user_type = serializers.SerializerMethodField('get_user_type') + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj.auth) + access_token = str(refresh.access_token) + return access_token + + def get_user_type(self, obj): + return JUNIOR + + def get_auth(self, obj): + return obj.auth.username + + def get_first_name(self, obj): + return obj.auth.first_name + + def get_last_name(self, obj): + return obj.auth.last_name + + class Meta(object): + """Meta info""" + model = Junior + fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', + 'updated_at', 'user_type'] + +class EmailVerificationSerializer(serializers.ModelSerializer): + """Email verification serializer""" + class Meta(object): + """Meta info""" + model = UserEmailOtp + fields = '__all__' + diff --git a/zod_bank/account/tests.py b/zod_bank/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py new file mode 100644 index 0000000..1350cea --- /dev/null +++ b/zod_bank/account/urls.py @@ -0,0 +1,20 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +"""Third party import""" +from rest_framework import routers +from .views import UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('user', UserLogin, basename='user') +router.register('superuser', UserLogin, basename='superuser') +router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') +router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') +router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') +router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/account/utils.py b/zod_bank/account/utils.py new file mode 100644 index 0000000..c6ffe7f --- /dev/null +++ b/zod_bank/account/utils.py @@ -0,0 +1,31 @@ +from django.core.mail import send_mail +from django.conf import settings +import random +from rest_framework import viewsets, status +from rest_framework.response import Response +def send_otp_email(recipient_email, otp): + subject = 'One-Time Password' + message = f'Your OTP is: {otp}' + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [recipient_email] + send_mail(subject, message, from_email, recipient_list) + return otp + +def custom_response(detail, data=None, response_status=status.HTTP_200_OK): + """Custom response code""" + if not data: + data = None + + return Response({"data": data, "message": detail, "status": "success", "code": response_status}) + + +def custom_error_response(detail, response_status): + """ + function is used for getting same global error response for all + :param detail: error message . + :param response_status: http status. + :return: Json response + """ + if not detail: + detail = {} + return Response({"error": detail, "status": "failed", "code": response_status}) diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py new file mode 100644 index 0000000..352b834 --- /dev/null +++ b/zod_bank/account/views.py @@ -0,0 +1,111 @@ +from rest_framework import viewsets, status +from rest_framework.decorators import action +from rest_framework.response import Response +from django.contrib.auth import authenticate, login +from guardian.models import Guardian +from junior.models import Junior +from account.models import UserProfile, UserPhoneOtp, UserEmailOtp +from django.contrib.auth.models import User +from .serializers import SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer +from django.views.decorators.csrf import csrf_exempt +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +from guardian.tasks import generate_otp + +from account.utils import custom_response, custom_error_response +class SendPhoneOtp(viewsets.ModelViewSet): + """Send otp on phone""" + def create(self, request, *args, **kwargs): + otp = generate_otp() + UserPhoneOtp.objects.create(country_code=self.request.data['country_code'], + phone=self.request.data['phone'], otp=otp) + return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK) + + +class UserPhoneVerification(viewsets.ModelViewSet): + """Send otp on phone""" + def list(self, request, *args, **kwargs): + try: + phone_data = UserPhoneOtp.objects.filter(phone=request.data['phone'], + otp=request.data['otp']).last() + if phone_data: + phone_data.is_verified = True + phone_data.save() + return custom_response(SUCCESS_CODE['3027'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + + + +class UserLogin(viewsets.ViewSet): + @action(methods=['post'], detail=False) + def login(self, request): + username = request.data.get('username') + password = request.data.get('password') + user = authenticate(request, username=username, password=password) + try: + if user is not None: + login(request, user) + guardian_data = Guardian.objects.filter(user__username=username, is_complete_profile=True).last() + if guardian_data: + serializer = GuardianSerializer(guardian_data) + junior_data = Junior.objects.filter(auth__username=username, is_complete_profile=True).last() + if junior_data: + serializer = JuniorSerializer(junior_data) + if user.is_superuser: + serializer = SuperUserSerializer(user) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) + except Exception as e: + user_profile_data = UserProfile.objects.filter(user__username=username).last() + email_verified = UserEmailOtp.objects.filter(email=username).last() + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + data = {"auth_token":access_token, "is_profile_complete": False, + "user_role": user_profile_data.user_type, + } + is_verified = False + if email_verified: + is_verified = email_verified.is_verified + if not is_verified: + otp = generate_otp() + email_verified.otp = otp + email_verified.save() + data.update({"email_otp":otp}) + return custom_response(ERROR_CODE['2024'], {"email_otp":otp, "is_email_verified": is_verified}, + response_status=status.HTTP_400_BAD_REQUEST) + data.update({"is_email_verified": is_verified}) + return custom_response(None, data, response_status=status.HTTP_200_OK) + +class UserEmailVerification(viewsets.ModelViewSet): + """User Email verification""" + serializer_class = EmailVerificationSerializer + + def list(self, request, *args, **kwargs): + try: + email_data = UserEmailOtp.objects.filter(email=request.data['email'], + otp=request.data['otp']).last() + if email_data: + email_data.is_verified = True + email_data.save() + return custom_response(SUCCESS_CODE['3011'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + +class ReSendEmailOtp(viewsets.ModelViewSet): + """Send otp on phone""" + def create(self, request, *args, **kwargs): + otp = generate_otp() + if User.objects.filter(email=request.data['email']): + UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + return custom_response(None, {'email_otp': otp}, response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) + diff --git a/zod_bank/base/__init__.py b/zod_bank/base/__init__.py new file mode 100644 index 0000000..860fa73 --- /dev/null +++ b/zod_bank/base/__init__.py @@ -0,0 +1,3 @@ +""" +This is init module of the Project Zod Bank +""" diff --git a/zod_bank/base/common_email.py b/zod_bank/base/common_email.py new file mode 100644 index 0000000..4eb2a36 --- /dev/null +++ b/zod_bank/base/common_email.py @@ -0,0 +1,30 @@ +""" +Common send_mail function +""" +import logging + +from django.core.mail import EmailMultiAlternatives + + +def send_mail(subject, message, from_email, recipient_list, html_message=None, cc=None, + fail_silently=False): + """ + Send Email + :param subject: + :param message: + :param from_email: + :param recipient_list: + :param html_message: + :param cc: + :param fail_silently: + :return: + """ + try: + mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, cc) + if html_message: + mail.attach_alternative(html_message, 'text/html') + + return mail.send(fail_silently) + except Exception as e: + logging.error(e) + return False diff --git a/zod_bank/base/constants.py b/zod_bank/base/constants.py new file mode 100644 index 0000000..72f2b0c --- /dev/null +++ b/zod_bank/base/constants.py @@ -0,0 +1,99 @@ +""" +This module contains constants used throughout the project +""" +import os + +# GOOGLE_URL used for interact with google server to verify user existence. +#GOOGLE_URL = "https://www.googleapis.com/plus/v1/" + + +# Super Admin string constant for 'role' +SUPER_ADMIN = "Super Admin" + +# Define jwt_token_expiration time in minutes for now token will expire after 3 days +JWT_TOKEN_EXPIRATION = 3 * 24 * 60 + +# Define common file extention +FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg", "csv", "doc", "docx", "odt", "pdf", "rtf", "txt", "wks", "wp", + "wpd") + +# Define file size in bytes(5MB = 5 * 1024 * 1024) +FILE_SIZE = 5 * 1024 * 1024 + +# String constant for configurable date for allocation lock period +ALLOCATION_LOCK_DATE = 1 + +sort_dict = { + '1': 'name', + '2': '-name' +} +USER_TYPE = ( + ('1', 'junior'), + ('2', 'guardian'), + ('3', 'superuser') +) +GENDERS = ( + ('1', 'Male'), + ('2', 'Female') +) +# duplicate name used defined in constant PROJECT_NAME +PROJECT_NAME = 'Zod Bank' +GUARDIAN = 'guardian' +JUNIOR = 'junior' +SUPERUSER = 'superuser' +# numbers used as a constant +NUMBER = { + 'point_zero': 0.0, + 'zero': 0, + 'one': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'twelve': 12, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty_four': 24, + 'twenty_one': 21, + 'twenty_two': 22, + 'twenty_five': 25, + 'thirty': 30, + 'thirty_five': 35, + 'thirty_six': 36, + 'forty': 40, + 'fifty': 50, + 'fifty_nine': 59, + 'sixty': 60, + 'seventy_five': 75, + 'eighty': 80, + 'ninty_five': 95, + 'ninty_six': 96, + 'ninety_nine': 99, + 'hundred': 100, + 'one_one_nine': 119, + 'one_twenty': 120, + 'four_zero_four': 404, + 'five_hundred': 500, + 'minus_one': -1, + 'point_three': 0.3, + 'point_seven': 0.7 +} + +# Define the byte into kb +BYTE_IMAGE_SIZE = 1024 + +# validate file size +MAX_FILE_SIZE = 1024 * 1024 * 5 + + diff --git a/zod_bank/base/custom_exceptions.py b/zod_bank/base/custom_exceptions.py new file mode 100644 index 0000000..5815504 --- /dev/null +++ b/zod_bank/base/custom_exceptions.py @@ -0,0 +1,16 @@ +""" +module containing override conflict error class +""" +# third party imports +from django.utils.translation import ugettext_lazy as _ +from rest_framework import status +from rest_framework.exceptions import APIException + + +class ConflictError(APIException): + """ + Override conflict error + """ + status_code = status.HTTP_409_CONFLICT + default_detail = _('Not allowed request.') + default_code = 'conflict_error' diff --git a/zod_bank/base/image_constants.py b/zod_bank/base/image_constants.py new file mode 100644 index 0000000..0737698 --- /dev/null +++ b/zod_bank/base/image_constants.py @@ -0,0 +1,16 @@ +""" +This module contains constants used throughout the project +""" +from zod_bank.settings import BUCKET_NAME + +# Define S3 folder url +S3_FOLDER_DIR = { + 'user_image': 'user_image/', +} + +# S3 bucket url +S3_URL = "https://"+BUCKET_NAME+".s3.amazonaws.com/" + +S3_FOLDER_URL = { + 'user_image_file': S3_URL+S3_FOLDER_DIR['user_image'], +} diff --git a/zod_bank/base/messages.py b/zod_bank/base/messages.py new file mode 100644 index 0000000..718d6be --- /dev/null +++ b/zod_bank/base/messages.py @@ -0,0 +1,92 @@ +""" +This module contains all the messages used all across the project +""" + +ERROR_CODE_REQUIRED = { + # Error code for email address + "1000": ["Required email address not found."], + # Error code for password + "1001": ["Required password not found."], + # Error code for Required Post parameters + "1002": ["Required POST parameters not found."], + # Error code for Required Get parameters + "1003": ["Required GET parameters not found."], + # Error code for Required Headers + "1004": ["Required headers were not found."], + # Error code for Required Put parameters + "1005": ["Required PUT parameters not found."], + # Error code for Required query parameters + "1006": ["Required query parameters is not valid."], + # Error code for Required Head parameters + "1008": ["Required HEAD parameters not found."] +} + +# Error code +ERROR_CODE = { + "2000": "Email not found.", + "2001": "Your account has not been verified. Please check your email and verify it.", + "2002": "Invalid login credentials.", + "2003": "An account already exists with this email address.", + "2004": "User doesn't exist.", + "2005": "Your account has been activated.", + "2006": "Your account is not activated.", + "2007": "Your account already activated.", + "2008": "Invalid OTP.", + "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.", + "2010": "Invalid Link.", + "2011": "Your profile has not been completed yet.", + "2012": "Password and Confirm password should be same.", + "2013": "Invalid token.", + "2014": "Your old password doesn't match.", + "2015": "Invalid old password.", + "2016": "Invalid search.", + "2017": "{model} object with {pk} does not exist", + "2018": "Attached File not found", + "2019": "Either File extension or File size doesn't meet the requirements", + "2020": "Enter valid mobile number", + "2021": "Already register", + "2022":"Invalid Guardian code", + "2023":"Invalid user", + "2024":"Email not verified" +} +SUCCESS_CODE = { + # Success code for password + "3001": "Sign up successfully", + # Success code for Thank you + "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.", + # Success code for account activation + "3003": "Your account has been activated.", + # Success code for password reset + "3004": "Password reset link has been sent to your email address", + # Success code for link verified + "3005": "Your link has been verified, it's valid", + # Success code for password reset + "3006": "Your password has been reset successfully.", + # Success code for password update + "3007": "Your password has been updated successfully.", + # Success code for valid link + "3008": "You have a valid link.", + # Success code for logged out + "3009": "You have successfully logged out!", + # Success code for check all fields + "3010": "All fields are valid", + "3011": "Email OTP Verified successfully", + "3012": "Phone OTP Verified successfully", + "3013": "Valid Guardian code", + "3014": "Password has been updated successfully." +} + +STATUS_CODE_ERROR = { + # Status code for Invalid Input + "4001": ["Invalid input."], + # Status code for Authentication credentials + "4002": ["Authentication credentials were not provided."], + # Status code for Permission + "4003": ["You do not have permission to perform this action."], + # Status code for not found + "4004": ["Not found."], + # Status code for method not allowed + "4005": ["Method not allowed."] +} + + diff --git a/zod_bank/base/routers.py b/zod_bank/base/routers.py new file mode 100644 index 0000000..e2df0e1 --- /dev/null +++ b/zod_bank/base/routers.py @@ -0,0 +1,17 @@ +""" +Custom routers for job sourcing . +""" +# third party imports +from rest_framework.routers import DefaultRouter + + +class OptionalSlashRouter(DefaultRouter): + """ + optional slash router class + """ + def __init__(self): + """ + explicitly appending '/' in urls if '/' doesn't exists for making common url patterns . + """ + super(OptionalSlashRouter, self).__init__() + self.trailing_slash = '/?' diff --git a/zod_bank/base/search_and_filters.py b/zod_bank/base/search_and_filters.py new file mode 100644 index 0000000..52330ef --- /dev/null +++ b/zod_bank/base/search_and_filters.py @@ -0,0 +1,49 @@ +""" +This module contains search methods that can be used to search over a particular field in a specific model. +Update SEARCH_MAP dict for searching with model name and fields name +""" +# python imports +import operator +from functools import reduce + +# third party imports +from django.contrib.auth import get_user_model +from django.db.models import Q + +SEARCH_MAP = { + get_user_model(): {'search_fields': ['first_name__icontains', 'last_name__icontains', 'username__icontains']} +} + + +def search_query(search_by, search_term): + """ + :param search_by: search fields + :param search_term: search value + :return: return query + """ + query = [] + if search_by: + for key in search_by: + query.append({key: search_term}) + return query + + +def get_search_fields(model): + """ + :param model: model name + :return: dict of searching field name + """ + return SEARCH_MAP[model]['search_fields'] + + +def global_search(search_data, model_name): + """ + :param search_data: search value + :param model_name: model name + :return: query + """ + # get search fields for the above model + search_fields = get_search_fields(model_name) + # build query + query = search_query(search_fields, search_data) + return model_name.objects.filter(reduce(operator.or_, [Q(**x) for x in query])) diff --git a/zod_bank/base/upload_file.py b/zod_bank/base/upload_file.py new file mode 100644 index 0000000..14f9e02 --- /dev/null +++ b/zod_bank/base/upload_file.py @@ -0,0 +1,177 @@ +""" +This file used for file uploaded +""" +import datetime +# python imports +import logging +import mimetypes +import os + +import boto3 +from django.core.files.storage import FileSystemStorage +# django imports +from django.utils.crypto import get_random_string +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK + +from base import constants +from base.constants import NUMBER +# local import +from resourcekit.settings import base_settings as settings +from resourcekit.settings.base_settings import BASE_DIR + + +def image_upload(folder, file_name, data): + """ + Function to upload files + :param folder:folder location string + :param file_name:file_name without ext string + :param data:data file obj + :return:Dictionary + """ + status = HTTP_400_BAD_REQUEST + img_name = None + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + bucket_name = settings.BUCKET_NAME + MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp') + fss = FileSystemStorage() + file = fss.save('tmp/' + str(file_name), data) + fss.url(file) + tmp_file = os.path.join(MEDIA_ROOT, str(file_name)) + s3_client.upload_file( + tmp_file, bucket_name, folder + str(file_name), + ExtraArgs={'ACL': 'public-read', 'ContentType': data.content_type} + ) + os.unlink(tmp_file) + img_name = file_name + status = HTTP_200_OK + except Exception as e: + error = e + logging.error(e) + return status, error, img_name + + +def file_delete(folder, file_name): + """ + To delete common file + :param folder: folder name str + :param file_name: file_name string type + """ + status = HTTP_400_BAD_REQUEST + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + s3_client.delete_object(Bucket=settings.BUCKET_NAME, Key=str(folder) + str(file_name)) + status = HTTP_200_OK + except Exception as e: + error = e + return status, error + + +def get_aws_obj(folder, file_name): + """ + To get aws file obj + :param folder: folder string type + :param file_name: file_name string type + """ + status = HTTP_400_BAD_REQUEST + obj = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + file_name = folder + file_name + obj = s3_client.get_object(Bucket=settings.BUCKET_NAME, Key=file_name) + status = HTTP_200_OK + except Exception as e: + logging.error(e) + return status, obj + + +def upload_image(post_data, folder): + """ + :param post_data: + :param folder: string type + :return: + """ + upload_obj = None + # Check Post data + if post_data: + date_now = datetime.datetime.now() + file_extension = os.path.splitext(str(post_data.name)) + file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower() + rand = get_random_string(NUMBER['twelve']) + image_name = str(rand) + date_now.strftime("%s") + "." + file_extension + upload_obj = image_upload(folder, image_name, post_data) + return upload_obj + + +def upload_voice_kit_image(post_data, folder, image_dir): + """ + :param post_data: + :param folder: string type + :param image_dir: image_dir + :return: + """ + upload_obj = None + # Check Post data + if post_data: + date_now = datetime.datetime.now() + file_extension = os.path.splitext(str(post_data)) + file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower() + rand = get_random_string(NUMBER['twelve']) + image_name = str(rand) + date_now.strftime("%s") + "." + file_extension + upload_obj = voice_kit_image_upload(folder, image_name, post_data, image_dir) + return upload_obj + + +def voice_kit_image_upload(folder, file_name, data, image_dir): + """ + Function to upload files + :param folder:folder location string + :param file_name:file_name without ext string + :param data:data file obj + :return:Dictionary + """ + status = HTTP_400_BAD_REQUEST + img_name = None + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + bucket_name = settings.BUCKET_NAME + MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp') + fss = FileSystemStorage() + with open(image_dir+data, 'rb') as f: + file = fss.save('tmp/' + str(file_name), f) + fss.url(file) + tmp_file = os.path.join(MEDIA_ROOT, str(file_name)) + s3_client.upload_file( + tmp_file, bucket_name, folder + str(file_name), + ExtraArgs={'ACL': 'public-read', 'ContentType': mimetypes.guess_type(file_name)[0]} + ) + os.unlink(tmp_file) + img_name = file_name + status = HTTP_200_OK + except Exception as e: + error = e + logging.error(e) + return status, error, img_name + diff --git a/zod_bank/guardian/__init__.py b/zod_bank/guardian/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/guardian/admin.py b/zod_bank/guardian/admin.py new file mode 100644 index 0000000..f2c8fbc --- /dev/null +++ b/zod_bank/guardian/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Guardian +# Register your models here. +@admin.register(Guardian) +class GuardianAdmin(admin.ModelAdmin): + list_display = ['user', 'family_name'] + + def __str__(self): + return self.user__email \ No newline at end of file diff --git a/zod_bank/guardian/apps.py b/zod_bank/guardian/apps.py new file mode 100644 index 0000000..b69ed5d --- /dev/null +++ b/zod_bank/guardian/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CustodianConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'guardian' diff --git a/zod_bank/guardian/migrations/0001_initial.py b/zod_bank/guardian/migrations/0001_initial.py new file mode 100644 index 0000000..0038a3b --- /dev/null +++ b/zod_bank/guardian/migrations/0001_initial.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Guardian', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('country_code', models.IntegerField(blank=True, null=True)), + ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)), + ('family_name', models.CharField(blank=True, default=None, max_length=50, null=True)), + ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=15, null=True)), + ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)), + ('guardian_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('junior_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)), + ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('is_active', models.BooleanField(default=True)), + ('is_complete_profile', models.BooleanField(default=False)), + ('passcode', models.IntegerField(blank=True, default=None, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')), + ], + options={ + 'verbose_name': 'Guardian', + 'db_table': 'guardians', + }, + ), + ] diff --git a/zod_bank/guardian/migrations/__init__.py b/zod_bank/guardian/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/guardian/models.py b/zod_bank/guardian/models.py new file mode 100644 index 0000000..2819bf3 --- /dev/null +++ b/zod_bank/guardian/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.contrib.auth import get_user_model +from base.constants import GENDERS +from django.contrib.postgres.fields import ArrayField +User = get_user_model() +# Create your models here. + +class Guardian(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email') + country_code = models.IntegerField(blank=True, null=True) + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + family_name = models.CharField(max_length=50, null=True, blank=True, default=None) + gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) + dob = models.DateField(max_length=15, null=True, blank=True, default=None) + guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) + junior_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) + referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(null=True, blank=True, default=None) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta class """ + db_table = 'guardians' + verbose_name = 'Guardian' + + def __str__(self): + return f'{self.user}' diff --git a/zod_bank/guardian/serializers.py b/zod_bank/guardian/serializers.py new file mode 100644 index 0000000..e55c766 --- /dev/null +++ b/zod_bank/guardian/serializers.py @@ -0,0 +1,91 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from .models import Guardian +from django.db import transaction +import random +from account.models import UserProfile +from junior.models import Junior +from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +class UserSerializer(serializers.ModelSerializer): + auth_token = serializers.SerializerMethodField('get_auth_token') + + + class Meta(object): + model = User + fields = ['email', 'password', 'auth_token'] + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj) + access_token = str(refresh.access_token) + return access_token + def create(self, validated_data): + email = validated_data.get('email') + user_type = self.context + password = validated_data.get('password') + try: + user = User.objects.create_user(username=email, email=email, password=password) + UserProfile.objects.create(user=user, user_type=user_type) + return user + except: + raise serializers.ValidationError({"details":ERROR_CODE['2021']}) + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance + +class CreateGuardianSerializer(serializers.ModelSerializer): + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + phone = serializers.CharField(max_length=20, required=False) + family_name = serializers.CharField(max_length=100, required=False) + country_code = serializers.IntegerField(required=False) + dob = serializers.DateField(required=False) + referral_code = serializers.CharField(max_length=100, required=False) + + class Meta(object): + model = Guardian + fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', + 'is_complete_profile'] + + def get_first_name(self,obj): + return obj.user.first_name + + def get_last_name(self,obj): + return obj.user.last_name + + def get_email(self,obj): + return obj.user.email + + def create(self, validated_data): + user = User.objects.filter(username=self.context['user']).last() + if user: + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + guardian, created = Guardian.objects.get_or_create(user=self.context['user']) + if created: + guardian.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + guardian.guardian_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + if guardian: + guardian.phone = validated_data.get('phone', guardian.phone) + guardian.gender = validated_data.get('gender',guardian.gender) + guardian.family_name = validated_data.get('family_name', guardian.family_name) + guardian.dob = validated_data.get('dob',guardian.dob) + guardian.passcode = validated_data.get('passcode', guardian.passcode) + complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, + guardian.dob, guardian.country_code, user.first_name, user.last_name]) + guardian.is_complete_profile = False + if complete_profile_field: + guardian.is_complete_profile = True + guardian.country_code = validated_data.get('country_code', guardian.country_code) + guardian.save() + return guardian + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/zod_bank/guardian/tasks.py b/zod_bank/guardian/tasks.py new file mode 100644 index 0000000..7a5dd90 --- /dev/null +++ b/zod_bank/guardian/tasks.py @@ -0,0 +1,6 @@ +"""task files""" +"""Django import""" +import random +def generate_otp(): + """generate random otp""" + return ''.join([str(random.randrange(9)) for _ in range(6)]) diff --git a/zod_bank/guardian/tests.py b/zod_bank/guardian/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/guardian/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/guardian/urls.py b/zod_bank/guardian/urls.py new file mode 100644 index 0000000..64aa80f --- /dev/null +++ b/zod_bank/guardian/urls.py @@ -0,0 +1,17 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +from .views import SignupViewset, UpdateGuardianProfile +"""Third party import""" +from rest_framework import routers + +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('sign-up', SignupViewset, basename='sign-up') +router.register('update-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/guardian/views.py b/zod_bank/guardian/views.py new file mode 100644 index 0000000..f43e4c8 --- /dev/null +++ b/zod_bank/guardian/views.py @@ -0,0 +1,44 @@ +from django.shortcuts import render +from rest_framework import (pagination, viewsets, status, generics, mixins) +from .serializers import CreateGuardianSerializer +from rest_framework.decorators import action +from rest_framework.response import Response +from django.views.decorators.csrf import csrf_exempt +# Create your views here. +from rest_framework import viewsets, status +from rest_framework.response import Response +from .serializers import UserSerializer +from django.contrib.auth.models import User +from rest_framework.permissions import IsAuthenticated +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from junior.models import Junior +from account.models import UserEmailOtp +from .tasks import generate_otp +from account.utils import send_otp_email +from account.utils import custom_response, custom_error_response +from base.messages import ERROR_CODE, SUCCESS_CODE +class SignupViewset(viewsets.ModelViewSet): + serializer_class = UserSerializer + + def create(self, request, *args, **kwargs): + serializer = UserSerializer(context=request.data['user_type'], data=request.data) + if serializer.is_valid(): + serializer.save() + otp = generate_otp() + UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + # send_otp_email(request.data['email'], otp) + return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class UpdateGuardianProfile(viewsets.ViewSet): + serializer_class = CreateGuardianSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + serializer = CreateGuardianSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/junior/__init__.py b/zod_bank/junior/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/junior/admin.py b/zod_bank/junior/admin.py new file mode 100644 index 0000000..07d6053 --- /dev/null +++ b/zod_bank/junior/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Junior +# Register your models here. +@admin.register(Junior) +class JuniorAdmin(admin.ModelAdmin): + list_display = ['auth'] + + def __str__(self): + return self.auth__email diff --git a/zod_bank/junior/apps.py b/zod_bank/junior/apps.py new file mode 100644 index 0000000..0232709 --- /dev/null +++ b/zod_bank/junior/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class JuniorConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'junior' diff --git a/zod_bank/junior/migrations/0001_initial.py b/zod_bank/junior/migrations/0001_initial.py new file mode 100644 index 0000000..e451c2e --- /dev/null +++ b/zod_bank/junior/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Junior', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)), + ('country_code', models.IntegerField(blank=True, null=True)), + ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=10, null=True)), + ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)), + ('junior_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('guardian_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)), + ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('is_active', models.BooleanField(default=True)), + ('is_complete_profile', models.BooleanField(default=False)), + ('passcode', models.IntegerField(blank=True, default=None, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('auth', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')), + ], + options={ + 'verbose_name': 'Junior', + 'db_table': 'junior', + }, + ), + ] diff --git a/zod_bank/junior/migrations/__init__.py b/zod_bank/junior/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/junior/models.py b/zod_bank/junior/models.py new file mode 100644 index 0000000..43c9d74 --- /dev/null +++ b/zod_bank/junior/models.py @@ -0,0 +1,32 @@ +from django.db import models +from django.contrib.auth import get_user_model +from base.constants import GENDERS +from guardian.models import Guardian +from django.contrib.postgres.fields import ArrayField +User = get_user_model() +# Create your models here. + +class Junior(models.Model): + auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + country_code = models.IntegerField(blank=True, null=True) + gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) + dob = models.DateField(max_length=15, null=True, blank=True, default=None) + # image = models.ImageField(upload_to='images/') + junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) + guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) + referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(null=True, blank=True, default=None) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta class """ + db_table = 'junior' + verbose_name = 'Junior' + + def __str__(self): + return f'{self.auth}' diff --git a/zod_bank/junior/serializers.py b/zod_bank/junior/serializers.py new file mode 100644 index 0000000..3e493e7 --- /dev/null +++ b/zod_bank/junior/serializers.py @@ -0,0 +1,73 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from .models import Guardian +from django.db import transaction +import random +from account.models import UserProfile +from junior.models import Junior +from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +from django.contrib.postgres.fields import ArrayField + +class ListCharField(serializers.ListField): + child = serializers.CharField() + + def to_representation(self, data): + return data + + def to_internal_value(self, data): + if isinstance(data, list): + return data + raise serializers.ValidationError({"details":"Invalid input. Expected a list of strings."}) + + +class CreateJuniorSerializer(serializers.ModelSerializer): + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + phone = serializers.CharField(max_length=20, required=False) + country_code = serializers.IntegerField(required=False) + dob = serializers.DateField(required=False) + referral_code = serializers.CharField(max_length=100, required=False) + guardian_code = ListCharField(required=False) + + class Meta(object): + model = Junior + fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', + 'passcode', 'is_complete_profile', 'guardian_code'] + + def get_first_name(self,obj): + return obj.auth.first_name + + def get_last_name(self,obj): + return obj.auth.last_name + + def get_email(self,obj): + return obj.auth.email + + def create(self, validated_data): + user = User.objects.filter(username=self.context['user']).last() + if user: + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + junior, created = Junior.objects.get_or_create(auth=self.context['user']) + if created: + junior.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + junior.junior_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + if junior: + junior.phone = validated_data.get('phone', junior.phone) + junior.gender = validated_data.get('gender',junior.gender) + junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) + junior.dob = validated_data.get('dob',junior.dob) + junior.passcode = validated_data.get('passcode', junior.passcode) + junior.is_complete_profile = validated_data.get('is_complete_profile', junior.is_complete_profile) + junior.country_code = validated_data.get('country_code', junior.country_code) + junior.save() + return junior + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/zod_bank/junior/tests.py b/zod_bank/junior/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/junior/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/junior/urls.py b/zod_bank/junior/urls.py new file mode 100644 index 0000000..cce385d --- /dev/null +++ b/zod_bank/junior/urls.py @@ -0,0 +1,17 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +from .views import UpdateJuniorProfile, ValidateGuardianCode +"""Third party import""" +from rest_framework import routers + +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('profile-update', UpdateJuniorProfile, basename='profile-update') +router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/junior/views.py b/zod_bank/junior/views.py new file mode 100644 index 0000000..4eb3a35 --- /dev/null +++ b/zod_bank/junior/views.py @@ -0,0 +1,40 @@ +from django.shortcuts import render +from rest_framework import (pagination, viewsets, status, generics, mixins) +from rest_framework.decorators import action +from rest_framework.response import Response +from django.views.decorators.csrf import csrf_exempt +# Create your views here. +from rest_framework import viewsets, status +from rest_framework.response import Response +from .serializers import CreateJuniorSerializer +from django.contrib.auth.models import User +from rest_framework.permissions import IsAuthenticated +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from junior.models import Junior +from guardian.models import Guardian +from base.messages import ERROR_CODE, SUCCESS_CODE +from account.utils import custom_response, custom_error_response + +class UpdateJuniorProfile(viewsets.ViewSet): + serializer_class = CreateJuniorSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + serializer = CreateJuniorSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ValidateGuardianCode(viewsets.ViewSet): + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + guardian_code = request.data.get('guardian_code') + for code in guardian_code: + guardian_data = Guardian.objects.filter(guardian_code=code).exists() + if guardian_data: + return custom_response(SUCCESS_CODE['3028'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/manage.py b/zod_bank/manage.py new file mode 100755 index 0000000..169c7c2 --- /dev/null +++ b/zod_bank/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/zod_bank/nginx/django.conf b/zod_bank/nginx/django.conf new file mode 100644 index 0000000..488e2c0 --- /dev/null +++ b/zod_bank/nginx/django.conf @@ -0,0 +1,24 @@ +upstream web { + ip_hash; + server web:8000; + } + + # portal + server { + location / { + proxy_pass http://web/; + proxy_set_header Host $http_host; + } + listen 8000; + client_max_body_size 512M; + server_name localhost; + proxy_read_timeout 900; + proxy_connect_timeout 900; + proxy_send_timeout 900; + #proxy_set_header Host $http_host; + + location /static { + autoindex on; + alias /usr/src/app/zod_bank/static/; + } + } diff --git a/zod_bank/requirements.txt b/zod_bank/requirements.txt new file mode 100644 index 0000000..e78af44 --- /dev/null +++ b/zod_bank/requirements.txt @@ -0,0 +1,55 @@ +aliyun-python-sdk-core==2.13.36 +aliyun-python-sdk-dysmsapi==2.1.2 +amqp==5.1.1 +asgiref==3.7.2 +async-timeout==4.0.2 +billiard==4.1.0 +boto3==1.26.157 +botocore==1.29.157 +celery==5.3.1 +cffi==1.15.1 +channels==4.0.0 +channels-redis==4.1.0 +click==8.1.3 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.3.0 +cron-descriptor==1.4.0 +cryptography==41.0.1 +decouple==0.0.7 +Django==4.2.2 +django-celery-beat==2.5.0 +django-celery-results==2.5.1 +django-cors-headers==4.1.0 +django-dotenv==1.4.2 +django-extensions==3.2.3 +django-ses==3.5.0 +django-storages==1.13.2 +django-timezone-field==5.1 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +drf-yasg==1.21.6 +inflection==0.5.1 +jmespath==0.10.0 +kombu==5.3.1 +msgpack==1.0.5 +packaging==23.1 +prompt-toolkit==3.0.38 +psycopg==3.1.9 +pycparser==2.21 +PyJWT==2.7.0 +python-crontab==2.7.1 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +pytz==2023.3 +PyYAML==6.0 +redis==4.5.5 +s3transfer==0.6.1 +six==1.16.0 +sqlparse==0.4.4 +typing_extensions==4.6.3 +tzdata==2023.3 +uritemplate==4.1.1 +urllib3==1.26.16 +vine==5.0.0 +wcwidth==0.2.6 diff --git a/zod_bank/zod_bank/__init__.py b/zod_bank/zod_bank/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/zod_bank/asgi.py b/zod_bank/zod_bank/asgi.py new file mode 100644 index 0000000..967882b --- /dev/null +++ b/zod_bank/zod_bank/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ZOD_Bank project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + +application = get_asgi_application() diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/zod_bank/settings.py new file mode 100644 index 0000000..0c8da76 --- /dev/null +++ b/zod_bank/zod_bank/settings.py @@ -0,0 +1,176 @@ +""" +Django settings for ZOD_Bank project. + +Generated by 'django-admin startproject' using Django 3.0.14. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +import os +from dotenv import load_dotenv +from datetime import timedelta +load_dotenv() +# OR, the same with increased verbosity: +load_dotenv(verbose=True) +env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env') +load_dotenv(dotenv_path=env_path) + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +# OR, the same with increased verbosity: +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '-pb+8w#)6qsh+w&tr+q$tholf7=54v%05e^9!lneiqqgtddg6q' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django_extensions', + 'storages', + 'drf_yasg', + 'corsheaders', + 'django.contrib.postgres', + 'rest_framework', + 'django_ses', + 'account', + 'junior', + 'guardian', +] +# CSRF_COOKIE_SECURE = False +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'zod_bank.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'zod_bank.wsgi.application' +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + # 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', +] +} +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), +} +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME':os.getenv('DB_NAME'), + 'USER':os.getenv('DB_USERNAME'), + 'PASSWORD':os.getenv('DB_PASSWORD'), + 'HOST':os.getenv('DB_HOST'), + 'PORT':os.getenv('DB_PORT'), + } +} +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# cors header settings +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +CORS_ORIGIN_ALLOW_ALL = True + +CORS_ALLOW_METHODS = ( + 'DELETE', + 'GET', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', +) + +CORS_ALLOW_HEADERS = ( + 'accept', + 'accept-encoding', + 'authorization', + 'content-type', + 'dnt', + 'origin', + 'account-agent', + 'x-csrftoken', + 'x-requested-with', +) + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + + + +STATIC_URL = '/static/' diff --git a/zod_bank/zod_bank/urls.py b/zod_bank/zod_bank/urls.py new file mode 100644 index 0000000..274451e --- /dev/null +++ b/zod_bank/zod_bank/urls.py @@ -0,0 +1,33 @@ +"""ZOD_Bank URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +# third-party import +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import path, include +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from django.urls import path + + +schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, ) + +urlpatterns = [ + path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), + path('admin/', admin.site.urls), + path('', include(('account.urls', 'account'), namespace='account')), + path('', include('guardian.urls')), + path('', include(('junior.urls', 'junior'), namespace='junior')), +] diff --git a/zod_bank/zod_bank/wsgi.py b/zod_bank/zod_bank/wsgi.py new file mode 100644 index 0000000..e6b5c4f --- /dev/null +++ b/zod_bank/zod_bank/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ZOD_Bank project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + +application = get_wsgi_application()