diff --git a/account/admin.py b/account/admin.py
index cf3ff23..be456ab 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, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails
+from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails, ForceUpdate
# Register your models here.
@admin.register(UserDelete)
@@ -39,6 +39,19 @@ class UserEmailOtpAdmin(admin.ModelAdmin):
"""Return object in email and otp format"""
return self.email + '-' + self.otp
+@admin.register(ForceUpdate)
+class ForceUpdateAdmin(admin.ModelAdmin):
+ """Force update"""
+ list_display = ['version', 'device_type']
+ readonly_fields = ('device_type',)
+
+ def has_add_permission(self, request):
+ count = ForceUpdate.objects.all().count()
+ if count < 2:
+ return True
+ return False
+ def has_delete_permission(self, request, obj=None):
+ return False
@admin.register(UserDeviceDetails)
class UserDeviceDetailsAdmin(admin.ModelAdmin):
"""User profile admin"""
diff --git a/account/custom_middleware.py b/account/custom_middleware.py
index 7a06e43..b3cc750 100644
--- a/account/custom_middleware.py
+++ b/account/custom_middleware.py
@@ -5,9 +5,11 @@ 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 account.models import UserDeviceDetails, ForceUpdate
from base.messages import ERROR_CODE, SUCCESS_CODE
-
+from base.constants import NUMBER
+from junior.models import Junior
+from guardian.models import Guardian
# Custom middleware
# when user login with
# multiple device simultaneously
@@ -15,6 +17,18 @@ from base.messages import ERROR_CODE, SUCCESS_CODE
# multiple devices only
# user can login in single
# device at a time"""
+# force update
+# use 308 status code for force update
+
+def custom_response(custom_error, response_status = status.HTTP_404_NOT_FOUND):
+ """custom response"""
+ response = Response(custom_error.data, status=response_status)
+ # 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
class CustomMiddleware(object):
"""Custom middleware"""
def __init__(self, get_response):
@@ -26,15 +40,33 @@ class CustomMiddleware(object):
response = self.get_response(request)
# Code to be executed after the view is called
device_id = request.META.get('HTTP_DEVICE_ID')
+ user_type = request.META.get('HTTP_USER_TYPE')
+ version = request.META.get('HTTP_VERSION')
+ device_type = str(request.META.get('HTTP_TYPE'))
+
+ api_endpoint = request.path
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)
+ # device details
+ if device_id:
+ device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
+ if not device_details and api_endpoint != '/api/v1/user/login/':
+ custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
+ response = custom_response(custom_error)
+ if user_type and str(user_type) == str(NUMBER['one']):
+ junior = Junior.objects.filter(auth=request.user, is_active=False).last()
+ if junior:
+ custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND)
+ response = custom_response(custom_error)
+ elif user_type and str(user_type) == str(NUMBER['two']):
+ guardian = Guardian.objects.filter(user=request.user, is_active=False).last()
+ if guardian:
+ custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND)
+ response = custom_response(custom_error)
+
+ if version and device_type:
+ force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last()
+ if not force_update:
+ custom_error = custom_error_response(ERROR_CODE['2079'],
+ response_status=status.HTTP_308_PERMANENT_REDIRECT)
+ response = custom_response(custom_error, status.HTTP_308_PERMANENT_REDIRECT)
return response
diff --git a/account/migrations/0010_forceupdate.py b/account/migrations/0010_forceupdate.py
new file mode 100644
index 0000000..84f5b12
--- /dev/null
+++ b/account/migrations/0010_forceupdate.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.2 on 2023-08-22 07:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0009_alter_userdevicedetails_device_id'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ForceUpdate',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('version', models.CharField(blank=True, max_length=50, null=True)),
+ ('device_type', models.CharField(blank=True, choices=[('1', 'android'), ('2', 'ios')], default=None, max_length=15, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'Force Update Version',
+ 'verbose_name_plural': 'Force Update Version',
+ 'db_table': 'force_update',
+ },
+ ),
+ ]
diff --git a/account/models.py b/account/models.py
index 784a60e..d13762b 100644
--- a/account/models.py
+++ b/account/models.py
@@ -2,8 +2,9 @@
"""Django import"""
from django.db import models
from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
"""App import"""
-from base.constants import USER_TYPE
+from base.constants import USER_TYPE, DEVICE_TYPE
# Create your models here.
class UserProfile(models.Model):
@@ -165,3 +166,25 @@ class UserDeviceDetails(models.Model):
def __str__(self):
return self.user.email
+
+
+
+
+class ForceUpdate(models.Model):
+ """
+ Force update
+ """
+ """Version ID"""
+ version = models.CharField(max_length=50, null=True, blank=True)
+ device_type = models.CharField(max_length=15, choices=DEVICE_TYPE, null=True, blank=True, default=None)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta(object):
+ """ Meta information """
+ db_table = 'force_update'
+ verbose_name = 'Force Update Version'
+ verbose_name_plural = 'Force Update Version'
+
+ def __str__(self):
+ return self.version
diff --git a/account/serializers.py b/account/serializers.py
index ebdc263..b783efd 100644
--- a/account/serializers.py
+++ b/account/serializers.py
@@ -18,7 +18,7 @@ import secrets
from guardian.models import Guardian
from junior.models import Junior
-from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp
+from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp, ForceUpdate
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
@@ -104,10 +104,12 @@ class ResetPasswordSerializer(serializers.Serializer):
return user_opt_details
return ''
+
class ChangePasswordSerializer(serializers.Serializer):
"""Update Password after verification"""
- current_password = serializers.CharField(max_length=100)
+ current_password = serializers.CharField(max_length=100, required=True)
new_password = serializers.CharField(required=True)
+
class Meta(object):
"""Meta info"""
model = User
@@ -118,25 +120,36 @@ class ChangePasswordSerializer(serializers.Serializer):
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):
+ """
+ change password
+ """
new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_password')
- """Check new password is different from 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()
- if user_details:
- user_details.set_password(new_password)
- user_details.save()
- return {'password':new_password}
- return ''
+ user_details = self.context
+ user_details.set_password(new_password)
+ user_details.save()
+ return {'password':new_password}
class ForgotPasswordSerializer(serializers.Serializer):
"""Forget password serializer"""
- email = serializers.EmailField()
+ email = serializers.EmailField(required=True)
+ def validate_email(self, value):
+ """
+ validate email exist ot not
+ value: string
+ return none
+ """
+ if not User.objects.get(email=value):
+ raise serializers.ValidationError({'details': ERROR_CODE['2004']})
+ return value
class AdminLoginSerializer(serializers.ModelSerializer):
"""admin login serializer"""
@@ -244,7 +257,7 @@ class GuardianSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Guardian
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
- 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active',
+ 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'is_deleted',
'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name']
@@ -287,14 +300,15 @@ class JuniorSerializer(serializers.ModelSerializer):
model = Junior
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']
+ 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited',
+ 'is_deleted']
class EmailVerificationSerializer(serializers.ModelSerializer):
"""Email verification serializer"""
class Meta(object):
"""Meta info"""
model = UserEmailOtp
- fields = '__all__'
+ fields = ('email',)
@@ -376,3 +390,12 @@ class UserPhoneOtpSerializer(serializers.ModelSerializer):
"""Meta info"""
model = UserPhoneOtp
fields = '__all__'
+
+class ForceUpdateSerializer(serializers.ModelSerializer):
+ """ ForceUpdate Serializer
+ """
+
+ class Meta(object):
+ """ meta info """
+ model = ForceUpdate
+ fields = ('id', 'version', 'device_type')
diff --git a/account/templates/templated_email/support_mail.email b/account/templates/templated_email/support_mail.email
index 50467a9..34d6156 100644
--- a/account/templates/templated_email/support_mail.email
+++ b/account/templates/templated_email/support_mail.email
@@ -8,14 +8,14 @@
|
- Hi {{name}},
+ Hi Support Team,
|
|
- {{name}} have some queries and need some support. Please support them by using their email address {{sender}}. Queries are:- {{ message }}
+ {{name}} have some queries and need some support. Please support them by using their email address {{sender}}. Queries are:-
{{ message }}
|
diff --git a/account/templates/templated_email/user_deactivate.email b/account/templates/templated_email/user_deactivate.email
new file mode 100644
index 0000000..90b3ee1
--- /dev/null
+++ b/account/templates/templated_email/user_deactivate.email
@@ -0,0 +1,22 @@
+{% extends "templated_email/email_base.email" %}
+
+{% block subject %}
+ Account Deactivated
+{% endblock %}
+
+{% block plain %}
+
+ |
+
+ Hi User,
+
+ |
+
+
+ |
+
+ Your account has been deactivated by admin. Please reach out to the admin for assistance.
+
+ |
+
+{% endblock %}
diff --git a/account/urls.py b/account/urls.py
index 02ac124..4944d67 100644
--- a/account/urls.py
+++ b/account/urls.py
@@ -29,7 +29,7 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer
GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet,
DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet,
UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView,
- AdminLoginViewSet)
+ AdminLoginViewSet, ForceUpdateViewSet)
"""Router"""
router = routers.SimpleRouter()
@@ -39,8 +39,6 @@ router.register('user', UserLogin, basename='user')
router.register('admin', AdminLoginViewSet, basename='admin')
"""google login end point"""
router.register('google-login', GoogleLoginViewSet, basename='admin')
-router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp')
-router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification')
"""email verification end point"""
router.register('user-email-verification', UserEmailVerification, basename='user-email-verification')
"""Resend email otp end point"""
@@ -57,6 +55,8 @@ router.register('delete', DeleteUserProfileAPIViewSet, basename='delete')
router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification')
"""update user account notification"""
router.register('update-user-notification', UpdateUserNotificationAPIViewSet, basename='update-user-notification')
+# Force update entry API
+router.register('force-update', ForceUpdateViewSet, basename='force-update')
"""Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
diff --git a/account/utils.py b/account/utils.py
index 52c016a..60a5c44 100644
--- a/account/utils.py
+++ b/account/utils.py
@@ -93,8 +93,9 @@ def junior_account_update(user_tb):
# Update junior account
junior_data.is_active = False
junior_data.is_verified = False
- junior_data.guardian_code = '{}'
- junior_data.guardian_code_status = str(NUMBER['one'])
+ junior_data.guardian_code = None
+ junior_data.guardian_code_status = None
+ junior_data.is_deleted = True
junior_data.save()
JuniorPoints.objects.filter(junior=junior_data).delete()
@@ -105,6 +106,7 @@ def guardian_account_update(user_tb):
# Update guardian account
guardian_data.is_active = False
guardian_data.is_verified = False
+ guardian_data.is_deleted = True
guardian_data.save()
jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
"""Disassociate relation between guardian and junior"""
@@ -127,6 +129,28 @@ def send_otp_email(recipient_email, otp):
)
return otp
+
+@shared_task()
+def send_all_email(template_name, email, otp):
+ """
+ Send all type of email by passing template name
+ template_name: string
+ email: string
+ otp: string
+ """
+ from_email = settings.EMAIL_FROM_ADDRESS
+ recipient_list = [email]
+ send_templated_mail(
+ template_name=template_name,
+ from_email=from_email,
+ recipient_list=recipient_list,
+ context={
+ 'verification_code': otp
+ }
+ )
+
+ return otp
+
@shared_task
def user_device_details(user, device_id):
"""
@@ -135,10 +159,12 @@ def user_device_details(user, device_id):
device_id: string
return
"""
- device_details, created = UserDeviceDetails.objects.get_or_create(user=user)
+ device_details, created = UserDeviceDetails.objects.get_or_create(user__id=user)
if device_details:
device_details.device_id = device_id
device_details.save()
+ return True
+ return False
def send_support_email(name, sender, subject, message):
@@ -178,8 +204,12 @@ def custom_error_response(detail, response_status):
if not detail:
"""when details is empty"""
detail = {}
- return Response({"error": detail, "status": "failed", "code": response_status})
-
+ if response_status == 406:
+ return Response({"error": detail, "status": "failed", "code": response_status,},
+ status=status.HTTP_308_PERMANENT_REDIRECT)
+ else:
+ return Response({"error": detail, "status": "failed", "code": response_status},
+ status=status.HTTP_400_BAD_REQUEST)
def get_user_data(attrs):
"""
@@ -251,3 +281,10 @@ def generate_code(value, user_id):
OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1)
+
+
+def get_user_full_name(user_obj):
+ """
+ to get user's full name
+ """
+ return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.first_name or user_obj.last_name else "User"
diff --git a/account/views.py b/account/views.py
index 227730e..e208163 100644
--- a/account/views.py
+++ b/account/views.py
@@ -4,6 +4,7 @@ import threading
from notifications.utils import remove_fcm_token
# django imports
+from rest_framework.viewsets import GenericViewSet, mixins
from datetime import datetime, timedelta
from rest_framework import viewsets, status, views
from rest_framework.decorators import action
@@ -26,20 +27,21 @@ from django.conf import settings
from guardian.models import Guardian
from junior.models import Junior, JuniorPoints
from guardian.utils import upload_image_to_alibaba
-from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification
+from account.models import (UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification,
+ ForceUpdate)
from django.contrib.auth.models import User
from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer,
GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer,
DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer,
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer,
- AdminLoginSerializer)
+ AdminLoginSerializer, ForceUpdateSerializer)
from rest_framework_simplejwt.tokens import RefreshToken
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, ZOD, JUN, GRD, USER_TYPE_FLAG
from guardian.tasks import generate_otp
from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response,
- generate_code, OTP_EXPIRY, user_device_details)
+ generate_code, OTP_EXPIRY, user_device_details, send_all_email)
from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer
@@ -49,7 +51,8 @@ class GoogleLoginMixin(object):
def google_login(request):
"""google login function"""
access_token = request.data.get('access_token')
- user_type = request.data.get('user_type')
+ user_type = request.META.get('HTTP_USER_TYPE')
+ device_id = request.META.get('HTTP_DEVICE_ID')
if not access_token:
return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST)
@@ -82,14 +85,29 @@ class GoogleLoginMixin(object):
if user_data.exists():
if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user_data.last()).last()
+ if not junior_query:
+ return custom_error_response(
+ ERROR_CODE["2071"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
serializer = JuniorSerializer(junior_query)
- if str(user_type) == '2':
+ elif str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user_data.last()).last()
+ if not guardian_query:
+ return custom_error_response(
+ ERROR_CODE["2070"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
serializer = GuardianSerializer(guardian_query)
+ else:
+ return custom_error_response(
+ ERROR_CODE["2069"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
- if not User.objects.filter(email__iexact=email).exists():
+ else:
user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name)
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True,
@@ -100,13 +118,23 @@ class GoogleLoginMixin(object):
serializer = JuniorSerializer(junior_query)
position = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_query, position=position)
- if str(user_type) == '2':
+ elif 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_code(GRD, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id)
)
serializer = GuardianSerializer(guardian_query)
+ else:
+ user_obj.delete()
+ return custom_error_response(
+ ERROR_CODE["2069"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
+ device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_obj)
+ if device_detail:
+ device_detail.device_id = device_id
+ device_detail.save()
# Return a JSON response with the user's email and name
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
@@ -117,16 +145,26 @@ class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet):
serializer_class = GoogleLoginSerializer
def create(self, request):
- """create method"""
+ """Payload
+ {
+ "access_token",
+ "user_type": "1"
+ }"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return self.google_login(request)
class SigninWithApple(views.APIView):
- """This API is for sign in with Apple for app."""
+ """This API is for sign in with Apple for app.
+ Payload
+ {
+ "access_token",
+ "user_type": "1"
+ }"""
def post(self, request):
token = request.data.get("access_token")
- user_type = request.data.get("user_type")
+ user_type = request.META.get('HTTP_USER_TYPE')
+ device_id = request.META.get('HTTP_DEVICE_ID')
try:
decoded_data = jwt.decode(token, options={"verify_signature": False})
user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True}
@@ -134,11 +172,26 @@ class SigninWithApple(views.APIView):
try:
user = User.objects.get(email=decoded_data.get("email"))
if str(user_type) == '1':
- junior_query = Junior.objects.filter(auth=user).last()
- serializer = JuniorSerializer(junior_query)
- if str(user_type) == '2':
- guardian_query = Guardian.objects.filter(user=user).last()
- serializer = GuardianSerializer(guardian_query)
+ junior_data = Junior.objects.filter(auth=user).last()
+ if not junior_data:
+ return custom_error_response(
+ ERROR_CODE["2071"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
+ serializer = JuniorSerializer(junior_data)
+ elif str(user_type) == '2':
+ guardian_data = Guardian.objects.filter(user=user).last()
+ if not guardian_data:
+ return custom_error_response(
+ ERROR_CODE["2070"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
+ serializer = GuardianSerializer(guardian_data)
+ else:
+ return custom_error_response(
+ ERROR_CODE["2069"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
@@ -152,12 +205,22 @@ class SigninWithApple(views.APIView):
serializer = JuniorSerializer(junior_query)
position = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_query, position=position)
- if str(user_type) == '2':
+ elif str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True,
signup_method='3',
guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id))
serializer = GuardianSerializer(guardian_query)
+ else:
+ user.delete()
+ return custom_error_response(
+ ERROR_CODE["2069"],
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
+ device_detail, created = UserDeviceDetails.objects.get_or_create(user=user)
+ if device_detail:
+ device_detail.device_id = device_id
+ device_detail.save()
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
except Exception as e:
@@ -193,18 +256,42 @@ class UpdateProfileImage(views.APIView):
return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST)
class ChangePasswordAPIView(views.APIView):
- """change password"""
+ """
+ change password"
+ """
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
+
def post(self, request):
- serializer = ChangePasswordSerializer(context=request.user, data=request.data)
+ """
+ POST request to change current login user password
+ Payload
+ { "current_password":"Demo@123",
+ "new_password":"Demo@123"
+ }
+ """
+ serializer = ChangePasswordSerializer(
+ context=request.user,
+ data=request.data
+ )
if serializer.is_valid():
serializer.save()
- return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK)
- return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+ return custom_response(
+ SUCCESS_CODE['3007'],
+ response_status=status.HTTP_200_OK
+ )
+ return custom_error_response(
+ serializer.errors,
+ response_status=status.HTTP_400_BAD_REQUEST
+ )
class ResetPasswordAPIView(views.APIView):
- """Reset password"""
+ """Reset password
+ Payload
+ {
+ "verification_code":"373770",
+ "password":"Demo@1323"
+ }"""
def post(self, request):
serializer = ResetPasswordSerializer(data=request.data)
if serializer.is_valid():
@@ -213,40 +300,43 @@ class ResetPasswordAPIView(views.APIView):
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ForgotPasswordAPIView(views.APIView):
- """Forgot password"""
+ """
+ Forgot password
+ """
+ serializer_class = ForgotPasswordSerializer
def post(self, request):
- serializer = ForgotPasswordSerializer(data=request.data)
- if serializer.is_valid():
- email = serializer.validated_data['email']
- try:
- User.objects.get(email=email)
- except User.DoesNotExist:
- return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND)
- verification_code = generate_otp()
- # Send the verification code to the user's email
- from_email = settings.EMAIL_FROM_ADDRESS
- recipient_list = [email]
- send_templated_mail(
- template_name='email_reset_verification.email',
- from_email=from_email,
- recipient_list=recipient_list,
- context={
- 'verification_code': verification_code
- }
- )
- expiry = OTP_EXPIRY
- user_data, created = UserEmailOtp.objects.get_or_create(email=email)
- if created:
- user_data.expired_at = expiry
- user_data.save()
- if user_data:
- user_data.otp = verification_code
- user_data.expired_at = expiry
- user_data.save()
- return custom_response(SUCCESS_CODE['3015'],
- response_status=status.HTTP_200_OK)
- return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+ """
+ Payload
+ {
+ "email": "abc@yopmail.com"
+ }
+ """
+ serializer = self.serializer_class(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ email = serializer.validated_data['email']
+ # generate otp
+ verification_code = generate_otp()
+ # Send the verification code to the user's email
+ send_all_email.delay(
+ 'email_reset_verification.email', email, verification_code
+ )
+ expiry = OTP_EXPIRY
+ user_data, created = UserEmailOtp.objects.get_or_create(
+ email=email
+ )
+ if created:
+ user_data.expired_at = expiry
+ user_data.save()
+ if user_data:
+ user_data.otp = verification_code
+ user_data.expired_at = expiry
+ user_data.save()
+ return custom_response(
+ SUCCESS_CODE['3015'],
+ response_status=status.HTTP_200_OK
+ )
class SendPhoneOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
@@ -287,7 +377,7 @@ class UserLogin(viewsets.ViewSet):
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
- user_type = request.data.get('user_type')
+ user_type = request.META.get('HTTP_USER_TYPE')
device_id = request.META.get('HTTP_DEVICE_ID')
user = authenticate(request, username=username, password=password)
@@ -295,22 +385,24 @@ class UserLogin(viewsets.ViewSet):
if user is not None:
login(request, user)
if str(user_type) == USER_TYPE_FLAG["TWO"]:
- guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last()
+ guardian_data = Guardian.objects.filter(user__username=username).last()
if guardian_data:
- serializer = GuardianSerializer(
- guardian_data, context={'user_type': user_type}
- ).data
+ if guardian_data.is_verified:
+ serializer = GuardianSerializer(
+ guardian_data, context={'user_type': user_type}
+ ).data
else:
return custom_error_response(
ERROR_CODE["2070"],
response_status=status.HTTP_401_UNAUTHORIZED
)
elif str(user_type) == USER_TYPE_FLAG["FIRST"]:
- junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last()
+ junior_data = Junior.objects.filter(auth__username=username).last()
if junior_data:
- serializer = JuniorSerializer(
- junior_data, context={'user_type': user_type}
- ).data
+ if junior_data.is_verified:
+ serializer = JuniorSerializer(
+ junior_data, context={'user_type': user_type}
+ ).data
else:
return custom_error_response(
ERROR_CODE["2071"],
@@ -321,8 +413,12 @@ class UserLogin(viewsets.ViewSet):
ERROR_CODE["2069"],
response_status=status.HTTP_401_UNAUTHORIZED
)
- # storing device id in using thread so the time would be reduced
- threading.Thread(target=user_device_details, args=(user, device_id))
+ # storing device id in using celery task so the time would be reduced
+ # user_device_details.delay(user.id, device_id)
+ 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)
@@ -399,14 +495,20 @@ class AdminLoginViewSet(viewsets.GenericViewSet):
class UserEmailVerification(viewsets.ModelViewSet):
- """User Email verification"""
+ """User Email verification
+ Payload
+ {
+ "email":"ramu@yopmail.com",
+ "otp":"361123"
+ }"""
serializer_class = EmailVerificationSerializer
+ http_method_names = ('post',)
- def list(self, request, *args, **kwargs):
+ def create(self, request, *args, **kwargs):
try:
- user_obj = User.objects.filter(username=self.request.GET.get('email')).last()
- email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'),
- otp=self.request.GET.get('otp')).last()
+ user_obj = User.objects.filter(username=self.request.data.get('email')).last()
+ email_data = UserEmailOtp.objects.filter(email=self.request.data.get('email'),
+ otp=self.request.data.get('otp')).last()
if email_data:
input_datetime_str = str(email_data.expired_at)
input_format = "%Y-%m-%d %H:%M:%S.%f%z"
@@ -420,12 +522,12 @@ class UserEmailVerification(viewsets.ModelViewSet):
email_data.is_verified = True
email_data.save()
if email_data.user_type == '1':
- junior_data = Junior.objects.filter(auth__email=self.request.GET.get('email')).last()
+ junior_data = Junior.objects.filter(auth__email=self.request.data.get('email')).last()
if junior_data:
junior_data.is_verified = True
junior_data.save()
else:
- guardian_data = Guardian.objects.filter(user__email=self.request.GET.get('email')).last()
+ guardian_data = Guardian.objects.filter(user__email=self.request.data.get('email')).last()
if guardian_data:
guardian_data.is_verified = True
guardian_data.save()
@@ -444,12 +546,14 @@ class UserEmailVerification(viewsets.ModelViewSet):
class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
serializer_class = EmailVerificationSerializer
- permission_classes = [IsAuthenticated]
-
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
+ """Param
+ {"email":"ashok@yopmail.com"}
+ """
otp = generate_otp()
if User.objects.filter(email=request.data['email']):
- expiry = OTP_EXPIRY
+ expiry = timezone.now() + timezone.timedelta(days=1)
email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email'])
if created:
email_data.expired_at = expiry
@@ -458,7 +562,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet):
email_data.otp = otp
email_data.expired_at = expiry
email_data.save()
- send_otp_email(request.data['email'], otp)
+ send_otp_email.delay(request.data['email'], otp)
return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST)
@@ -467,23 +571,28 @@ class ProfileAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
serializer_class = JuniorProfileSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('get',)
def list(self, request, *args, **kwargs):
- """profile view"""
- if str(self.request.GET.get('user_type')) == '1':
+ """profile view
+ Params
+ user_type"""
+ user_type = request.META.get('HTTP_USER_TYPE')
+ if str(user_type) == '1':
junior_data = Junior.objects.filter(auth=self.request.user).last()
if junior_data:
serializer = JuniorProfileSerializer(junior_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
- elif str(self.request.GET.get('user_type')) == '2':
+ elif str(user_type) == '2':
guardian_data = Guardian.objects.filter(user=self.request.user).last()
if guardian_data:
serializer = GuardianProfileSerializer(guardian_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
-
+ return custom_error_response(None, response_status=status.HTTP_400_BAD_REQUEST)
class UploadImageAPIViewSet(viewsets.ModelViewSet):
"""upload task image"""
serializer_class = DefaultTaskImagesSerializer
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""upload images"""
image_data = request.data['image_url']
@@ -503,6 +612,7 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
serializer_class = DefaultTaskImagesDetailsSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = DefaultTaskImages.objects.all()
@@ -511,7 +621,13 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet):
class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet):
- """ Delete user API view set """
+ """ Delete user API view set
+ {"user_type":1,
+ "signup_method":"1",
+ "password":"Demo@123"}
+ signup_method 1 for manual
+ 2 for google login
+ 3 for apple login"""
@action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer,
permission_classes=[IsAuthenticated])
@@ -533,8 +649,9 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet):
"""notification viewset"""
serializer_class = UserNotificationSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('get',)
def list(self, request, *args, **kwargs):
- """profile view"""
+ """notification view"""
queryset = UserNotification.objects.filter(user=request.user)
serializer = UserNotificationSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
@@ -544,9 +661,14 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
"""Update notification viewset"""
serializer_class = UpdateUserNotificationSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """profile view"""
+ """Payload
+ {"email_notification": false,
+ "sms_notification": false,
+ "push_notification": false}
+ """
serializer = UpdateUserNotificationSerializer(data=request.data,
context=request.user)
if serializer.is_valid():
@@ -556,7 +678,12 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
class SendSupportEmail(views.APIView):
- """support email api"""
+ """support email api
+ payload
+ name
+ email
+ message
+ """
permission_classes = (IsAuthenticated,)
def post(self, request):
@@ -600,3 +727,27 @@ class AccessTokenAPIView(views.APIView):
data = {"auth_token": access_token}
return custom_response(None, data, response_status=status.HTTP_200_OK)
+
+class ForceUpdateViewSet(GenericViewSet, mixins.CreateModelMixin):
+ """FAQ view set"""
+
+ serializer_class = ForceUpdateSerializer
+ http_method_names = ['post']
+
+
+ def create(self, request, *args, **kwargs):
+ """
+ faq create api method
+ :param request:
+ :param args: version, device type
+ :param kwargs:
+ :return: success message
+ """
+ if ForceUpdate.objects.all().count() >= 2:
+ return custom_error_response(ERROR_CODE['2080'], response_status=status.HTTP_400_BAD_REQUEST)
+ obj_data = [ForceUpdate(**item) for item in request.data]
+ try:
+ ForceUpdate.objects.bulk_create(obj_data)
+ return custom_response(SUCCESS_CODE["3046"], response_status=status.HTTP_200_OK)
+ except Exception as e:
+ return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/base/constants.py b/base/constants.py
index 9688c0f..da203ae 100644
--- a/base/constants.py
+++ b/base/constants.py
@@ -27,7 +27,7 @@ NUMBER = {
'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600
}
-
+none = "none"
# Super Admin string constant for 'role'
SUPER_ADMIN = "Super Admin"
@@ -43,14 +43,18 @@ FILE_SIZE = 5 * 1024 * 1024
# String constant for configurable date for allocation lock period
ALLOCATION_LOCK_DATE = 1
-
+# guardian code status tuple
+guardian_code_tuple = ('1','3')
"""user type"""
USER_TYPE = (
('1', 'junior'),
('2', 'guardian'),
('3', 'superuser')
)
-
+DEVICE_TYPE = (
+ ('1', 'android'),
+ ('2', 'ios')
+)
USER_TYPE_FLAG = {
"FIRST" : "1",
"TWO" : "2",
diff --git a/base/messages.py b/base/messages.py
index 10fbfaa..54c427f 100644
--- a/base/messages.py
+++ b/base/messages.py
@@ -96,8 +96,22 @@ ERROR_CODE = {
"2067": "Action not allowed. User type missing.",
"2068": "No guardian associated with this junior",
"2069": "Invalid user type",
- "2070": "You did not find as a guardian",
- "2071": "You did not find as a junior"
+ "2070": "You do not find as a guardian",
+ "2071": "You do not find as a junior",
+ "2072": "You can not approve or reject this task because junior does not exist in the system",
+ "2073": "You can not approve or reject this junior because junior does not exist in the system",
+ "2074": "You can not complete this task because you does not exist in the system",
+ # deactivate account
+ "2075": "Your account is deactivated. Please contact with admin",
+ "2076": "This junior already associate with you",
+ "2077": "You can not add guardian",
+ "2078": "This junior is not associate with you",
+ # force update
+ "2079": "Please update your app version for enjoying uninterrupted services",
+ "2080": "Can not add App version",
+ "2081": "A junior can only be associated with a maximum of 3 guardian",
+ # guardian code not exist
+ "2082": "Guardian code does not exist"
}
"""Success message code"""
@@ -163,6 +177,9 @@ SUCCESS_CODE = {
"3043": "Read article card successfully",
# remove guardian code request
"3044": "Remove guardian code request successfully",
+ # create faq
+ "3045": "Create FAQ data",
+ "3046": "Add App version successfully"
}
"""status code error"""
diff --git a/base/tasks.py b/base/tasks.py
index 791be81..fbeca1d 100644
--- a/base/tasks.py
+++ b/base/tasks.py
@@ -1,29 +1,88 @@
"""
web_admin tasks file
"""
+import datetime
+
# third party imports
from celery import shared_task
from templated_email import send_templated_mail
# django imports
from django.conf import settings
+from django.db.models import F, Window
+from django.db.models.functions.window import Rank
+
+# local imports
+from base.constants import PENDING, IN_PROGRESS, JUNIOR
+from guardian.models import JuniorTask
+from junior.models import JuniorPoints
+from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING, NOTIFICATION_DICT, TOP_JUNIOR
+from notifications.models import Notification
+from notifications.utils import send_notification, get_from_user_details, send_notification_multiple_user
@shared_task
-def send_email_otp(email, verification_code):
+def send_email(recipient_list, template, context: dict = None):
"""
used to send otp on email
- :param email: e-mail
- :param verification_code: otp
+ :param context:
+ :param recipient_list: e-mail list
+ :param template: email template
"""
+ if context is None:
+ context = {}
from_email = settings.EMAIL_FROM_ADDRESS
- recipient_list = [email]
send_templated_mail(
- template_name='email_reset_verification.email',
+ template_name=template,
from_email=from_email,
recipient_list=recipient_list,
- context={
- 'verification_code': verification_code
- }
+ context=context
)
return True
+
+
+@shared_task()
+def notify_task_expiry():
+ """
+ task to send notification for those task which expiring soon
+ :return:
+ """
+ all_pending_tasks = JuniorTask.objects.filter(
+ task_status__in=[PENDING, IN_PROGRESS],
+ due_date__range=[datetime.datetime.now().date(),
+ (datetime.datetime.now().date() + datetime.timedelta(days=1))])
+ if pending_tasks := all_pending_tasks.filter(task_status=PENDING):
+ for task in pending_tasks:
+ send_notification(PENDING_TASK_EXPIRING, None, None, task.junior.auth.id,
+ {'task_id': task.id})
+ if in_progress_tasks := all_pending_tasks.filter(task_status=IN_PROGRESS):
+ for task in in_progress_tasks:
+ send_notification(IN_PROGRESS_TASK_EXPIRING, task.junior.auth.id, JUNIOR, task.guardian.user.id,
+ {'task_id': task.id})
+ return True
+
+
+@shared_task()
+def notify_top_junior():
+ """
+ task to send notification for top leaderboard junior to all junior's
+ :return:
+ """
+ junior_points_qs = JuniorPoints.objects.filter(
+ junior__is_verified=True
+ ).select_related(
+ 'junior', 'junior__auth'
+ ).annotate(rank=Window(
+ expression=Rank(),
+ order_by=[F('total_points').desc(), 'junior__created_at'])
+ ).order_by('-total_points', 'junior__created_at')
+
+ prev_top_position = junior_points_qs.filter(position=1).first()
+ new_top_position = junior_points_qs.filter(rank=1).first()
+ if prev_top_position != new_top_position:
+ send_notification_multiple_user(TOP_JUNIOR, new_top_position.junior.auth.id, JUNIOR,
+ {'points': new_top_position.total_points})
+ for junior_point in junior_points_qs:
+ junior_point.position = junior_point.rank
+ junior_point.save()
+ return True
diff --git a/celerybeat-schedule b/celerybeat-schedule
index f2510fc..f457bb5 100644
Binary files a/celerybeat-schedule and b/celerybeat-schedule differ
diff --git a/guardian/migrations/0021_guardian_is_deleted.py b/guardian/migrations/0021_guardian_is_deleted.py
new file mode 100644
index 0000000..11833c6
--- /dev/null
+++ b/guardian/migrations/0021_guardian_is_deleted.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-08-17 12:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guardian', '0020_alter_juniortask_task_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='guardian',
+ name='is_deleted',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/guardian/models.py b/guardian/models.py
index 45afee7..5c6457a 100644
--- a/guardian/models.py
+++ b/guardian/models.py
@@ -57,6 +57,8 @@ class Guardian(models.Model):
is_invited = models.BooleanField(default=False)
# Profile activity"""
is_password_set = models.BooleanField(default=True)
+ # guardian profile deleted or not"""
+ is_deleted = models.BooleanField(default=False)
"""Profile activity"""
is_active = models.BooleanField(default=True)
"""guardian is verified or not"""
diff --git a/guardian/serializers.py b/guardian/serializers.py
index 1dabc8d..4206d7a 100644
--- a/guardian/serializers.py
+++ b/guardian/serializers.py
@@ -1,6 +1,7 @@
"""Serializer of Guardian"""
# third party imports
import logging
+from django.contrib.auth import password_validation
from rest_framework import serializers
# Import Refresh token of jwt
from rest_framework_simplejwt.tokens import RefreshToken
@@ -23,17 +24,19 @@ from account.models import UserProfile, UserEmailOtp, UserNotification
from account.utils import generate_code
from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user
+from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIAN
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from .utils import real_time, convert_timedelta_into_datetime, update_referral_points
# notification's constant
-from notifications.constants import TASK_POINTS, TASK_REJECTED
+from notifications.constants import TASK_APPROVED, TASK_REJECTED
# send notification function
from notifications.utils import send_notification
-
+from django.core.exceptions import ValidationError
+from django.utils.translation import gettext as _
# In this serializer file
# define user serializer,
+# define password validation
# create guardian serializer,
# task serializer,
# guardian serializer,
@@ -42,10 +45,51 @@ from notifications.utils import send_notification
# guardian profile serializer,
# approve junior serializer,
# approve task serializer,
+from rest_framework import serializers
+
+class PasswordValidator:
+ """Password validation"""
+ def __init__(self, min_length=8, max_length=None, require_uppercase=True, require_numbers=True):
+ self.min_length = min_length
+ self.max_length = max_length
+ self.require_uppercase = require_uppercase
+ self.require_numbers = require_numbers
+
+ def __call__(self, value):
+ self.enforce_password_policy(value)
+
+ def enforce_password_policy(self, password):
+ # add validation for password
+ special_characters = "!@#$%^&*()_-+=<>?/[]{}|"
+ if len(password) < self.min_length:
+ raise serializers.ValidationError(
+ _("Password must be at least %(min_length)d characters long.") % {'min_length': self.min_length}
+ )
+
+ if self.max_length is not None and len(password) > self.max_length:
+ # must be 8 character
+ raise serializers.ValidationError(
+ _("Password must be at most %(max_length)d characters long.") % {'max_length': self.max_length}
+ )
+
+ if self.require_uppercase and not any(char.isupper() for char in password):
+ # must contain upper case letter
+ raise serializers.ValidationError(_("Password must contain at least one uppercase letter."))
+
+ if self.require_numbers and not any(char.isdigit() for char in password):
+ # must contain digit
+ raise serializers.ValidationError(_("Password must contain at least one digit."))
+ if self.require_numbers and not any(char in special_characters for char in password):
+ # must contain special character
+ raise serializers.ValidationError(_("Password must contain at least one special character."))
+
+
+
class UserSerializer(serializers.ModelSerializer):
"""User serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
+ password = serializers.CharField(write_only=True, validators=[PasswordValidator()])
class Meta(object):
"""Meta info"""
@@ -214,7 +258,7 @@ class GuardianDetailSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Guardian
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
- 'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image',
+ 'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'is_deleted',
'updated_at']
class TaskDetailsSerializer(serializers.ModelSerializer):
"""Task detail serializer"""
@@ -279,10 +323,11 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer):
'requested_on', 'rejected_on', 'completed_on',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
+
class TopJuniorSerializer(serializers.ModelSerializer):
"""Top junior serializer"""
junior = JuniorDetailSerializer()
- position = serializers.IntegerField()
+ position = serializers.SerializerMethodField()
class Meta(object):
"""Meta info"""
@@ -292,9 +337,13 @@ class TopJuniorSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
"""Convert instance to representation"""
representation = super().to_representation(instance)
- representation['position'] = instance.position
return representation
+ @staticmethod
+ def get_position(obj):
+ """ get position/rank """
+ return obj.rank
+
class GuardianProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
@@ -340,7 +389,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name','country_code', 'phone', 'gender', 'dob',
'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code',
'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method',
- 'updated_at', 'passcode']
+ 'updated_at', 'passcode','is_deleted']
class ApproveJuniorSerializer(serializers.ModelSerializer):
@@ -353,9 +402,9 @@ class ApproveJuniorSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""update guardian code"""
instance = self.context['junior']
- instance.guardian_code = [self.context['guardian_code']]
- instance.guardian_code_approved = True
- instance.guardian_code_status = str(NUMBER['two'])
+ guardian_code = self.context['guardian_code']
+ index = instance.guardian_code.index(guardian_code)
+ instance.guardian_code_status[index] = str(NUMBER['two'])
instance.save()
return instance
@@ -383,7 +432,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer):
# update complete time of task
# instance.completed_on = real_time()
instance.completed_on = timezone.now().astimezone(pytz.utc)
- send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {})
+ send_notification.delay(TASK_APPROVED, instance.guardian.user.id, GUARDIAN,
+ junior_details.auth.id, {'task_id': instance.id})
else:
# reject the task
instance.task_status = str(NUMBER['three'])
@@ -391,7 +441,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer):
# update reject time of task
# instance.rejected_on = real_time()
instance.rejected_on = timezone.now().astimezone(pytz.utc)
- send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {})
+ send_notification.delay(TASK_REJECTED, instance.guardian.user.id, GUARDIAN,
+ junior_details.auth.id, {'task_id': instance.id})
instance.save()
junior_data.save()
return junior_details
@@ -461,4 +512,7 @@ class GuardianDetailListSerializer(serializers.ModelSerializer):
def get_guardian_code_status(self,obj):
"""guardian code status"""
- return obj.junior.guardian_code_status
+ if obj.guardian.guardian_code in obj.junior.guardian_code:
+ index = obj.junior.guardian_code.index(obj.guardian.guardian_code)
+ data = obj.junior.guardian_code_status[index]
+ return data
diff --git a/guardian/tasks.py b/guardian/tasks.py
index 9cf39b3..f40d232 100644
--- a/guardian/tasks.py
+++ b/guardian/tasks.py
@@ -1,7 +1,12 @@
"""task files"""
-"""Django import"""
+
+# Django import
import secrets
+
+
def generate_otp():
- """generate random otp"""
+ """
+ generate random otp
+ """
digits = "0123456789"
return "".join(secrets.choice(digits) for _ in range(6))
diff --git a/guardian/urls.py b/guardian/urls.py
index e95ea8e..4a1d006 100644
--- a/guardian/urls.py
+++ b/guardian/urls.py
@@ -1,7 +1,7 @@
""" Urls files"""
"""Django import"""
from django.urls import path, include
-from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView,
+from .views import (SignupViewset, UpdateGuardianProfile, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView,
GuardianListAPIView)
"""Third party import"""
@@ -25,8 +25,6 @@ router.register('sign-up', SignupViewset, basename='sign-up')
router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
# Create Task API"""
router.register('create-task', CreateTaskAPIView, basename='create-task')
-# All Task list API"""
-router.register('all-task-list', AllTaskListAPIView, basename='all-task-list')
# Task list bases on the status API"""
router.register('task-list', TaskListAPIView, basename='task-list')
# Leaderboard API"""
diff --git a/guardian/utils.py b/guardian/utils.py
index 57e8080..1d40c34 100644
--- a/guardian/utils.py
+++ b/guardian/utils.py
@@ -22,6 +22,8 @@ from zod_bank.celery import app
from notifications.constants import REFERRAL_POINTS
# send notification function
from notifications.utils import send_notification
+
+
# Define upload image on
# ali baba cloud
# firstly save image
@@ -41,18 +43,41 @@ def upload_image_to_alibaba(image, filename):
# Save the image object to a temporary file
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
"""write image in temporary file"""
- if type(image) == bytes:
- temp_file.write(image)
- else:
- temp_file.write(image.read())
- """auth of bucket"""
- auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET)
- """fetch bucket details"""
- bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME)
- # Upload the temporary file to Alibaba OSS
- bucket.put_object_from_file(filename, temp_file.name)
- """create perfect url for image"""
- new_filename = filename.replace(' ', '%20')
+ temp_file.write(image.read())
+ return upload_file_to_alibaba(temp_file, filename)
+
+
+def upload_base64_image_to_alibaba(image, filename):
+ """
+ upload image on oss alibaba bucket
+ """
+ # Save the image object to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False) as temp_file:
+ # write image in temporary file
+ temp_file.write(image)
+ return upload_file_to_alibaba(temp_file, filename)
+
+
+def upload_excel_file_to_alibaba(response, filename):
+ """
+ upload excel file on oss alibaba bucket
+ """
+ # Save the image object to a temporary file
+ with tempfile.NamedTemporaryFile(delete=False) as temp_file:
+ # write image in temporary file
+ temp_file.write(response.content)
+ return upload_file_to_alibaba(temp_file, filename)
+
+
+def upload_file_to_alibaba(temp_file, filename):
+ """auth of bucket"""
+ auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET)
+ """fetch bucket details"""
+ bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME)
+ # Upload the temporary file to Alibaba OSS
+ bucket.put_object_from_file(filename, temp_file.name)
+ """create perfect url for image"""
+ new_filename = filename.replace(' ', '%20')
return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}"
@@ -92,7 +117,7 @@ def update_referral_points(referral_code, referral_code_used):
junior_query.total_points = junior_query.total_points + NUMBER['five']
junior_query.referral_points = junior_query.referral_points + NUMBER['five']
junior_query.save()
- send_notification.delay(REFERRAL_POINTS, None, junior_queryset.auth.id, {})
+ send_notification.delay(REFERRAL_POINTS, None, None, junior_queryset.auth.id, {})
@@ -102,7 +127,7 @@ def update_expired_task_status(data=None):
Update task of the status if due date is in past
"""
try:
- task_status = [str(NUMBER['one']), str(NUMBER['two']), str(NUMBER['four'])]
+ task_status = [str(NUMBER['one']), str(NUMBER['two'])]
JuniorTask.objects.filter(due_date__lt=datetime.today().date(),
task_status__in=task_status).update(task_status=str(NUMBER['six']))
except ObjectDoesNotExist as e:
diff --git a/guardian/views.py b/guardian/views.py
index 3948e6f..e120681 100644
--- a/guardian/views.py
+++ b/guardian/views.py
@@ -6,11 +6,13 @@
# Import PageNumberPagination
# Import User
# Import timezone
+from django.db.models import F, Window
+from django.db.models.functions.window import Rank
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 base.constants import guardian_code_tuple
from rest_framework.filters import SearchFilter
from django.utils import timezone
@@ -32,13 +34,13 @@ from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializ
GuardianDetailListSerializer)
from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
-from account.models import UserEmailOtp, UserNotification
+from account.models import UserEmailOtp, UserNotification, UserDeviceDetails
from .tasks import generate_otp
from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import NUMBER, GUARDIAN_CODE_STATUS
+from base.constants import NUMBER, GUARDIAN_CODE_STATUS, GUARDIAN
from .utils import upload_image_to_alibaba
-from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING
+from notifications.constants import REGISTRATION, TASK_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED
from notifications.utils import send_notification
""" Define APIs """
@@ -57,8 +59,10 @@ class SignupViewset(viewsets.ModelViewSet):
"""Signup view set"""
queryset = User.objects.all()
serializer_class = UserSerializer
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Create user profile"""
+ device_id = request.META.get('HTTP_DEVICE_ID')
if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]:
serializer = UserSerializer(context=request.data['user_type'], data=request.data)
if serializer.is_valid():
@@ -66,24 +70,25 @@ class SignupViewset(viewsets.ModelViewSet):
"""Generate otp"""
otp = generate_otp()
# expire otp after 1 day
- expiry = OTP_EXPIRY
+ 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)
- # send push notification for registration
- send_notification.delay(REGISTRATION, None, user.id, {})
+ send_otp_email.delay(request.data['email'], otp)
+ UserDeviceDetails.objects.create(user=user, device_id=device_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:
return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST)
-class UpdateGuardianProfile(viewsets.ViewSet):
+
+class UpdateGuardianProfile(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = CreateGuardianSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Create guardian profile"""
@@ -126,7 +131,11 @@ class AllTaskListAPIView(viewsets.ModelViewSet):
class TaskListAPIView(viewsets.ModelViewSet):
- """Update guardian profile"""
+ """Task list
+ Params
+ status
+ search
+ page"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
@@ -163,42 +172,56 @@ class CreateTaskAPIView(viewsets.ModelViewSet):
http_method_names = ('post', )
def create(self, request, *args, **kwargs):
+ """
+ image should be in form data
+ """
try:
image = request.data['default_image']
junior = request.data['junior']
- allowed_extensions = ['.jpg', '.jpeg', '.png']
- if not any(extension in str(image) for extension in allowed_extensions):
- return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST)
- if not junior.isnumeric():
- """junior value must be integer"""
- return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
- data = request.data
- if 'https' in str(image):
- image_data = image
+ junior_id = Junior.objects.filter(id=junior).last()
+ if junior_id:
+ guardian_data = Guardian.objects.filter(user=request.user).last()
+ index = junior_id.guardian_code.index(guardian_data.guardian_code)
+ status_index = junior_id.guardian_code_status[index]
+ if status_index == str(NUMBER['three']):
+ return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST)
+ allowed_extensions = ['.jpg', '.jpeg', '.png']
+ if not any(extension in str(image) for extension in allowed_extensions):
+ return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST)
+ if not junior.isnumeric():
+ """junior value must be integer"""
+ return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
+ data = request.data
+ if 'https' in str(image):
+ image_data = image
+ else:
+ filename = f"images/{image}"
+ if image and image.size == NUMBER['zero']:
+ return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
+ 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
+ task = serializer.save()
+
+ send_notification.delay(TASK_ASSIGNED, request.auth.payload['user_id'], GUARDIAN,
+ junior_id.auth.id, {'task_id': task.id})
+ 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)
else:
- filename = f"images/{image}"
- if image and image.size == NUMBER['zero']:
- return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
- 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()
- junior_id = Junior.objects.filter(id=junior).last()
- send_notification.delay(TASK_CREATED, None, junior_id.auth.id, {})
- 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)
+ return custom_error_response(ERROR_CODE['2047'], 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 SearchTaskListAPIView(viewsets.ModelViewSet):
- """Update guardian profile"""
+ """Filter task"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
+ http_method_names = ('get',)
def get_queryset(self):
"""Get the queryset for the view"""
@@ -209,7 +232,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
return junior_queryset
def list(self, request, *args, **kwargs):
- """Create guardian profile"""
+ """Filter task"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
@@ -223,10 +246,11 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
class TopJuniorListAPIView(viewsets.ModelViewSet):
- """Top juniors list"""
- queryset = JuniorPoints.objects.all()
+ """Top juniors list
+ No Params"""
serializer_class = TopJuniorSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('get',)
def get_serializer_context(self):
# context list
@@ -234,97 +258,116 @@ class TopJuniorListAPIView(viewsets.ModelViewSet):
context.update({'view': self})
return context
+ def get_queryset(self):
+ queryset = JuniorPoints.objects.filter(
+ junior__is_verified=True
+ ).select_related(
+ 'junior', 'junior__auth'
+ ).annotate(rank=Window(
+ expression=Rank(),
+ order_by=[F('total_points').desc(), 'junior__created_at'])
+ ).order_by('-total_points', 'junior__created_at')
+ return queryset
+
def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks"""
try:
- 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):
- junior.position = index + 1
- send_notification.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {})
- junior.save()
- serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], many=True)
+ junior_total_points = self.get_queryset()[:15]
+ serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
-class ApproveJuniorAPIView(viewsets.ViewSet):
+class ApproveJuniorAPIView(viewsets.ModelViewSet):
"""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
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ Use below param
+ {"junior_id":"75",
+ "action":"1"}
+ """
try:
- queryset = self.get_queryset()
+ 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()
+ if junior_queryset and (junior_queryset.is_deleted or not junior_queryset.is_active):
+ return custom_error_response(ERROR_CODE['2073'], response_status=status.HTTP_400_BAD_REQUEST)
# 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']},
+ serializer = ApproveJuniorSerializer(context={"guardian_code": guardian.guardian_code,
+ "junior": junior_queryset,
+ "action": request.data['action']},
data=request.data)
if serializer.is_valid():
# save serializer
serializer.save()
+ send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN,
+ junior_queryset.auth.id, {})
return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else:
- queryset[1].guardian_code = None
- queryset[1].guardian_code_status = str(NUMBER['one'])
- queryset[1].save()
+ if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code):
+ junior_queryset.guardian_code.remove('-')
+ if junior_queryset.guardian_code_status and ('-' in junior_queryset.guardian_code_status):
+ junior_queryset.guardian_code_status.remove('-')
+ index = junior_queryset.guardian_code.index(guardian.guardian_code)
+ junior_queryset.guardian_code.remove(guardian.guardian_code)
+ junior_queryset.guardian_code_status.pop(index)
+ junior_queryset.save()
+ send_notification.delay(ASSOCIATE_REJECTED, guardian.user.id, GUARDIAN, junior_queryset.auth.id, {})
return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
-class ApproveTaskAPIView(viewsets.ViewSet):
+class ApproveTaskAPIView(viewsets.ModelViewSet):
"""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
-
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ Params
+ {"junior_id":"82",
+ "task_id":"43",
+ "action":"1"}
+ action 1 for approve
+ 2 for reject
+ """
# action 1 is use for approve and 2 for reject
try:
- queryset = self.get_queryset()
+ 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()
+ if task_queryset and (task_queryset.junior.is_deleted or not task_queryset.junior.is_active):
+ return custom_error_response(ERROR_CODE['2072'], response_status=status.HTTP_400_BAD_REQUEST)
# use ApproveJuniorSerializer serializer
- serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code,
- "task_instance": queryset[1],
+ serializer = ApproveTaskSerializer(context={"guardian_code": guardian.guardian_code,
+ "task_instance": task_queryset,
"action": str(request.data['action']),
"junior": self.request.data['junior_id']},
data=request.data)
unexpected_task_status = [str(NUMBER['five']), str(NUMBER['six'])]
if (str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid()
- and queryset[1] and queryset[1].task_status not in unexpected_task_status):
+ and task_queryset and task_queryset.task_status not in unexpected_task_status):
# 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()
- and queryset[1] and queryset[1].task_status not in unexpected_task_status):
+ and task_queryset and task_queryset.task_status not in unexpected_task_status):
# 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)
+ return custom_error_response(ERROR_CODE['2038'], 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 GuardianListAPIView(viewsets.ModelViewSet):
"""Guardian list of assosicated junior"""
@@ -333,7 +376,8 @@ class GuardianListAPIView(viewsets.ModelViewSet):
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
- """ junior list"""
+ """ Guardian list of assosicated junior
+ No Params"""
try:
guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user)
# fetch junior object
diff --git a/junior/admin.py b/junior/admin.py
index 6c6cdf9..2ffda51 100644
--- a/junior/admin.py
+++ b/junior/admin.py
@@ -3,8 +3,9 @@
from django.contrib import admin
"""Import Django app"""
from .models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
- JuniorArticleCard)
+ JuniorArticleCard, FAQ)
# Register your models here.
+admin.site.register(FAQ)
@admin.register(JuniorArticle)
class JuniorArticleAdmin(admin.ModelAdmin):
"""Junior Admin"""
diff --git a/junior/migrations/0026_faq_alter_juniorarticle_options_and_more.py b/junior/migrations/0026_faq_alter_juniorarticle_options_and_more.py
new file mode 100644
index 0000000..12243a2
--- /dev/null
+++ b/junior/migrations/0026_faq_alter_juniorarticle_options_and_more.py
@@ -0,0 +1,39 @@
+# Generated by Django 4.2.2 on 2023-08-17 09:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0025_alter_juniorarticle_junior'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FAQ',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('question', models.IntegerField(max_length=100)),
+ ('description', models.CharField(max_length=500)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'FAQ',
+ 'verbose_name_plural': 'FAQ',
+ },
+ ),
+ migrations.AlterModelOptions(
+ name='juniorarticle',
+ options={'verbose_name': 'Junior Article', 'verbose_name_plural': 'Junior Article'},
+ ),
+ migrations.AlterModelOptions(
+ name='juniorarticlecard',
+ options={'verbose_name': 'Junior Article Card', 'verbose_name_plural': 'Junior Article Card'},
+ ),
+ migrations.AlterModelOptions(
+ name='juniorarticlepoints',
+ options={'verbose_name': 'Junior Article Points', 'verbose_name_plural': 'Junior Article Points'},
+ ),
+ ]
diff --git a/junior/migrations/0027_alter_faq_question.py b/junior/migrations/0027_alter_faq_question.py
new file mode 100644
index 0000000..46fefd8
--- /dev/null
+++ b/junior/migrations/0027_alter_faq_question.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-08-17 09:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0026_faq_alter_juniorarticle_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='faq',
+ name='question',
+ field=models.CharField(max_length=100),
+ ),
+ ]
diff --git a/junior/migrations/0028_faq_status.py b/junior/migrations/0028_faq_status.py
new file mode 100644
index 0000000..bdfbf67
--- /dev/null
+++ b/junior/migrations/0028_faq_status.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-08-17 10:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0027_alter_faq_question'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='faq',
+ name='status',
+ field=models.IntegerField(blank=True, default=1, null=True),
+ ),
+ ]
diff --git a/junior/migrations/0029_junior_is_deleted.py b/junior/migrations/0029_junior_is_deleted.py
new file mode 100644
index 0000000..a39f60f
--- /dev/null
+++ b/junior/migrations/0029_junior_is_deleted.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-08-17 12:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0028_faq_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='junior',
+ name='is_deleted',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/junior/migrations/0030_remove_junior_guardian_code_status.py b/junior/migrations/0030_remove_junior_guardian_code_status.py
new file mode 100644
index 0000000..6949e9a
--- /dev/null
+++ b/junior/migrations/0030_remove_junior_guardian_code_status.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.2 on 2023-08-26 08:59
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0029_junior_is_deleted'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='junior',
+ name='guardian_code_status',
+ ),
+ ]
diff --git a/junior/migrations/0031_junior_guardian_code_status.py b/junior/migrations/0031_junior_guardian_code_status.py
new file mode 100644
index 0000000..c342f28
--- /dev/null
+++ b/junior/migrations/0031_junior_guardian_code_status.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.2 on 2023-08-26 08:59
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0030_remove_junior_guardian_code_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='junior',
+ name='guardian_code_status',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None),
+ ),
+ ]
diff --git a/junior/models.py b/junior/models.py
index 025843e..559acfe 100644
--- a/junior/models.py
+++ b/junior/models.py
@@ -68,15 +68,17 @@ class Junior(models.Model):
is_password_set = models.BooleanField(default=True)
# junior profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
+ # junior profile deleted or not"""
+ is_deleted = 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)
"""guardian code is approved or not"""
guardian_code_approved = models.BooleanField(default=False)
- # guardian code status"""
- guardian_code_status = models.CharField(max_length=31, choices=GUARDIAN_CODE_STATUS, default='1',
- null=True, blank=True)
+ # # guardian code status"""
+ guardian_code_status = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None), null=True,
+ )
# Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -158,6 +160,12 @@ class JuniorArticlePoints(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+ class Meta(object):
+ """ Meta class """
+ verbose_name = 'Junior Article Points'
+ # another name of the model"""
+ verbose_name_plural = 'Junior Article Points'
+
def __str__(self):
"""Return title"""
return f'{self.id} | {self.question}'
@@ -178,6 +186,12 @@ class JuniorArticle(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+ class Meta(object):
+ """ Meta class """
+ verbose_name = 'Junior Article'
+ # another name of the model"""
+ verbose_name_plural = 'Junior Article'
+
def __str__(self):
"""Return title"""
return f'{self.id} | {self.article}'
@@ -197,6 +211,34 @@ class JuniorArticleCard(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
+ class Meta(object):
+ """ Meta class """
+ verbose_name = 'Junior Article Card'
+ # another name of the model"""
+ verbose_name_plural = 'Junior Article Card'
+
def __str__(self):
"""Return title"""
return f'{self.id} | {self.article}'
+
+
+class FAQ(models.Model):
+ """FAQ model"""
+ # questions"""
+ question = models.CharField(max_length=100)
+ # answer"""
+ description = models.CharField(max_length=500)
+ # status
+ status = models.IntegerField(default=1, null=True, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta(object):
+ """ Meta class """
+ verbose_name = 'FAQ'
+ # another name of the model"""
+ verbose_name_plural = 'FAQ'
+
+ def __str__(self):
+ """Return email id"""
+ return f'{self.question}'
diff --git a/junior/serializers.py b/junior/serializers.py
index c3e2d72..de5f671 100644
--- a/junior/serializers.py
+++ b/junior/serializers.py
@@ -12,19 +12,19 @@ from rest_framework_simplejwt.tokens import RefreshToken
# local imports
from account.utils import send_otp_email, generate_code
-from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints
+from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, FAQ
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, JUN, ZOD, EXPIRED,
- GUARDIAN_CODE_STATUS)
+ GUARDIAN_CODE_STATUS, JUNIOR)
from guardian.models import Guardian, JuniorTask
from account.models import UserEmailOtp, UserNotification
-from junior.utils import junior_notification_email, junior_approval_mail
+from junior.utils import junior_notification_email, junior_approval_mail, get_junior_leaderboard_rank
from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime
from notifications.utils import send_notification
-from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION,
- TASK_SUBMITTED)
-
+from notifications.constants import (ASSOCIATE_REQUEST, ASSOCIATE_JUNIOR, TASK_ACTION,
+ )
+from web_admin.models import ArticleCard
class ListCharField(serializers.ListField):
"""Serializer for Array field"""
@@ -88,17 +88,27 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
if junior:
"""update details according to the data get from request"""
junior.gender = validated_data.get('gender',junior.gender)
- """Update guardian code"""
- junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code)
- """condition for guardian code"""
+ # Update guardian code"""
+ # condition for guardian code
if guardian_code:
- junior.guardian_code = guardian_code
+ if junior.guardian_code and guardian_code:
+ if guardian_code[0] in junior.guardian_code:
+ raise serializers.ValidationError({"error":ERROR_CODE['2076'],"code":"400", "status":"failed"})
+ if not junior.guardian_code:
+ junior.guardian_code = []
+ junior.guardian_code_status = []
+ junior.guardian_code.extend(guardian_code)
+ junior.guardian_code_status.extend(str(NUMBER['three']))
+ elif len(junior.guardian_code) < 3 and len(guardian_code) < 3:
+ junior.guardian_code.extend(guardian_code)
+ junior.guardian_code_status.extend(str(NUMBER['three']))
+ else:
+ raise serializers.ValidationError({"error":ERROR_CODE['2081'],"code":"400", "status":"failed"})
guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last()
if guardian_data:
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior)
- junior.guardian_code_status = str(NUMBER['three'])
- junior_approval_mail(user.email, user.first_name)
- send_notification.delay(APPROVED_JUNIOR, None, guardian_data.user.id, {})
+ send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {})
+ junior_approval_mail.delay(user.email, user.first_name)
junior.dob = validated_data.get('dob', junior.dob)
junior.passcode = validated_data.get('passcode', junior.passcode)
junior.country_name = validated_data.get('country_name', junior.country_name)
@@ -147,7 +157,7 @@ class JuniorDetailSerializer(serializers.ModelSerializer):
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'image', 'is_invited', 'referral_code','is_active', 'is_complete_profile',
- 'created_at', 'image', 'updated_at']
+ 'created_at', 'image', 'is_deleted', 'updated_at']
class JuniorDetailListSerializer(serializers.ModelSerializer):
"""junior serializer"""
@@ -163,6 +173,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position')
+ guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status')
def get_auth(self, obj):
@@ -179,9 +190,8 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
return data
def get_position(self, obj):
- data = JuniorPoints.objects.filter(junior=obj).last()
- if data:
- return data.position
+ return get_junior_leaderboard_rank(obj)
+
def get_points(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last()
if data:
@@ -208,13 +218,21 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
def get_pending_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count()
return data
+
+ def get_guardian_code_status(self, obj):
+ if self.context['guardian_code'] in obj.guardian_code:
+ index = obj.guardian_code.index(self.context['guardian_code'])
+ if obj.guardian_code_status:
+ data = obj.guardian_code_status[index]
+ return data
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'country_name', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task',
- 'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status']
+ 'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status',
+ 'is_deleted']
class JuniorProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
@@ -257,7 +275,7 @@ class JuniorProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method',
- 'is_invited', 'passcode', 'guardian_code_approved']
+ 'is_invited', 'passcode', 'guardian_code_approved', 'is_deleted']
class AddJuniorSerializer(serializers.ModelSerializer):
"""Add junior serializer"""
@@ -290,7 +308,7 @@ class AddJuniorSerializer(serializers.ModelSerializer):
referral_code=generate_code(ZOD, user_data.id),
referral_code_used=guardian_data.referral_code,
is_password_set=False, is_verified=True,
- guardian_code_status=GUARDIAN_CODE_STATUS[1][0])
+ guardian_code_status=[str(NUMBER['two'])])
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship)
total_junior = Junior.objects.all().count()
@@ -303,9 +321,9 @@ class AddJuniorSerializer(serializers.ModelSerializer):
# add push notification
UserNotification.objects.get_or_create(user=user_data)
"""Notification email"""
- junior_notification_email(email, full_name, email, password)
+ junior_notification_email.delay(email, full_name, email, password)
# push notification
- send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {})
+ send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {})
return junior_data
@@ -318,9 +336,13 @@ class RemoveJuniorSerializer(serializers.ModelSerializer):
fields = ('id', 'is_invited')
def update(self, instance, validated_data):
if instance:
+ guardian_code = self.context['guardian_code']
instance.is_invited = False
- instance.guardian_code = '{}'
- instance.guardian_code_status = str(NUMBER['one'])
+ if instance.guardian_code and ('-' in instance.guardian_code):
+ instance.guardian_code.remove('-')
+ index = instance.guardian_code.index(guardian_code)
+ instance.guardian_code.remove(guardian_code)
+ instance.guardian_code_status.pop(index)
instance.save()
return instance
@@ -337,8 +359,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer):
instance.task_status = str(NUMBER['four'])
instance.is_approved = False
instance.save()
- send_notification.delay(TASK_SUBMITTED, None, instance.junior.auth.id, {})
- send_notification.delay(TASK_ACTION, None, instance.guardian.user.id, {})
+ send_notification.delay(TASK_ACTION, instance.junior.auth.id, JUNIOR,
+ instance.guardian.user.id, {'task_id': instance.id})
return instance
class JuniorPointsSerializer(serializers.ModelSerializer):
@@ -358,10 +380,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer):
return obj.junior.id
def get_position(self, obj):
- data = JuniorPoints.objects.filter(junior=obj.junior).last()
- if data:
- return data.position
- return 99999
+ return get_junior_leaderboard_rank(obj.junior)
def get_points(self, obj):
"""total points"""
points = JuniorPoints.objects.filter(junior=obj.junior).last()
@@ -393,7 +412,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Junior
fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task',
- 'requested_task', 'rejected_task', 'expired_task']
+ 'requested_task', 'rejected_task', 'expired_task', 'is_deleted']
class AddGuardianSerializer(serializers.ModelSerializer):
"""Add guardian serializer"""
@@ -447,9 +466,8 @@ class AddGuardianSerializer(serializers.ModelSerializer):
"""Notification email"""
junior_notification_email(email, full_name, email, password)
- junior_approval_mail(email, full_name)
- send_notification.delay(INVITED_GUARDIAN, None, junior_data.auth.id, {})
- send_notification.delay(APPROVED_JUNIOR, None, guardian_data.user.id, {})
+ junior_approval_mail.delay(email, full_name)
+ send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth.id, JUNIOR, guardian_data.user.id, {})
return guardian_data
class StartTaskSerializer(serializers.ModelSerializer):
@@ -493,8 +511,6 @@ class ReAssignTaskSerializer(serializers.ModelSerializer):
instance.save()
return instance
-
-
class RemoveGuardianCodeSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
@@ -502,7 +518,33 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer):
model = Junior
fields = ('id', )
def update(self, instance, validated_data):
- instance.guardian_code = None
- instance.guardian_code_status = str(NUMBER['one'])
+ guardian_code = self.context['guardian_code']
+ if guardian_code in instance.guardian_code:
+ if instance.guardian_code and ('-' in instance.guardian_code):
+ instance.guardian_code.remove('-')
+ if instance.guardian_code_status and ('-' in instance.guardian_code_status):
+ instance.guardian_code_status.remove('-')
+ index = instance.guardian_code.index(guardian_code)
+ instance.guardian_code.remove(guardian_code)
+ instance.guardian_code_status.pop(index)
+ else:
+ raise serializers.ValidationError({"error":ERROR_CODE['2082'],"code":"400", "status":"failed"})
instance.save()
return instance
+
+class FAQSerializer(serializers.ModelSerializer):
+ """FAQ Serializer"""
+
+ class Meta(object):
+ """meta info"""
+ model = FAQ
+ fields = ('id', 'question', 'description')
+
+class CreateArticleCardSerializer(serializers.ModelSerializer):
+ """Article card Serializer"""
+
+ class Meta(object):
+ """meta info"""
+ model = ArticleCard
+ fields = ('id', 'article')
+
diff --git a/junior/urls.py b/junior/urls.py
index b145d4f..3c597c3 100644
--- a/junior/urls.py
+++ b/junior/urls.py
@@ -6,7 +6,7 @@ from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView
CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode,
InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView,
StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView,
- CreateArticleCardAPIView, RemoveGuardianCodeAPIView)
+ CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet)
"""Third party import"""
from rest_framework import routers
@@ -51,6 +51,8 @@ router.register('start-assessment', StartAssessmentAPIView, basename='start-asse
router.register('check-answer', CheckAnswerAPIView, basename='check-answer')
# start article"""
router.register('create-article-card', CreateArticleCardAPIView, basename='create-article-card')
+# FAQ API
+router.register('faq', FAQViewSet, basename='faq')
# Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
@@ -60,5 +62,5 @@ urlpatterns = [
path('api/v1/reassign-task/', ReAssignJuniorTaskAPIView.as_view()),
path('api/v1/complete-article/', CompleteArticleAPIView.as_view()),
path('api/v1/read-article-card/', ReadArticleCardAPIView.as_view()),
- path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view()),
+ path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view())
]
diff --git a/junior/utils.py b/junior/utils.py
index ba177a8..eac6ac9 100644
--- a/junior/utils.py
+++ b/junior/utils.py
@@ -5,7 +5,8 @@ from django.conf import settings
from templated_email import send_templated_mail
from .models import JuniorPoints
from base.constants import NUMBER
-from django.db.models import F
+from django.db.models import F, Window
+from django.db.models.functions.window import Rank
# junior notification
# email for sending email
# when guardian create junior profile
@@ -14,6 +15,8 @@ from django.db.models import F
# being part of the zod bank and access the platform
# define junior notification email
# junior approval email
+from celery import shared_task
+@shared_task()
def junior_notification_email(recipient_email, full_name, email, password):
"""Notification email"""
from_email = settings.EMAIL_FROM_ADDRESS
@@ -32,7 +35,7 @@ def junior_notification_email(recipient_email, full_name, email, password):
}
)
return full_name
-
+@shared_task()
def junior_approval_mail(guardian, full_name):
"""junior approval mail"""
from_email = settings.EMAIL_FROM_ADDRESS
@@ -59,3 +62,21 @@ def update_positions_based_on_points():
junior_point.position = position
junior_point.save()
position += NUMBER['one']
+
+
+def get_junior_leaderboard_rank(junior_obj):
+ """
+ to get junior's position/rank
+ :param junior_obj:
+ :return: junior's position/rank
+ """
+ queryset = JuniorPoints.objects.filter(
+ junior__is_verified=True
+ ).select_related('junior', 'junior__auth').annotate(rank=Window(
+ expression=Rank(),
+ order_by=[F('total_points').desc(), 'junior__created_at']
+ )).order_by('-total_points', 'junior__created_at')
+
+ junior = next((query for query in queryset if query.junior == junior_obj), None)
+
+ return junior.rank if junior else None
diff --git a/junior/views.py b/junior/views.py
index a86083d..64aaffb 100644
--- a/junior/views.py
+++ b/junior/views.py
@@ -10,8 +10,12 @@ from django.db.models import F
import datetime
import requests
-"""Django app import"""
+from rest_framework.viewsets import GenericViewSet, mixins
+"""Django app import"""
+from drf_yasg.utils import swagger_auto_schema
+from drf_yasg import openapi
+from drf_yasg.views import get_schema_view
# Import guardian's model,
# Import junior's model,
# Import account's model,
@@ -30,20 +34,20 @@ import requests
# Import constants
from django.db.models import Sum
from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
- JuniorArticleCard)
+ JuniorArticleCard, FAQ)
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,
RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer,
AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer,
- RemoveGuardianCodeSerializer)
+ RemoveGuardianCodeSerializer, FAQSerializer, CreateArticleCardSerializer)
from guardian.models import Guardian, JuniorTask
from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
-from base.constants import NUMBER, ARTICLE_STATUS
+from base.constants import NUMBER, ARTICLE_STATUS, none, GUARDIAN
from account.utils import custom_response, custom_error_response
from guardian.utils import upload_image_to_alibaba
from .utils import update_positions_based_on_points
from notifications.utils import send_notification
-from notifications.constants import REMOVE_JUNIOR
+from notifications.constants import REMOVE_JUNIOR, ARTICLE_REWARD_POINTS, ASSOCIATE_EXISTING_JUNIOR
from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard
from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer,
StartAssessmentSerializer)
@@ -63,13 +67,14 @@ from web_admin.serializers.article_serializer import (ArticleSerializer, Article
# Start task
# by junior API
# Create your views here.
-class UpdateJuniorProfile(viewsets.ViewSet):
+class UpdateJuniorProfile(viewsets.ModelViewSet):
"""Update junior profile"""
serializer_class = CreateJuniorSerializer
permission_classes = [IsAuthenticated]
+ http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """Use CreateJuniorSerializer"""
+ """Create Junior Profile"""
try:
request_data = request.data
image = request.data.get('image')
@@ -94,14 +99,22 @@ class UpdateJuniorProfile(viewsets.ViewSet):
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)
except Exception as e:
- return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
+ if e.detail:
+ error_detail = e.detail.get('error', None)
+ else:
+ error_detail = str(e)
+ return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST)
-class ValidateGuardianCode(viewsets.ViewSet):
+class ValidateGuardianCode(viewsets.ModelViewSet):
"""Check guardian code exist or not"""
permission_classes = [IsAuthenticated]
+ http_method_names = ('get',)
def list(self, request, *args, **kwargs):
- """check guardian code"""
+ """check guardian code
+ Params
+ "guardian_code"
+ """
try:
guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code:
@@ -132,14 +145,15 @@ class JuniorListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
""" junior list"""
try:
- update_positions_based_on_points()
+ # update_positions_based_on_points, function removed
guardian_data = Guardian.objects.filter(user__email=request.user).last()
# fetch junior object
if guardian_data:
queryset = self.get_queryset()
queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code))
# use JuniorDetailListSerializer serializer
- serializer = JuniorDetailListSerializer(queryset, many=True)
+ serializer = JuniorDetailListSerializer(queryset, context={"guardian_code":
+ guardian_data.guardian_code}, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK)
except Exception as e:
@@ -153,7 +167,14 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ add junior
+ { "gender":"1",
+ "first_name":"abc",
+ "last_name":"xyz",
+ "dob":"2023-12-12",
+ "relationship":"2",
+ "email":"abc@yopmail.com"
+ }"""
try:
info_data = {'user': request.user, 'relationship': str(request.data['relationship']),
'email': request.data['email'], 'first_name': request.data['first_name'],
@@ -169,7 +190,13 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
image_url = upload_image_to_alibaba(profile_image, filename)
info_data.update({"image": image_url})
if user := User.objects.filter(username=request.data['email']).first():
- self.associate_guardian(user)
+ data = self.associate_guardian(user)
+ if data == none:
+ return custom_error_response(ERROR_CODE['2077'], response_status=status.HTTP_400_BAD_REQUEST)
+ elif not data:
+ return custom_error_response(ERROR_CODE['2076'], response_status=status.HTTP_400_BAD_REQUEST)
+ elif data == "Max":
+ return custom_error_response(ERROR_CODE['2081'], response_status=status.HTTP_400_BAD_REQUEST)
return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK)
# use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info_data)
@@ -182,18 +209,37 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def associate_guardian(self, user):
- junior = Junior.objects.filter(auth=user).first()
+ junior = Junior.objects.filter(auth__email=self.request.data['email']).first()
guardian = Guardian.objects.filter(user=self.request.user).first()
- junior.guardian_code = [guardian.guardian_code]
- junior.guardian_code_status = str(NUMBER['two'])
+ if junior.guardian_code and ('-' in junior.guardian_code):
+ junior.guardian_code.remove('-')
+ if not junior:
+ return none
+ if junior.guardian_code and (guardian.guardian_code in junior.guardian_code):
+ return False
+ if not junior.guardian_code:
+ junior.guardian_code = [guardian.guardian_code]
+ if type(junior.guardian_code) is list and len(junior.guardian_code) < 3:
+ junior.guardian_code.append(guardian.guardian_code)
+ else:
+ return "Max"
+ if junior.guardian_code_status and ('-' in junior.guardian_code_status):
+ junior.guardian_code_status.remove('-')
+ if not junior.guardian_code_status:
+ junior.guardian_code_status = [str(NUMBER['two'])]
+ else:
+ junior.guardian_code_status.append(str(NUMBER['two']))
junior.save()
- JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior,
- relationship=str(self.request.data['relationship']))
+ jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior)
+ if jun_data:
+ jun_data.relationship = str(self.request.data['relationship'])
+ jun_data.save()
+ send_notification.delay(ASSOCIATE_EXISTING_JUNIOR, self.request.user.id, GUARDIAN, junior.auth.id, {})
return True
class InvitedJuniorAPIView(viewsets.ModelViewSet):
- """Junior list of assosicated guardian"""
+ """Invited Junior list of assosicated guardian"""
serializer_class = JuniorDetailListSerializer
permission_classes = [IsAuthenticated]
@@ -207,7 +253,8 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
is_invited=True)
return junior_queryset
def list(self, request, *args, **kwargs):
- """ junior list"""
+ """ Invited Junior list of assosicated guardian
+ No Params"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
@@ -221,12 +268,25 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
class FilterJuniorAPIView(viewsets.ModelViewSet):
- """Update guardian profile"""
+ """filter junior profile"""
serializer_class = JuniorDetailListSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
http_method_names = ('get',)
+ @swagger_auto_schema(
+ manual_parameters=[
+ # Example of a query parameter
+ openapi.Parameter(
+ 'title',
+ openapi.IN_QUERY,
+ description='title of the name',
+ type=openapi.TYPE_STRING,
+ ),
+ # Add more parameters as needed
+ ]
+ )
+
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
@@ -237,7 +297,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
return queryset
def list(self, request, *args, **kwargs):
- """Create guardian profile"""
+ """Filter junior"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
@@ -251,7 +311,9 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
class RemoveJuniorAPIView(views.APIView):
- """Remove junior API"""
+ """Remove junior API
+ Params
+ id=37"""
serializer_class = RemoveJuniorSerializer
model = Junior
permission_classes = [IsAuthenticated]
@@ -265,11 +327,13 @@ class RemoveJuniorAPIView(views.APIView):
guardian_code__icontains=str(guardian.guardian_code)).last()
if junior_queryset:
# use RemoveJuniorSerializer serializer
- serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True)
+ serializer = RemoveJuniorSerializer(junior_queryset, context={"guardian_code":guardian.guardian_code},
+ data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
- send_notification.delay(REMOVE_JUNIOR, None, junior_queryset.auth.id, {})
+ JuniorGuardianRelationship.objects.filter(guardian=guardian, junior=junior_queryset).delete()
+ send_notification.delay(REMOVE_JUNIOR, None, None, junior_queryset.auth.id, {})
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:
@@ -279,44 +343,46 @@ class RemoveJuniorAPIView(views.APIView):
class JuniorTaskListAPIView(viewsets.ModelViewSet):
- """Update guardian profile"""
+ """Junior task list"""
serializer_class = TaskDetailsjuniorSerializer
permission_classes = [IsAuthenticated]
+ filter_backends = (SearchFilter,)
+ search_fields = ['task_name', ]
pagination_class = PageNumberPagination
http_method_names = ('get',)
+ def get_queryset(self):
+ queryset = JuniorTask.objects.filter(junior__auth=self.request.user
+ ).prefetch_related('junior', 'junior__auth'
+ ).order_by('due_date', 'created_at')
+
+ queryset = self.filter_queryset(queryset)
+ return queryset
+
def list(self, request, *args, **kwargs):
- """Create guardian profile"""
+ """Junior task list
+ status=0
+ search='title'
+ page=1"""
try:
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')
+ queryset = self.get_queryset()
+ if status_value and status_value != '0':
+ queryset = queryset.filter(task_status=status_value)
paginator = self.pagination_class()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
- # use TaskDetailsSerializer serializer
- serializer = TaskDetailsjuniorSerializer(paginated_queryset, many=True)
+ # use TaskDetails juniorSerializer serializer
+ serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CompleteJuniorTaskAPIView(views.APIView):
- """Update junior task API"""
+ """Payload
+ task_id
+ image"""
serializer_class = CompleteTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
@@ -334,8 +400,11 @@ class CompleteJuniorTaskAPIView(views.APIView):
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()
+ task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user
+ ).select_related('guardian', 'junior').last()
if task_queryset:
+ if task_queryset.junior.is_deleted or not task_queryset.junior.is_active:
+ return custom_error_response(ERROR_CODE['2074'], response_status=status.HTTP_400_BAD_REQUEST)
# use CompleteTaskSerializer serializer
if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]:
"""Already request send """
@@ -358,9 +427,10 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet):
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
- """profile view"""
+ """Junior Points
+ No Params"""
try:
- update_positions_based_on_points()
+ # update_positions_based_on_points, function removed
queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last()
# update position of junior
serializer = JuniorPointsSerializer(queryset)
@@ -369,7 +439,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
-class ValidateReferralCode(viewsets.ViewSet):
+class ValidateReferralCode(viewsets.ModelViewSet):
"""Check guardian code exist or not"""
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
@@ -404,7 +474,13 @@ class InviteGuardianAPIView(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ add guardian
+ {
+ "first_name":"abc",
+ "last_name":"xyz",
+ "email":"abc@yopmail.com",
+ "relationship":2
+ }"""
try:
if request.data['email'] == '':
return custom_error_response(ERROR_CODE['2062'], response_status=status.HTTP_400_BAD_REQUEST)
@@ -422,7 +498,11 @@ class InviteGuardianAPIView(viewsets.ModelViewSet):
class StartTaskAPIView(views.APIView):
- """Update junior task API"""
+ """Update junior task API
+ Paylod
+ {
+ "task_id":28
+ }"""
serializer_class = StartTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
@@ -446,7 +526,13 @@ class StartTaskAPIView(views.APIView):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ReAssignJuniorTaskAPIView(views.APIView):
- """Update junior task API"""
+ """Update junior task API
+ Payload
+ {
+ "task_id":34,
+ "due_date":"2023-08-22"
+ }
+ """
serializer_class = ReAssignTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
@@ -475,7 +561,10 @@ class StartArticleAPIView(viewsets.ModelViewSet):
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ Payload
+ {
+ "article_id":"2"
+ }"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article_id = request.data.get('article_id')
@@ -497,7 +586,7 @@ class StartArticleAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class StartAssessmentAPIView(viewsets.ModelViewSet):
- """Junior Points viewset"""
+ """Question answer viewset"""
serializer_class = StartAssessmentSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
@@ -510,7 +599,9 @@ class StartAssessmentAPIView(viewsets.ModelViewSet):
).order_by('-created_at')
return article
def list(self, request, *args, **kwargs):
- """profile view"""
+ """Params
+ article_id
+ """
try:
queryset = self.get_queryset()
@@ -522,7 +613,9 @@ class StartAssessmentAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CheckAnswerAPIView(viewsets.ModelViewSet):
- """Junior Points viewset"""
+ """Params
+ question_id=1
+ answer_id=1"""
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
@@ -531,13 +624,16 @@ class CheckAnswerAPIView(viewsets.ModelViewSet):
article = ArticleSurvey.objects.filter(id=question_id).last()
return article
def list(self, request, *args, **kwargs):
- """profile view"""
+ """ Params
+ question_id=1
+ answer_id=1
+ """
try:
answer_id = self.request.GET.get('answer_id')
current_page = self.request.GET.get('current_page')
queryset = self.get_queryset()
- submit_ans = SurveyOption.objects.filter(id=answer_id, is_answer=True).last()
+ submit_ans = SurveyOption.objects.filter(id=answer_id).last()
junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user,
question=queryset)
if submit_ans:
@@ -555,7 +651,9 @@ class CheckAnswerAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CompleteArticleAPIView(views.APIView):
- """Remove junior API"""
+ """Params
+ article_id
+ """
permission_classes = [IsAuthenticated]
http_method_names = ('put', 'get',)
def put(self, request, format=None):
@@ -569,7 +667,8 @@ class CompleteArticleAPIView(views.APIView):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def get(self, request, *args, **kwargs):
- """ junior list"""
+ """ Params
+ article_id=1"""
try:
article_id = self.request.GET.get('article_id')
total_earn_points = JuniorArticlePoints.objects.filter(junior__auth=request.user,
@@ -577,18 +676,24 @@ class CompleteArticleAPIView(views.APIView):
is_answer_correct=True).aggregate(
total_earn_points=Sum('earn_points'))['total_earn_points']
data = {"total_earn_points":total_earn_points}
+ send_notification.delay(ARTICLE_REWARD_POINTS, None, None,
+ request.user.id, {'points': total_earn_points})
return custom_response(SUCCESS_CODE['3042'], data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ReadArticleCardAPIView(views.APIView):
- """Remove junior API"""
+ """Read article card API"""
permission_classes = [IsAuthenticated]
http_method_names = ('put',)
def put(self, request, *args, **kwargs):
- """ junior list"""
+ """ Read article card
+ Payload
+ {"article_id":"1",
+ "article_card":"2",
+ "current_page":"2"}"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article = self.request.data.get('article_id')
@@ -606,11 +711,14 @@ class ReadArticleCardAPIView(views.APIView):
class CreateArticleCardAPIView(viewsets.ModelViewSet):
"""Start article"""
+ serializer_class = CreateArticleCardSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
- """ junior list"""
+ """ create article card
+ Params
+ {"article_id":1}"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article_id = request.data.get('article_id')
@@ -629,23 +737,75 @@ class CreateArticleCardAPIView(viewsets.ModelViewSet):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class RemoveGuardianCodeAPIView(views.APIView):
- """Update junior task API"""
+ """Remove guardian code request API
+ Payload
+ {"guardian_code"
+ :"GRD037"
+ }"""
serializer_class = RemoveGuardianCodeSerializer
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
+ guardian_code = self.request.data.get("guardian_code")
+ guardian_data = Guardian.objects.filter(guardian_code=guardian_code).last()
junior_queryset = Junior.objects.filter(auth=self.request.user).last()
if junior_queryset:
# use RemoveGuardianCodeSerializer serializer
- serializer = RemoveGuardianCodeSerializer(junior_queryset, data=request.data, partial=True)
+ serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code},
+ data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
+ JuniorGuardianRelationship.objects.filter(guardian=guardian_data, junior=junior_queryset).delete()
return custom_response(SUCCESS_CODE['3044'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
# task in another state
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
+ except Exception as e:
+ error_detail = e.detail.get('error', None)
+ return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST)
+
+
+class FAQViewSet(GenericViewSet, mixins.CreateModelMixin,
+ mixins.ListModelMixin):
+ """FAQ view set"""
+
+ serializer_class = FAQSerializer
+ permission_classes = [IsAuthenticated]
+ http_method_names = ['get', 'post']
+
+ def get_queryset(self):
+ return FAQ.objects.all()
+
+ def create(self, request, *args, **kwargs):
+ """
+ faq create api method
+ :param request:
+ :param args: question, description
+ :param kwargs:
+ :return: success message
+ """
+ obj_data = [FAQ(**item) for item in request.data]
+ try:
+ FAQ.objects.bulk_create(obj_data)
+ return custom_response(SUCCESS_CODE["3045"], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
+
+
+ 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, response_status=status.HTTP_200_OK)
+
diff --git a/notifications/admin.py b/notifications/admin.py
index 382e97b..c7cc895 100644
--- a/notifications/admin.py
+++ b/notifications/admin.py
@@ -10,3 +10,4 @@ from notifications.models import Notification
class NotificationAdmin(admin.ModelAdmin):
"""Notification Admin"""
list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read']
+ list_filter = ['notification_type']
diff --git a/notifications/constants.py b/notifications/constants.py
index b861142..85b0d2d 100644
--- a/notifications/constants.py
+++ b/notifications/constants.py
@@ -1,19 +1,26 @@
"""
notification constants file
"""
-from base.constants import NUMBER
-REGISTRATION = NUMBER['one']
-TASK_CREATED = NUMBER['two']
-INVITED_GUARDIAN = NUMBER['three']
-APPROVED_JUNIOR = NUMBER['four']
-REFERRAL_POINTS = NUMBER['five']
-TASK_POINTS = NUMBER['six']
-TASK_REJECTED = NUMBER['seven']
-SKIPPED_PROFILE_SETUP = NUMBER['eight']
-TASK_SUBMITTED = NUMBER['nine']
-TASK_ACTION = NUMBER['ten']
-LEADERBOARD_RANKING = NUMBER['eleven']
-REMOVE_JUNIOR = NUMBER['twelve']
+REGISTRATION = 1
+ASSOCIATE_REQUEST = 3
+ASSOCIATE_REJECTED = 4
+ASSOCIATE_APPROVED = 5
+REFERRAL_POINTS = 6
+ASSOCIATE_JUNIOR = 7
+ASSOCIATE_EXISTING_JUNIOR = 8
+
+TASK_ASSIGNED = 9
+TASK_ACTION = 10
+TASK_REJECTED = 11
+TASK_APPROVED = 12
+PENDING_TASK_EXPIRING = 13
+IN_PROGRESS_TASK_EXPIRING = 14
+TOP_JUNIOR = 15
+
+NEW_ARTICLE_PUBLISHED = 16
+ARTICLE_REWARD_POINTS = 17
+REMOVE_JUNIOR = 18
+
TEST_NOTIFICATION = 99
NOTIFICATION_DICT = {
@@ -21,52 +28,102 @@ NOTIFICATION_DICT = {
"title": "Successfully registered!",
"body": "You have registered successfully. Now login and complete your profile."
},
- TASK_CREATED: {
- "title": "Task created!",
- "body": "Task created successfully."
+ # user will receive notification as soon junior
+ # sign up application using their guardian code for association
+ ASSOCIATE_REQUEST: {
+ "title": "Associate request!",
+ "body": "You have request from {from_user} to associate with you."
},
- INVITED_GUARDIAN: {
- "title": "Invite guardian",
- "body": "Invite guardian successfully"
+ # Juniors will receive notification when
+ # custodians reject their request for associate
+ ASSOCIATE_REJECTED: {
+ "title": "Associate request rejected!",
+ "body": "Your request to associate has been rejected by {from_user}."
},
- APPROVED_JUNIOR: {
- "title": "Approve junior",
- "body": "You have request from junior to associate with you"
+ # Juniors will receive notification when
+ # custodians approve their request for associate
+ ASSOCIATE_APPROVED: {
+ "title": "Associate request approved!",
+ "body": "Your request to associate has been approved by {from_user}."
},
+ # Juniors will receive Notifications
+ # for every Points earned by referrals
REFERRAL_POINTS: {
- "title": "Earn Referral points",
+ "title": "Earn Referral points!",
"body": "You earn 5 points for referral."
},
- TASK_POINTS: {
- "title": "Earn Task points!",
- "body": "You earn 5 points for task."
+ # Juniors will receive notification
+ # once any custodians add them in their account
+ ASSOCIATE_JUNIOR: {
+ "title": "Profile already setup!",
+ "body": "Your guardian has already setup your profile."
},
- TASK_REJECTED: {
- "title": "Task rejected!",
- "body": "Your task has been rejected."
+ ASSOCIATE_EXISTING_JUNIOR: {
+ "title": "Associated to guardian",
+ "body": "Your are associated to your guardian {from_user}."
},
- SKIPPED_PROFILE_SETUP: {
- "title": "Skipped profile setup!",
- "body": "Your guardian has been setup your profile."
- },
- TASK_SUBMITTED: {
- "title": "Task submitted!",
- "body": "Your task has been submitted successfully."
+ # Juniors will receive Notification
+ # for every Task Assign by Custodians
+ TASK_ASSIGNED: {
+ "title": "New task assigned!",
+ "body": "{from_user} has assigned you a new task."
},
+ # Guardian will receive notification as soon
+ # as junior send task for approval
TASK_ACTION: {
- "title": "Task approval!",
- "body": "You have request for task approval."
+ "title": "Task completion approval!",
+ "body": "{from_user} completed their task {task_name}."
},
- LEADERBOARD_RANKING: {
- "title": "Leader board rank!",
- "body": "Your rank is ."
+ # Juniors will receive notification as soon
+ # as their task is rejected by custodians
+ TASK_REJECTED: {
+ "title": "Task completion rejected!",
+ "body": "Your task completion request has been rejected by {from_user}."
},
+ # Juniors will receive notification as soon as their task is approved by custodians
+ # and for every Points earned by Task completion
+ TASK_APPROVED: {
+ "title": "Task completion approved!",
+ "body": "Your task completion request has been approved by {from_user}. "
+ "Also you earned 5 points for successful completion."
+ },
+ # Juniors will receive notification when their task end date about to end
+ PENDING_TASK_EXPIRING: {
+ "title": "Task expiring soon!",
+ "body": "Your task {task_name} is expiring soon. Please complete it."
+ },
+ # User will receive notification when their assigned task is about to end
+ # and juniors have not performed any action
+ IN_PROGRESS_TASK_EXPIRING: {
+ "title": "Task expiring soon!",
+ "body": "{from_user} didn't take any action on assigned task {task_name} and it's expiring soon. "
+ "Please assist to complete it."
+ },
+ # Juniors will receive Notification
+ # related to Leaderboard progress
+ TOP_JUNIOR: {
+ "title": "Leaderboard topper!",
+ "body": "{from_user} is on top in leaderboard with {points} points."
+ },
+ # Juniors will receive notification
+ # when admin add any new financial learnings
+ NEW_ARTICLE_PUBLISHED: {
+ "title": "Time to read!",
+ "body": "A new article has been published."
+ },
+ # Juniors will receive notification when they earn points by reading financial Learning
+ ARTICLE_REWARD_POINTS: {
+ "title": "Article reward points!",
+ "body": "You are rewarded with {points} points for reading article and answering questions. "
+ },
+ # Juniors will receive notification as soon as their custodians remove them from account
REMOVE_JUNIOR: {
"title": "Disassociate by guardian!",
- "body": "Your guardian disassociate you ."
+ "body": "Your guardian has disassociated you."
},
+ # Test notification
TEST_NOTIFICATION: {
"title": "Test Notification",
- "body": "This notification is for testing purpose"
+ "body": "This notification is for testing purpose from {from_user}."
}
}
diff --git a/notifications/serializers.py b/notifications/serializers.py
index a061369..2f0222f 100644
--- a/notifications/serializers.py
+++ b/notifications/serializers.py
@@ -35,10 +35,13 @@ class NotificationListSerializer(serializers.ModelSerializer):
class Meta(object):
"""meta info"""
model = Notification
- fields = ['id', 'data', 'is_read']
+ fields = ['id', 'data', 'is_read', 'created_at']
+
class ReadNotificationSerializer(serializers.ModelSerializer):
"""User task Serializer"""
+ id = serializers.ListSerializer(child=serializers.IntegerField())
+
class Meta(object):
"""Meta class"""
model = Notification
diff --git a/notifications/urls.py b/notifications/urls.py
index 713aae3..b184d02 100644
--- a/notifications/urls.py
+++ b/notifications/urls.py
@@ -6,7 +6,7 @@ from django.urls import path, include
from rest_framework import routers
# local imports
-from notifications.views import NotificationViewSet, ReadNotification
+from notifications.views import NotificationViewSet
# initiate router
router = routers.SimpleRouter()
@@ -15,5 +15,4 @@ router.register('notifications', NotificationViewSet, basename='notifications')
urlpatterns = [
path('api/v1/', include(router.urls)),
- path('api/v1/read-notification/', ReadNotification.as_view()),
]
diff --git a/notifications/utils.py b/notifications/utils.py
index ba980e6..bd2a8bd 100644
--- a/notifications/utils.py
+++ b/notifications/utils.py
@@ -8,21 +8,24 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati
# 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
+from django.db.models import Q
# local imports
-
+from account.models import UserNotification
+from account.utils import get_user_full_name
+from base.constants import GUARDIAN, JUNIOR
+from guardian.models import Guardian, JuniorTask
+from junior.models import Junior
+from notifications.constants import NOTIFICATION_DICT
+from notifications.models import Notification
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,
+ device, _ = FCMDevice.objects.update_or_create(user_id=user_id,
+ defaults={'device_id': device_id, 'type': device_type,
'active': True,
'registration_id': registration_id})
return device
@@ -39,30 +42,140 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None:
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
+def get_from_user_details(from_user_id, from_user_type):
+ """
+ used to get from user details
+ """
+ from_user = None
+ from_user_name = None
+ from_user_image = None
+ if from_user_id:
+ if from_user_type == GUARDIAN:
+ guardian = Guardian.objects.filter(user_id=from_user_id).select_related('user').first()
+ from_user = guardian.user
+ from_user_name = get_user_full_name(from_user)
+ from_user_image = guardian.image
+ elif from_user_type == JUNIOR:
+ junior = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first()
+ from_user = junior.auth
+ from_user_name = get_user_full_name(from_user)
+ from_user_image = junior.image
+ return from_user_name, from_user_image, from_user
+
+
+def get_notification_data(notification_type, from_user_id, from_user_type, to_user_id, extra_data):
+ """
+ get notification and push data
+ :param from_user_type: GUARDIAN or JUNIOR
+ :param notification_type: notification_type
+ :param from_user_id: from_user obj
+ :param to_user_id: to_user obj
+ :param extra_data: any extra data provided
+ :return: notification and push data
+ """
+ push_data = NOTIFICATION_DICT[notification_type].copy()
+ notification_data = push_data.copy()
+ task_name = None
+ points = extra_data.get('points', None)
+ if 'task_id' in extra_data:
+ task = JuniorTask.objects.filter(id=extra_data.get('task_id')).first()
+ task_name = task.task_name
+ extra_data['task_name'] = task_name
+ extra_data['task_image'] = task.image if task.image else task.default_image
+
+ from_user_name, from_user_image, from_user = get_from_user_details(from_user_id, from_user_type)
+
+ push_data['body'] = push_data['body'].format(from_user=from_user_name, task_name=task_name, points=points)
+ notification_data['body'] = notification_data['body'].format(from_user=from_user_name,
+ task_name=task_name, points=points)
+ notification_data['from_user'] = from_user_name
+ notification_data['from_user_image'] = from_user_image
+
+ notification_data.update(extra_data)
+ to_user = User.objects.filter(id=to_user_id).first()
+ return notification_data, push_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)
+def send_notification(notification_type, from_user_id, from_user_type, to_user_id, extra_data):
+ """
+ used to send the push for the given notification type
+ """
+ notification_data, push_data, from_user, to_user = get_notification_data(notification_type, from_user_id,
+ from_user_type, to_user_id, extra_data)
user_notification_type = UserNotification.objects.filter(user=to_user).first()
- data = notification_data
+ notification_data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()})
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)
+ notification_to=to_user, data=notification_data)
+ if user_notification_type and user_notification_type.push_notification:
+ send_push(to_user, push_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)
+ Message(notification=FirebaseNotification(data['title'], data['body']), data=data)
)
+
+
+def send_multiple_push(queryset, data):
+ """ used to send same notification to multiple users """
+ FCMDevice.objects.filter(user__in=queryset, active=True).send_message(
+ Message(notification=FirebaseNotification(data['title'], data['body']), data=data)
+ )
+
+
+@shared_task()
+def send_notification_multiple_user(notification_type, from_user_id, from_user_type,
+ extra_data: dict = {}):
+ """
+ used to send notification to multiple user for the given notification type
+ """
+ to_user_list = User.objects.filter(junior_profile__is_verified=True, is_superuser=False
+ ).exclude(junior_profile__isnull=True, guardian_profile__isnull=True)
+
+ notification_data, push_data, from_user, _ = get_notification_data(notification_type, from_user_id,
+ from_user_type, None, extra_data)
+
+ notification_list = []
+ for user in to_user_list:
+ notification_copy_data = notification_data.copy()
+ notification_copy_data.update(
+ {'badge': Notification.objects.filter(notification_to=user, is_read=False).count()})
+ notification_list.append(Notification(notification_type=notification_type,
+ notification_to=user,
+ notification_from=from_user,
+ data=notification_copy_data))
+ Notification.objects.bulk_create(notification_list)
+ to_user_list = to_user_list.filter(user_notification__push_notification=True)
+ send_multiple_push(to_user_list, push_data)
+
+
+@shared_task()
+def send_notification_to_guardian(notification_type, from_user_id, to_user_id, extra_data):
+ """
+ :param notification_type:
+ :param from_user_id:
+ :param to_user_id:
+ :param extra_data:
+ :return:
+ """
+ if from_user_id:
+ from_user = Junior.objects.filter(auth_id=from_user_id).first()
+ extra_data['from_user_image'] = from_user.image
+ send_notification(notification_type, from_user_id, to_user_id, extra_data)
+
+
+@shared_task()
+def send_notification_to_junior(notification_type, from_user_id, to_user_id, extra_data):
+ """
+ :param notification_type:
+ :param from_user_id:
+ :param to_user_id:
+ :param extra_data:
+ :return:
+ """
+ if from_user_id:
+ from_user = Guardian.objects.filter(user_id=from_user_id).first()
+ extra_data['from_user_image'] = from_user.image
+ send_notification(notification_type, from_user_id, to_user_id, extra_data)
diff --git a/notifications/views.py b/notifications/views.py
index c66d655..812502e 100644
--- a/notifications/views.py
+++ b/notifications/views.py
@@ -4,40 +4,39 @@ notifications views file
# django imports
from django.db.models import Q
from rest_framework.decorators import action
-from rest_framework.permissions import IsAuthenticated
+from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework import viewsets, status, views
+
# local imports
from account.utils import custom_response, custom_error_response
from base.messages import SUCCESS_CODE, ERROR_CODE
+from base.tasks import notify_task_expiry, notify_top_junior
from notifications.constants import TEST_NOTIFICATION
-# Import serializer
from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer
from notifications.utils import send_notification
-# Import model
from notifications.models import Notification
class NotificationViewSet(viewsets.GenericViewSet):
- """ used to do the notification actions """
+ """
+ used to do the notification actions
+ """
serializer_class = NotificationListSerializer
permission_classes = [IsAuthenticated, ]
def list(self, request, *args, **kwargs) -> Response:
- """ list the notifications """
+ """
+ to list user's notifications
+ :param request:
+ :return:
+ """
queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id']
).select_related('notification_to').order_by('-id')
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
- self.mark_notifications_as_read(serializer.data)
- return custom_response(None, serializer.data)
-
- @staticmethod
- def mark_notifications_as_read(data):
- """ used to mark notification queryset as read """
- ids = [obj['id'] for obj in data]
- Notification.objects.filter(id__in=ids).update(is_read=True)
+ return custom_response(None, serializer.data, count=queryset.count())
@action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice)
def fcm_registration(self, request):
@@ -53,38 +52,20 @@ class NotificationViewSet(viewsets.GenericViewSet):
@action(methods=['get'], detail=False, url_path='test', url_name='test')
def send_test_notification(self, request):
"""
- to send test notification
+ to test send notification, task expiry, top junior
:return:
"""
- send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {})
+ notify_task_expiry()
+ notify_top_junior()
+ send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'],
+ {})
return custom_response(SUCCESS_CODE["3000"])
- @action(methods=['get'], detail=False, url_path='list', url_name='list',
- serializer_class=NotificationListSerializer)
- def notification_list(self, request):
+ @action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False,
+ serializer_class=ReadNotificationSerializer)
+ def mark_as_read(self, request, *args, **kwargs):
"""
notification list
"""
- try:
- queryset = Notification.objects.filter(notification_to=request.user)
- serializer = NotificationListSerializer(queryset, many=True)
- return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
- except Exception as e:
- return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
-
-
-class ReadNotification(views.APIView):
- """Update notification API"""
- serializer_class = ReadNotificationSerializer
- model = Notification
- permission_classes = [IsAuthenticated]
-
- def put(self, request, format=None):
- try:
- notification_id = self.request.data.get('notification_id')
- notification_queryset = Notification.objects.filter(id__in=notification_id,
- notification_to=self.request.user).update(is_read=True)
- if notification_queryset:
- return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)
- except Exception as e:
- return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
+ Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True)
+ return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)
diff --git a/requirements.txt b/requirements.txt
index f1540e7..624a176 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -99,3 +99,6 @@ uritemplate==4.1.1
urllib3==1.26.16
vine==5.0.0
wcwidth==0.2.6
+
+pandas==2.0.3
+XlsxWriter==3.1.2
\ No newline at end of file
diff --git a/web_admin/pagination.py b/web_admin/pagination.py
index 6a6aff4..c2eed5e 100644
--- a/web_admin/pagination.py
+++ b/web_admin/pagination.py
@@ -1,13 +1,18 @@
"""
web_admin pagination file
"""
+# third party imports
from rest_framework.pagination import PageNumberPagination
+from base.constants import NUMBER
+
class CustomPageNumberPagination(PageNumberPagination):
"""
custom paginator class
"""
- page_size = 10 # Set the desired page size
+ # Set the desired page size
+ page_size = NUMBER['ten']
page_size_query_param = 'page_size'
- max_page_size = 100 # Set a maximum page size if needed
+ # Set a maximum page size if needed
+ max_page_size = NUMBER['hundred']
diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py
index fd85118..7871615 100644
--- a/web_admin/serializers/analytics_serializer.py
+++ b/web_admin/serializers/analytics_serializer.py
@@ -1,12 +1,25 @@
"""
web_admin analytics serializer file
"""
+# third party imports
from rest_framework import serializers
+# django imports
+from django.contrib.auth import get_user_model
+
+from account.utils import get_user_full_name
+# local imports
+from base.constants import USER_TYPE, JUNIOR
+
from junior.models import JuniorPoints, Junior
+USER = get_user_model()
+
class JuniorLeaderboardSerializer(serializers.ModelSerializer):
+ """
+ junior leaderboard serializer
+ """
name = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
last_name = serializers.SerializerMethodField()
@@ -16,7 +29,7 @@ class JuniorLeaderboardSerializer(serializers.ModelSerializer):
meta class
"""
model = Junior
- fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image')
+ fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image', 'is_deleted')
@staticmethod
def get_name(obj):
@@ -24,7 +37,7 @@ class JuniorLeaderboardSerializer(serializers.ModelSerializer):
:param obj: junior object
:return: full name
"""
- return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name
+ return get_user_full_name(obj.auth)
@staticmethod
def get_first_name(obj):
@@ -44,9 +57,94 @@ class JuniorLeaderboardSerializer(serializers.ModelSerializer):
class LeaderboardSerializer(serializers.ModelSerializer):
+ """
+ leaderboard serializer
+ """
+ user_id = serializers.SerializerMethodField()
+ user_type = serializers.SerializerMethodField()
junior = JuniorLeaderboardSerializer()
rank = serializers.IntegerField()
class Meta:
+ """
+ meta class
+ """
model = JuniorPoints
- fields = ('total_points', 'rank', 'junior')
+ fields = ('user_id', 'user_type', 'total_points', 'rank', 'junior')
+
+ @staticmethod
+ def get_user_id(obj):
+ return obj.junior.auth.id
+
+ @staticmethod
+ def get_user_type(obj):
+ return JUNIOR
+
+
+class UserCSVReportSerializer(serializers.ModelSerializer):
+ """
+ user csv/xls report serializer
+ """
+ name = serializers.SerializerMethodField()
+ phone_number = serializers.SerializerMethodField()
+ user_type = serializers.SerializerMethodField()
+ is_active = serializers.SerializerMethodField()
+ date_joined = serializers.SerializerMethodField()
+
+ class Meta:
+ """
+ meta class
+ """
+ model = USER
+ fields = ('name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined')
+
+ @staticmethod
+ def get_name(obj):
+ """
+ :param obj: user object
+ :return: full name
+ """
+ return get_user_full_name(obj)
+
+ @staticmethod
+ def get_phone_number(obj):
+ """
+ :param obj: user object
+ :return: user phone number
+ """
+ if profile := (obj.guardian_profile.all().first() or obj.junior_profile.all().first()):
+ return f"+{profile.country_code}{profile.phone}" \
+ if profile.country_code and profile.phone else profile.phone
+ else:
+ return None
+
+ @staticmethod
+ def get_user_type(obj):
+ """
+ :param obj: user object
+ :return: user type
+ """
+ if obj.guardian_profile.all().first():
+ return dict(USER_TYPE).get('2').capitalize()
+ elif obj.junior_profile.all().first():
+ return dict(USER_TYPE).get('1').capitalize()
+ else:
+ return None
+
+ @staticmethod
+ def get_is_active(obj):
+ """
+ :param obj: user object
+ :return: user type
+ """
+ if profile := (obj.guardian_profile.all().first() or obj.junior_profile.all().first()):
+ return "Active" if profile.is_active else "Inactive"
+
+ @staticmethod
+ def get_date_joined(obj):
+ """
+ :param obj: user obj
+ :return: formatted date
+ """
+ date = obj.date_joined.strftime("%d %b %Y")
+ return date
diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py
index e125acf..9fa5651 100644
--- a/web_admin/serializers/article_serializer.py
+++ b/web_admin/serializers/article_serializer.py
@@ -10,6 +10,8 @@ from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE
# local imports
from base.messages import ERROR_CODE
from guardian.utils import upload_image_to_alibaba
+from notifications.constants import NEW_ARTICLE_PUBLISHED
+from notifications.utils import send_notification_multiple_user
from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage
from web_admin.utils import pop_id, get_image_url
from junior.models import JuniorArticlePoints, JuniorArticle
@@ -119,11 +121,15 @@ class ArticleSerializer(serializers.ModelSerializer):
option = pop_id(option)
SurveyOption.objects.create(survey=survey_obj, **option)
+ # All juniors will receive notification when admin add any new financial learnings/article
+ send_notification_multiple_user.delay(NEW_ARTICLE_PUBLISHED, None, None, {})
+
return article
def update(self, instance, validated_data):
"""
to update article and related table
+ :param validated_data:
:param instance: article object,
:return: article object
"""
@@ -219,8 +225,7 @@ class ArticleListSerializer(serializers.ModelSerializer):
"""
serializer for article API
"""
- article_cards = ArticleCardSerializer(many=True)
- article_survey = ArticleSurveySerializer(many=True)
+ image = serializers.SerializerMethodField('get_image')
total_points = serializers.SerializerMethodField('get_total_points')
is_completed = serializers.SerializerMethodField('get_is_completed')
@@ -229,12 +234,16 @@ class ArticleListSerializer(serializers.ModelSerializer):
meta class
"""
model = Article
- fields = ('id', 'title', 'description', 'article_cards', 'article_survey', 'total_points', 'is_completed')
+ fields = ('id', 'title', 'description','image', 'total_points', 'is_completed')
+ def get_image(self, obj):
+ """article image"""
+ if obj.article_cards.first():
+ return obj.article_cards.first().image_url
+ return None
def get_total_points(self, obj):
"""total points of article"""
- total_question = ArticleSurvey.objects.filter(article=obj).count()
- return total_question * NUMBER['five']
+ return obj.article_survey.all().count() * NUMBER['five']
def get_is_completed(self, obj):
"""complete all question"""
@@ -268,14 +277,14 @@ class ArticleQuestionSerializer(serializers.ModelSerializer):
ans_obj = SurveyOption.objects.filter(survey=obj, is_answer=True).last()
if ans_obj:
return ans_obj.id
- return str("None")
+ return None
def get_attempted_answer(self, obj):
"""attempt question or not"""
context_data = self.context.get('user')
junior_article_obj = JuniorArticlePoints.objects.filter(junior__auth=context_data,
- question=obj, is_answer_correct=True).last()
- if junior_article_obj:
+ question=obj).last()
+ if junior_article_obj and junior_article_obj.submitted_answer:
return junior_article_obj.submitted_answer.id
return None
diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py
index 712e284..bda89bd 100644
--- a/web_admin/serializers/auth_serializer.py
+++ b/web_admin/serializers/auth_serializer.py
@@ -14,7 +14,7 @@ from account.models import UserEmailOtp
from base.constants import USER_TYPE
from base.messages import ERROR_CODE
from guardian.tasks import generate_otp
-from base.tasks import send_email_otp
+from base.tasks import send_email
USER = get_user_model()
@@ -48,11 +48,13 @@ class AdminOTPSerializer(serializers.ModelSerializer):
:return: user_data
"""
email = validated_data['email']
-
verification_code = generate_otp()
-
+ template = 'email_reset_verification.email'
# Send the verification code to the user's email
- send_email_otp.delay(email, verification_code)
+ data = {
+ "verification_code": verification_code
+ }
+ send_email.delay([email], template, data)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.update_or_create(email=email,
diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py
index 4bb0709..c8d0b1f 100644
--- a/web_admin/serializers/user_management_serializer.py
+++ b/web_admin/serializers/user_management_serializer.py
@@ -5,7 +5,8 @@ web_admin user_management serializers file
from rest_framework import serializers
from django.contrib.auth import get_user_model
-from base.constants import USER_TYPE
+from account.utils import get_user_full_name
+from base.constants import USER_TYPE, GUARDIAN, JUNIOR
# local imports
from base.messages import ERROR_CODE, SUCCESS_CODE
from guardian.models import Guardian
@@ -37,7 +38,7 @@ class UserManagementListSerializer(serializers.ModelSerializer):
:param obj: user object
:return: full name
"""
- return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name
+ return get_user_full_name(obj)
@staticmethod
def get_country_code(obj):
@@ -108,7 +109,7 @@ class GuardianSerializer(serializers.ModelSerializer):
"""
model = Guardian
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
- 'is_active', 'country_name', 'image', 'email')
+ 'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs):
"""
@@ -144,7 +145,7 @@ class GuardianSerializer(serializers.ModelSerializer):
:param obj: guardian object
:return: full name
"""
- return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name
+ return get_user_full_name(obj.user)
@staticmethod
def get_first_name(obj):
@@ -187,7 +188,7 @@ class JuniorSerializer(serializers.ModelSerializer):
"""
model = Junior
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
- 'is_active', 'country_name', 'image', 'email')
+ 'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs):
"""
@@ -210,10 +211,10 @@ class JuniorSerializer(serializers.ModelSerializer):
"""
instance.auth.email = self.validated_data.get('email', instance.auth.email)
instance.auth.username = self.validated_data.get('email', instance.auth.username)
- instance.auth.save()
+ instance.auth.save(update_fields=['email', 'username'])
instance.country_code = validated_data.get('country_code', instance.country_code)
instance.phone = validated_data.get('phone', instance.phone)
- instance.save()
+ instance.save(update_fields=['country_code', 'phone'])
return instance
@staticmethod
@@ -222,7 +223,7 @@ class JuniorSerializer(serializers.ModelSerializer):
:param obj: junior object
:return: full name
"""
- return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name
+ return get_user_full_name(obj.auth)
@staticmethod
def get_first_name(obj):
@@ -265,33 +266,30 @@ class UserManagementDetailSerializer(serializers.ModelSerializer):
model = USER
fields = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users')
- @staticmethod
- def get_user_type(obj):
+ def get_user_type(self, obj):
"""
:param obj: user object
:return: user type
"""
- if obj.guardian_profile.all().first():
- return dict(USER_TYPE).get('2')
- elif obj.junior_profile.all().first():
- return dict(USER_TYPE).get('1')
- else:
- return None
+ return GUARDIAN if self.context['user_type'] == GUARDIAN else JUNIOR
- @staticmethod
- def get_associated_users(obj):
+ def get_associated_users(self, obj):
"""
:param obj: user object
:return: associated user
"""
- if profile := obj.guardian_profile.all().first():
+ if self.context['user_type'] == GUARDIAN:
+ profile = obj.guardian_profile.all().only('user_id', 'guardian_code').first()
if profile.guardian_code:
- junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True)
+ junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code],
+ is_verified=True).select_related('auth')
serializer = JuniorSerializer(junior, many=True)
return serializer.data
- elif profile := obj.junior_profile.all().first():
+ elif self.context['user_type'] == JUNIOR:
+ profile = obj.junior_profile.all().only('auth_id', 'guardian_code').first()
if profile.guardian_code:
- guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True)
+ guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code,
+ is_verified=True).select_related('user')
serializer = GuardianSerializer(guardian, many=True)
return serializer.data
else:
diff --git a/web_admin/utils.py b/web_admin/utils.py
index 9870b30..3dbb3b2 100644
--- a/web_admin/utils.py
+++ b/web_admin/utils.py
@@ -2,9 +2,10 @@
web_utils file
"""
import base64
+import datetime
-from base.constants import ARTICLE_CARD_IMAGE_FOLDER
-from guardian.utils import upload_image_to_alibaba
+from base.constants import ARTICLE_CARD_IMAGE_FOLDER, DATE_FORMAT
+from guardian.utils import upload_image_to_alibaba, upload_base64_image_to_alibaba
def pop_id(data):
@@ -29,10 +30,10 @@ def get_image_url(data):
return data['image_url']
elif 'image_url' in data and type(data['image_url']) == str and data['image_url'].startswith('data:image'):
base64_image = base64.b64decode(data.get('image_url').split(',')[1])
- image_name = f"{data['title']} {data.pop('image_name')}" if 'image_name' in data else data['title']
+ image_name = data.pop('image_name') if 'image_name' in data else f"{data['title']}.jpg"
filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image_name}"
# upload image on ali baba
- image_url = upload_image_to_alibaba(base64_image, filename)
+ image_url = upload_base64_image_to_alibaba(base64_image, filename)
return image_url
elif 'image' in data and data['image'] is not None:
image = data.pop('image')
@@ -40,3 +41,21 @@ def get_image_url(data):
# upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
return image_url
+
+
+def get_dates(start_date, end_date):
+ """
+ to get start and end date
+ :param start_date: format (yyyy-mm-dd)
+ :param end_date: format (yyyy-mm-dd)
+ :return: start and end date
+ """
+
+ if start_date and end_date:
+ start_date = datetime.datetime.strptime(start_date, DATE_FORMAT).date()
+ end_date = datetime.datetime.strptime(end_date, DATE_FORMAT).date()
+ else:
+ end_date = datetime.date.today()
+ start_date = end_date - datetime.timedelta(days=6)
+
+ return start_date, end_date
diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py
index 8c21cb3..926cd47 100644
--- a/web_admin/views/analytics.py
+++ b/web_admin/views/analytics.py
@@ -3,6 +3,9 @@ web_admin analytics view file
"""
# python imports
import datetime
+import io
+import pandas as pd
+import xlsxwriter
# third party imports
from rest_framework.viewsets import GenericViewSet
@@ -16,15 +19,18 @@ from django.db.models import Count
from django.db.models.functions import TruncDate
from django.db.models import F, Window
from django.db.models.functions.window import Rank
+from django.http import HttpResponse
# local imports
-from account.utils import custom_response
-from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT
+from account.utils import custom_response, get_user_full_name
+from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT, TASK_STATUS
from guardian.models import JuniorTask
+from guardian.utils import upload_excel_file_to_alibaba
from junior.models import JuniorPoints
from web_admin.pagination import CustomPageNumberPagination
from web_admin.permission import AdminPermission
-from web_admin.serializers.analytics_serializer import LeaderboardSerializer
+from web_admin.serializers.analytics_serializer import LeaderboardSerializer, UserCSVReportSerializer
+from web_admin.utils import get_dates
USER = get_user_model()
@@ -47,7 +53,7 @@ class AnalyticsViewSet(GenericViewSet):
).prefetch_related('guardian_profile',
'junior_profile'
).exclude(junior_profile__isnull=True,
- guardian_profile__isnull=True).order_by('date_joined')
+ guardian_profile__isnull=True).order_by('-date_joined')
return user_qs
@action(methods=['get'], url_name='users-count', url_path='users-count', detail=False)
@@ -58,13 +64,8 @@ class AnalyticsViewSet(GenericViewSet):
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
-
- end_date = datetime.date.today()
- start_date = end_date - datetime.timedelta(days=6)
-
- if request.query_params.get('start_date') and request.query_params.get('end_date'):
- start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT)
- end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT)
+ start_date, end_date = get_dates(request.query_params.get('start_date'),
+ request.query_params.get('end_date'))
user_qs = self.get_queryset()
queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1))))
@@ -83,12 +84,8 @@ class AnalyticsViewSet(GenericViewSet):
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
- end_date = datetime.date.today()
- start_date = end_date - datetime.timedelta(days=6)
-
- if request.query_params.get('start_date') and request.query_params.get('end_date'):
- start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT)
- end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT)
+ start_date, end_date = get_dates(request.query_params.get('start_date'),
+ request.query_params.get('end_date'))
user_qs = self.get_queryset()
signup_data = user_qs.filter(date_joined__range=[start_date, (end_date + datetime.timedelta(days=1))]
@@ -105,22 +102,20 @@ class AnalyticsViewSet(GenericViewSet):
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
- end_date = datetime.date.today()
- start_date = end_date - datetime.timedelta(days=6)
-
- if request.query_params.get('start_date') and request.query_params.get('end_date'):
- start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT)
- end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT)
+ start_date, end_date = get_dates(request.query_params.get('start_date'),
+ request.query_params.get('end_date'))
assign_tasks = JuniorTask.objects.filter(
created_at__range=[start_date, (end_date + datetime.timedelta(days=1))]
- ).exclude(task_status__in=[PENDING, EXPIRED])
+ )
data = {
'task_completed': assign_tasks.filter(task_status=COMPLETED).count(),
+ 'task_pending': assign_tasks.filter(task_status=PENDING).count(),
'task_in_progress': assign_tasks.filter(task_status=IN_PROGRESS).count(),
'task_requested': assign_tasks.filter(task_status=REQUESTED).count(),
'task_rejected': assign_tasks.filter(task_status=REJECTED).count(),
+ 'task_expired': assign_tasks.filter(task_status=EXPIRED).count(),
}
return custom_response(None, data)
@@ -133,11 +128,120 @@ class AnalyticsViewSet(GenericViewSet):
:param request:
:return:
"""
- queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window(
- expression=Rank(),
- order_by=[F('total_points').desc(), 'junior__created_at']
- )).order_by('-total_points', 'junior__created_at')
+ queryset = JuniorPoints.objects.filter(
+ junior__is_verified=True
+ ).select_related('junior', 'junior__auth').annotate(rank=Window(
+ expression=Rank(),
+ order_by=[F('total_points').desc(), 'junior__created_at']
+ )).order_by('-total_points', 'junior__created_at')
paginator = CustomPageNumberPagination()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data)
+
+ @action(methods=['get'], url_name='export-excel', url_path='export-excel', detail=False)
+ def export_excel(self, request):
+ """
+ to export users count, task details and top juniors in csv/excel file
+ :param request: start_date: date format (yyyy-mm-dd)
+ :param request: end_date: date format (yyyy-mm-dd)
+ :return:
+ """
+
+ response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+ response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.xlsx"'
+
+ start_date, end_date = get_dates(request.query_params.get('start_date'),
+ request.query_params.get('end_date'))
+
+ # Use BytesIO for binary data
+ buffer = io.BytesIO()
+
+ # Create an XlsxWriter Workbook object
+ workbook = xlsxwriter.Workbook(buffer)
+
+ # Add sheets
+ sheets = ['Users', 'Assign Tasks', 'Juniors Leaderboard']
+
+ for sheet_name in sheets:
+ worksheet = workbook.add_worksheet(name=sheet_name)
+
+ # sheet 1 for Total Users
+ if sheet_name == 'Users':
+ user_qs = self.get_queryset()
+ queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1))))
+ serializer = UserCSVReportSerializer(queryset, many=True)
+
+ df_users = pd.DataFrame([
+ {'Name': user['name'], 'Email': user['email'],
+ 'Phone Number': user['phone_number'], 'User Type': user['user_type'],
+ 'Status': user['is_active'], 'Date Joined': user['date_joined']}
+ for user in serializer.data
+ ])
+ write_excel_worksheet(worksheet, df_users)
+
+ # sheet 2 for Assign Task
+ elif sheet_name == 'Assign Tasks':
+ assign_tasks = JuniorTask.objects.filter(
+ created_at__range=[start_date, (end_date + datetime.timedelta(days=1))]
+ ).select_related('junior__auth', 'guardian__user').only('task_name', 'task_status',
+ 'junior__auth__first_name',
+ 'junior__auth__last_name',
+ 'guardian__user__first_name',
+ 'guardian__user__last_name',)
+
+ df_tasks = pd.DataFrame([
+ {'Task Name': task.task_name, 'Assign To': get_user_full_name(task.junior.auth),
+ 'Assign By': get_user_full_name(task.guardian.user),
+ 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()}
+ for task in assign_tasks
+ ])
+
+ write_excel_worksheet(worksheet, df_tasks)
+
+ # sheet 3 for Juniors Leaderboard and rank
+ elif sheet_name == 'Juniors Leaderboard':
+ queryset = JuniorPoints.objects.filter(
+ junior__is_verified=True
+ ).select_related('junior', 'junior__auth').annotate(rank=Window(
+ expression=Rank(),
+ order_by=[F('total_points').desc(), 'junior__created_at']
+ )).order_by('-total_points', 'junior__created_at')[:15]
+ df_leaderboard = pd.DataFrame([
+ {
+ 'Name': get_user_full_name(junior.junior.auth),
+ 'Points': junior.total_points,
+ 'Rank': junior.rank
+ }
+ for junior in queryset
+ ])
+
+ write_excel_worksheet(worksheet, df_leaderboard)
+
+ # Close the workbook to save the content
+ workbook.close()
+
+ # Reset the buffer position and write the content to the response
+ buffer.seek(0)
+ response.write(buffer.getvalue())
+ buffer.close()
+
+ filename = f"{'analytics'}/{'ZOD_Bank_Analytics.xlsx'}"
+ file_link = upload_excel_file_to_alibaba(response, filename)
+ return custom_response(None, file_link)
+
+
+def write_excel_worksheet(worksheet, dataframe):
+ """
+ to perform write action on worksheets
+ :param worksheet:
+ :param dataframe:
+ :return: worksheet
+ """
+ for idx, col in enumerate(dataframe.columns):
+ # Write header
+ worksheet.write(0, idx, col)
+ for row_num, row in enumerate(dataframe.values, start=1):
+ for col_num, value in enumerate(row):
+ worksheet.write(row_num, col_num, value)
+ return worksheet
diff --git a/web_admin/views/article.py b/web_admin/views/article.py
index 13c41c2..902f579 100644
--- a/web_admin/views/article.py
+++ b/web_admin/views/article.py
@@ -44,9 +44,20 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def create(self, request, *args, **kwargs):
"""
article create api method
- :param request:
- :param args:
- :param kwargs:
+ :param request: { "title": "string", "description": "string",
+ "article_cards": [
+ { "title": "string",
+ "description": "string",
+ "image_name": "string",
+ "image_url": "string"
+ } ],
+ "article_survey": [
+ { "question": "string",
+ "options": [
+ { "option": "string",
+ "is_answer": true }
+ ] }
+ ] }
:return: success message
"""
serializer = self.serializer_class(data=request.data)
@@ -57,9 +68,24 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def update(self, request, *args, **kwargs):
"""
article update api method
- :param request:
- :param args:
- :param kwargs:
+ :param request: article_id,
+ { "title": "string", "description": "string",
+ "article_cards": [
+ { "id": 0,
+ "title": "string",
+ "description": "string",
+ "image_name": "string",
+ "image_url": "string"
+ } ],
+ "article_survey": [
+ { "id": 0,
+ "question": "string",
+ "options": [
+ { "id": 0,
+ "option": "string",
+ "is_answer": true
+ } ]
+ } ] }
:return: success message
"""
article = self.get_object()
@@ -72,8 +98,6 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
"""
article list api method
:param request:
- :param args:
- :param kwargs:
:return: list of article
"""
queryset = self.get_queryset()
@@ -86,9 +110,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def retrieve(self, request, *args, **kwargs):
"""
article detail api method
- :param request:
- :param args:
- :param kwargs:
+ :param request: article_id
:return: article detail data
"""
queryset = self.get_object()
@@ -98,9 +120,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def destroy(self, request, *args, **kwargs):
"""
article delete (soft delete) api method
- :param request:
- :param args:
- :param kwargs:
+ :param request: article_id
:return: success message
"""
article = self.get_object()
@@ -177,7 +197,10 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m
def create(self, request, *args, **kwargs):
"""
api method to upload default article card images
- :param request:
+ :param request: {
+ "image_name": "string",
+ "image": "image_file"
+ }
:return: success message
"""
serializer = self.serializer_class(data=request.data)
@@ -206,10 +229,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin):
http_method_names = ['get',]
def get_queryset(self):
- article = self.queryset.objects.filter(is_deleted=False).prefetch_related(
- 'article_cards', 'article_survey', 'article_survey__options'
- ).order_by('-created_at')
- queryset = self.filter_queryset(article)
+ queryset = self.queryset.objects.filter(is_deleted=False, is_published=True).order_by('-created_at')
return queryset
def list(self, request, *args, **kwargs):
@@ -225,7 +245,9 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin):
return custom_response(None, data=serializer.data)
class ArticleCardListViewSet(viewsets.ModelViewSet):
- """Junior Points viewset"""
+ """Article card list
+ use below query param
+ article_id"""
serializer_class = ArticleCardlistSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
@@ -234,7 +256,9 @@ class ArticleCardListViewSet(viewsets.ModelViewSet):
"""get queryset"""
return ArticleCard.objects.filter(article=self.request.GET.get('article_id'))
def list(self, request, *args, **kwargs):
- """profile view"""
+ """Article card list
+ use below query param
+ article_id"""
try:
queryset = self.get_queryset()
diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py
index fae973e..73f19e5 100644
--- a/web_admin/views/auth.py
+++ b/web_admin/views/auth.py
@@ -27,6 +27,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_otp(self, request):
"""
api method to send otp
+ :param request: {"email": "string"}
:return: success message
"""
serializer = self.serializer_class(data=request.data)
@@ -40,6 +41,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_verify_otp(self, request):
"""
api method to verify otp
+ :param request: {"email": "string", "otp": "otp"}
:return: success message
"""
serializer = self.serializer_class(data=request.data)
@@ -52,6 +54,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_create_password(self, request):
"""
api method to create new password
+ :param request: {"email": "string", "new_password": "string", "confirm_password": "string"}
:return: success message
"""
serializer = self.serializer_class(data=request.data)
diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py
index 8f53a73..6980a7a 100644
--- a/web_admin/views/user_management.py
+++ b/web_admin/views/user_management.py
@@ -12,8 +12,11 @@ from django.db.models import Q
# local imports
from account.utils import custom_response, custom_error_response
-from base.constants import USER_TYPE
+from base.constants import USER_TYPE, GUARDIAN, JUNIOR
from base.messages import SUCCESS_CODE, ERROR_CODE
+from base.tasks import send_email
+from guardian.models import Guardian
+from junior.models import Junior
from web_admin.permission import AdminPermission
from web_admin.serializers.user_management_serializer import (UserManagementListSerializer,
UserManagementDetailSerializer, GuardianSerializer,
@@ -70,12 +73,14 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
"""
if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
+
queryset = self.queryset
- if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
- queryset = queryset.filter(guardian_profile__user__id=kwargs['pk'])
- elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
- queryset = queryset.filter(junior_profile__auth__id=kwargs['pk'])
- serializer = UserManagementDetailSerializer(queryset, many=True)
+ queryset = queryset.filter(id=kwargs['pk'])
+
+ serializer = UserManagementDetailSerializer(
+ queryset, many=True,
+ context={'user_type': self.request.query_params.get('user_type')}
+ )
return custom_response(None, data=serializer.data)
def partial_update(self, request, *args, **kwargs):
@@ -87,15 +92,16 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
"""
if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
- queryset = self.queryset
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
- user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first()
- serializer = GuardianSerializer(user_obj.guardian_profile.all().first(),
+ guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True
+ ).select_related('user').first()
+ serializer = GuardianSerializer(guardian,
request.data, context={'user_id': kwargs['pk']})
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
- user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first()
- serializer = JuniorSerializer(user_obj.junior_profile.all().first(),
+ junior = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True
+ ).select_related('auth').first()
+ serializer = JuniorSerializer(junior,
request.data, context={'user_id': kwargs['pk']})
serializer.is_valid(raise_exception=True)
@@ -110,17 +116,21 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
user_type {'guardian' for Guardian, 'junior' for Junior} mandatory
:return: success message
"""
- if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]:
+ user_type = self.request.query_params.get('user_type')
+ if user_type not in [GUARDIAN, JUNIOR]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
- queryset = self.queryset
- if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
- user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first()
- obj = user_obj.guardian_profile.all().first()
- obj.is_active = False if obj.is_active else True
- obj.save()
- elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
- user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first()
- obj = user_obj.junior_profile.all().first()
- obj.is_active = False if obj.is_active else True
- obj.save()
+
+ email_template = 'user_deactivate.email'
+
+ if user_type == GUARDIAN:
+ obj = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).select_related('user').first()
+ elif user_type == JUNIOR:
+ obj = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True).select_related('auth').first()
+
+ if obj.is_active:
+ obj.is_active = False
+ send_email([obj.user.email if user_type == GUARDIAN else obj.auth.email], email_template)
+ else:
+ obj.is_active = True
+ obj.save()
return custom_response(SUCCESS_CODE['3038'])
diff --git a/zod_bank/celery.py b/zod_bank/celery.py
index 5cc2829..039ea03 100644
--- a/zod_bank/celery.py
+++ b/zod_bank/celery.py
@@ -27,19 +27,24 @@ app.config_from_object('django.conf:settings')
# Load task modules from all registered Django apps.
app.autodiscover_tasks()
+# scheduled task
+app.conf.beat_schedule = {
+ "expired_task": {
+ "task": "guardian.utils.update_expired_task_status",
+ "schedule": crontab(minute=0, hour=0),
+ },
+ 'notify_task_expiry': {
+ 'task': 'base.tasks.notify_task_expiry',
+ 'schedule': crontab(minute='0', hour='18'),
+ },
+ 'notify_top_junior': {
+ 'task': 'base.tasks.notify_top_junior',
+ 'schedule': crontab(minute='0', hour='*/2'),
+ },
+}
+
@app.task(bind=True)
def debug_task(self):
""" celery debug task """
print(f'Request: {self.request!r}')
-
-
-"""cron task"""
-
-
-app.conf.beat_schedule = {
- "expired_task": {
- "task": "guardian.utils.update_expired_task_status",
- "schedule": crontab(minute=0, hour=0),
- },
-}
diff --git a/zod_bank/settings.py b/zod_bank/settings.py
index 2d003c1..781df80 100644
--- a/zod_bank/settings.py
+++ b/zod_bank/settings.py
@@ -125,6 +125,7 @@ SIMPLE_JWT = {
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
+ # default db setting
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME':os.getenv('DB_NAME'),
@@ -177,6 +178,9 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
+# database query logs settings
+# Allows us to check db hits
+# useful to optimize db query and hit
LOGGING = {
"version": 1,
"filters": {
@@ -193,6 +197,7 @@ LOGGING = {
"class": "logging.StreamHandler"
}
},
+ # database logger
"loggers": {
"django.db.backends": {
"level": "DEBUG",
@@ -242,6 +247,7 @@ CORS_ALLOW_HEADERS = (
'x-requested-with',
)
+# CORS header settings
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: *',
)
@@ -297,5 +303,7 @@ STATIC_URL = 'static/'
# define static root
STATIC_ROOT = 'static'
+# media url
MEDIA_URL = "/media/"
+# media path
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media')