diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..063a8af --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/static +/media +.idea/ +*.pyc +media/ +*.name +*.iml +*.log +*.xml +*.pyo +.DS_Store +.idea +venv/* +static/* +*.pem +*.sqlite3 +/migrations/__pycache__/ +/__pycache__/ +/*.pyc +*/__pycache__/*.pyc +__pycache__/ +*.env +ve/* + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..77c6fa8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9 +ENV PYTHONUNBUFFERED 1 +RUN mkdir /usr/src/app +WORKDIR /usr/src/app +COPY . . +RUN apt-get update +RUN apt-get install wkhtmltopdf -y +RUN apt install -y gdal-bin python3-gdal +RUN pip install -r requirements.txt +WORKDIR /usr/src/app + diff --git a/account/__init__.py b/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/account/admin.py b/account/admin.py new file mode 100644 index 0000000..7dbf869 --- /dev/null +++ b/account/admin.py @@ -0,0 +1,31 @@ +"""Account admin""" +from django.contrib import admin + +"""Import django app""" +from .models import UserProfile, UserEmailOtp, UserPhoneOtp +# Register your models here. +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + """User profile admin""" + list_display = ['user'] + + def __str__(self): + return self.user__email + +@admin.register(UserEmailOtp) +class UserEmailOtpAdmin(admin.ModelAdmin): + """User Email otp admin""" + list_display = ['email'] + + def __str__(self): + """Return object in email and otp format""" + return self.email + '-' + self.otp + +@admin.register(UserPhoneOtp) +class UserPhoneOtpAdmin(admin.ModelAdmin): + """User Phone otp admin""" + list_display = ['phone'] + + def __str__(self): + """Return object in phone number and otp format""" + return self.phone + '-' + self.otp diff --git a/account/apps.py b/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/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/account/migrations/0001_initial.py b/account/migrations/0001_initial.py new file mode 100644 index 0000000..3a50122 --- /dev/null +++ b/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/account/migrations/__init__.py b/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/account/models.py b/account/models.py new file mode 100644 index 0000000..ff8bb5d --- /dev/null +++ b/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/account/serializers.py b/account/serializers.py new file mode 100644 index 0000000..afd923c --- /dev/null +++ b/account/serializers.py @@ -0,0 +1,178 @@ +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 GoogleSignInSerializer(serializers.Serializer): + """Google login Serializer""" + email = serializers.EmailField() + + def create(self, validated_data): + """Create or update user model""" + with transaction.atomic(): + print("validated_data====>",validated_data) + if User.objects.filter(email__iexact=self.validated_data['email']).exists(): + return User.objects.get(email__iexact=self.validated_data['email']) + + if not User.objects.filter(email__iexact=self.validated_data['email']).exists(): + instance = User.objects.create(username=self.validated_data['email'], + email=self.validated_data['email']) + return instance + + +class ResetPasswordSerializer(serializers.Serializer): + """Reset Password after verification""" + verification_code = serializers.CharField(max_length=10) + password = serializers.CharField(required=True) + class Meta(object): + """Meta info""" + model = User + + def create(self, validated_data): + verification_code = validated_data.pop('verification_code') + password = validated_data.pop('password') + print("verification_code===>",verification_code) + print("password===>", password) + user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last() + print("user_opt_details===>",user_opt_details) + if user_opt_details: + print("qqqqqqqqqq") + user_details = User.objects.filter(email=user_opt_details.email).last() + if user_details: + print("333333333==>",user_details.password) + user_details.set_password(password) + user_details.save() + return {'password':password} + return user_opt_details + return '' + +class ChangePasswordSerializer(serializers.Serializer): + """Update Password after verification""" + current_password = serializers.CharField(max_length=100) + new_password = serializers.CharField(required=True) + class Meta(object): + """Meta info""" + model = User + + def validate_current_password(self, value): + user = self.context + if self.context.password not in ('', None): + if user.check_password(value): + return value + raise serializers.ValidationError({"error":"Invalid Current password"}) + def create(self, validated_data): + new_password = validated_data.pop('new_password') + user_details = User.objects.filter(email=self.context).last() + print("user_details==>", user_details) + if user_details: + print("333333333==>",user_details.password) + user_details.set_password(new_password) + user_details.save() + return {'password':new_password} + return '' + + + +class ForgotPasswordSerializer(serializers.Serializer): + """Forget password serializer""" + email = serializers.EmailField() + +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/account/templates/templated_email/email_base.email b/account/templates/templated_email/email_base.email new file mode 100644 index 0000000..5721e28 --- /dev/null +++ b/account/templates/templated_email/email_base.email @@ -0,0 +1,54 @@ + +{% block subject %}DinDin{% endblock %} +{% load static %} + +{% block html %} + + + + + Zod Bank | OTP + + + + + + + + + + +
+ + + + + {% block plain %} + {% endblock %} + + + +
+
+

-

+

Cheers!

+

Zod Bank Team

+
+
+ + + +{% endblock %} diff --git a/account/templates/templated_email/email_otp_verification.email b/account/templates/templated_email/email_otp_verification.email new file mode 100644 index 0000000..8b3c693 --- /dev/null +++ b/account/templates/templated_email/email_otp_verification.email @@ -0,0 +1,23 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + OTP Verification +{% endblock %} + +{% block plain %} + + +

+ Hi User, +

+ + + + +

+ You are receiving this email for email verification. Please use {{ otp }} as the verification code for your email address & username. + +

+ + +{% endblock %} diff --git a/account/templates/templated_email/email_reset_verification.email b/account/templates/templated_email/email_reset_verification.email new file mode 100644 index 0000000..e2f8ebf --- /dev/null +++ b/account/templates/templated_email/email_reset_verification.email @@ -0,0 +1,23 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + Password Reset Verification Code +{% endblock %} + +{% block plain %} + + +

+ Hi User, +

+ + + + +

+ You are receiving this email for reset password verification. Please use {{ verification_code }} as the verification code. + +

+ + +{% endblock %} diff --git a/account/tests.py b/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/account/urls.py b/account/urls.py new file mode 100644 index 0000000..eab8be4 --- /dev/null +++ b/account/urls.py @@ -0,0 +1,26 @@ +""" 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, + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView) +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('user', UserLogin, basename='user') +router.register('admin', UserLogin, basename='admin') +# router.register('google-login', GoogleLoginAPIViewset, basename='admin') +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)), + path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), + path('api/v1/reset-password/', ResetPasswordAPIView.as_view()), + path('api/v1/change-password/', ChangePasswordAPIView.as_view()) +] diff --git a/account/utils.py b/account/utils.py new file mode 100644 index 0000000..f20ef19 --- /dev/null +++ b/account/utils.py @@ -0,0 +1,38 @@ +"""Account utils""" +"""Third party Django app""" +from django.conf import settings +from rest_framework import viewsets, status +from rest_framework.response import Response + +from templated_email import send_templated_mail +def send_otp_email(recipient_email, otp): + from_email = settings.EMAIL_HOST_USER + recipient_list = [recipient_email] + send_templated_mail( + template_name='email_otp_verification.email', + from_email=from_email, + recipient_list=recipient_list, + context={ + 'otp': otp + } + ) + 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/account/views.py b/account/views.py new file mode 100644 index 0000000..69e4e95 --- /dev/null +++ b/account/views.py @@ -0,0 +1,220 @@ +from rest_framework import viewsets, status, views +from rest_framework.decorators import action +import random +import logging +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, + ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, + GoogleSignInSerializer) +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +from guardian.tasks import generate_otp +from django.conf import settings +from account.utils import send_otp_email +from account.utils import custom_response, custom_error_response +from django.core.mail import EmailMessage +from django.core.mail import send_mail +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from templated_email import send_templated_mail + +# class GoogleLoginAPIViewset(viewsets.ModelViewSet): +# """Google Login""" +# serializer_class = GoogleSignInSerializer +# +# def create(self, request, *args, **kwargs): +# """ +# Override default behaviour of create method +# """ +# provider_type = [] +# serializer = self.get_serializer(data=request.data) +# if serializer.is_valid(raise_exception=True): +# # provider = self.get_provider_view(request.data.get('provider')) +# # if User is not authenticated then send error message +# # if not provider.is_authenticated(request): +# # return custom_error_response({}, status.HTTP_400_BAD_REQUEST) +# +# user = serializer.save() +# if User.objects.filter(email__iexact=user.email).exists(): +# print("ppppppppppppp") +# return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK) +# return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) + + + +class ChangePasswordAPIView(views.APIView): + permission_classes = [IsAuthenticated] + def post(self, request): + serializer = ChangePasswordSerializer(context=request.user, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class ResetPasswordAPIView(views.APIView): + def post(self, request): + serializer = ResetPasswordSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class ForgotPasswordAPIView(views.APIView): + def post(self, request): + serializer = ForgotPasswordSerializer(data=request.data) + if serializer.is_valid(): + email = serializer.validated_data['email'] + try: + User.objects.get(email=email) + except User.DoesNotExist: + return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) + verification_code = ''.join([str(random.randrange(9)) for _ in range(6)]) + # Send the verification code to the user's email + from_email = settings.EMAIL_HOST_USER + recipient_list = [email] + send_templated_mail( + template_name='email_reset_verification.email', + from_email=from_email, + recipient_list=recipient_list, + context={ + 'verification_code': verification_code + } + ) + user_data, created = UserEmailOtp.objects.get_or_create(email=email) + if user_data: + user_data.otp = verification_code + user_data.save() + return custom_response(SUCCESS_CODE['3015'], + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class SendPhoneOtp(viewsets.ModelViewSet): + """Send otp on phone""" + def create(self, request, *args, **kwargs): + otp = generate_otp() + phone_number = self.request.data['phone'] + if phone_number.isdigit() and len(phone_number) == 10: + phone_otp, created = UserPhoneOtp.objects.get_or_create(country_code=self.request.data['country_code'], + phone=self.request.data['phone']) + if phone_otp: + phone_otp.otp = otp + phone_otp.save() + return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2020'], response_status=status.HTTP_400_BAD_REQUEST) + + +class UserPhoneVerification(viewsets.ModelViewSet): + """Send otp on phone""" + def list(self, request, *args, **kwargs): + try: + phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'), + otp=self.request.GET.get('otp')).last() + if phone_data: + phone_data.is_verified = True + phone_data.save() + return custom_response(SUCCESS_CODE['3012'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception: + 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) + return custom_response(SUCCESS_CODE['3003'], 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: + logging.error(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_200_OK) + data.update({"is_email_verified": is_verified}) + return custom_response(None, data, response_status=status.HTTP_200_OK) + + @action(methods=['post'], detail=False) + def admin_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) + if user.is_superuser: + serializer = SuperUserSerializer(user) + return custom_response(SUCCESS_CODE['3003'], 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: + logging.error(e) + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + data = {"auth_token": access_token, "user_role": '3'} + 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: + user_obj = User.objects.filter(username=self.request.GET.get('email')).last() + email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'), + otp=self.request.GET.get('otp')).last() + if email_data: + email_data.is_verified = True + email_data.save() + refresh = RefreshToken.for_user(user_obj) + access_token = str(refresh.access_token) + return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, 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: + logging.error(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']): + email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) + if email_data: + email_data.otp = otp + email_data.save() + send_otp_email(request.data['email'], otp) + return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/__init__.py b/base/__init__.py new file mode 100644 index 0000000..860fa73 --- /dev/null +++ b/base/__init__.py @@ -0,0 +1,3 @@ +""" +This is init module of the Project Zod Bank +""" diff --git a/base/common_email.py b/base/common_email.py new file mode 100644 index 0000000..4eb2a36 --- /dev/null +++ b/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/base/constants.py b/base/constants.py new file mode 100644 index 0000000..b66ed5f --- /dev/null +++ b/base/constants.py @@ -0,0 +1,51 @@ +""" +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") + +# 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 + +# Define the byte into kb +BYTE_IMAGE_SIZE = 1024 + +# validate file size +MAX_FILE_SIZE = 1024 * 1024 * 5 + + diff --git a/base/image_constants.py b/base/image_constants.py new file mode 100644 index 0000000..0737698 --- /dev/null +++ b/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/base/messages.py b/base/messages.py new file mode 100644 index 0000000..37507fd --- /dev/null +++ b/base/messages.py @@ -0,0 +1,94 @@ +""" +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 not found.", + "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": "Log in successfully", + # 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 changed 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.", + "3015": "Verification code sent on your email.", + "3016": "Send otp on your Email 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/base/routers.py b/base/routers.py new file mode 100644 index 0000000..e2df0e1 --- /dev/null +++ b/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/base/upload_file.py b/base/upload_file.py new file mode 100644 index 0000000..2d47ad2 --- /dev/null +++ b/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 zod_bank.settings import base_settings as settings +from zod_bank.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/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..700bf1b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + nginx: + image: nginx:latest + container_name: nginx + ports: + - "8000:8000" + volumes: + - ./nginx:/etc/nginx/conf.d + - .:/usr/src/app + depends_on: + - web + web: + build: . + container_name: django + command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" + volumes: + - .:/usr/src/app diff --git a/guardian/__init__.py b/guardian/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/guardian/admin.py b/guardian/admin.py new file mode 100644 index 0000000..a3bd40c --- /dev/null +++ b/guardian/admin.py @@ -0,0 +1,14 @@ +"""Guardian admin""" +"""Third party Django app""" +from django.contrib import admin +"""Import Django app""" +from .models import Guardian +# Register your models here. +@admin.register(Guardian) +class GuardianAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['user', 'family_name'] + + def __str__(self): + """Return email id""" + return self.user__email diff --git a/guardian/apps.py b/guardian/apps.py new file mode 100644 index 0000000..fcaf209 --- /dev/null +++ b/guardian/apps.py @@ -0,0 +1,9 @@ +"""Guardian app file""" +"""Third party Django app""" +from django.apps import AppConfig + + +class CustodianConfig(AppConfig): + """Guardian config""" + default_auto_field = 'django.db.models.BigAutoField' + name = 'guardian' diff --git a/guardian/migrations/0001_initial.py b/guardian/migrations/0001_initial.py new file mode 100644 index 0000000..0038a3b --- /dev/null +++ b/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/guardian/migrations/0002_remove_guardian_junior_code.py b/guardian/migrations/0002_remove_guardian_junior_code.py new file mode 100644 index 0000000..6996d0a --- /dev/null +++ b/guardian/migrations/0002_remove_guardian_junior_code.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.2 on 2023-06-27 06:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='guardian', + name='junior_code', + ), + ] diff --git a/guardian/migrations/0003_guardian_country_name.py b/guardian/migrations/0003_guardian_country_name.py new file mode 100644 index 0000000..ea9858b --- /dev/null +++ b/guardian/migrations/0003_guardian_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-27 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0002_remove_guardian_junior_code'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=30, null=True), + ), + ] diff --git a/guardian/migrations/__init__.py b/guardian/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/guardian/models.py b/guardian/models.py new file mode 100644 index 0000000..d9228b7 --- /dev/null +++ b/guardian/models.py @@ -0,0 +1,40 @@ +"""Guardian model file""" +"""Third party Django app""" +from django.db import models +from django.contrib.auth import get_user_model +"""Import Django app""" +from base.constants import GENDERS +User = get_user_model() +# Create your models here. + +class Guardian(models.Model): + """Guardian model""" + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email') + """Contact details""" + country_code = models.IntegerField(blank=True, null=True) + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + country_name = models.CharField(max_length=30, null=True, blank=True, default=None) + """Personal info""" + 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) + """Codes""" + guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) + 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) + """Profile activity""" + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(null=True, blank=True, default=None) + """Profile created and updated time""" + 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 email id""" + return f'{self.user}' diff --git a/guardian/serializers.py b/guardian/serializers.py new file mode 100644 index 0000000..f2c0c46 --- /dev/null +++ b/guardian/serializers.py @@ -0,0 +1,119 @@ +"""Serializer of Guardian""" +"""Third party Django app""" +import logging +import random +from rest_framework import serializers +from rest_framework_simplejwt.tokens import RefreshToken +from django.db import transaction +from django.contrib.auth.models import User +"""Import Django app""" +from .models import Guardian +from account.models import UserProfile +from base.messages import ERROR_CODE, SUCCESS_CODE +class UserSerializer(serializers.ModelSerializer): + """User serializer""" + auth_token = serializers.SerializerMethodField('get_auth_token') + + class Meta(object): + """Meta info""" + model = User + fields = ['email', 'password', 'auth_token'] + + def get_auth_token(self, obj): + """generate auth token""" + refresh = RefreshToken.for_user(obj) + access_token = str(refresh.access_token) + return access_token + def create(self, validated_data): + """fetch data""" + email = validated_data.get('email') + user_type = self.context + password = validated_data.get('password') + try: + """Create user profile""" + user = User.objects.create_user(username=email, email=email, password=password) + UserProfile.objects.create(user=user, user_type=user_type) + return user + except Exception as e: + """Error handling""" + logging.error(e) + raise serializers.ValidationError({"details":ERROR_CODE['2021']}) + + def save(self, **kwargs): + """save the data""" + with transaction.atomic(): + instance = super().save(**kwargs) + return instance + +class CreateGuardianSerializer(serializers.ModelSerializer): + """Create guardian serializer""" + """Basic info""" + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + """Contact details""" + phone = serializers.CharField(max_length=20, required=False) + country_code = serializers.IntegerField(required=False) + family_name = serializers.CharField(max_length=100, required=False) + dob = serializers.DateField(required=False) + referral_code = serializers.CharField(max_length=100, required=False) + + class Meta(object): + """Meta info""" + model = Guardian + fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', + 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used', + 'country_name'] + + def get_first_name(self,obj): + """first name of guardian""" + return obj.user.first_name + + def get_last_name(self,obj): + """last name of guardian""" + return obj.user.last_name + + def get_email(self,obj): + """emailof guardian""" + return obj.user.email + + def create(self, validated_data): + """Create guardian profile""" + user = User.objects.filter(username=self.context['user']).last() + if user: + """Save first and last name of guardian""" + if self.context.get('first_name') != '' and self.context.get('last_name') != '': + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + """Create guardian data""" + guardian, created = Guardian.objects.get_or_create(user=self.context['user']) + if created: + """Create referral code and guardian code""" + 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: + """update details according to the data get from request""" + 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) + """Update country code and phone number""" + guardian.phone = validated_data.get('phone', guardian.phone) + guardian.country_code = validated_data.get('country_code', guardian.country_code) + guardian.passcode = validated_data.get('passcode', guardian.passcode) + guardian.country_name = validated_data.get('country_name', guardian.country_name) + guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) + """Complete profile of the junior if below all data are filled""" + complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_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.save() + return guardian + + def save(self, **kwargs): + """Save the data into junior table""" + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/guardian/tasks.py b/guardian/tasks.py new file mode 100644 index 0000000..7a5dd90 --- /dev/null +++ b/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/guardian/tests.py b/guardian/tests.py new file mode 100644 index 0000000..3036e8b --- /dev/null +++ b/guardian/tests.py @@ -0,0 +1,5 @@ +"""Test file of Guardian""" +"""Third party Django app""" +from django.test import TestCase + +# Create your tests here. diff --git a/guardian/urls.py b/guardian/urls.py new file mode 100644 index 0000000..5399f2b --- /dev/null +++ b/guardian/urls.py @@ -0,0 +1,19 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from .views import SignupViewset, UpdateGuardianProfile +"""Third party import""" +from rest_framework import routers + +"""Define Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +"""Sign up API""" +router.register('sign-up', SignupViewset, basename='sign-up') +"""Create guardian profile API""" +router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +"""Define Url pattern""" +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/guardian/views.py b/guardian/views.py new file mode 100644 index 0000000..72e5b94 --- /dev/null +++ b/guardian/views.py @@ -0,0 +1,47 @@ +"""Views of Guardian""" +"""Third party Django app""" +from rest_framework.permissions import IsAuthenticated +from rest_framework import viewsets, status +"""Import Django app""" +from .serializers import UserSerializer +from .serializers import CreateGuardianSerializer +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 +# Create your views here. +class SignupViewset(viewsets.ModelViewSet): + """Signup view set""" + serializer_class = UserSerializer + + def create(self, request, *args, **kwargs): + """Create user profile""" + serializer = UserSerializer(context=request.data['user_type'], data=request.data) + if serializer.is_valid(): + serializer.save() + """Generate otp""" + otp = generate_otp() + UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + """Send email to the register user""" + 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): + """Update guardian profile""" + serializer_class = CreateGuardianSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + """Create guardian profile""" + 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(): + """save serializer""" + 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/junior/__init__.py b/junior/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/junior/admin.py b/junior/admin.py new file mode 100644 index 0000000..87cd7d8 --- /dev/null +++ b/junior/admin.py @@ -0,0 +1,14 @@ +"""Junior admin""" +"""Third party Django app""" +from django.contrib import admin +"""Import Django app""" +from .models import Junior +# Register your models here. +@admin.register(Junior) +class JuniorAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['auth'] + + def __str__(self): + """Return email id""" + return self.auth__email diff --git a/junior/apps.py b/junior/apps.py new file mode 100644 index 0000000..f3df25e --- /dev/null +++ b/junior/apps.py @@ -0,0 +1,8 @@ +"""App file""" +"""Import AppConfig""" +from django.apps import AppConfig + +class JuniorConfig(AppConfig): + """Junior config""" + default_auto_field = 'django.db.models.BigAutoField' + name = 'junior' diff --git a/junior/migrations/0001_initial.py b/junior/migrations/0001_initial.py new file mode 100644 index 0000000..e451c2e --- /dev/null +++ b/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/junior/migrations/0002_junior_country_name.py b/junior/migrations/0002_junior_country_name.py new file mode 100644 index 0000000..0dd74bd --- /dev/null +++ b/junior/migrations/0002_junior_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-27 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=30, null=True), + ), + ] diff --git a/junior/migrations/__init__.py b/junior/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/junior/models.py b/junior/models.py new file mode 100644 index 0000000..b389e3a --- /dev/null +++ b/junior/models.py @@ -0,0 +1,42 @@ +"""Junior model """ +"""Import django""" +from django.db import models +from django.contrib.auth import get_user_model +from django.contrib.postgres.fields import ArrayField +"""Import django app""" +from base.constants import GENDERS +User = get_user_model() +# Create your models here. + +class Junior(models.Model): + """Junior model""" + auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') + """Contact details""" + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + country_code = models.IntegerField(blank=True, null=True) + country_name = models.CharField(max_length=30, null=True, blank=True, default=None) + """Personal info""" + 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/') + """Codes""" + 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) + """Profile activity""" + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(null=True, blank=True, default=None) + """Profile created and updated time""" + 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 email id""" + return f'{self.auth}' diff --git a/junior/serializers.py b/junior/serializers.py new file mode 100644 index 0000000..a741203 --- /dev/null +++ b/junior/serializers.py @@ -0,0 +1,95 @@ +"""Serializer file for junior""" +"""Import Django 3rd party app""" +from rest_framework import serializers +from django.contrib.auth.models import User +from django.db import transaction +import random +"""Import django app""" +from junior.models import Junior + +class ListCharField(serializers.ListField): + """Serializer for Array field""" + child = serializers.CharField() + + def to_representation(self, data): + """to represent the data""" + return data + + def to_internal_value(self, data): + """internal value""" + if isinstance(data, list): + return data + raise serializers.ValidationError({"details":"Invalid input. Expected a list of strings."}) + + +class CreateJuniorSerializer(serializers.ModelSerializer): + """Create junior serializer""" + 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): + """Meta info""" + model = Junior + fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', + 'passcode', 'is_complete_profile', 'guardian_code', 'referral_code_used', + 'country_name'] + + def get_first_name(self,obj): + """first name of junior""" + return obj.auth.first_name + + def get_last_name(self,obj): + """last name of junior""" + return obj.auth.last_name + + def get_email(self,obj): + """email of junior""" + return obj.auth.email + + def create(self, validated_data): + """Create junior profile""" + user = User.objects.filter(username=self.context['user']).last() + if user: + """Save first and last name of junior""" + if self.context.get('first_name') != '' and self.context.get('last_name') != '': + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + """Create junior data""" + junior, created = Junior.objects.get_or_create(auth=self.context['user']) + if created: + """Create referral code and junior code""" + 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: + """update details according to the data get from request""" + junior.gender = validated_data.get('gender',junior.gender) + """Update guardian code""" + 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.country_name = validated_data.get('country_name', junior.country_name) + """Update country code and phone number""" + junior.phone = validated_data.get('phone', junior.phone) + junior.country_code = validated_data.get('country_code', junior.country_code) + junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) + """Complete profile of the junior if below all data are filled""" + complete_profile_field = all([junior.phone, junior.gender, junior.country_name, + junior.dob, junior.country_code, user.first_name, user.last_name]) + junior.is_complete_profile = False + if complete_profile_field: + junior.is_complete_profile = True + junior.save() + return junior + + def save(self, **kwargs): + """Save the data into junior table""" + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/junior/tests.py b/junior/tests.py new file mode 100644 index 0000000..1a75974 --- /dev/null +++ b/junior/tests.py @@ -0,0 +1,5 @@ +"""Junior test file""" +"""Import TestCase""" +from django.test import TestCase + +# Create your tests here. diff --git a/junior/urls.py b/junior/urls.py new file mode 100644 index 0000000..2b64fe4 --- /dev/null +++ b/junior/urls.py @@ -0,0 +1,19 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from .views import UpdateJuniorProfile, ValidateGuardianCode +"""Third party import""" +from rest_framework import routers + +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +"""Create junior profile API""" +router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') +"""validate guardian code API""" +router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') +"""Define url pattern""" +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/junior/views.py b/junior/views.py new file mode 100644 index 0000000..153bb41 --- /dev/null +++ b/junior/views.py @@ -0,0 +1,40 @@ +"""Junior view file""" +from rest_framework import viewsets, status +from rest_framework.permissions import IsAuthenticated +"""Django app import""" +from junior.models import Junior +from .serializers import CreateJuniorSerializer +from guardian.models import Guardian +from base.messages import ERROR_CODE, SUCCESS_CODE +from account.utils import custom_response, custom_error_response +# Create your views here. +class UpdateJuniorProfile(viewsets.ViewSet): + """Update junior profile""" + serializer_class = CreateJuniorSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + """Use CreateJuniorSerializer""" + 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(): + """save serializer""" + 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) + +class ValidateGuardianCode(viewsets.ViewSet): + """Check guardian code exist or not""" + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + """check guardian code""" + guardian_code = self.request.GET.get('guardian_code').split(',') + for code in guardian_code: + guardian_data = Guardian.objects.filter(guardian_code=code).exists() + if guardian_data: + return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..fe9e065 --- /dev/null +++ b/manage.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +"""Django import""" +import os +import sys + + +def main(): + """Main function""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + try: + """Import execute from command line function""" + from django.core.management import execute_from_command_line + except ImportError as exc: + """Show Exception error""" + 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/nginx/django.conf b/nginx/django.conf new file mode 100644 index 0000000..488e2c0 --- /dev/null +++ b/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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a875fbf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,61 @@ +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-phonenumber-field==7.1.0 +django-render-block==0.9.2 +django-ses==3.5.0 +django-smtp-ssl==1.0 +django-storages==1.13.2 +django-templated-email==3.0.1 +django-timezone-field==5.1 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +drf-yasg==1.21.6 +gunicorn==20.1.0 +inflection==0.5.1 +jmespath==0.10.0 +kombu==5.3.1 +msgpack==1.0.5 +packaging==23.1 +phonenumbers==8.13.15 +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/__init__.py b/zod_bank/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/asgi.py b/zod_bank/asgi.py new file mode 100644 index 0000000..967882b --- /dev/null +++ b/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/settings.py b/zod_bank/settings.py new file mode 100644 index 0000000..1c758b7 --- /dev/null +++ b/zod_bank/settings.py @@ -0,0 +1,191 @@ +""" +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', +] + +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/""" + + +# Email settings +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = 'infozodbank@gmail.com' +# Replace with your Gmail email password or App password +EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' + +# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +# EMAIL_HOST = os.getenv('EMAIL_HOST') +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') +# EMAIL_PORT = os.getenv('EMAIL_PORT') + +STATIC_URL = 'static/' +STATIC_ROOT = 'static' diff --git a/zod_bank/urls.py b/zod_bank/urls.py new file mode 100644 index 0000000..274451e --- /dev/null +++ b/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/wsgi.py b/zod_bank/wsgi.py new file mode 100644 index 0000000..e6b5c4f --- /dev/null +++ b/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()