Compare commits

..

53 Commits

Author SHA1 Message Date
5ee01af825 article api changes 2023-07-21 11:35:58 +05:30
532b56c687 added article point 2023-07-20 18:01:53 +05:30
4410712b11 sign up issue resolved 2023-07-20 12:29:41 +05:30
4002d1e561 celery settings 2023-07-19 17:48:27 +05:30
3223a2b050 notification set up and celery configuration 2023-07-19 17:00:58 +05:30
2e7d36485c Merge pull request #86 from KiwiTechLLC/sprint3
jira-34 allocated points
2023-07-19 15:04:41 +05:30
86e2c75afc jira-34 allocated points 2023-07-19 15:01:18 +05:30
e238c85674 Merge pull request #85 from KiwiTechLLC/ZDBBCK-001
cors error settings
2023-07-19 15:00:12 +05:30
5429140a5c cors error settings 2023-07-19 14:58:14 +05:30
6e6ba881f6 Merge pull request #84 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-19 13:15:40 +05:30
74fc656bd1 checking email settings 2023-07-19 13:11:36 +05:30
e9b0e480e6 Merge pull request #83 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-18 19:39:27 +05:30
49fc8d9dca checking email settings 2023-07-18 19:37:30 +05:30
39271282e2 Merge pull request #82 from KiwiTechLLC/sprint3
jira-34 points earned
2023-07-18 19:27:13 +05:30
9b84d176af jira-34 points earned 2023-07-18 19:18:38 +05:30
49336e2e18 Merge pull request #81 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-18 18:30:13 +05:30
0df748ccbf checking email settings 2023-07-18 18:27:27 +05:30
9d600a1ea8 Merge pull request #80 from KiwiTechLLC/sprint3
email setting changes
2023-07-18 17:34:31 +05:30
06d841f444 email setting changes 2023-07-18 17:32:34 +05:30
e3a5d52a1d Merge pull request #79 from KiwiTechLLC/sprint3
sonar fixes
2023-07-18 16:58:20 +05:30
130dcd83e7 sonar fixes 2023-07-18 16:56:24 +05:30
19a2a3d1db Merge pull request #78 from KiwiTechLLC/ZDBBCK-001
remove fcm token
2023-07-18 16:32:51 +05:30
02f01b8a16 Merge pull request #77 from KiwiTechLLC/sprint3
Sprint3
2023-07-18 16:31:20 +05:30
3d84c03163 remove fcm token 2023-07-18 16:29:11 +05:30
5cc43aa2b8 jira-33 mark as complete the task 2023-07-18 15:59:26 +05:30
4c0cac7cb0 jira-32 list of all task of junior API, changes in Search task API 2023-07-18 15:19:50 +05:30
6aa198d1ad Merge pull request #76 from KiwiTechLLC/ZDBBCK-001
[ZDBBCK-001]:- notification app, api for device registration and fcm token
2023-07-18 15:03:18 +05:30
ba4d5933de notification app, api for device registration and fcm token 2023-07-18 14:13:30 +05:30
4d04f16cee Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint3 2023-07-18 11:02:03 +05:30
032186bf43 task list 2023-07-18 11:01:16 +05:30
04342133ef Merge pull request #75 from KiwiTechLLC/ZDBBCK-001
[ZDBBCK-001]:- web admin article api
2023-07-17 19:18:11 +05:30
751af642b8 added admin permission 2023-07-17 18:58:09 +05:30
e90326fcb6 Merge branch 'dev' into ZDBBCK-001 2023-07-17 18:50:43 +05:30
0cec3a5cf1 Merge pull request #74 from KiwiTechLLC/sprint3
Sprint3
2023-07-17 18:28:06 +05:30
ae7250b34a changes in env 2023-07-17 18:17:00 +05:30
8f1f49de45 jira-21 approve task API 2023-07-17 18:00:19 +05:30
de5d3b19d0 conflict resolved 2023-07-17 17:53:11 +05:30
2b0fc56190 Merge pull request #73 from KiwiTechLLC/sprint3
sonar issues
2023-07-17 17:13:42 +05:30
4779749b0c success msg 2023-07-17 17:05:45 +05:30
6d6d21137f sonar issues 2023-07-17 17:00:33 +05:30
cd944b10f5 Merge pull request #72 from KiwiTechLLC/sprint3
sonar issues
2023-07-17 16:34:19 +05:30
1399b585e8 sonar issues 2023-07-17 16:07:06 +05:30
2d3100286d Merge pull request #71 from KiwiTechLLC/sprint2
sonar issues fixed
2023-07-17 12:21:13 +05:30
761e723cd3 Merge pull request #70 from KiwiTechLLC/sprint2
sonar issue
2023-07-15 23:08:38 +05:30
71e7601d6b Merge pull request #69 from KiwiTechLLC/sprint2
Sprint2
2023-07-14 21:31:27 +05:30
45410fa0ca list, view, edit api for article 2023-07-14 18:47:07 +05:30
ac00543312 Merge pull request #68 from KiwiTechLLC/sprint2
Sprint2
2023-07-14 15:22:40 +05:30
a4d9997580 web_admin module added, api for article created 2023-07-13 19:51:17 +05:30
c43db90219 Merge pull request #65 from KiwiTechLLC/sprint2
jira-18 approval API
2023-07-13 19:30:32 +05:30
1ea4720ab4 Merge pull request #63 from KiwiTechLLC/sprint2
referral code
2023-07-13 17:33:12 +05:30
dae6ffbed1 Merge pull request #61 from KiwiTechLLC/sprint2
changes in image size
2023-07-13 16:41:33 +05:30
aadced6a13 Merge pull request #59 from KiwiTechLLC/sprint2
check size of the image
2023-07-13 13:56:30 +05:30
c3f7750349 Merge pull request #58 from KiwiTechLLC/sprint2
Sprint2
2023-07-13 13:06:56 +05:30
50 changed files with 1567 additions and 109 deletions

View File

@ -1,9 +1,10 @@
"""Account serializer"""
"""Django Import"""
import random
# 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,
@ -18,8 +19,8 @@ from rest_framework_simplejwt.tokens import RefreshToken
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
@ -40,7 +41,7 @@ from .utils import delete_user_account_condition_social, delete_user_account_con
# 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)
@ -90,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()
@ -112,6 +114,7 @@ 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'])
@ -137,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.auth)
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"""
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):
@ -170,9 +186,9 @@ class GuardianSerializer(serializers.ModelSerializer):
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"""
@ -209,7 +225,7 @@ class JuniorSerializer(serializers.ModelSerializer):
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
refresh = RefreshToken.for_user(obj.auth)
refresh_token = str(refresh)
return refresh_token
@ -217,7 +233,7 @@ class JuniorSerializer(serializers.ModelSerializer):
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
@ -232,7 +248,7 @@ class JuniorSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Junior
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active',
'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):
@ -251,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
@ -273,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
@ -305,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)

View File

@ -15,7 +15,7 @@
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for join the ZOD bank platform. Please use <b>{{ url }} </b> link to join the platform.
You are receiving this email for joining the ZOD bank platform. Please use <b>{{ url }} </b> link to join the platform.
<br> Your credentials are:- username = <b>{{email}}</b> and password <b>{{password}}</b><br> <br>Below are the steps to complete the account and how to use this platform.
</p>

View File

@ -10,6 +10,7 @@ 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
@ -55,6 +56,7 @@ def delete_user_account_condition(user, user_type_data, user_type, user_tb, data
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
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,
@ -74,6 +76,7 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
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,
@ -84,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 = '{}'
@ -93,6 +97,7 @@ 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()

View File

@ -1,4 +1,6 @@
"""Account view """
from notifications.utils import remove_fcm_token
"""Django import"""
from datetime import datetime, timedelta
from rest_framework import viewsets, status, views
@ -38,7 +40,7 @@ from account.utils import (send_otp_email, send_support_email, custom_response,
from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer
class GoogleLoginMixin:
class GoogleLoginMixin(object):
"""google login mixin"""
@staticmethod
def google_login(self, request):
@ -214,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]
@ -321,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):
@ -431,11 +433,11 @@ class ProfileAPIViewSet(viewsets.ModelViewSet):
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']:
@ -531,6 +533,10 @@ class LogoutAPIView(views.APIView):
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)

View File

@ -21,31 +21,10 @@ GRD = 'GRD'
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
'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
}
@ -65,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'),
@ -80,7 +55,7 @@ GENDERS = (
('1', 'Male'),
('2', 'Female')
)
"""Task status"""
# Task status"""
TASK_STATUS = (
('1', 'pending'),
('2', 'in-progress'),
@ -88,13 +63,13 @@ 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')
@ -110,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'
@ -120,3 +96,10 @@ BYTE_IMAGE_SIZE = 1024
# validate file size
MAX_FILE_SIZE = 1024 * 1024 * 5
ARTICLE_SURVEY_POINTS = 5
MAX_ARTICLE_CARD = 6
# min and max survey
MIN_ARTICLE_SURVEY = 5
MAX_ARTICLE_SURVEY = 10

View File

@ -47,13 +47,16 @@ ERROR_CODE = {
"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",
@ -62,10 +65,20 @@ ERROR_CODE = {
"2035": "Image should not be 0 kb",
"2036": "Choose valid user",
# log in multiple device msg
"2037": "You are already log in another device"
"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
@ -98,8 +111,16 @@ SUCCESS_CODE = {
"3020": "Logged out successfully.",
"3021": "Add junior successfully",
"3022": "Remove junior successfully",
"3023": "Approved junior successfully",
"3024": "Reject junior request 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"
}
"""status code error"""
STATUS_CODE_ERROR = {

View File

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

View File

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

View File

@ -8,6 +8,33 @@ from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_ME
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):
@ -81,6 +108,12 @@ class JuniorTask(models.Model):
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)

View File

@ -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_code
from account.serializers import JuniorSerializer
from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, JUN, ZOD, GRD
from junior.models import Junior, JuniorPoints
from .utils import real_time, convert_timedelta_into_datetime
# 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,10 +62,12 @@ 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':
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 == '2':
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
@ -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):
@ -140,6 +170,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 +204,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):
@ -227,6 +274,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)
@ -256,4 +304,36 @@ class ApproveJuniorSerializer(serializers.ModelSerializer):
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_task_points = junior_data.total_task_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_task_points = junior_data.total_task_points - instance.points
# update reject time of task
instance.rejected_on = real_time()
instance.save()
junior_data.save()
return instance

View File

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

View File

@ -2,7 +2,7 @@
"""Django import"""
from django.urls import path, include
from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView)
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView)
"""Third party import"""
from rest_framework import routers
@ -34,6 +34,8 @@ router.register('top-junior', TopJuniorListAPIView, basename='top-junior')
router.register('filter-task', SearchTaskListAPIView, basename='filter-task')
# 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)),

View File

@ -5,6 +5,12 @@ 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
# Define upload image on
# ali baba cloud
@ -13,6 +19,8 @@ import tempfile
# 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
def upload_image_to_alibaba(image, filename):
"""upload image on oss alibaba bucket"""
@ -30,3 +38,24 @@ 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

View File

@ -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, ApproveJuniorSerializer)
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,
@ -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,6 +201,7 @@ 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
@ -186,18 +227,57 @@ class ApproveJuniorAPIView(viewsets.ViewSet):
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)

View File

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

View File

@ -63,6 +63,8 @@ class Junior(models.Model):
is_invited = models.BooleanField(default=False)
# 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"""

View File

@ -3,7 +3,7 @@
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
@ -16,6 +16,7 @@ from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED,
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
class ListCharField(serializers.ListField):
@ -277,12 +278,13 @@ class AddJuniorSerializer(serializers.ModelSerializer):
relationship=validated_data.get('relationship'),
junior_code=generate_code(JUN, user_data.id),
referral_code=generate_code(ZOD, user_data.id),
referral_code_used=guardian_data.referral_code)
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 +307,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_task_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']

View File

@ -2,7 +2,8 @@
"""Django import"""
from django.urls import path, include
from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView,
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView)
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView,
CompleteJuniorTaskAPIView, JuniorPointsListAPIView)
"""Third party import"""
from rest_framework import routers
@ -17,7 +18,8 @@ router = routers.SimpleRouter()
# junior list,
# add junior list, invited junior,
# filter-junior,
# remove junior"""
# remove junior,
# junior task list
"""API End points with router"""
router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update')
# validate guardian code API"""
@ -30,8 +32,13 @@ router.register('add-junior', AddJuniorAPIView, basename='add-junior')
router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior')
# Filter junior list API"""
router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior')
# 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')
# 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())
]

View File

@ -1,18 +1,43 @@
"""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
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 your views here.
class UpdateJuniorProfile(viewsets.ViewSet):
"""Update junior profile"""
@ -29,6 +54,7 @@ class UpdateJuniorProfile(viewsets.ViewSet):
if 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)
request_data = {"image": image_url}
serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url,
@ -51,6 +77,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)
@ -66,8 +93,14 @@ class JuniorListAPIView(viewsets.ModelViewSet):
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()
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)
@ -80,8 +113,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)
@ -105,6 +140,7 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
queryset = self.get_queryset()
paginator = self.pagination_class()
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)
@ -128,13 +164,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]
@ -142,13 +180,95 @@ 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':
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':
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':
queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at')
elif search is None and str(status_value) != '0':
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)
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()
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)

View File

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

View File

12
notifications/admin.py Normal file
View File

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

13
notifications/apps.py Normal file
View File

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

View File

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

View File

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

View File

24
notifications/models.py Normal file
View File

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

View File

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

6
notifications/tests.py Normal file
View File

@ -0,0 +1,6 @@
"""
notification test file
"""
from django.test import TestCase
# Create your tests here.

18
notifications/urls.py Normal file
View File

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

69
notifications/utils.py Normal file
View File

@ -0,0 +1,69 @@
"""
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)
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 """
notification_data = data.pop('data', None)
user.fcmdevice_set.filter(active=True).send_message(
Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data)
)

42
notifications/views.py Normal file
View File

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

View File

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

0
web_admin/__init__.py Normal file
View File

32
web_admin/admin.py Normal file
View File

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

13
web_admin/apps.py Normal file
View File

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

View File

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

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-20 11:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='articlecard',
name='image',
field=models.URLField(blank=True, default=None, null=True),
),
]

View File

67
web_admin/models.py Normal file
View File

@ -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.URLField(null=True, blank=True, default=None)
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}'

26
web_admin/permission.py Normal file
View File

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

172
web_admin/serializers.py Normal file
View File

@ -0,0 +1,172 @@
"""
web_admin serializers file
"""
# django imports
from rest_framework import serializers
from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY
# local imports
from base.messages import ERROR_CODE
from guardian.utils import upload_image_to_alibaba
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)
image = serializers.FileField(required=False)
class Meta:
"""
meta class
"""
model = ArticleCard
fields = ('id', 'title', 'description', 'image')
def create(self, validated_data):
image = validated_data.get('image', '')
filename = f"article/{image.name}"
# upload image on ali baba
validated_data['image'] = upload_image_to_alibaba(image, filename)
article_card = ArticleCard.objects.create(article_id='1', **validated_data)
return article_card
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', '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(MAX_ARTICLE_CARD):
raise serializers.ValidationError({'details': ERROR_CODE['2039']})
if article_survey is None or len(article_survey) < int(MIN_ARTICLE_SURVEY) or int(
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, points=ARTICLE_SURVEY_POINTS, **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

6
web_admin/tests.py Normal file
View File

@ -0,0 +1,6 @@
"""
web_admin test file
"""
from django.test import TestCase
# Create your tests here.

18
web_admin/urls.py Normal file
View File

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

13
web_admin/utils.py Normal file
View File

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

149
web_admin/views.py Normal file
View File

@ -0,0 +1,149 @@
"""
web_admin views file
"""
# django imports
from rest_framework.viewsets import GenericViewSet, mixins
from rest_framework.response import Response
from rest_framework.filters import OrderingFilter, SearchFilter
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, ArticleCardSerializer
class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin,
mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin):
"""
article api
"""
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticated, AdminPermission]
queryset = Article
filter_backends = (OrderingFilter, SearchFilter,)
search_fields = ['title']
http_method_names = ['get', 'post', 'put', 'delete']
def get_queryset(self):
article = self.queryset.objects.filter(is_deleted=False).prefetch_related(
'article_cards', 'article_survey', 'article_survey__survey_options'
).order_by('-created_at')
queryset = self.filter_queryset(article)
return queryset
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.get_object()
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.get_queryset()
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.get_object()
serializer = self.serializer_class(queryset, many=False)
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.get_object()
article.is_deleted = True
article.save()
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='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)
@action(methods=['post'], url_name='test-add-card', url_path='test-add-card',
detail=False, serializer_class=ArticleCardSerializer)
def add_card(self, request):
"""
:param request:
:return:
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return custom_response(SUCCESS_CODE["3000"])

View File

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

34
zod_bank/celery.py Normal file
View File

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

View File

@ -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',
@ -70,8 +84,10 @@ MIDDLEWARE = [
'account.custom_middleware.CustomMiddleware'
]
# define root
ROOT_URLCONF = 'zod_bank.urls'
# define templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -88,15 +104,18 @@ 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=2, minutes=59, seconds=59, microseconds=999999),
'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999),
@ -113,7 +132,7 @@ DATABASES = {
'PORT':os.getenv('DB_PORT'),
}
}
# define swagger setting
SWAGGER_SETTINGS = {
"exclude_namespaces": [],
"api_version": '0.1',
@ -140,6 +159,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',
@ -159,20 +179,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',
@ -181,7 +202,7 @@ CORS_ALLOW_METHODS = (
'POST',
'PUT',
)
# cors allow header
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
@ -194,24 +215,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')
ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME')
@ -219,6 +264,10 @@ ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT')
ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION')
# 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')

View File

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