Merge pull request #12 from KiwiTechLLC/qa

Qa to stage First commit
This commit is contained in:
dilipshrivastwa-kiwi
2023-06-28 17:53:09 +05:30
committed by GitHub
56 changed files with 2169 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -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/*

11
Dockerfile Normal file
View File

@ -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

0
account/__init__.py Normal file
View File

31
account/admin.py Normal file
View File

@ -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

6
account/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'account'

View File

@ -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',
},
),
]

View File

75
account/models.py Normal file
View File

@ -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

178
account/serializers.py Normal file
View File

@ -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__'

View File

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
{% block subject %}DinDin{% endblock %}
{% load static %}
{% block html %}
<html lang="en" style="height: 100%;">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>Zod Bank | OTP</title>
<style type="text/css">
@media all and (max-width: 599px) {
.block {
display: block !important;
width: 100%;
}
.top-space {
padding: 15px 0 0 !important;
}
}
</style>
</head>
<body style="margin: 0; padding: 0; background: #f7f8f9; height: 100%;">
<!-- begin template body -->
<table style="background:#f7f8f9; border: 0; border-collapse: separate; border-spacing: 0px; margin: auto; width: 100%; font-family: Arial, Helvetica, sans-serif; height: 100%;" aria-describedby="email-data-wrapper">
<tr>
<td style="padding: 0;">
<table style="background-color: white; border-collapse: separate; border-spacing: 0px; border: 1px solid #e4e8eb; width: 100%; max-width: 600px; margin-right:auto; margin-left: auto;
font-family: Arial, Helvetica, sans-serif;" aria-describedby="email-data-wrapper">
<tr>
<td style="padding: 41px 30px 40px; height: 39px; background: url({% static 'images/backgrounds/email_template.png' %}) left center no-repeat; background-size: cover;">
</td>
</tr>
{% block plain %}
{% endblock %}
<tr>
<td style="padding: 0 27px;">
<p style="margin: 0; font-size: 14px; line-height: 20px; color: #505050; font-weight: 400;">-</p>
<p style="margin: 0; font-size: 14px; line-height: 20px; color: #505050; font-weight: 400;">Cheers!</p>
<p style="margin: 0 0 30px; font-size: 14px; line-height: 20px; color: #505050; font-weight: 700;">Zod Bank Team</p>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<!-- end template body -->
</body>
</html>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
OTP Verification
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for email verification. Please use <b>{{ otp }} </b>as the verification code for your email address & username.
</p>
</td>
</tr>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Password Reset Verification Code
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for reset password verification. Please use <b>{{ verification_code }} </b>as the verification code.
</p>
</td>
</tr>
{% endblock %}

3
account/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

26
account/urls.py Normal file
View File

@ -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())
]

38
account/utils.py Normal file
View File

@ -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})

220
account/views.py Normal file
View File

@ -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)

3
base/__init__.py Normal file
View File

@ -0,0 +1,3 @@
"""
This is init module of the Project Zod Bank
"""

30
base/common_email.py Normal file
View File

@ -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

51
base/constants.py Normal file
View File

@ -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

16
base/image_constants.py Normal file
View File

@ -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'],
}

94
base/messages.py Normal file
View File

@ -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."]
}

17
base/routers.py Normal file
View File

@ -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 = '/?'

177
base/upload_file.py Normal file
View File

@ -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

18
docker-compose.yml Normal file
View File

@ -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

0
guardian/__init__.py Normal file
View File

14
guardian/admin.py Normal file
View File

@ -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

9
guardian/apps.py Normal file
View File

@ -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'

View File

@ -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',
},
),
]

View File

@ -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',
),
]

View File

@ -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),
),
]

View File

40
guardian/models.py Normal file
View File

@ -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}'

119
guardian/serializers.py Normal file
View File

@ -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

6
guardian/tasks.py Normal file
View File

@ -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)])

5
guardian/tests.py Normal file
View File

@ -0,0 +1,5 @@
"""Test file of Guardian"""
"""Third party Django app"""
from django.test import TestCase
# Create your tests here.

19
guardian/urls.py Normal file
View File

@ -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)),
]

47
guardian/views.py Normal file
View File

@ -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)

0
junior/__init__.py Normal file
View File

14
junior/admin.py Normal file
View File

@ -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

8
junior/apps.py Normal file
View File

@ -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'

View File

@ -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',
},
),
]

View File

@ -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),
),
]

View File

42
junior/models.py Normal file
View File

@ -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}'

95
junior/serializers.py Normal file
View File

@ -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

5
junior/tests.py Normal file
View File

@ -0,0 +1,5 @@
"""Junior test file"""
"""Import TestCase"""
from django.test import TestCase
# Create your tests here.

19
junior/urls.py Normal file
View File

@ -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)),
]

40
junior/views.py Normal file
View File

@ -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)

25
manage.py Executable file
View File

@ -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()

24
nginx/django.conf Normal file
View File

@ -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/;
}
}

61
requirements.txt Normal file
View File

@ -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

0
zod_bank/__init__.py Normal file
View File

16
zod_bank/asgi.py Normal file
View File

@ -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()

191
zod_bank/settings.py Normal file
View File

@ -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'

33
zod_bank/urls.py Normal file
View File

@ -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')),
]

16
zod_bank/wsgi.py Normal file
View File

@ -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()