diff --git a/account/admin.py b/account/admin.py
index 7b59c84..cf3ff23 100644
--- a/account/admin.py
+++ b/account/admin.py
@@ -2,7 +2,7 @@
from django.contrib import admin
"""Import django app"""
-from .models import UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserNotification, UserDelete
+from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails
# Register your models here.
@admin.register(UserDelete)
@@ -19,6 +19,7 @@ class UserNotificationAdmin(admin.ModelAdmin):
list_display = ['user', 'push_notification', 'email_notification', 'sms_notification']
def __str__(self):
+ """Return image url"""
return self.image_url
@admin.register(DefaultTaskImages)
class DefaultTaskImagesAdmin(admin.ModelAdmin):
@@ -26,6 +27,7 @@ class DefaultTaskImagesAdmin(admin.ModelAdmin):
list_display = ['task_name', 'image_url']
def __str__(self):
+ """Return image url"""
return self.image_url
@admin.register(UserEmailOtp)
@@ -37,11 +39,11 @@ class UserEmailOtpAdmin(admin.ModelAdmin):
"""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']
+@admin.register(UserDeviceDetails)
+class UserDeviceDetailsAdmin(admin.ModelAdmin):
+ """User profile admin"""
+ list_display = ['user', 'device_id']
def __str__(self):
- """Return object in phone number and otp format"""
- return self.phone + '-' + self.otp
+ """Return user email"""
+ return self.user.email
diff --git a/account/custom_middleware.py b/account/custom_middleware.py
new file mode 100644
index 0000000..7a06e43
--- /dev/null
+++ b/account/custom_middleware.py
@@ -0,0 +1,40 @@
+"""middleware file"""
+"""Django import"""
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.renderers import JSONRenderer
+"""App django"""
+from account.utils import custom_error_response
+from account.models import UserDeviceDetails
+from base.messages import ERROR_CODE, SUCCESS_CODE
+
+# Custom middleware
+# when user login with
+# multiple device simultaneously
+# It restricted login in
+# multiple devices only
+# user can login in single
+# device at a time"""
+class CustomMiddleware(object):
+ """Custom middleware"""
+ def __init__(self, get_response):
+ """response"""
+ self.get_response = get_response
+
+ def __call__(self, request):
+ # Code to be executed before the view is called
+ response = self.get_response(request)
+ # Code to be executed after the view is called
+ device_id = request.META.get('HTTP_DEVICE_ID')
+ if request.user.is_authenticated:
+ """device details"""
+ device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
+ if device_id and not device_details:
+ custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
+ response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND)
+ # Set content type header to "application/json"
+ response['Content-Type'] = 'application/json'
+ # Render the response as JSON
+ renderer = JSONRenderer()
+ response.content = renderer.render(response.data)
+ return response
diff --git a/account/migrations/0007_alter_defaulttaskimages_options_and_more.py b/account/migrations/0007_alter_defaulttaskimages_options_and_more.py
new file mode 100644
index 0000000..4c134d7
--- /dev/null
+++ b/account/migrations/0007_alter_defaulttaskimages_options_and_more.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.2 on 2023-07-14 09:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0006_alter_useremailotp_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='defaulttaskimages',
+ options={'verbose_name': 'Default Task images', 'verbose_name_plural': 'Default Task images'},
+ ),
+ migrations.AlterModelOptions(
+ name='userdelete',
+ options={'verbose_name': 'Deleted User', 'verbose_name_plural': 'Deleted User'},
+ ),
+ ]
diff --git a/account/migrations/0008_userdevicedetails.py b/account/migrations/0008_userdevicedetails.py
new file mode 100644
index 0000000..0b655f6
--- /dev/null
+++ b/account/migrations/0008_userdevicedetails.py
@@ -0,0 +1,31 @@
+# Generated by Django 4.2.2 on 2023-07-14 11:08
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('account', '0007_alter_defaulttaskimages_options_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserDeviceDetails',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('device_id', models.CharField(max_length=500)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_device_details', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'User Device Details',
+ 'verbose_name_plural': 'User Device Details',
+ 'db_table': 'user_device_details',
+ },
+ ),
+ ]
diff --git a/account/models.py b/account/models.py
index 60184c5..d515e01 100644
--- a/account/models.py
+++ b/account/models.py
@@ -90,6 +90,8 @@ class DefaultTaskImages(models.Model):
class Meta(object):
""" Meta information """
db_table = 'default_task_image'
+ verbose_name = 'Default Task images'
+ verbose_name_plural = 'Default Task images'
def __str__(self):
"""return phone as an object"""
@@ -112,6 +114,8 @@ class UserDelete(models.Model):
class Meta(object):
""" Meta information """
db_table = 'user_delete_information'
+ verbose_name = 'Deleted User'
+ verbose_name_plural = 'Deleted User'
def __str__(self):
return self.user.email
@@ -141,3 +145,23 @@ class UserNotification(models.Model):
def __str__(self):
return self.user.email
+
+class UserDeviceDetails(models.Model):
+ """
+ User notification details
+ """
+ user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_device_details')
+ """Device ID"""
+ device_id = models.CharField(max_length=500)
+
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta(object):
+ """ Meta information """
+ db_table = 'user_device_details'
+ verbose_name = 'User Device Details'
+ verbose_name_plural = 'User Device Details'
+
+ def __str__(self):
+ return self.user.email
diff --git a/account/serializers.py b/account/serializers.py
index 6eaeea9..d4fd335 100644
--- a/account/serializers.py
+++ b/account/serializers.py
@@ -1,17 +1,47 @@
"""Account serializer"""
-"""Django Imoprt"""
-import random
+"""Django Import"""
+# Import Refresh token of jwt
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import RefreshToken
+import secrets
"""App import"""
+# Import guardian's model,
+# Import junior's model,
+# Import account's model,
+# Import constant from
+# base package,
+# Import messages from
+# base package,
+# Import some functions
+# from utils file"""
+
from guardian.models import Guardian
from junior.models import Junior
from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp
-from base.constants import GUARDIAN, JUNIOR, SUPERUSER
-from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
+from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER
+from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
from .utils import delete_user_account_condition_social, delete_user_account_condition
+# In this serializer file
+# define google login serializer
+# update junior profile,
+# update guardian profile,
+# super admin serializer,
+# reset password,
+# forgot password,
+# change password,
+# basic junior serializer,
+# basic guardian serializer,
+# user delete account serializer,
+# user notification serializer,
+# update user notification serializer,
+# default task's images serializer,
+# upload default task's images serializer,
+# email verification serializer,
+# phone otp serializer
+
+# create all serializer here
class GoogleLoginSerializer(serializers.Serializer):
"""google login serializer"""
access_token = serializers.CharField(max_length=5000, required=True)
@@ -61,6 +91,7 @@ class ResetPasswordSerializer(serializers.Serializer):
def create(self, validated_data):
verification_code = validated_data.pop('verification_code')
password = validated_data.pop('password')
+ # fetch email otp object of the user
user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last()
if user_opt_details:
user_details = User.objects.filter(email=user_opt_details.email).last()
@@ -83,12 +114,14 @@ class ChangePasswordSerializer(serializers.Serializer):
def validate_current_password(self, value):
user = self.context
+ # check old password
if self.context.password not in ('', None) and user.check_password(value):
return value
raise serializers.ValidationError(ERROR_CODE['2015'])
def create(self, validated_data):
new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_password')
+ """Check new password is different from current password"""
if new_password == current_password:
raise serializers.ValidationError({"details": ERROR_CODE['2026']})
user_details = User.objects.filter(email=self.context).last()
@@ -107,15 +140,28 @@ class ForgotPasswordSerializer(serializers.Serializer):
class SuperUserSerializer(serializers.ModelSerializer):
"""Super admin serializer"""
user_type = serializers.SerializerMethodField('get_user_type')
+ auth_token = serializers.SerializerMethodField('get_auth_token')
+ refresh_token = serializers.SerializerMethodField('get_refresh_token')
+
+ def get_auth_token(self, obj):
+ refresh = RefreshToken.for_user(obj)
+ access_token = str(refresh.access_token)
+ return access_token
+
+ def get_refresh_token(self, obj):
+ refresh = RefreshToken.for_user(obj)
+ refresh_token = str(refresh)
+ return refresh_token
def get_user_type(self, obj):
"""user type"""
- return SUPERUSER
+ return str(NUMBER['three'])
class Meta(object):
"""Meta info"""
model = User
- fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type']
+ fields = ['id', 'auth_token', 'refresh_token', 'username', 'email', 'first_name',
+ 'last_name', 'is_active', 'user_type']
class GuardianSerializer(serializers.ModelSerializer):
@@ -125,19 +171,24 @@ class GuardianSerializer(serializers.ModelSerializer):
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
+ refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
access_token = str(refresh.access_token)
return access_token
+ def get_refresh_token(self, obj):
+ refresh = RefreshToken.for_user(obj.user)
+ refresh_token = str(refresh)
+ return refresh_token
def get_user_type(self, obj):
"""user type"""
email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last()
- if email_verified and email_verified.user_type != None:
+ if email_verified and email_verified.user_type is not None:
return email_verified.user_type
- return '2'
+ return str(NUMBER['two'])
def get_auth(self, obj):
"""user email address"""
@@ -154,9 +205,9 @@ class GuardianSerializer(serializers.ModelSerializer):
class Meta(object):
"""Meta info"""
model = Guardian
- fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name',
- 'gender', 'dob', 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image',
- 'created_at', 'updated_at', 'user_type', 'country_name']
+ fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
+ 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active',
+ 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name']
class JuniorSerializer(serializers.ModelSerializer):
@@ -166,17 +217,23 @@ class JuniorSerializer(serializers.ModelSerializer):
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
+ refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.auth)
access_token = str(refresh.access_token)
return access_token
+ def get_refresh_token(self, obj):
+ refresh = RefreshToken.for_user(obj.auth)
+ refresh_token = str(refresh)
+ return refresh_token
+
def get_user_type(self, obj):
email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last()
if email_verified and email_verified.user_type is not None:
return email_verified.user_type
- return '1'
+ return str(NUMBER['one'])
def get_auth(self, obj):
return obj.auth.username
@@ -190,9 +247,9 @@ class JuniorSerializer(serializers.ModelSerializer):
class Meta(object):
"""Meta info"""
model = Junior
- fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
- 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
- 'updated_at', 'user_type', 'country_name','is_invited']
+ fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
+ 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set',
+ 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited']
class EmailVerificationSerializer(serializers.ModelSerializer):
"""Email verification serializer"""
@@ -210,6 +267,7 @@ class DefaultTaskImagesSerializer(serializers.ModelSerializer):
model = DefaultTaskImages
fields = ['id', 'task_name', 'image_url']
def create(self, validated_data):
+ # create default task object
data = DefaultTaskImages.objects.create(**validated_data)
return data
@@ -232,10 +290,11 @@ class UserDeleteSerializer(serializers.ModelSerializer):
data = validated_data.get('reason')
passwd = self.context['password']
signup_method = self.context['signup_method']
- random_num = random.randint(0, 10000)
+ random_num = secrets.randbelow(10001)
user_tb = User.objects.filter(id=user.id).last()
user_type_datas = UserEmailOtp.objects.filter(email=user.email).last()
- if user_tb and user_tb.check_password(passwd) and signup_method == '1':
+ # check password and sign up method
+ if user_tb and user_tb.check_password(passwd) and signup_method == str(NUMBER['one']):
user_type_data = user_type_datas.user_type
instance = delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num)
return instance
@@ -264,6 +323,7 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer):
def create(self, validated_data):
instance = UserNotification.objects.filter(user=self.context).last()
if instance:
+ # change notification status
instance.push_notification = validated_data.get('push_notification',instance.push_notification)
instance.email_notification = validated_data.get('email_notification', instance.email_notification)
instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification)
diff --git a/account/templates/templated_email/junior_notification_email.email b/account/templates/templated_email/junior_notification_email.email
index ea9c214..9f489ce 100644
--- a/account/templates/templated_email/junior_notification_email.email
+++ b/account/templates/templated_email/junior_notification_email.email
@@ -15,7 +15,7 @@
|
- You are receiving this email for join the ZOD bank platform. Please use {{ url }} link to join the platform.
+ You are receiving this email for joining the ZOD bank platform. Please use {{ url }} link to join the platform.
Your credentials are:- username = {{email}} and password {{password}} Below are the steps to complete the account and how to use this platform.
diff --git a/account/urls.py b/account/urls.py
index f2a1d62..ef3d026 100644
--- a/account/urls.py
+++ b/account/urls.py
@@ -3,12 +3,32 @@
from django.urls import path, include
"""Third party import"""
from rest_framework import routers
-"""Import view functions"""
+# Import view functions
+# UserLogin views,
+# SendPhoneOtp views,
+# UserPhoneVerification views,
+# UserEmailVerification views,
+# ReSendEmailOtp views,
+# ForgotPasswordAPIView views,
+# ResetPasswordAPIView views,
+# ChangePasswordAPIView views,
+# UpdateProfileImage views,
+# GoogleLoginViewSet views,
+# SigninWithApple views,
+# ProfileAPIViewSet views,
+# UploadImageAPIViewSet views,
+# DefaultImageAPIViewSet views,
+# DeleteUserProfileAPIViewSet views,
+# UserNotificationAPIViewSet views,
+# UpdateUserNotificationAPIViewSet views,
+# SendSupportEmail views,
+# LogoutAPIView views,
+# AccessTokenAPIView views"""
from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp,
ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage,
GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet,
DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet,
- UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView)
+ UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView)
"""Router"""
router = routers.SimpleRouter()
@@ -45,5 +65,6 @@ urlpatterns = [
path('api/v1/update-profile-image/', UpdateProfileImage.as_view()),
path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'),
path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email'),
- path('api/v1/logout/', LogoutAPIView.as_view(), name='logout')
+ path('api/v1/logout/', LogoutAPIView.as_view(), name='logout'),
+ path('api/v1/generate-token/', AccessTokenAPIView.as_view(), name='generate-token')
]
diff --git a/account/utils.py b/account/utils.py
index 3b74d24..9aca671 100644
--- a/account/utils.py
+++ b/account/utils.py
@@ -10,14 +10,40 @@ import string
from datetime import datetime
from calendar import timegm
from uuid import uuid4
+# Import secrets module for generating random number
import secrets
from rest_framework import serializers
+# Django App Import
+# Import models from junior App,
+# Import models from guardian App,
+# Import models from account App,
+# Import messages from base package"""
from junior.models import Junior
from guardian.models import Guardian
from account.models import UserDelete
from base.messages import ERROR_CODE
+# Define delete
+# user account condition,
+# Define delete
+# user account
+# condition for social
+# login account,
+# Update junior account,
+# Update guardian account,
+# Define custom email for otp verification,
+# Define support email for user's query,
+# Define custom success response,
+# Define custom error response,
+# Generate access token,
+# refresh token by using jwt,
+# Define function for generating
+# guardian code, junior code,
+# referral code,
+# Define function for generating
+# alphanumeric code
+
def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num):
"""delete user account"""
if user_type == '1' and user_type_data == '1':
@@ -28,10 +54,11 @@ def delete_user_account_condition(user, user_type_data, user_type, user_tb, data
raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"})
user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
- user_tb.password = 'None'
d_email = user_tb.email
o_mail = user.email
+ # update user email with dummy email
user_tb.save()
+ """create object in user delete model"""
instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail,
is_active=True, reason=data)
@@ -47,10 +74,11 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n
raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"})
user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
- user_tb.password = 'None'
dummy_email = user_tb.email
old_mail = user.email
+ # update user email with dummy email
user_tb.save()
+ """create object in user delete model"""
instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail,
is_active=True, reason=data)
@@ -59,6 +87,7 @@ def junior_account_update(user_tb):
"""junior account delete"""
junior_data = Junior.objects.filter(auth__email=user_tb.email).first()
if junior_data:
+ # Update junior account
junior_data.is_active = False
junior_data.is_verified = False
junior_data.guardian_code = '{}'
@@ -68,10 +97,12 @@ def guardian_account_update(user_tb):
"""update guardian account after delete the user account"""
guardian_data = Guardian.objects.filter(user__email=user_tb.email).first()
if guardian_data:
+ # Update guardian account
guardian_data.is_active = False
guardian_data.is_verified = False
guardian_data.save()
jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
+ """Disassociate relation between guardian and junior"""
for data in jun_data:
data.guardian_code.remove(guardian_data.guardian_code)
data.save()
@@ -79,6 +110,7 @@ def send_otp_email(recipient_email, otp):
"""Send otp on email with template"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [recipient_email]
+ """Send otp on email"""
send_templated_mail(
template_name='email_otp_verification.email',
from_email=from_email,
@@ -93,6 +125,7 @@ def send_support_email(name, sender, subject, message):
"""Send otp on email with template"""
to_email = [settings.EMAIL_FROM_ADDRESS]
from_email = settings.DEFAULT_ADDRESS
+ """Send support email to zod bank support team"""
send_templated_mail(
template_name='support_mail.email',
from_email=from_email,
@@ -183,7 +216,15 @@ def get_token(data: dict = dict):
def generate_alphanumeric_code(length):
+ """Generate alphanumeric code"""
alphabet = string.ascii_letters + string.digits
code = ''.join(secrets.choice(alphabet) for _ in range(length))
return code
+
+def generate_code(value, user_id):
+ """generate referral, junior and guardian code"""
+ code = value + str(user_id).zfill(3)
+ return code
+
+
diff --git a/account/views.py b/account/views.py
index 4e71ecd..c19cd4f 100644
--- a/account/views.py
+++ b/account/views.py
@@ -1,21 +1,29 @@
"""Account view """
+from notifications.utils import remove_fcm_token
+
"""Django import"""
from datetime import datetime, timedelta
from rest_framework import viewsets, status, views
from rest_framework.decorators import action
import random
import logging
-from PIL import Image
-from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
import jwt
from django.contrib.auth import logout
-"""App Import"""
-from guardian.utils import upload_image_to_alibaba
from django.contrib.auth import authenticate, login
+from rest_framework.permissions import IsAuthenticated
+from templated_email import send_templated_mail
+import google.oauth2.credentials
+import google.auth.transport.requests
+from rest_framework import status
+import requests
+from rest_framework.response import Response
+from django.conf import settings
+"""App Import"""
from guardian.models import Guardian
from junior.models import Junior
-from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification
+from guardian.utils import upload_image_to_alibaba
+from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification
from django.contrib.auth.models import User
"""Account serializer"""
from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
@@ -25,24 +33,17 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer)
from rest_framework_simplejwt.tokens import RefreshToken
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import NUMBER
+from base.constants import NUMBER, ZOD, JUN, GRD
from guardian.tasks import generate_otp
from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response,
- generate_alphanumeric_code)
-from rest_framework.permissions import IsAuthenticated
-from templated_email import send_templated_mail
-import google.oauth2.credentials
-import google.auth.transport.requests
-from rest_framework import status
-import requests
-from rest_framework.response import Response
-from django.conf import settings
+ generate_code)
from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer
-class GoogleLoginMixin:
+class GoogleLoginMixin(object):
"""google login mixin"""
- def google_login(self, request):
+ @staticmethod
+ def google_login(request):
"""google login function"""
access_token = request.data.get('access_token')
user_type = request.data.get('user_type')
@@ -90,15 +91,15 @@ class GoogleLoginMixin:
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True,
image=profile_picture, signup_method='2',
- junior_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6)
+ junior_code=generate_code(JUN, user_obj.id),
+ referral_code=generate_code(ZOD, user_obj.id)
)
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True,
image=profile_picture,signup_method='2',
- guardian_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6)
+ guardian_code=generate_code(GRD, user_obj.id),
+ referral_code=generate_code(ZOD, user_obj.id)
)
serializer = GuardianSerializer(guardian_query)
# Return a JSON response with the user's email and name
@@ -141,14 +142,14 @@ class SigninWithApple(views.APIView):
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True,
signup_method='3',
- junior_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6))
+ junior_code=generate_code(JUN, user.id),
+ referral_code=generate_code(ZOD, user.id))
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True,
signup_method='3',
- guardian_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6))
+ guardian_code=generate_code(GRD, user.id),
+ referral_code=generate_code(ZOD, user.id))
serializer = GuardianSerializer(guardian_query)
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
@@ -181,6 +182,7 @@ class UpdateProfileImage(views.APIView):
return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
+ logging.error(e)
return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST)
class ChangePasswordAPIView(views.APIView):
@@ -205,6 +207,7 @@ class ResetPasswordAPIView(views.APIView):
class ForgotPasswordAPIView(views.APIView):
"""Forgot password"""
+
def post(self, request):
serializer = ForgotPasswordSerializer(data=request.data)
if serializer.is_valid():
@@ -213,7 +216,7 @@ class ForgotPasswordAPIView(views.APIView):
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)])
+ verification_code = generate_otp()
# Send the verification code to the user's email
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
@@ -225,7 +228,6 @@ class ForgotPasswordAPIView(views.APIView):
'verification_code': verification_code
}
)
-
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.get_or_create(email=email)
if created:
@@ -281,6 +283,7 @@ class UserLogin(viewsets.ViewSet):
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
+ device_id = request.META.get('HTTP_DEVICE_ID')
user = authenticate(request, username=username, password=password)
try:
@@ -292,6 +295,10 @@ class UserLogin(viewsets.ViewSet):
junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last()
if junior_data:
serializer = JuniorSerializer(junior_data).data
+ device_details, created = UserDeviceDetails.objects.get_or_create(user=user)
+ if device_details:
+ device_details.device_id = device_id
+ device_details.save()
return custom_response(SUCCESS_CODE['3003'], serializer, response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
@@ -300,7 +307,8 @@ class UserLogin(viewsets.ViewSet):
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,
+ refresh_token = str(refresh)
+ data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False,
"user_type": email_verified.user_type,
}
is_verified = False
@@ -315,7 +323,7 @@ class UserLogin(viewsets.ViewSet):
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)
+ return custom_response(SUCCESS_CODE['3003'], data, response_status=status.HTTP_200_OK)
@action(methods=['post'], detail=False)
def admin_login(self, request):
@@ -334,7 +342,8 @@ class UserLogin(viewsets.ViewSet):
logging.error(e)
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
- data = {"auth_token": access_token, "user_role": '3'}
+ refresh_token = str(refresh)
+ data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'}
return custom_response(None, data, response_status=status.HTTP_200_OK)
class UserEmailVerification(viewsets.ModelViewSet):
@@ -371,7 +380,9 @@ class UserEmailVerification(viewsets.ModelViewSet):
guardian_data.save()
refresh = RefreshToken.for_user(user_obj)
access_token = str(refresh.access_token)
- return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token},
+ refresh_token = str(refresh)
+ return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token,
+ "refresh_token":refresh_token},
response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
@@ -383,6 +394,8 @@ class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserEmailOtp.objects.all()
serializer_class = EmailVerificationSerializer
+ permission_classes = [IsAuthenticated]
+
def create(self, request, *args, **kwargs):
otp = generate_otp()
if User.objects.filter(email=request.data['email']):
@@ -404,6 +417,8 @@ class ProfileAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = User.objects.all()
serializer_class = JuniorProfileSerializer
+ permission_classes = [IsAuthenticated]
+
def list(self, request, *args, **kwargs):
"""profile view"""
if str(self.request.GET.get('user_type')) == '1':
@@ -417,14 +432,12 @@ class ProfileAPIViewSet(viewsets.ModelViewSet):
serializer = GuardianProfileSerializer(guardian_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
-
-
class UploadImageAPIViewSet(viewsets.ModelViewSet):
- """Profile viewset"""
+ """upload task image"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesSerializer
def create(self, request, *args, **kwargs):
- """profile view"""
+ """upload images"""
image_data = request.data['image_url']
filename = f"default_task_images/{image_data.name}"
if image_data.size == NUMBER['zero']:
@@ -442,6 +455,7 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesDetailsSerializer
+ permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = DefaultTaskImages.objects.all()
@@ -472,6 +486,7 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet):
"""notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UserNotificationSerializer
+ permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = self.queryset.filter(user=request.user)
@@ -483,6 +498,7 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
"""Update notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UpdateUserNotificationSerializer
+ permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
"""profile view"""
@@ -496,6 +512,8 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
class SendSupportEmail(views.APIView):
"""support email api"""
+ permission_classes = (IsAuthenticated,)
+
def post(self, request):
name = request.data.get('name')
sender = request.data.get('email')
@@ -510,11 +528,30 @@ class SendSupportEmail(views.APIView):
else:
return custom_error_response(ERROR_CODE['2033'], response_status=status.HTTP_400_BAD_REQUEST)
-
class LogoutAPIView(views.APIView):
+ """Log out API"""
permission_classes = (IsAuthenticated,)
def post(self, request):
+ remove_fcm_token(
+ request.auth.payload['user_id'],
+ request.META['HTTP_AUTHORIZATION'].split(" ")[1],
+ request.data.get('registration_id', ""))
logout(request)
request.session.flush()
return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK)
+
+
+class AccessTokenAPIView(views.APIView):
+ """generate access token API"""
+
+ def post(self, request):
+ # Assuming you have a refresh_token string
+ refresh_token = request.data['refresh_token']
+ # Create a RefreshToken instance from the refresh token string
+ refresh = RefreshToken(refresh_token)
+ # Generate a new access token
+ access_token = str(refresh.access_token)
+ data = {"auth_token": access_token}
+ return custom_response(None, data, response_status=status.HTTP_200_OK)
+
diff --git a/base/constants.py b/base/constants.py
index 7c83134..29da8db 100644
--- a/base/constants.py
+++ b/base/constants.py
@@ -6,52 +6,25 @@ import os
# GOOGLE_URL used for interact with google server to verify user existence.
#GOOGLE_URL = "https://www.googleapis.com/plus/v1/"
+# Define Code prefix word
+# for guardian code,
+# junior code,
+# referral code"""
+ZOD = 'ZOD'
+JUN = 'JUN'
+GRD = 'GRD'
+# Define number variable
+# from zero to
+# twenty and
+# some standard
+# number"""
NUMBER = {
- 'point_zero': 0.0,
- 'zero': 0,
- 'one': 1,
- 'two': 2,
- 'three': 3,
- 'four': 4,
- 'five': 5,
- 'six': 6,
- 'seven': 7,
- 'eight': 8,
- 'nine': 9,
- 'ten': 10,
- 'eleven': 11,
- 'twelve': 12,
- 'thirteen': 13,
- 'fourteen': 14,
- 'fifteen': 15,
- 'sixteen': 16,
- 'seventeen': 17,
- 'eighteen': 18,
- 'nineteen': 19,
- 'twenty_four': 24,
- 'twenty_one': 21,
- 'twenty_two': 22,
- 'twenty_five': 25,
- 'thirty': 30,
- 'thirty_five': 35,
- 'thirty_six': 36,
- 'forty': 40,
- 'fifty': 50,
- 'fifty_nine': 59,
- 'sixty': 60,
- 'seventy_five': 75,
- 'eighty': 80,
- 'ninty_five': 95,
- 'ninty_six': 96,
- 'ninety_nine': 99,
- 'hundred': 100,
- 'one_one_nine': 119,
- 'one_twenty': 120,
- 'four_zero_four': 404,
- 'five_hundred': 500,
- 'minus_one': -1,
- 'point_three': 0.3,
- 'point_seven': 0.7
+ 'point_zero': 0.0, 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7,
+ 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15,
+ 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20,
+ 'twenty_one': 21, 'twenty_two': 22,'twenty_three': 23, 'twenty_four': 24, 'twenty_five': 25,
+ 'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninty': 90,
+ 'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600
}
@@ -71,10 +44,6 @@ 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"""
USER_TYPE = (
('1', 'junior'),
@@ -86,7 +55,7 @@ GENDERS = (
('1', 'Male'),
('2', 'Female')
)
-"""Task status"""
+# Task status"""
TASK_STATUS = (
('1', 'pending'),
('2', 'in-progress'),
@@ -94,17 +63,20 @@ TASK_STATUS = (
('4', 'requested'),
('5', 'completed')
)
-"""sign up method"""
+# sign up method
SIGNUP_METHODS = (
('1', 'manual'),
('2', 'google'),
('3', 'apple')
)
-"""relationship"""
+# relationship
RELATIONSHIP = (
('1', 'parent'),
('2', 'legal_guardian')
)
+"""
+Define task status
+in a number"""
PENDING = 1
IN_PROGRESS = 2
REJECTED = 3
@@ -113,6 +85,7 @@ COMPLETED = 5
TASK_POINTS = 5
# duplicate name used defined in constant PROJECT_NAME
PROJECT_NAME = 'Zod Bank'
+# define user type constant
GUARDIAN = 'guardian'
JUNIOR = 'junior'
SUPERUSER = 'superuser'
diff --git a/base/messages.py b/base/messages.py
index 2c0d9cd..998a5b2 100644
--- a/base/messages.py
+++ b/base/messages.py
@@ -42,26 +42,43 @@ ERROR_CODE = {
"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",
+ "2019": "Invalid Referral code",
"2020": "Enter valid mobile number",
"2021": "Already register",
"2022": "Invalid Guardian code",
"2023": "Invalid user",
+ # email not verified
"2024": "Email not verified",
"2025": "Invalid input. Expected a list of strings.",
+ # check old and new password
"2026": "New password should not same as old password",
"2027": "data should contain `identityToken`",
"2028": "You are not authorized person to sign up on this platform",
"2029": "Validity of otp verification is expired",
"2030": "Use correct user type and token",
+ # invalid password
"2031": "Invalid password",
"2032": "Failed to send email",
"2033": "Missing required fields",
"2034": "Junior is not associated",
+ # image size
"2035": "Image should not be 0 kb",
- "2036": "Choose valid user"
+ "2036": "Choose valid user",
+ # log in multiple device msg
+ "2037": "You are already log in another device",
+ "2038": "Choose valid action for task",
+ # card length limit
+ "2039": "Add at least one article card or maximum 6",
+ "2040": "Add at least 5 article survey or maximum 10",
+ # add article msg
+ "2041": "Article with given id doesn't exist.",
+ "2042": "Article Card with given id doesn't exist.",
+ "2043": "Article Survey with given id doesn't exist.",
+ "2044": "Task does not exist"
}
+"""Success message code"""
SUCCESS_CODE = {
+ "3000": "ok",
# Success code for password
"3001": "Sign up successfully",
# Success code for Thank you
@@ -93,9 +110,20 @@ SUCCESS_CODE = {
"3019": "Support Email sent successfully",
"3020": "Logged out successfully.",
"3021": "Add junior successfully",
- "3022": "Remove junior successfully"
+ "3022": "Remove junior successfully",
+ "3023": "Junior is approved successfully",
+ "3024": "Junior request is rejected successfully",
+ "3025": "Task is approved successfully",
+ "3026": "Task is rejected successfully",
+ "3027": "Article has been created successfully.",
+ "3028": "Article has been updated successfully.",
+ "3029": "Article has been deleted successfully.",
+ "3030": "Article Card has been removed successfully.",
+ "3031": "Article Survey has been removed successfully.",
+ "3032": "Task request sent successfully",
+ "3033": "Valid Referral code",
}
-
+"""status code error"""
STATUS_CODE_ERROR = {
# Status code for Invalid Input
"4001": ["Invalid input."],
diff --git a/docker-compose.yml b/docker-compose.yml
index 700bf1b..b9bc35b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,3 +16,22 @@ services:
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
+
+ broker:
+ image: rabbitmq:3.7
+ container_name: rabbitmq
+ volumes:
+ - .:/usr/src/app
+ ports:
+ - 5673:5673
+
+ worker:
+ build: .
+ image: celery
+ container_name: dev_celery
+ restart: "always"
+ command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
+ volumes:
+ - .:/usr/src/app
+ depends_on:
+ - broker
diff --git a/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py b/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py
new file mode 100644
index 0000000..45edd24
--- /dev/null
+++ b/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.2 on 2023-07-14 09:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guardian', '0014_guardian_signup_method'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='guardian',
+ options={'verbose_name': 'Guardian', 'verbose_name_plural': 'Guardian'},
+ ),
+ migrations.AlterModelOptions(
+ name='juniortask',
+ options={'verbose_name': 'Junior Task', 'verbose_name_plural': 'Junior Task'},
+ ),
+ ]
diff --git a/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py b/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py
new file mode 100644
index 0000000..2223e0e
--- /dev/null
+++ b/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.2 on 2023-07-18 07:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guardian', '0015_alter_guardian_options_alter_juniortask_options'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='juniortask',
+ name='completed_on',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='juniortask',
+ name='rejected_on',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='juniortask',
+ name='requested_on',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/guardian/models.py b/guardian/models.py
index 921138a..b4a31dd 100644
--- a/guardian/models.py
+++ b/guardian/models.py
@@ -4,9 +4,37 @@ from django.db import models
from django.contrib.auth import get_user_model
"""Import Django app"""
from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS
+"""import Junior model"""
from junior.models import Junior
"""Add user model"""
User = get_user_model()
+
+# Create your models here.
+# Define junior model with
+# various fields like
+# phone, country code,
+# country name,
+# gender,
+# date of birth,
+# profile image,
+# signup method,
+# guardian code,
+# referral code,
+# referral code that used by the guardian
+# is invited guardian
+# profile is active or not
+# profile is complete or not
+# passcode
+# guardian is verified or not
+"""Define junior task model"""
+# define name of the Task
+# task description
+# points of the task
+# default image of the task
+# image uploaded by junior
+# task status
+# task approved or not
+
# Create your models here.
class Guardian(models.Model):
@@ -15,23 +43,31 @@ class Guardian(models.Model):
"""Contact details"""
country_code = models.IntegerField(blank=True, null=True)
phone = models.CharField(max_length=31, null=True, blank=True, default=None)
+ """country name of the guardian"""
country_name = models.CharField(max_length=100, null=True, blank=True, default=None)
"""Image info"""
image = models.URLField(null=True, blank=True, default=None)
"""Personal info"""
family_name = models.CharField(max_length=50, null=True, blank=True, default=None)
+ """gender of the guardian"""
gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None)
+ """date of birth of the guardian"""
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
"""Profile activity"""
is_active = models.BooleanField(default=True)
+ """guardian is verified or not"""
is_verified = models.BooleanField(default=False)
+ """guardian profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
+ """passcode of the guardian profile"""
passcode = models.IntegerField(null=True, blank=True, default=None)
"""Sign up method"""
signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1')
- """Codes"""
+ """Guardian Codes"""
guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ """Referral code"""
referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ """Referral code that is used by guardian while signup"""
referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
@@ -40,7 +76,10 @@ class Guardian(models.Model):
class Meta(object):
""" Meta class """
db_table = 'guardians'
+ """verbose name of the model"""
verbose_name = 'Guardian'
+ """change another name"""
+ verbose_name_plural = 'Guardian'
def __str__(self):
"""Return email id"""
@@ -51,20 +90,30 @@ class JuniorTask(models.Model):
guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian')
"""task details"""
task_name = models.CharField(max_length=100)
+ """task description"""
task_description = models.CharField(max_length=500)
"""points of the task"""
points = models.IntegerField(default=TASK_POINTS)
+ """last date of the task"""
due_date = models.DateField(auto_now_add=False, null=True, blank=True)
- """Images of task"""
+ """Images of task that is upload by guardian"""
default_image = models.URLField(null=True, blank=True, default=None)
+ """image that is uploaded by junior"""
image = models.URLField(null=True, blank=True, default=None)
"""associated junior with the task"""
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior')
"""task status"""
task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING)
- """task stage"""
+ """task is active or not"""
is_active = models.BooleanField(default=True)
+ """Task is approved or not"""
is_approved = models.BooleanField(default=False)
+ """request task on particular date"""
+ requested_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
+ """reject task on particular date"""
+ rejected_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
+ """complete task on particular date"""
+ completed_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -72,7 +121,9 @@ class JuniorTask(models.Model):
class Meta(object):
""" Meta class """
db_table = 'junior_task'
+ """verbose name of the model"""
verbose_name = 'Junior Task'
+ verbose_name_plural = 'Junior Task'
def __str__(self):
"""Return email id"""
diff --git a/guardian/serializers.py b/guardian/serializers.py
index 65def21..a5ad978 100644
--- a/guardian/serializers.py
+++ b/guardian/serializers.py
@@ -1,20 +1,44 @@
"""Serializer of Guardian"""
"""Third party Django app"""
import logging
-import random
from rest_framework import serializers
+# Import Refresh token of jwt
from rest_framework_simplejwt.tokens import RefreshToken
from django.db import transaction
from django.contrib.auth.models import User
+from datetime import datetime, time
"""Import Django app"""
+# Import guardian's model,
+# Import junior's model,
+# Import account's model,
+# Import constant from
+# base package,
+# Import messages from
+# base package,
+# Import some functions
+# from utils file"""
from .models import Guardian, JuniorTask
from account.models import UserProfile, UserEmailOtp, UserNotification
-from account.utils import generate_alphanumeric_code
-from account.serializers import JuniorSerializer
+from account.utils import generate_code
from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import NUMBER
+from base.constants import NUMBER, JUN, ZOD, GRD
from junior.models import Junior, JuniorPoints
+from .utils import real_time, convert_timedelta_into_datetime, update_referral_points
+
+
+
+# In this serializer file
+# define user serializer,
+# create guardian serializer,
+# task serializer,
+# guardian serializer,
+# task details serializer,
+# top junior serializer,
+# guardian profile serializer,
+# approve junior serializer,
+# approve task serializer,
+
class UserSerializer(serializers.ModelSerializer):
"""User serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
@@ -38,12 +62,14 @@ class UserSerializer(serializers.ModelSerializer):
"""Create user profile"""
user = User.objects.create_user(username=email, email=email, password=password)
UserNotification.objects.create(user=user)
- if user_type == '1':
- Junior.objects.create(auth=user, junior_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6))
- if user_type == '2':
- Guardian.objects.create(user=user, guardian_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6))
+ if user_type == str(NUMBER['one']):
+ # create junior profile
+ Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id),
+ referral_code=generate_code(ZOD, user.id))
+ if user_type == str(NUMBER['two']):
+ # create guardian profile
+ Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id),
+ referral_code=generate_code(ZOD, user.id))
return user
except Exception as e:
"""Error handling"""
@@ -56,6 +82,7 @@ class UserSerializer(serializers.ModelSerializer):
"code": 400, "status":"failed",
})
+# update guardian profile
class CreateGuardianSerializer(serializers.ModelSerializer):
"""Create guardian serializer"""
"""Basic info"""
@@ -65,9 +92,12 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
"""Contact details"""
phone = serializers.CharField(max_length=20, required=False)
country_code = serializers.IntegerField(required=False)
+ # basic info
family_name = serializers.CharField(max_length=100, required=False)
dob = serializers.DateField(required=False)
+ # code info
referral_code = serializers.CharField(max_length=100, required=False)
+ # image info
image = serializers.URLField(required=False)
class Meta(object):
@@ -111,7 +141,6 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
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)
guardian.image = validated_data.get('image', guardian.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([guardian.gender, guardian.country_name,
@@ -120,6 +149,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
guardian.is_complete_profile = False
if complete_profile_field:
guardian.is_complete_profile = True
+ referral_code_used = validated_data.get('referral_code_used')
+ update_referral_points(guardian.referral_code, referral_code_used)
+ guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used)
guardian.save()
return guardian
@@ -140,6 +172,7 @@ class TaskSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""create default task image data"""
validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last()
+ # update image of the task
images = self.context['image']
validated_data['default_image'] = images
instance = JuniorTask.objects.create(**validated_data)
@@ -173,11 +206,27 @@ class TaskDetailsSerializer(serializers.ModelSerializer):
"""Task detail serializer"""
junior = JuniorDetailSerializer()
+ remaining_time = serializers.SerializerMethodField('get_remaining_time')
+
+ def get_remaining_time(self, obj):
+ """ remaining time to complete task"""
+ due_date_datetime = datetime.combine(obj.due_date, datetime.max.time())
+ # fetch real time
+ current_datetime = real_time()
+ # Perform the subtraction
+ if due_date_datetime > current_datetime:
+ time_difference = due_date_datetime - current_datetime
+ time_only = convert_timedelta_into_datetime(time_difference)
+ return str(time_difference.days) + ' days ' + str(time_only)
+ return str(NUMBER['zero']) + ' days ' + '00:00:00:00000'
+
+
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image',
- 'junior', 'task_status', 'is_active', 'created_at','updated_at']
+ 'requested_on', 'rejected_on', 'completed_on',
+ 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TopJuniorSerializer(serializers.ModelSerializer):
@@ -188,7 +237,7 @@ class TopJuniorSerializer(serializers.ModelSerializer):
class Meta(object):
"""Meta info"""
model = JuniorPoints
- fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at']
+ fields = ['id', 'junior', 'total_points', 'position', 'created_at', 'updated_at']
def to_representation(self, instance):
"""Convert instance to representation"""
@@ -227,6 +276,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
"""total filled fields count"""
total_field_list = [obj.user.first_name, obj.country_name,
obj.gender, obj.dob, obj.image]
+ # count total complete field
total_complete_field = [data for data in total_field_list if data != '' and data is not None]
return len(total_complete_field)
@@ -241,3 +291,51 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code',
'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method',
'updated_at', 'passcode']
+
+class ApproveJuniorSerializer(serializers.ModelSerializer):
+ """approve junior serializer"""
+ class Meta(object):
+ """Meta info"""
+ model = Junior
+ fields = ['id', 'guardian_code']
+
+ def create(self, validated_data):
+ """update guardian code"""
+ instance = self.context['junior']
+ instance.guardian_code = [self.context['guardian_code']]
+ instance.save()
+ return instance
+
+class ApproveTaskSerializer(serializers.ModelSerializer):
+ """approve task serializer"""
+ class Meta(object):
+ """Meta info"""
+ model = JuniorTask
+ fields = ['id', 'task_status', 'is_approved']
+
+ def create(self, validated_data):
+ """update task status """
+ instance = self.context['task_instance']
+ junior = self.context['junior']
+ junior_details = Junior.objects.filter(id=junior).last()
+ junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details)
+ if self.context['action'] == str(NUMBER['one']):
+ # approve the task
+ instance.task_status = str(NUMBER['five'])
+ instance.is_approved = True
+ # update total task point
+ junior_data.total_points = junior_data.total_points + instance.points
+ # update complete time of task
+ instance.completed_on = real_time()
+ else:
+ # reject the task
+ instance.task_status = str(NUMBER['three'])
+ instance.is_approved = False
+ # update total task point
+ junior_data.total_points = junior_data.total_points - instance.points
+ # update reject time of task
+ instance.rejected_on = real_time()
+ instance.save()
+ junior_data.save()
+ return instance
+
diff --git a/guardian/tasks.py b/guardian/tasks.py
index 7a5dd90..9cf39b3 100644
--- a/guardian/tasks.py
+++ b/guardian/tasks.py
@@ -1,6 +1,7 @@
"""task files"""
"""Django import"""
-import random
+import secrets
def generate_otp():
"""generate random otp"""
- return ''.join([str(random.randrange(9)) for _ in range(6)])
+ digits = "0123456789"
+ return "".join(secrets.choice(digits) for _ in range(6))
diff --git a/guardian/urls.py b/guardian/urls.py
index b69b041..b14aa77 100644
--- a/guardian/urls.py
+++ b/guardian/urls.py
@@ -2,29 +2,41 @@
"""Django import"""
from django.urls import path, include
from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView,
- SearchTaskListAPIView, TopJuniorListAPIView)
+ SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView)
"""Third party import"""
from rest_framework import routers
"""Define Router"""
router = routers.SimpleRouter()
-"""API End points with router"""
+# API End points with router
+# in this file
+# we define various api end point
+# that is covered in this guardian
+# section API:- like
+# sign-up, create guardian profile,
+# create-task,
+# all task list, top junior,
+# filter-task"""
"""Sign up API"""
router.register('sign-up', SignupViewset, basename='sign-up')
-"""Create guardian profile API"""
+# Create guardian profile API"""
router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
-"""Create Task API"""
+# Create Task API"""
router.register('create-task', CreateTaskAPIView, basename='create-task')
-"""All Task list API"""
+# All Task list API"""
router.register('all-task-list', AllTaskListAPIView, basename='all-task-list')
-"""Task list bases on the status API"""
+# Task list bases on the status API"""
router.register('task-list', TaskListAPIView, basename='task-list')
-"""Leaderboard API"""
+# Leaderboard API"""
router.register('top-junior', TopJuniorListAPIView, basename='top-junior')
-"""Search Task list on the bases of status, due date, and task title API"""
+# Search Task list on the bases of status, due date, and task title API"""
router.register('filter-task', SearchTaskListAPIView, basename='filter-task')
-"""Define Url pattern"""
+# Approve junior API"""
+router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior')
+# Approve junior API"""
+router.register('approve-task', ApproveTaskAPIView, basename='approve-task')
+# Define Url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
]
diff --git a/guardian/utils.py b/guardian/utils.py
index f565e4b..45bf69b 100644
--- a/guardian/utils.py
+++ b/guardian/utils.py
@@ -5,6 +5,28 @@ import oss2
from django.conf import settings
"""Import tempfile"""
import tempfile
+# Import date time module's function
+from datetime import datetime, time
+# Import real time client module
+import ntplib
+# import Number constant
+from base.constants import NUMBER
+# Import Junior's model
+from junior.models import Junior, JuniorPoints
+
+# Define upload image on
+# ali baba cloud
+# firstly save image
+# in temporary file
+# then check bucket name
+# then upload on ali baba
+# bucket and reform the image url"""
+# fetch real time data without depend on system time
+# convert time delta into date time object
+# update junior total points
+# update referral points
+# if any one use referral code of the junior
+# junior earn 5 points
def upload_image_to_alibaba(image, filename):
"""upload image on oss alibaba bucket"""
@@ -22,3 +44,36 @@ def upload_image_to_alibaba(image, filename):
new_filename = filename.replace(' ', '%20')
return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}"
+def real_time():
+ """ real time """
+ # Fetch real time.
+ # time is not depend on system time
+ # Get the current datetime
+ ntp_client = ntplib.NTPClient()
+ ntp_server = 'pool.ntp.org'
+ response = ntp_client.request(ntp_server)
+ current_datetime = datetime.fromtimestamp(response.tx_time)
+ return current_datetime
+
+def convert_timedelta_into_datetime(time_difference):
+ """convert date time"""
+ # convert timedelta into datetime format
+ hours = time_difference.seconds // NUMBER['thirty_six_hundred']
+ minutes = (time_difference.seconds // NUMBER['sixty']) % NUMBER['sixty']
+ seconds = time_difference.seconds % NUMBER['sixty']
+ microseconds = time_difference.microseconds
+ # Create a new time object with the extracted time components
+ time_only = time(hours, minutes, seconds, microseconds)
+ return time_only
+
+def update_referral_points(referral_code, referral_code_used):
+ """Update benefit of referral points"""
+ junior_queryset = Junior.objects.filter(referral_code=referral_code_used).last()
+ if junior_queryset and junior_queryset.referral_code != referral_code:
+ # create data if junior points is not exist
+ junior_query, created = JuniorPoints.objects.get_or_create(junior=junior_queryset)
+ if junior_query:
+ # update junior total points and referral points
+ junior_query.total_points = junior_query.total_points + NUMBER['five']
+ junior_query.referral_points = junior_query.referral_points + NUMBER['five']
+ junior_query.save()
diff --git a/guardian/views.py b/guardian/views.py
index 61bf9b9..a320385 100644
--- a/guardian/views.py
+++ b/guardian/views.py
@@ -1,18 +1,28 @@
"""Views of Guardian"""
-"""Third party Django app"""
+
+# django imports
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, status
from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User
from django.utils import timezone
-from PIL import Image
-from datetime import datetime, timedelta
-"""Import Django app"""
+
+# Import guardian's model,
+# Import junior's model,
+# Import account's model,
+# Import constant from
+# base package,
+# Import messages from
+# base package,
+# Import some functions
+# from utils file
+# Import account's serializer
+# Import account's task
+
from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer,
- TopJuniorSerializer)
+ TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer)
from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints
-from junior.serializers import JuniorDetailSerializer
from account.models import UserEmailOtp, UserNotification
from .tasks import generate_otp
from account.utils import send_otp_email
@@ -20,8 +30,21 @@ from account.utils import custom_response, custom_error_response
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from .utils import upload_image_to_alibaba
-from django.db.models import Sum
+from notifications.constants import REGISTRATION
+from notifications.utils import send_notification
+
+""" Define APIs """
+# Define Signup API,
+# update guardian profile,
+# list of all task
+# list of task according to the status of the task
+# create task API
+# search task by name of the task API
+# top junior API,
+# approve junior API
+# approve task API
# Create your views here.
+# create approve task API
class SignupViewset(viewsets.ModelViewSet):
"""Signup view set"""
queryset = User.objects.all()
@@ -31,15 +54,17 @@ class SignupViewset(viewsets.ModelViewSet):
if request.data['user_type'] in ['1', '2']:
serializer = UserSerializer(context=request.data['user_type'], data=request.data)
if serializer.is_valid():
- serializer.save()
+ user = serializer.save()
"""Generate otp"""
otp = generate_otp()
expiry = timezone.now() + timezone.timedelta(days=1)
+ # create user email otp object
UserEmailOtp.objects.create(email=request.data['email'], otp=otp,
user_type=str(request.data['user_type']), expired_at=expiry)
"""Send email to the register user"""
send_otp_email(request.data['email'], otp)
- return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp},
+ send_notification(REGISTRATION, None, user.id, {})
+ return custom_response(SUCCESS_CODE['3001'],
response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
@@ -60,6 +85,7 @@ class UpdateGuardianProfile(viewsets.ViewSet):
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
+ # upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
data = {"image":image_url}
serializer = CreateGuardianSerializer(context={"user":request.user,
@@ -70,7 +96,7 @@ class UpdateGuardianProfile(viewsets.ViewSet):
if serializer.is_valid():
"""save serializer"""
serializer.save()
- return custom_response(None, serializer.data,response_status=status.HTTP_200_OK)
+ 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)
@@ -83,6 +109,7 @@ class AllTaskListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = JuniorTask.objects.filter(guardian__user=request.user)
+ # use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -96,13 +123,22 @@ class TaskListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
status_value = self.request.GET.get('status')
- if str(status_value) == '0':
+ search = self.request.GET.get('search')
+ if search and str(status_value) == '0':
+ queryset = JuniorTask.objects.filter(guardian__user=request.user,
+ task_name__icontains=search).order_by('due_date', 'created_at')
+ elif search and str(status_value) != '0':
+ queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search,
+ task_status=status_value).order_by('due_date', 'created_at')
+ if search is None and str(status_value) == '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at')
- else:
+ elif search is None and str(status_value) != '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user,
task_status=status_value).order_by('due_date','created_at')
paginator = self.pagination_class()
+ # use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
+ # use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -123,8 +159,10 @@ class CreateTaskAPIView(viewsets.ModelViewSet):
image_url = upload_image_to_alibaba(image, filename)
image_data = image_url
data.pop('default_image')
+ # use TaskSerializer serializer
serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data)
if serializer.is_valid():
+ # save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
@@ -139,6 +177,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
+ # fetch junior query
junior_queryset = JuniorTask.objects.filter(guardian__user=self.request.user, task_name__icontains=title)\
.order_by('due_date', 'created_at')
return junior_queryset
@@ -148,8 +187,9 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
queryset = self.get_queryset()
paginator = self.pagination_class()
+ # use pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
-
+ # use TaskSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -161,13 +201,14 @@ class TopJuniorListAPIView(viewsets.ModelViewSet):
queryset = JuniorPoints.objects.all()
def get_serializer_context(self):
+ # context list
context = super().get_serializer_context()
context.update({'view': self})
return context
def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks"""
- junior_total_points = self.get_queryset().order_by('-total_task_points')
+ junior_total_points = self.get_queryset().order_by('-total_points')
# Update the position field for each JuniorPoints object
for index, junior in enumerate(junior_total_points):
@@ -178,3 +219,65 @@ class TopJuniorListAPIView(viewsets.ModelViewSet):
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
+class ApproveJuniorAPIView(viewsets.ViewSet):
+ """approve junior by guardian"""
+ serializer_class = ApproveJuniorSerializer
+ permission_classes = [IsAuthenticated]
+
+ def get_queryset(self):
+ """Get the queryset for the view"""
+ guardian = Guardian.objects.filter(user__email=self.request.user).last()
+ # fetch junior query
+ junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last()
+ return guardian, junior_queryset
+
+ def create(self, request, *args, **kwargs):
+ """ junior list"""
+ queryset = self.get_queryset()
+ # action 1 is use for approve and 2 for reject
+ if request.data['action'] == '1':
+ # use ApproveJuniorSerializer serializer
+ serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code,
+ "junior": queryset[1], "action": request.data['action']},
+ data=request.data)
+ if serializer.is_valid():
+ # save serializer
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
+ else:
+ return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
+
+class ApproveTaskAPIView(viewsets.ViewSet):
+ """approve junior by guardian"""
+ serializer_class = ApproveTaskSerializer
+ permission_classes = [IsAuthenticated]
+
+ def get_queryset(self):
+ """Get the queryset for the view"""
+ guardian = Guardian.objects.filter(user__email=self.request.user).last()
+ # task query
+ task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'),
+ guardian=guardian,
+ junior=self.request.data.get('junior_id')).last()
+ return guardian, task_queryset
+
+ def create(self, request, *args, **kwargs):
+ """ junior list"""
+ # action 1 is use for approve and 2 for reject
+ queryset = self.get_queryset()
+ # use ApproveJuniorSerializer serializer
+ serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code,
+ "task_instance": queryset[1],
+ "action": str(request.data['action']),
+ "junior": self.request.data['junior_id']},
+ data=request.data)
+ if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid():
+ # save serializer
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK)
+ elif str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid():
+ # save serializer
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK)
+ else:
+ return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/junior/admin.py b/junior/admin.py
index 3764f48..99f1cd0 100644
--- a/junior/admin.py
+++ b/junior/admin.py
@@ -16,7 +16,7 @@ class JuniorAdmin(admin.ModelAdmin):
@admin.register(JuniorPoints)
class JuniorPointsAdmin(admin.ModelAdmin):
"""Junior Points Admin"""
- list_display = ['junior', 'total_task_points', 'position']
+ list_display = ['junior', 'total_points', 'position']
def __str__(self):
"""Return email id"""
diff --git a/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py b/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py
new file mode 100644
index 0000000..266d53c
--- /dev/null
+++ b/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.2 on 2023-07-14 09:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0012_junior_is_invited'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='junior',
+ options={'verbose_name': 'Junior', 'verbose_name_plural': 'Junior'},
+ ),
+ migrations.AlterModelOptions(
+ name='juniorpoints',
+ options={'verbose_name': 'Junior Task Points', 'verbose_name_plural': 'Junior Task Points'},
+ ),
+ ]
diff --git a/junior/migrations/0014_junior_is_password_set.py b/junior/migrations/0014_junior_is_password_set.py
new file mode 100644
index 0000000..a71e6c0
--- /dev/null
+++ b/junior/migrations/0014_junior_is_password_set.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-07-18 09:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0013_alter_junior_options_alter_juniorpoints_options'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='junior',
+ name='is_password_set',
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py b/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py
new file mode 100644
index 0000000..038adba
--- /dev/null
+++ b/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-07-19 09:40
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0014_junior_is_password_set'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='juniorpoints',
+ old_name='total_task_points',
+ new_name='total_points',
+ ),
+ ]
diff --git a/junior/migrations/0016_juniorpoints_referral_points.py b/junior/migrations/0016_juniorpoints_referral_points.py
new file mode 100644
index 0000000..a70f45d
--- /dev/null
+++ b/junior/migrations/0016_juniorpoints_referral_points.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-07-19 11:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0015_rename_total_task_points_juniorpoints_total_points'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='juniorpoints',
+ name='referral_points',
+ field=models.IntegerField(blank=True, default=0, null=True),
+ ),
+ ]
diff --git a/junior/models.py b/junior/models.py
index 58b1ae3..28754b1 100644
--- a/junior/models.py
+++ b/junior/models.py
@@ -1,42 +1,77 @@
"""Junior model """
"""Import django"""
from django.db import models
+"""Import get_user_model function"""
from django.contrib.auth import get_user_model
+"""Import ArrayField"""
from django.contrib.postgres.fields import ArrayField
"""Import django app"""
from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP
+"""Define User model"""
User = get_user_model()
# Create your models here.
+# Define junior model with
+# various fields like
+# phone, country code,
+# country name,
+# gender,
+# date of birth,
+# profile image,
+# relationship type of the guardian
+# signup method,
+# guardian code,
+# junior code,
+# referral code,
+# referral code that used by the junior
+# is invited junior
+# profile is active or not
+# profile is complete or not
+# passcode
+# junior is verified or not
+"""Define junior points model"""
+# points of the junior
+# position of the junior
class Junior(models.Model):
"""Junior model"""
auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email')
- """Contact details"""
+ # Contact details"""
phone = models.CharField(max_length=31, null=True, blank=True, default=None)
country_code = models.IntegerField(blank=True, null=True)
+ # country name of the guardian"""
country_name = models.CharField(max_length=100, null=True, blank=True, default=None)
- """Personal info"""
+ # Personal info"""
gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None)
+ # Date of birth"""
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
+ # Image of the junior"""
image = models.URLField(null=True, blank=True, default=None)
- """relationship"""
+ # relationship"""
relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True,
default='1')
- """Sign up method"""
+ # Sign up method"""
signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1')
- """Codes"""
+ # Codes"""
junior_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ # Guardian Codes"""
guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True)
+ # Referral code"""
referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ # Referral code that is used by junior while signup"""
referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
- """invited junior"""
+ # invited junior"""
is_invited = models.BooleanField(default=False)
- """Profile activity"""
+ # Profile activity"""
is_active = models.BooleanField(default=True)
+ # check password is set or not
+ is_password_set = models.BooleanField(default=True)
+ # junior profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
+ # passcode of the junior profile"""
passcode = models.IntegerField(null=True, blank=True, default=None)
+ # junior is verified or not"""
is_verified = models.BooleanField(default=False)
- """Profile created and updated time"""
+ # Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -44,6 +79,8 @@ class Junior(models.Model):
""" Meta class """
db_table = 'junior'
verbose_name = 'Junior'
+ # another name of the model"""
+ verbose_name_plural = 'Junior'
def __str__(self):
"""Return email id"""
@@ -52,11 +89,13 @@ class Junior(models.Model):
class JuniorPoints(models.Model):
"""Junior model"""
junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points')
- """Contact details"""
- total_task_points = models.IntegerField(blank=True, null=True, default=0)
- """position of the junior"""
+ # Total earned points"""
+ total_points = models.IntegerField(blank=True, null=True, default=0)
+ # referral points"""
+ referral_points = models.IntegerField(blank=True, null=True, default=0)
+ # position of the junior"""
position = models.IntegerField(blank=True, null=True, default=99999)
- """Profile created and updated time"""
+ # Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -64,6 +103,8 @@ class JuniorPoints(models.Model):
""" Meta class """
db_table = 'junior_task_points'
verbose_name = 'Junior Task Points'
+ # another name of the model"""
+ verbose_name_plural = 'Junior Task Points'
def __str__(self):
"""Return email id"""
diff --git a/junior/serializers.py b/junior/serializers.py
index 2df13ca..b1d1615 100644
--- a/junior/serializers.py
+++ b/junior/serializers.py
@@ -3,19 +3,21 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from django.db import transaction
-import random
+from datetime import datetime
from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken
"""Import django app"""
-from account.utils import send_otp_email, generate_alphanumeric_code
+from account.utils import send_otp_email, generate_code
from junior.models import Junior, JuniorPoints
from guardian.tasks import generate_otp
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER
+from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD
from guardian.models import Guardian, JuniorTask
from account.models import UserEmailOtp
from junior.utils import junior_notification_email, junior_approval_mail
+from guardian.utils import real_time, update_referral_points
+
class ListCharField(serializers.ListField):
@@ -91,7 +93,6 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
"""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)
junior.image = validated_data.get('image', junior.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([junior.gender, junior.country_name, junior.image,
@@ -99,6 +100,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
junior.is_complete_profile = False
if complete_profile_field:
junior.is_complete_profile = True
+ referral_code_used = validated_data.get('referral_code_used')
+ update_referral_points(junior.referral_code, referral_code_used)
+ junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used)
junior.save()
return junior
@@ -275,14 +279,15 @@ class AddJuniorSerializer(serializers.ModelSerializer):
junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'),
dob=validated_data.get('dob'), is_invited=True,
relationship=validated_data.get('relationship'),
- junior_code=generate_alphanumeric_code(6),
- referral_code=generate_alphanumeric_code(6),
- referral_code_used=guardian_data.referral_code)
+ junior_code=generate_code(JUN, user_data.id),
+ referral_code=generate_code(ZOD, user_data.id),
+ referral_code_used=guardian_data.referral_code,
+ is_password_set=False, is_verified=True)
"""Generate otp"""
otp_value = generate_otp()
expiry_time = timezone.now() + timezone.timedelta(days=1)
UserEmailOtp.objects.create(email=email, otp=otp_value,
- user_type='1', expired_at=expiry_time)
+ user_type='1', expired_at=expiry_time, is_verified=True)
"""Send email to the register user"""
send_otp_email(email, otp_value)
"""Notification email"""
@@ -305,3 +310,67 @@ class RemoveJuniorSerializer(serializers.ModelSerializer):
instance.save()
return instance
+
+class CompleteTaskSerializer(serializers.ModelSerializer):
+ """User task Serializer"""
+ class Meta(object):
+ """Meta class"""
+ model = JuniorTask
+ fields = ('id', 'image')
+ def update(self, instance, validated_data):
+ instance.image = validated_data.get('image', instance.image)
+ instance.requested_on = real_time()
+ instance.task_status = str(NUMBER['four'])
+ instance.is_approved = False
+ instance.save()
+ return instance
+
+class JuniorPointsSerializer(serializers.ModelSerializer):
+ """Junior points serializer"""
+ junior_id = serializers.SerializerMethodField('get_junior_id')
+ total_points = serializers.SerializerMethodField('get_points')
+ in_progress_task = serializers.SerializerMethodField('get_in_progress_task')
+ completed_task = serializers.SerializerMethodField('get_completed_task')
+ requested_task = serializers.SerializerMethodField('get_requested_task')
+ rejected_task = serializers.SerializerMethodField('get_rejected_task')
+ pending_task = serializers.SerializerMethodField('get_pending_task')
+ position = serializers.SerializerMethodField('get_position')
+
+ def get_junior_id(self, obj):
+ """junior id"""
+ return obj.junior.id
+
+ def get_position(self, obj):
+ data = JuniorPoints.objects.filter(junior=obj.junior).last()
+ if data:
+ return data.position
+ return 99999
+ def get_points(self, obj):
+ """total points"""
+ points = JuniorPoints.objects.filter(junior=obj.junior).last()
+ if points:
+ return points.total_points
+
+ def get_in_progress_task(self, obj):
+ return JuniorTask.objects.filter(junior=obj.junior, task_status=IN_PROGRESS).count()
+
+ def get_completed_task(self, obj):
+ return JuniorTask.objects.filter(junior=obj.junior, task_status=COMPLETED).count()
+
+
+ def get_requested_task(self, obj):
+ return JuniorTask.objects.filter(junior=obj.junior, task_status=REQUESTED).count()
+
+
+ def get_rejected_task(self, obj):
+ return JuniorTask.objects.filter(junior=obj.junior, task_status=REJECTED).count()
+
+
+ def get_pending_task(self, obj):
+ return JuniorTask.objects.filter(junior=obj.junior, task_status=PENDING).count()
+
+ class Meta(object):
+ """Meta info"""
+ model = Junior
+ fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task',
+ 'requested_task', 'rejected_task']
diff --git a/junior/urls.py b/junior/urls.py
index 162f693..e4a160d 100644
--- a/junior/urls.py
+++ b/junior/urls.py
@@ -2,28 +2,45 @@
"""Django import"""
from django.urls import path, include
from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView,
- InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView)
+ InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView,
+ CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode)
"""Third party import"""
from rest_framework import routers
"""Router"""
router = routers.SimpleRouter()
-
+# API End points with router
+# in this file
+# we define various api end point
+# that is covered in this guardian
+# section API:- like
+# Create junior profile API, validate junior profile,
+# junior list,
+# add junior list, invited junior,
+# filter-junior,
+# remove junior,
+# junior task list
"""API End points with router"""
-"""Create junior profile API"""
router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update')
-"""validate guardian code API"""
+# validate guardian code API"""
router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code')
-"""junior list API"""
+# junior list API"""
router.register('junior-list', JuniorListAPIView, basename='junior-list')
-"""Add junior list API"""
+# Add junior list API"""
router.register('add-junior', AddJuniorAPIView, basename='add-junior')
-"""Invited junior list API"""
+# Invited junior list API"""
router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior')
-"""Filter junior list API"""
+# Filter junior list API"""
router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior')
-"""Define url pattern"""
+# junior's task list API"""
+router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list')
+# junior's task list API"""
+router.register('junior-points', JuniorPointsListAPIView, basename='junior-points')
+# validate referral code API"""
+router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code')
+# Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
- path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view())
+ path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()),
+ path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view())
]
diff --git a/junior/utils.py b/junior/utils.py
index 7d19a2d..e1fb5df 100644
--- a/junior/utils.py
+++ b/junior/utils.py
@@ -1,28 +1,23 @@
"""Account utils"""
"""Import django"""
from django.conf import settings
-from rest_framework import viewsets, status
-from rest_framework.response import Response
"""Third party Django app"""
from templated_email import send_templated_mail
-import jwt
-import string
-from datetime import datetime
-from calendar import timegm
-from uuid import uuid4
-import secrets
-from rest_framework import serializers
-from junior.models import Junior
-from guardian.models import Guardian
-from account.models import UserDelete
-from base.messages import ERROR_CODE
-
-
+# junior notification
+# email for sending email
+# when guardian create junior profile
+# guardian get email when junior send
+# request for approving the profile and
+# being part of the zod bank and access the platform
+# define junior notification email
+# junior approval email
def junior_notification_email(recipient_email, full_name, email, password):
"""Notification email"""
from_email = settings.EMAIL_FROM_ADDRESS
+ # recipient email"""
recipient_list = [recipient_email]
+ # use send template mail for sending email"""
send_templated_mail(
template_name='junior_notification_email.email',
from_email=from_email,
@@ -40,6 +35,7 @@ def junior_approval_mail(guardian, full_name):
"""junior approval mail"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [guardian]
+ # use send template mail for sending email"""
send_templated_mail(
template_name='junior_approval_mail.email',
from_email=from_email,
diff --git a/junior/views.py b/junior/views.py
index a1053d4..592326a 100644
--- a/junior/views.py
+++ b/junior/views.py
@@ -1,18 +1,46 @@
"""Junior view file"""
+import os
+
from rest_framework import viewsets, status, generics,views
from rest_framework.permissions import IsAuthenticated
from rest_framework.pagination import PageNumberPagination
-from rest_framework.response import Response
-from PIL import Image
+import requests
"""Django app import"""
+
+# Import guardian's model,
+# Import junior's model,
+# Import account's model,
+# Import constant from
+# base package,
+# Import messages from
+# base package,
+# Import some functions
+# from utils file
+# Import account's serializer
+# Import account's task
+# import junior serializer
from junior.models import Junior
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\
- RemoveJuniorSerializer)
-from guardian.models import Guardian
+ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer)
+from guardian.models import Guardian, JuniorTask
+from guardian.serializers import TaskDetailsSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from account.utils import custom_response, custom_error_response
from guardian.utils import upload_image_to_alibaba
+
+""" Define APIs """
+# Define validate guardian code API,
+# update junior profile,
+# list of all assosicated junior
+# Add junior API
+# invite junior API
+# search junior API
+# remove junior API,
+# approve junior API
+# create referral code
+# validation API
+
# Create your views here.
class UpdateJuniorProfile(viewsets.ViewSet):
"""Update junior profile"""
@@ -28,7 +56,9 @@ class UpdateJuniorProfile(viewsets.ViewSet):
if image:
if image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
+ # convert into file
filename = f"images/{image.name}"
+ # upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
request_data = {"image": image_url}
serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url,
@@ -51,6 +81,7 @@ class ValidateGuardianCode(viewsets.ViewSet):
"""check guardian code"""
guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code:
+ # fetch guardian object
guardian_data = Guardian.objects.filter(guardian_code=code).exists()
if guardian_data:
return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK)
@@ -62,10 +93,19 @@ class JuniorListAPIView(viewsets.ModelViewSet):
serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
+ permission_classes = [IsAuthenticated]
+
def list(self, request, *args, **kwargs):
""" junior list"""
+ auth_token = self.request.META['HTTP_AUTHORIZATION']
+ headers_token = {
+ 'Authorization': auth_token
+ }
+ requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token)
guardian_data = Guardian.objects.filter(user__email=request.user).last()
+ # fetch junior object
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
+ # use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -78,8 +118,10 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
""" junior list"""
info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name']}
+ # use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info)
if serializer.is_valid():
+ # save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
@@ -102,7 +144,9 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
""" junior list"""
queryset = self.get_queryset()
paginator = self.pagination_class()
+ # pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
+ # use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -118,6 +162,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
guardian_data = Guardian.objects.filter(user__email=self.request.user).last()
+ # fetch junior query
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code),
is_invited=True, auth__first_name=title)
return queryset
@@ -126,13 +171,15 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
"""Create guardian profile"""
queryset = self.get_queryset()
paginator = self.pagination_class()
+ # use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
+ # use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class RemoveJuniorAPIView(views.APIView):
- """Eater Update API"""
+ """Remove junior API"""
serializer_class = RemoveJuniorSerializer
model = Junior
permission_classes = [IsAuthenticated]
@@ -140,13 +187,129 @@ class RemoveJuniorAPIView(views.APIView):
def put(self, request, format=None):
junior_id = self.request.GET.get('id')
guardian = Guardian.objects.filter(user__email=self.request.user).last()
+ # fetch junior query
junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code),
is_invited=True).last()
if junior_queryset:
+ # use RemoveJuniorSerializer serializer
serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True)
if serializer.is_valid():
+ # save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST)
+
+
+class JuniorTaskListAPIView(viewsets.ModelViewSet):
+ """Update guardian profile"""
+ serializer_class = TaskDetailsSerializer
+ permission_classes = [IsAuthenticated]
+ pagination_class = PageNumberPagination
+ queryset = JuniorTask.objects.all()
+
+ def list(self, request, *args, **kwargs):
+ """Create guardian profile"""
+ status_value = self.request.GET.get('status')
+ search = self.request.GET.get('search')
+ if search and str(status_value) == '0':
+ # search with title and for all task list
+ queryset = JuniorTask.objects.filter(junior__auth=request.user,
+ task_name__icontains=search).order_by('due_date', 'created_at')
+ elif search and str(status_value) != '0':
+ # search with title and fetch task list with status wise
+ queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search,
+ task_status=status_value).order_by('due_date', 'created_at')
+ if search is None and str(status_value) == '0':
+ # fetch all task list
+ queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at')
+ elif search is None and str(status_value) != '0':
+ # fetch task list with status wise
+ queryset = JuniorTask.objects.filter(junior__auth=request.user,
+ task_status=status_value).order_by('due_date','created_at')
+ paginator = self.pagination_class()
+ # use Pagination
+ paginated_queryset = paginator.paginate_queryset(queryset, request)
+ # use TaskDetailsSerializer serializer
+ serializer = TaskDetailsSerializer(paginated_queryset, many=True)
+ return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
+
+
+class CompleteJuniorTaskAPIView(views.APIView):
+ """Update junior task API"""
+ serializer_class = CompleteTaskSerializer
+ model = JuniorTask
+ permission_classes = [IsAuthenticated]
+
+ def put(self, request, format=None):
+ try:
+ task_id = self.request.data.get('task_id')
+ image = request.data['image']
+ if image and image.size == NUMBER['zero']:
+ return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
+ # create file
+ filename = f"images/{image.name}"
+ # upload image
+ filename = f"images/{image.name}"
+
+ image_url = upload_image_to_alibaba(image, filename)
+ # fetch junior query
+ task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last()
+ if task_queryset:
+ # use RemoveJuniorSerializer serializer
+ serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True)
+ if serializer.is_valid():
+ # save serializer
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+ else:
+ return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST)
+ except Exception as e:
+ return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
+
+class JuniorPointsListAPIView(viewsets.ModelViewSet):
+ """Junior Points viewset"""
+ serializer_class = JuniorPointsSerializer
+ permission_classes = [IsAuthenticated]
+
+ def get_queryset(self):
+ """get queryset"""
+ return JuniorTask.objects.filter(junior__auth__email=self.request.user).last()
+ def list(self, request, *args, **kwargs):
+ """profile view"""
+ queryset = self.get_queryset()
+ # update position of junior
+ token = self.request.META['HTTP_AUTHORIZATION']
+ headers = {
+ 'Authorization': token
+ }
+ requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers)
+ serializer = JuniorPointsSerializer(queryset)
+ return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
+
+class ValidateReferralCode(viewsets.ViewSet):
+ """Check guardian code exist or not"""
+ permission_classes = [IsAuthenticated]
+
+ def get_queryset(self):
+ """Get queryset based on referral_code."""
+ referral_code = self.request.GET.get('referral_code')
+ if referral_code:
+ # search referral code in guardian model
+ guardian_queryset = Guardian.objects.filter(referral_code=referral_code).last()
+ if guardian_queryset:
+ return guardian_queryset
+ else:
+ # search referral code in junior model
+ junior_queryset = Junior.objects.filter(referral_code=referral_code).last()
+ if junior_queryset:
+ return junior_queryset
+ return None
+
+ def list(self, request, *args, **kwargs):
+ """check guardian code"""
+ if self.get_queryset():
+ return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK)
+ return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/manage.py b/manage.py
index a19d33b..81f9c88 100755
--- a/manage.py
+++ b/manage.py
@@ -1,11 +1,15 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
"""Django import"""
-"""Import OS module"""
+# Import OS module
import os
-"""Import sys module"""
+# Import sys module"""
import sys
+# define all function
+# execute command line
+# Import execute from command line
+# fetch django settings
def main():
"""Main function"""
diff --git a/notifications/__init__.py b/notifications/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/notifications/admin.py b/notifications/admin.py
new file mode 100644
index 0000000..382e97b
--- /dev/null
+++ b/notifications/admin.py
@@ -0,0 +1,12 @@
+"""
+notification admin file
+"""
+from django.contrib import admin
+
+from notifications.models import Notification
+
+
+@admin.register(Notification)
+class NotificationAdmin(admin.ModelAdmin):
+ """Notification Admin"""
+ list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read']
diff --git a/notifications/apps.py b/notifications/apps.py
new file mode 100644
index 0000000..366a893
--- /dev/null
+++ b/notifications/apps.py
@@ -0,0 +1,13 @@
+"""
+notification app file
+"""
+# django imports
+from django.apps import AppConfig
+
+
+class NotificationsConfig(AppConfig):
+ """
+ notification app config
+ """
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'notifications'
diff --git a/notifications/constants.py b/notifications/constants.py
new file mode 100644
index 0000000..b72ed61
--- /dev/null
+++ b/notifications/constants.py
@@ -0,0 +1,16 @@
+"""
+notification constants file
+"""
+REGISTRATION = 1
+TEST_NOTIFICATION = 99
+
+NOTIFICATION_DICT = {
+ REGISTRATION: {
+ "title": "Successfully registered!",
+ "body": "You have registered successfully. Now login and complete your profile."
+ },
+ TEST_NOTIFICATION: {
+ "title": "Test Notification",
+ "body": "This notification is for testing purpose"
+ }
+}
diff --git a/notifications/migrations/0001_initial.py b/notifications/migrations/0001_initial.py
new file mode 100644
index 0000000..ea73b4a
--- /dev/null
+++ b/notifications/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+# Generated by Django 4.2.2 on 2023-07-19 07:40
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Notification',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('notification_type', models.CharField(blank=True, max_length=50, null=True)),
+ ('data', models.JSONField(blank=True, default=dict, null=True)),
+ ('is_read', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(default=django.utils.timezone.now)),
+ ('notification_from', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='from_notification', to=settings.AUTH_USER_MODEL)),
+ ('notification_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_notification', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/notifications/migrations/__init__.py b/notifications/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/notifications/models.py b/notifications/models.py
new file mode 100644
index 0000000..5d6d02c
--- /dev/null
+++ b/notifications/models.py
@@ -0,0 +1,24 @@
+"""
+notification models file
+"""
+# django imports
+from django.db import models
+from django.contrib.auth import get_user_model
+from django.utils import timezone
+
+USER = get_user_model()
+
+
+class Notification(models.Model):
+ """ used to save the notifications """
+ notification_type = models.CharField(max_length=50, blank=True, null=True)
+ notification_to = models.ForeignKey(USER, related_name='to_notification', on_delete=models.CASCADE)
+ notification_from = models.ForeignKey(USER, related_name='from_notification', on_delete=models.SET_NULL,
+ blank=True, null=True)
+ data = models.JSONField(default=dict, blank=True, null=True)
+ is_read = models.BooleanField(default=False)
+ created_at = models.DateTimeField(default=timezone.now)
+
+ def __str__(self):
+ """ string representation """
+ return f"{self.notification_to.id} | {self.notification_to.email}"
diff --git a/notifications/serializers.py b/notifications/serializers.py
new file mode 100644
index 0000000..053096f
--- /dev/null
+++ b/notifications/serializers.py
@@ -0,0 +1,28 @@
+"""
+notification serializer file
+"""
+# third party imports
+from rest_framework import serializers
+
+# local imports
+from notifications.utils import register_fcm_token
+
+
+class RegisterDevice(serializers.Serializer):
+ """
+ used to create and validate register device token
+ """
+ registration_id = serializers.CharField(max_length=250)
+ device_id = serializers.CharField(max_length=250)
+ type = serializers.ChoiceField(choices=["ios", "web", "android"])
+
+ class Meta:
+ """ meta class """
+ fields = ('registration_id', 'type', 'device_id')
+
+ def create(self, validated_data):
+ """ override this method to create device token for users """
+ registration_id = validated_data['registration_id']
+ device_type = validated_data['type']
+ return register_fcm_token(self.context['user_id'], registration_id,
+ validated_data['device_id'], device_type)
diff --git a/notifications/tests.py b/notifications/tests.py
new file mode 100644
index 0000000..fd087b9
--- /dev/null
+++ b/notifications/tests.py
@@ -0,0 +1,6 @@
+"""
+notification test file
+"""
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/notifications/urls.py b/notifications/urls.py
new file mode 100644
index 0000000..b184d02
--- /dev/null
+++ b/notifications/urls.py
@@ -0,0 +1,18 @@
+"""
+notifications urls file
+"""
+# django imports
+from django.urls import path, include
+from rest_framework import routers
+
+# local imports
+from notifications.views import NotificationViewSet
+
+# initiate router
+router = routers.SimpleRouter()
+
+router.register('notifications', NotificationViewSet, basename='notifications')
+
+urlpatterns = [
+ path('api/v1/', include(router.urls)),
+]
diff --git a/notifications/utils.py b/notifications/utils.py
new file mode 100644
index 0000000..26d6778
--- /dev/null
+++ b/notifications/utils.py
@@ -0,0 +1,71 @@
+"""
+notifications utils file
+"""
+# third party imports
+from fcm_django.models import FCMDevice
+from celery import shared_task
+from firebase_admin.messaging import Message, Notification as FirebaseNotification
+
+# django imports
+from django.contrib.auth import get_user_model
+
+from account.models import UserNotification
+from notifications.constants import NOTIFICATION_DICT
+from notifications.models import Notification
+
+# local imports
+
+
+User = get_user_model()
+
+
+def register_fcm_token(user_id, registration_id, device_id, device_type):
+ """ used to register the fcm device token"""
+ device, _ = FCMDevice.objects.update_or_create(device_id=device_id,
+ defaults={'user_id': user_id, 'type': device_type,
+ 'active': True,
+ 'registration_id': registration_id})
+ return device
+
+
+def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None:
+ """
+ remove access_token and registration_token
+ """
+ try:
+ # remove fcm token for this device
+ FCMDevice.objects.filter(user_id=user_id).delete()
+ except Exception as e:
+ print(e)
+
+
+def get_basic_detail(notification_type, from_user_id, to_user_id):
+ """ used to get the basic details """
+ notification_data = NOTIFICATION_DICT[notification_type]
+ from_user = User.objects.get(id=from_user_id) if from_user_id else None
+ to_user = User.objects.get(id=to_user_id)
+ return notification_data, from_user, to_user
+
+
+@shared_task()
+def send_notification(notification_type, from_user_id, to_user_id, extra_data):
+ """ used to send the push for the given notification type """
+ (notification_data, from_user, to_user) = get_basic_detail(notification_type, from_user_id, to_user_id)
+ print(notification_data, to_user)
+ user_notification_type = UserNotification.objects.filter(user=to_user).first()
+ # data = notification_data.data
+ data = notification_data
+ Notification.objects.create(notification_type=notification_type, notification_from=from_user,
+ notification_to=to_user, data=data)
+ if user_notification_type.push_notification:
+ data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()})
+ send_push(to_user, data)
+
+
+def send_push(user, data):
+ """ used to send push notification to specific user """
+ # if user.push_notification:
+ notification_data = data.pop('data', None)
+ user.fcmdevice_set.filter(active=True).send_message(
+ Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data)
+ )
diff --git a/notifications/views.py b/notifications/views.py
new file mode 100644
index 0000000..d9e5fd7
--- /dev/null
+++ b/notifications/views.py
@@ -0,0 +1,42 @@
+"""
+notifications views file
+"""
+# django imports
+from django.db.models import Q
+from rest_framework import viewsets
+from rest_framework.decorators import action
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+
+# local imports
+from account.utils import custom_response
+from base.messages import SUCCESS_CODE
+from notifications.constants import TEST_NOTIFICATION
+from notifications.serializers import RegisterDevice
+from notifications.utils import send_notification
+
+
+class NotificationViewSet(viewsets.GenericViewSet):
+ """ used to do the notification actions """
+ serializer_class = None
+ permission_classes = [IsAuthenticated, ]
+
+ @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice)
+ def fcm_registration(self, request):
+ """
+ used to save the fcm token
+ """
+ serializer = self.get_serializer_class()(data=request.data,
+ context={'user_id': request.auth.payload['user_id']})
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+ return custom_response(SUCCESS_CODE["3000"])
+
+ @action(methods=['get'], detail=False, url_path='test', url_name='test', serializer_class=None)
+ def send_test_notification(self, request):
+ """
+ to send test notification
+ :return:
+ """
+ send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {})
+ return custom_response(SUCCESS_CODE["3000"])
diff --git a/requirements.txt b/requirements.txt
index 5dd7f76..85e0fbb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -51,6 +51,8 @@ jmespath==0.10.0
kombu==5.3.1
MarkupSafe==2.1.3
msgpack==1.0.5
+ntplib==0.4.0
+numpy==1.25.1
oss2==2.18.0
packaging==23.1
phonenumbers==8.13.15
@@ -79,3 +81,5 @@ uritemplate==4.1.1
urllib3==1.26.16
vine==5.0.0
wcwidth==0.2.6
+
+fcm-django==2.0.0
\ No newline at end of file
diff --git a/web_admin/__init__.py b/web_admin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web_admin/admin.py b/web_admin/admin.py
new file mode 100644
index 0000000..b1df483
--- /dev/null
+++ b/web_admin/admin.py
@@ -0,0 +1,32 @@
+"""
+web_admin admin file
+"""
+# django imports
+from django.contrib import admin
+
+# local imports
+from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption
+
+
+@admin.register(Article)
+class ArticleAdmin(admin.ModelAdmin):
+ """Article Admin"""
+ list_display = ['id', 'title', 'description', 'is_published', 'is_deleted']
+
+
+@admin.register(ArticleCard)
+class ArticleCardAdmin(admin.ModelAdmin):
+ """Article Card Admin"""
+ list_display = ['id', 'article', 'title', 'description', 'image']
+
+
+@admin.register(ArticleSurvey)
+class ArticleSurveyAdmin(admin.ModelAdmin):
+ """Article Survey Admin"""
+ list_display = ['id', 'article', 'question', 'points']
+
+
+@admin.register(SurveyOption)
+class SurveyOptionAdmin(admin.ModelAdmin):
+ """Survey Option Admin"""
+ list_display = ['id', 'survey', 'option', 'is_answer']
diff --git a/web_admin/apps.py b/web_admin/apps.py
new file mode 100644
index 0000000..2842e02
--- /dev/null
+++ b/web_admin/apps.py
@@ -0,0 +1,13 @@
+"""
+web_admin app file
+"""
+# django imports
+from django.apps import AppConfig
+
+
+class WebAdminConfig(AppConfig):
+ """
+ web admin app config
+ """
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'web_admin'
diff --git a/web_admin/migrations/0001_initial.py b/web_admin/migrations/0001_initial.py
new file mode 100644
index 0000000..ad25a50
--- /dev/null
+++ b/web_admin/migrations/0001_initial.py
@@ -0,0 +1,61 @@
+# Generated by Django 4.2.2 on 2023-07-14 13:15
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Article',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=255)),
+ ('description', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('is_published', models.BooleanField(default=True)),
+ ('is_deleted', models.BooleanField(default=False)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ArticleSurvey',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('question', models.CharField(max_length=255)),
+ ('points', models.IntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_survey', to='web_admin.article')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='SurveyOption',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('option', models.CharField(max_length=255)),
+ ('is_answer', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='survey_options', to='web_admin.articlesurvey')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ArticleCard',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=255)),
+ ('description', models.TextField()),
+ ('image', models.ImageField(upload_to='card_images/')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_cards', to='web_admin.article')),
+ ],
+ ),
+ ]
diff --git a/web_admin/migrations/__init__.py b/web_admin/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web_admin/models.py b/web_admin/models.py
new file mode 100644
index 0000000..1e49c6b
--- /dev/null
+++ b/web_admin/models.py
@@ -0,0 +1,67 @@
+"""
+web_admin model file
+"""
+# django imports
+from django.db import models
+
+
+class Article(models.Model):
+ """
+ Article model
+ """
+ title = models.CharField(max_length=255)
+ description = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ is_published = models.BooleanField(default=True)
+ is_deleted = models.BooleanField(default=False)
+
+ def __str__(self):
+ """Return title"""
+ return f'{self.id} | {self.title}'
+
+
+class ArticleCard(models.Model):
+ """
+ Article Card model
+ """
+ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards')
+ title = models.CharField(max_length=255)
+ description = models.TextField()
+ image = models.ImageField(upload_to='card_images/')
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ """Return title"""
+ return f'{self.id} | {self.title}'
+
+
+class ArticleSurvey(models.Model):
+ """
+ Article Survey model
+ """
+ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey')
+ question = models.CharField(max_length=255)
+ points = models.IntegerField()
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ """Return title"""
+ return f'{self.id} | {self.article}'
+
+
+class SurveyOption(models.Model):
+ """
+ Survey Options model
+ """
+ survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options')
+ option = models.CharField(max_length=255)
+ is_answer = models.BooleanField(default=False)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ """Return title"""
+ return f'{self.id} | {self.survey}'
diff --git a/web_admin/permission.py b/web_admin/permission.py
new file mode 100644
index 0000000..5ecf33a
--- /dev/null
+++ b/web_admin/permission.py
@@ -0,0 +1,26 @@
+"""
+web_admin permission classes
+"""
+# django imports
+from rest_framework import permissions
+
+
+class AdminPermission(permissions.BasePermission):
+ """
+ to check for usertype admin only
+ """
+ def has_permission(self, request, view):
+ """
+ Return True if user_type is admin
+ """
+ if request.user.is_superuser:
+ return True
+ return False
+
+ def has_object_permission(self, request, view, obj):
+ """
+ check for object level permission
+ """
+ if request.user.is_superuser:
+ return True
+ return False
diff --git a/web_admin/serializers.py b/web_admin/serializers.py
new file mode 100644
index 0000000..57d4278
--- /dev/null
+++ b/web_admin/serializers.py
@@ -0,0 +1,162 @@
+"""
+web_admin serializers file
+"""
+# django imports
+from rest_framework import serializers
+from django.conf import settings
+
+# local imports
+from base.messages import ERROR_CODE
+from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey
+from web_admin.utils import pop_id
+
+
+class ArticleCardSerializer(serializers.ModelSerializer):
+ """
+ Article Card serializer
+ """
+ id = serializers.IntegerField(required=False)
+
+ class Meta:
+ """
+ meta class
+ """
+ model = ArticleCard
+ fields = ('id', 'title', 'description')
+
+
+class SurveyOptionSerializer(serializers.ModelSerializer):
+ """
+ survey option serializer
+ """
+ id = serializers.IntegerField(required=False)
+
+ class Meta:
+ """
+ meta class
+ """
+ model = SurveyOption
+ fields = ('id', 'option', 'is_answer')
+
+
+class ArticleSurveySerializer(serializers.ModelSerializer):
+ """
+ article survey serializer
+ """
+ id = serializers.IntegerField(required=False)
+ survey_options = SurveyOptionSerializer(many=True)
+
+ class Meta:
+ """
+ meta class
+ """
+ model = ArticleSurvey
+ fields = ('id', 'question', 'points', 'survey_options')
+
+
+class ArticleSerializer(serializers.ModelSerializer):
+ """
+ serializer for article API
+ """
+ article_cards = ArticleCardSerializer(many=True)
+ article_survey = ArticleSurveySerializer(many=True)
+
+ class Meta:
+ """
+ meta class
+ """
+ model = Article
+ fields = ('id', 'title', 'description', 'article_cards', 'article_survey')
+
+ def validate(self, attrs):
+ """
+ to validate request data
+ :param attrs:
+ :return: validated attrs
+ """
+ article_cards = attrs.get('article_cards', None)
+ article_survey = attrs.get('article_survey', None)
+ if article_cards is None or len(article_cards) > int(settings.MAX_ARTICLE_CARD):
+ raise serializers.ValidationError({'details': ERROR_CODE['2039']})
+ if article_survey is None or len(article_survey) < int(settings.MIN_ARTICLE_SURVEY) or int(
+ settings.MAX_ARTICLE_SURVEY) < len(article_survey):
+ raise serializers.ValidationError({'details': ERROR_CODE['2040']})
+ return attrs
+
+ def create(self, validated_data):
+ """
+ to create article.
+ ID in post data dict is for update api.
+ :param validated_data:
+ :return: article object
+ """
+ article_cards = validated_data.pop('article_cards')
+ article_survey = validated_data.pop('article_survey')
+
+ article = Article.objects.create(**validated_data)
+
+ for card in article_cards:
+ card = pop_id(card)
+ ArticleCard.objects.create(article=article, **card)
+
+ for survey in article_survey:
+ survey = pop_id(survey)
+ options = survey.pop('survey_options')
+ survey_obj = ArticleSurvey.objects.create(article=article, **survey)
+ for option in options:
+ option = pop_id(option)
+ SurveyOption.objects.create(survey=survey_obj, **option)
+
+ return article
+
+ def update(self, instance, validated_data):
+ """
+ to update article and related table
+ :param instance:
+ :param validated_data:
+ :return: article object
+ """
+ article_cards = validated_data.pop('article_cards')
+ article_survey = validated_data.pop('article_survey')
+ instance.title = validated_data.get('title', instance.title)
+ instance.description = validated_data.get('description', instance.description)
+ instance.save()
+
+ # Update or create cards
+ for card_data in article_cards:
+ card_id = card_data.get('id', None)
+ if card_id:
+ card = ArticleCard.objects.get(id=card_id, article=instance)
+ card.title = card_data.get('title', card.title)
+ card.description = card_data.get('description', card.description)
+ card.image = card_data.get('image', card.image)
+ card.save()
+ else:
+ card_data = pop_id(card_data)
+ ArticleCard.objects.create(article=instance, **card_data)
+
+ # Update or create survey sections
+ for survey_data in article_survey:
+ survey_id = survey_data.get('id', None)
+ options_data = survey_data.pop('survey_options')
+ if survey_id:
+ survey = ArticleSurvey.objects.get(id=survey_id, article=instance)
+ survey.question = survey_data.get('question', survey.question)
+ survey.save()
+ else:
+ survey_data = pop_id(survey_data)
+ survey = ArticleSurvey.objects.create(article=instance, **survey_data)
+
+ # Update or create survey options
+ for option_data in options_data:
+ option_id = option_data.get('id', None)
+ if option_id:
+ option = SurveyOption.objects.get(id=option_id, survey=survey)
+ option.option = option_data.get('option', option.option)
+ option.is_answer = option_data.get('is_answer', option.is_answer)
+ option.save()
+ else:
+ option_data = pop_id(option_data)
+ SurveyOption.objects.create(survey=survey, **option_data)
+
+ return instance
diff --git a/web_admin/tests.py b/web_admin/tests.py
new file mode 100644
index 0000000..8800281
--- /dev/null
+++ b/web_admin/tests.py
@@ -0,0 +1,6 @@
+"""
+web_admin test file
+"""
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/web_admin/urls.py b/web_admin/urls.py
new file mode 100644
index 0000000..95bc7d9
--- /dev/null
+++ b/web_admin/urls.py
@@ -0,0 +1,18 @@
+"""
+web_admin urls file
+"""
+# django imports
+from django.urls import path, include
+from rest_framework import routers
+
+# local imports
+from web_admin.views import ArticleViewSet
+
+# initiate router
+router = routers.SimpleRouter()
+
+router.register('article', ArticleViewSet, basename='article')
+
+urlpatterns = [
+ path('api/v1/', include(router.urls)),
+]
diff --git a/web_admin/utils.py b/web_admin/utils.py
new file mode 100644
index 0000000..2e09e2f
--- /dev/null
+++ b/web_admin/utils.py
@@ -0,0 +1,13 @@
+"""
+web_utils file
+"""
+
+
+def pop_id(data):
+ """
+ to pop id, not in use
+ :param data:
+ :return: data
+ """
+ data.pop('id') if 'id' in data else data
+ return data
diff --git a/web_admin/views.py b/web_admin/views.py
new file mode 100644
index 0000000..8003e2c
--- /dev/null
+++ b/web_admin/views.py
@@ -0,0 +1,142 @@
+"""
+web_admin views file
+"""
+# django imports
+from rest_framework.viewsets import GenericViewSet, mixins
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.decorators import action
+from rest_framework.permissions import IsAuthenticated
+
+# local imports
+from account.utils import custom_response, custom_error_response
+from base.messages import SUCCESS_CODE, ERROR_CODE
+from web_admin.models import Article, ArticleCard, ArticleSurvey
+from web_admin.permission import AdminPermission
+from web_admin.serializers import ArticleSerializer
+
+
+class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin,
+ mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin):
+ """
+ article api
+ """
+ serializer_class = ArticleSerializer
+ permission_classes = [IsAuthenticated, AdminPermission]
+ queryset = Article.objects.prefetch_related('article_cards',
+ 'article_survey',
+ 'article_survey__survey_options').order_by('-created_at')
+
+ http_method_names = ['get', 'post', 'put', 'delete']
+
+ def create(self, request, *args, **kwargs):
+ """
+ article create api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: success message
+ """
+ serializer = self.serializer_class(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+ return custom_response(SUCCESS_CODE["3027"])
+
+ def update(self, request, *args, **kwargs):
+ """
+ article update api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: success message
+ """
+ article = self.queryset.filter(id=kwargs['pk']).first()
+ serializer = self.serializer_class(article, data=request.data)
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+ return custom_response(SUCCESS_CODE["3028"])
+
+ def list(self, request, *args, **kwargs):
+ """
+ article list api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: list of article
+ """
+ queryset = self.queryset.filter(is_deleted=False)
+ paginator = self.pagination_class()
+ paginated_queryset = paginator.paginate_queryset(queryset, request)
+ serializer = self.serializer_class(paginated_queryset, many=True)
+ return custom_response(None, data=serializer.data)
+
+ def retrieve(self, request, *args, **kwargs):
+ """
+ article detail api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: article detail data
+ """
+ queryset = self.queryset.filter(id=kwargs['pk'], is_deleted=False)
+ serializer = self.serializer_class(queryset, many=True)
+ return custom_response(None, data=serializer.data)
+
+ def destroy(self, request, *args, **kwargs):
+ """
+ article delete (soft delete) api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: success message
+ """
+ article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True)
+ if article:
+ return custom_response(SUCCESS_CODE["3029"])
+ return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST)
+
+ @action(methods=['get'], url_name='', url_path='', detail=False)
+ def search_article(self, request):
+ """
+ article search api method
+ :param request:
+ :return: searched article
+ """
+ search = request.GET.get('search')
+ queryset = self.queryset.filter(title__icontains=search)
+ paginator = self.pagination_class()
+ paginated_queryset = paginator.paginate_queryset(queryset, request)
+ serializer = self.serializer_class(paginated_queryset, many=True)
+ return custom_response(None, data=serializer.data)
+
+ @action(methods=['get'], url_name='remove_card', url_path='remove_card',
+ detail=True, serializer_class=None)
+ def remove_article_card(self, request, *args, **kwargs):
+ """
+ article card remove (delete) api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: success message
+ """
+ try:
+ ArticleCard.objects.filter(id=kwargs['pk']).first().delete()
+ return custom_response(SUCCESS_CODE["3030"])
+ except AttributeError:
+ return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST)
+
+ @action(methods=['get'], url_name='remove_survey', url_path='remove_survey',
+ detail=True, serializer_class=None)
+ def remove_article_survey(self, request, *args, **kwargs):
+ """
+ article survey remove (delete) api method
+ :param request:
+ :param args:
+ :param kwargs:
+ :return: success message
+ """
+ try:
+ ArticleSurvey.objects.filter(id=kwargs['pk']).first().delete()
+ return custom_response(SUCCESS_CODE["3031"])
+ except AttributeError:
+ return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/zod-bank-fcm-credentials.json b/zod-bank-fcm-credentials.json
new file mode 100644
index 0000000..75b13ab
--- /dev/null
+++ b/zod-bank-fcm-credentials.json
@@ -0,0 +1,13 @@
+{
+ "type": "service_account",
+ "project_id": "zod-bank-ebb2a",
+ "private_key_id": "f1115f1b1fece77ba7a119b70e2502ce0777a7d4",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCvLWEobyVN7D4+\nSZ4NwcugwuVk9MV7CjhQGB8lqzf/1Z0plBytjpjM75+orFk5n2tnOgTxsWCqR1R7\nLry4x2QH3HgJebd/TZTIyfepcAeuzUVhq9prgWVRsvxjpihMPZufA/Q1GEX5TBwX\nEasBW91Qwas2NBhUrzotnUBxOshVB4zCo3Ls9dbAN9O2O6paUMvcofSsRZ9XkB6P\nFFKy6nbQ3Bo+Lw3ntUfG1JQgkkxto2Vudiiq6J2dE6Eih2acEhEezQoJVpkMK+si\nlGp88T3j8nTx3o6ol99ke+3ZejPVE5sUbuhokSV/tS1Goy3whP+ys9lQtIyt3/mJ\nlmkoB9ShAgMBAAECggEAAk3H0GFF08C3EBWyDqH5dbLXbtH92e/UeNAMNcV4mGbY\n5GKGFywmEVmg2N22EPqa/s+Y6QxxrD9u9G1+EhbnIIx7olOBkScJ9C0c9CWztCon\ntPaofd1E1cI7I7UfVTg2ZLAHrBN4H70eadYc4va8bBtHj0EHYONz7S8sEBQ1Qna2\nIQuCEWY6MzhwCNEFIJd8ikd0GnkAJCuInK3F+2c37kugdgjRKxkTIfWmhNIfyWn3\ngoui5wltuuDKETVj9VUMyN6P7kffIJzS4mMRm/9F92scpPRbK+X1TrHpFsO826oP\nUuSBi5oOZYNyAvMBXGdnemoUNtAE+xg4kqwYJu5T0QKBgQDlbO08yH5MSLUxXZfL\nkCYg1mCUC4ySpL/qVFNuyvY+ex9gIg6gj4vEpK8s1lt0yvxrTng/MXxMMg7o9dO8\nmSlv/665twk/7NfkexJPWs1ph+54tud3Sa0TiVw6U5ObPMr1EYc7RnoELDR9Exc1\nUDJB+T3f3SGJicZn4pALyx132wKBgQDDd9lhqWLXJeG7JkInhLywuSVmnk077hY8\nxAfHqlO+/EEkM/YBtUvtC4KNiSkVftQwSM80NRiHlPvlWZ0G8l9RAfM/xxL/XUq6\nOu1fW1uJuukJgXFePV9SQLzfgd1fk1f/dKuiPSNhCzgTD7dFks0Ip6FD6/PCdN7x\neqRUqDccMwKBgQCTk1mW+7CiCTLkKjv2KScdgEhncnZd7bO1W8C/R7bVwgUQpVeb\nWDqjpvs3cDssCVYNAFDA9Wfq61hD6bzlV/AbpvARbfd5MzQ8OB4zBUmUVGfFJoIF\nbVLzeivlKNWNybETqs6+Bjt+a6Dnw1vuY0OwxE5Urb1g50rEkCvwKhsueQKBgQDB\nUt3rG46oX80cPkCbuUquNs/o6JRWu6m+u9s9/RYLBI6g8ctT8S2A6ytaNNgvbFsM\nzlYwunriTdW9Bp6p6jmfcyBUad4+NtTbz8BJ2Z91Xylwv1eS73xBa8nh/R0nlCEq\nhQfj1DgTmPcC0z5eT0z+TFzRQqK6JsEBcFzrZdvrxQKBgQDGEtWElG5XoDwnwO5e\nmfFLRKHfG+Xa6FChl2eeDIGOxv/Y6SMwT68t0gtnDFQGtcIMvC+kC/1Rv1v8yrOD\nCzEToh91Fw1c1eayNnsLq07qYnVWoMa7xFFSDhtBAnepqlU+81qaDvp+yILTIYSP\nwUuk3NpdJs2LkMetm5zJC9PJ2w==\n-----END PRIVATE KEY-----\n",
+ "client_email": "firebase-adminsdk-umcph@zod-bank-ebb2a.iam.gserviceaccount.com",
+ "client_id": "102629742363778142120",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-umcph%40zod-bank-ebb2a.iam.gserviceaccount.com",
+ "universe_domain": "googleapis.com"
+}
diff --git a/zod_bank/celery.py b/zod_bank/celery.py
new file mode 100644
index 0000000..177c318
--- /dev/null
+++ b/zod_bank/celery.py
@@ -0,0 +1,34 @@
+"""
+Celery Basic configuration
+"""
+
+# python imports
+import os
+
+# third party imports
+from celery import Celery
+from dotenv import load_dotenv
+from celery.schedules import crontab
+
+# 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)
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', os.getenv('SETTINGS'))
+
+# celery app
+app = Celery()
+
+app.config_from_object('django.conf:settings')
+
+# Load task modules from all registered Django apps.
+app.autodiscover_tasks()
+
+
+@app.task(bind=True)
+def debug_task(self):
+ """ celery debug task """
+ print(f'Request: {self.request!r}')
diff --git a/zod_bank/settings.py b/zod_bank/settings.py
index d23e0d7..5c6823c 100644
--- a/zod_bank/settings.py
+++ b/zod_bank/settings.py
@@ -9,13 +9,16 @@ 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/
"""
-
+# Django Import
import os
from dotenv import load_dotenv
from datetime import timedelta
+from firebase_admin import initialize_app
+
load_dotenv()
# OR, the same with increased verbosity:
load_dotenv(verbose=True)
+# env path
env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env')
load_dotenv(dotenv_path=env_path)
@@ -29,37 +32,48 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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'
-
+SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = os.getenv('DEBUG')
+# cors allow setting
+CORS_ORIGIN_ALLOW_ALL = True
+
+# allow all host
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
+ # Add your installed Django apps here
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ # Add Django rest frame work apps here
'django_extensions',
'storages',
'drf_yasg',
'corsheaders',
'django.contrib.postgres',
'rest_framework',
+ 'fcm_django',
+ # Add your custom apps here.
'django_ses',
'account',
'junior',
'guardian',
+ 'notifications',
+ 'web_admin',
# 'social_django'
]
-
+# define middle ware here
MIDDLEWARE = [
+ # Add your middleware classes here.
+ 'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -67,10 +81,13 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'account.custom_middleware.CustomMiddleware'
]
+# define root
ROOT_URLCONF = 'zod_bank.urls'
+# define templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@@ -87,18 +104,22 @@ TEMPLATES = [
},
]
+# define wsgi
WSGI_APPLICATION = 'zod_bank.wsgi.application'
+# define rest frame work
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
- 'PAGE_SIZE': 5,
+ 'PAGE_SIZE': 10,
}
+# define jwt token
SIMPLE_JWT = {
- 'ACCESS_TOKEN_LIFETIME': timedelta(hours=23, minutes=59, seconds=59, microseconds=999999),
- 'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
+ 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999),
+ 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999),
+
}
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
@@ -112,7 +133,7 @@ DATABASES = {
'PORT':os.getenv('DB_PORT'),
}
}
-
+# define swagger setting
SWAGGER_SETTINGS = {
"exclude_namespaces": [],
"api_version": '0.1',
@@ -139,6 +160,7 @@ SWAGGER_SETTINGS = {
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
+# password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
@@ -158,20 +180,21 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
+# language code
LANGUAGE_CODE = 'en-us'
-
+# time zone
TIME_ZONE = 'UTC'
-
+# define I18N
USE_I18N = True
-
+# define L10N
USE_L10N = True
-
+# define TZ
USE_TZ = True
# cors header settings
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
-CORS_ORIGIN_ALLOW_ALL = True
+# cors allow method
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
@@ -180,7 +203,7 @@ CORS_ALLOW_METHODS = (
'POST',
'PUT',
)
-
+# cors allow header
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
@@ -193,24 +216,48 @@ CORS_ALLOW_HEADERS = (
'x-requested-with',
)
+CORS_EXPOSE_HEADERS = (
+ 'Access-Control-Allow-Origin: *',
+)
+
+# Firebase settings
+FIREBASE_APP = initialize_app()
+
+# fcm django settings
+FCM_DJANGO_SETTINGS = {
+ "APP_VERBOSE_NAME": "ZOD_Bank",
+ "ONE_DEVICE_PER_USER": False,
+ "DELETE_INACTIVE_DEVICES": True,
+ "UPDATE_ON_DUPLICATE_REG_ID": True,
+}
+
"""Static files (CSS, JavaScript, Images)
https://docs.djangoproject.com/en/3.0/howto/static-files/"""
-
+# google client id
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
+# google client secret key
GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
+# CELERY SETUP
+CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL')
+CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND')
+CELERY_ACCEPT_CONTENT = ['application/json']
+CELERY_TASK_SERIALIZER = 'json'
+CELERY_RESULT_SERIALIZER = 'json'
-EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
-EMAIL_HOST="smtp.sendgrid.net"
-EMAIL_PORT="587"
-EMAIL_USE_TLS="True"
-EMAIL_HOST_USER="apikey"
-EMAIL_HOST_PASSWORD="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"
-EMAIL_FROM_ADDRESS="support@zodbank.com"
-DEFAULT_ADDRESS="zodbank@yopmail.com"
+# email settings
+EMAIL_BACKEND = os.getenv("MAIL_BACKEND")
+EMAIL_HOST = os.getenv("MAIL_HOST")
+EMAIL_PORT = os.getenv("MAIL_PORT")
+EMAIL_USE_TLS = os.getenv("MAIL_USE_TLS")
+EMAIL_HOST_USER = os.getenv("MAIL_HOST_USER")
+EMAIL_HOST_PASSWORD = os.getenv("MAIL_HOST_PASSWORD")
+EMAIL_FROM_ADDRESS = os.getenv("MAIL_FROM_ADDRESS")
+DEFAULT_ADDRESS = os.getenv("DEFAULT_ADDRESS")
+# ali baba cloud settings
ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID')
ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET')
@@ -218,7 +265,14 @@ ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME')
ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT')
ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION')
+MAX_ARTICLE_CARD = os.getenv('MAX_ARTICLE_CARD', 6)
+MIN_ARTICLE_SURVEY = os.getenv('MIN_ARTICLE_SURVEY', 5)
+MAX_ARTICLE_SURVEY = os.getenv('MAX_ARTICLE_SURVEY', 10)
+# define static url
STATIC_URL = 'static/'
+# define static root
STATIC_ROOT = 'static'
+MEDIA_URL = "/media/"
+MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media')
diff --git a/zod_bank/urls.py b/zod_bank/urls.py
index 274451e..a34ef93 100644
--- a/zod_bank/urls.py
+++ b/zod_bank/urls.py
@@ -30,4 +30,6 @@ urlpatterns = [
path('', include(('account.urls', 'account'), namespace='account')),
path('', include('guardian.urls')),
path('', include(('junior.urls', 'junior'), namespace='junior')),
+ path('', include(('notifications.urls', 'notifications'), namespace='notifications')),
+ path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')),
]
|