From 5d386322e44cec026d48060fcdac73ce19bf0077 Mon Sep 17 00:00:00 2001 From: dilipshrivastwa-kiwi Date: Thu, 15 Jun 2023 15:11:18 +0530 Subject: [PATCH 001/372] add git ignore file --- .gitignore | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..063a8af --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/static +/media +.idea/ +*.pyc +media/ +*.name +*.iml +*.log +*.xml +*.pyo +.DS_Store +.idea +venv/* +static/* +*.pem +*.sqlite3 +/migrations/__pycache__/ +/__pycache__/ +/*.pyc +*/__pycache__/*.pyc +__pycache__/ +*.env +ve/* + From 8bc3a307c03b879c26acfad66bd1bcc8e61df615 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 23 Jun 2023 19:13:49 +0530 Subject: [PATCH 002/372] Initial Commit --- zod_bank/account/__init__.py | 0 zod_bank/account/admin.py | 25 +++ zod_bank/account/apps.py | 6 + zod_bank/account/migrations/0001_initial.py | 65 +++++++ zod_bank/account/migrations/__init__.py | 0 zod_bank/account/models.py | 75 ++++++++ zod_bank/account/serializers.py | 104 +++++++++++ zod_bank/account/tests.py | 3 + zod_bank/account/urls.py | 20 +++ zod_bank/account/utils.py | 31 ++++ zod_bank/account/views.py | 111 ++++++++++++ zod_bank/base/__init__.py | 3 + zod_bank/base/common_email.py | 30 ++++ zod_bank/base/constants.py | 99 +++++++++++ zod_bank/base/custom_exceptions.py | 16 ++ zod_bank/base/image_constants.py | 16 ++ zod_bank/base/messages.py | 92 ++++++++++ zod_bank/base/routers.py | 17 ++ zod_bank/base/search_and_filters.py | 49 +++++ zod_bank/base/upload_file.py | 177 +++++++++++++++++++ zod_bank/guardian/__init__.py | 0 zod_bank/guardian/admin.py | 9 + zod_bank/guardian/apps.py | 6 + zod_bank/guardian/migrations/0001_initial.py | 43 +++++ zod_bank/guardian/migrations/__init__.py | 0 zod_bank/guardian/models.py | 31 ++++ zod_bank/guardian/serializers.py | 91 ++++++++++ zod_bank/guardian/tasks.py | 6 + zod_bank/guardian/tests.py | 3 + zod_bank/guardian/urls.py | 17 ++ zod_bank/guardian/views.py | 44 +++++ zod_bank/junior/__init__.py | 0 zod_bank/junior/admin.py | 9 + zod_bank/junior/apps.py | 6 + zod_bank/junior/migrations/0001_initial.py | 42 +++++ zod_bank/junior/migrations/__init__.py | 0 zod_bank/junior/models.py | 32 ++++ zod_bank/junior/serializers.py | 73 ++++++++ zod_bank/junior/tests.py | 3 + zod_bank/junior/urls.py | 17 ++ zod_bank/junior/views.py | 40 +++++ zod_bank/manage.py | 21 +++ zod_bank/nginx/django.conf | 24 +++ zod_bank/requirements.txt | 55 ++++++ zod_bank/zod_bank/__init__.py | 0 zod_bank/zod_bank/asgi.py | 16 ++ zod_bank/zod_bank/settings.py | 176 ++++++++++++++++++ zod_bank/zod_bank/urls.py | 33 ++++ zod_bank/zod_bank/wsgi.py | 16 ++ 49 files changed, 1752 insertions(+) create mode 100644 zod_bank/account/__init__.py create mode 100644 zod_bank/account/admin.py create mode 100644 zod_bank/account/apps.py create mode 100644 zod_bank/account/migrations/0001_initial.py create mode 100644 zod_bank/account/migrations/__init__.py create mode 100644 zod_bank/account/models.py create mode 100644 zod_bank/account/serializers.py create mode 100644 zod_bank/account/tests.py create mode 100644 zod_bank/account/urls.py create mode 100644 zod_bank/account/utils.py create mode 100644 zod_bank/account/views.py create mode 100644 zod_bank/base/__init__.py create mode 100644 zod_bank/base/common_email.py create mode 100644 zod_bank/base/constants.py create mode 100644 zod_bank/base/custom_exceptions.py create mode 100644 zod_bank/base/image_constants.py create mode 100644 zod_bank/base/messages.py create mode 100644 zod_bank/base/routers.py create mode 100644 zod_bank/base/search_and_filters.py create mode 100644 zod_bank/base/upload_file.py create mode 100644 zod_bank/guardian/__init__.py create mode 100644 zod_bank/guardian/admin.py create mode 100644 zod_bank/guardian/apps.py create mode 100644 zod_bank/guardian/migrations/0001_initial.py create mode 100644 zod_bank/guardian/migrations/__init__.py create mode 100644 zod_bank/guardian/models.py create mode 100644 zod_bank/guardian/serializers.py create mode 100644 zod_bank/guardian/tasks.py create mode 100644 zod_bank/guardian/tests.py create mode 100644 zod_bank/guardian/urls.py create mode 100644 zod_bank/guardian/views.py create mode 100644 zod_bank/junior/__init__.py create mode 100644 zod_bank/junior/admin.py create mode 100644 zod_bank/junior/apps.py create mode 100644 zod_bank/junior/migrations/0001_initial.py create mode 100644 zod_bank/junior/migrations/__init__.py create mode 100644 zod_bank/junior/models.py create mode 100644 zod_bank/junior/serializers.py create mode 100644 zod_bank/junior/tests.py create mode 100644 zod_bank/junior/urls.py create mode 100644 zod_bank/junior/views.py create mode 100755 zod_bank/manage.py create mode 100644 zod_bank/nginx/django.conf create mode 100644 zod_bank/requirements.txt create mode 100644 zod_bank/zod_bank/__init__.py create mode 100644 zod_bank/zod_bank/asgi.py create mode 100644 zod_bank/zod_bank/settings.py create mode 100644 zod_bank/zod_bank/urls.py create mode 100644 zod_bank/zod_bank/wsgi.py diff --git a/zod_bank/account/__init__.py b/zod_bank/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/account/admin.py b/zod_bank/account/admin.py new file mode 100644 index 0000000..fce7bde --- /dev/null +++ b/zod_bank/account/admin.py @@ -0,0 +1,25 @@ +from django.contrib import admin + +# Register your models here. +from .models import UserProfile, UserEmailOtp, UserPhoneOtp +# Register your models here. +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + list_display = ['user'] + + def __str__(self): + return self.user__email + +@admin.register(UserEmailOtp) +class UserEmailOtpAdmin(admin.ModelAdmin): + list_display = ['email'] + + def __str__(self): + return self.email + '-' + self.otp + +@admin.register(UserPhoneOtp) +class UserPhoneOtpAdmin(admin.ModelAdmin): + list_display = ['phone'] + + def __str__(self): + return self.phone + '-' + self.otp diff --git a/zod_bank/account/apps.py b/zod_bank/account/apps.py new file mode 100644 index 0000000..2b08f1a --- /dev/null +++ b/zod_bank/account/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'account' diff --git a/zod_bank/account/migrations/0001_initial.py b/zod_bank/account/migrations/0001_initial.py new file mode 100644 index 0000000..3a50122 --- /dev/null +++ b/zod_bank/account/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserEmailOtp', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(max_length=254)), + ('otp', models.CharField(max_length=10)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('expired_at', models.DateTimeField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + options={ + 'db_table': 'user_email_otp', + }, + ), + migrations.CreateModel( + name='UserPhoneOtp', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('country_code', models.IntegerField()), + ('phone', models.CharField(max_length=17)), + ('otp', models.CharField(max_length=10)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('expired_at', models.DateTimeField(blank=True, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + options={ + 'db_table': 'user_phone_otp', + }, + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_type', models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True)), + ('is_verified', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_active', models.BooleanField(default=False)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'user_profile', + }, + ), + ] diff --git a/zod_bank/account/migrations/__init__.py b/zod_bank/account/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/account/models.py b/zod_bank/account/models.py new file mode 100644 index 0000000..ff8bb5d --- /dev/null +++ b/zod_bank/account/models.py @@ -0,0 +1,75 @@ +from django.db import models +import random +from django.contrib.auth.models import User +from base.constants import USER_TYPE +# Create your models here. + +class UserProfile(models.Model): + """ + User details + """ + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile') + user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_active = models.BooleanField(default=False) + + class Meta(object): + """ Meta information """ + db_table = 'user_profile' + + def __str__(self): + """return phone as an object""" + return f'{self.user}' + +class UserPhoneOtp(models.Model): + """ + This class is used to verify user email and their contact no. + """ + """user details""" + country_code = models.IntegerField() + phone = models.CharField(max_length=17) + """otp details""" + otp = models.CharField(max_length=10) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + expired_at = models.DateTimeField(blank=True, null=True) + is_active = models.BooleanField(default=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_phone_otp' + + def __str__(self): + """return phone as an object""" + return self.phone + +class UserEmailOtp(models.Model): + """ + This class is used to verify user email and their contact no. + """ + """user details""" + email = models.EmailField() + """otp details""" + otp = models.CharField(max_length=10) + is_verified = models.BooleanField(default=False) + + # OTP validity + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + expired_at = models.DateTimeField(blank=True, null=True) + is_active = models.BooleanField(default=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_email_otp' + + def __str__(self): + """return phone as an object""" + return self.email diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py new file mode 100644 index 0000000..03b1012 --- /dev/null +++ b/zod_bank/account/serializers.py @@ -0,0 +1,104 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from guardian.models import Guardian +from junior.models import Junior +from account.models import UserProfile, UserEmailOtp, UserPhoneOtp +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from django.db import transaction +from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR +from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth import authenticate +from rest_framework import viewsets, status +from rest_framework.decorators import action +from django.contrib.auth import authenticate, login +from rest_framework_simplejwt.tokens import RefreshToken + +class SuperUserSerializer(serializers.ModelSerializer): + user_type = serializers.SerializerMethodField('get_user_type') + + def get_user_type(self, obj): + """user type""" + return SUPERUSER + + class Meta(object): + """Meta info""" + model = User + fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type'] + + +class GuardianSerializer(serializers.ModelSerializer): + """guardian serializer""" + user_type = serializers.SerializerMethodField('get_user_type') + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj.user) + access_token = str(refresh.access_token) + return access_token + + def get_user_type(self, obj): + """user type""" + return GUARDIAN + + def get_auth(self, obj): + """user email address""" + return obj.user.username + + def get_first_name(self, obj): + """user first name""" + return obj.user.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.user.last_name + + class Meta(object): + """Meta info""" + model = Guardian + fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', + 'referral_code', 'is_active', 'is_complete_profile', 'passcode', + 'created_at', 'updated_at', 'user_type'] + + +class JuniorSerializer(serializers.ModelSerializer): + """junior serializer""" + user_type = serializers.SerializerMethodField('get_user_type') + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj.auth) + access_token = str(refresh.access_token) + return access_token + + def get_user_type(self, obj): + return JUNIOR + + def get_auth(self, obj): + return obj.auth.username + + def get_first_name(self, obj): + return obj.auth.first_name + + def get_last_name(self, obj): + return obj.auth.last_name + + class Meta(object): + """Meta info""" + model = Junior + fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', + 'updated_at', 'user_type'] + +class EmailVerificationSerializer(serializers.ModelSerializer): + """Email verification serializer""" + class Meta(object): + """Meta info""" + model = UserEmailOtp + fields = '__all__' + diff --git a/zod_bank/account/tests.py b/zod_bank/account/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/account/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py new file mode 100644 index 0000000..1350cea --- /dev/null +++ b/zod_bank/account/urls.py @@ -0,0 +1,20 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +"""Third party import""" +from rest_framework import routers +from .views import UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('user', UserLogin, basename='user') +router.register('superuser', UserLogin, basename='superuser') +router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') +router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') +router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') +router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/account/utils.py b/zod_bank/account/utils.py new file mode 100644 index 0000000..c6ffe7f --- /dev/null +++ b/zod_bank/account/utils.py @@ -0,0 +1,31 @@ +from django.core.mail import send_mail +from django.conf import settings +import random +from rest_framework import viewsets, status +from rest_framework.response import Response +def send_otp_email(recipient_email, otp): + subject = 'One-Time Password' + message = f'Your OTP is: {otp}' + from_email = settings.DEFAULT_FROM_EMAIL + recipient_list = [recipient_email] + send_mail(subject, message, from_email, recipient_list) + return otp + +def custom_response(detail, data=None, response_status=status.HTTP_200_OK): + """Custom response code""" + if not data: + data = None + + return Response({"data": data, "message": detail, "status": "success", "code": response_status}) + + +def custom_error_response(detail, response_status): + """ + function is used for getting same global error response for all + :param detail: error message . + :param response_status: http status. + :return: Json response + """ + if not detail: + detail = {} + return Response({"error": detail, "status": "failed", "code": response_status}) diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py new file mode 100644 index 0000000..352b834 --- /dev/null +++ b/zod_bank/account/views.py @@ -0,0 +1,111 @@ +from rest_framework import viewsets, status +from rest_framework.decorators import action +from rest_framework.response import Response +from django.contrib.auth import authenticate, login +from guardian.models import Guardian +from junior.models import Junior +from account.models import UserProfile, UserPhoneOtp, UserEmailOtp +from django.contrib.auth.models import User +from .serializers import SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer +from django.views.decorators.csrf import csrf_exempt +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +from guardian.tasks import generate_otp + +from account.utils import custom_response, custom_error_response +class SendPhoneOtp(viewsets.ModelViewSet): + """Send otp on phone""" + def create(self, request, *args, **kwargs): + otp = generate_otp() + UserPhoneOtp.objects.create(country_code=self.request.data['country_code'], + phone=self.request.data['phone'], otp=otp) + return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK) + + +class UserPhoneVerification(viewsets.ModelViewSet): + """Send otp on phone""" + def list(self, request, *args, **kwargs): + try: + phone_data = UserPhoneOtp.objects.filter(phone=request.data['phone'], + otp=request.data['otp']).last() + if phone_data: + phone_data.is_verified = True + phone_data.save() + return custom_response(SUCCESS_CODE['3027'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + + + +class UserLogin(viewsets.ViewSet): + @action(methods=['post'], detail=False) + def login(self, request): + username = request.data.get('username') + password = request.data.get('password') + user = authenticate(request, username=username, password=password) + try: + if user is not None: + login(request, user) + guardian_data = Guardian.objects.filter(user__username=username, is_complete_profile=True).last() + if guardian_data: + serializer = GuardianSerializer(guardian_data) + junior_data = Junior.objects.filter(auth__username=username, is_complete_profile=True).last() + if junior_data: + serializer = JuniorSerializer(junior_data) + if user.is_superuser: + serializer = SuperUserSerializer(user) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) + except Exception as e: + user_profile_data = UserProfile.objects.filter(user__username=username).last() + email_verified = UserEmailOtp.objects.filter(email=username).last() + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + data = {"auth_token":access_token, "is_profile_complete": False, + "user_role": user_profile_data.user_type, + } + is_verified = False + if email_verified: + is_verified = email_verified.is_verified + if not is_verified: + otp = generate_otp() + email_verified.otp = otp + email_verified.save() + data.update({"email_otp":otp}) + return custom_response(ERROR_CODE['2024'], {"email_otp":otp, "is_email_verified": is_verified}, + response_status=status.HTTP_400_BAD_REQUEST) + data.update({"is_email_verified": is_verified}) + return custom_response(None, data, response_status=status.HTTP_200_OK) + +class UserEmailVerification(viewsets.ModelViewSet): + """User Email verification""" + serializer_class = EmailVerificationSerializer + + def list(self, request, *args, **kwargs): + try: + email_data = UserEmailOtp.objects.filter(email=request.data['email'], + otp=request.data['otp']).last() + if email_data: + email_data.is_verified = True + email_data.save() + return custom_response(SUCCESS_CODE['3011'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) + +class ReSendEmailOtp(viewsets.ModelViewSet): + """Send otp on phone""" + def create(self, request, *args, **kwargs): + otp = generate_otp() + if User.objects.filter(email=request.data['email']): + UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + return custom_response(None, {'email_otp': otp}, response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) + diff --git a/zod_bank/base/__init__.py b/zod_bank/base/__init__.py new file mode 100644 index 0000000..860fa73 --- /dev/null +++ b/zod_bank/base/__init__.py @@ -0,0 +1,3 @@ +""" +This is init module of the Project Zod Bank +""" diff --git a/zod_bank/base/common_email.py b/zod_bank/base/common_email.py new file mode 100644 index 0000000..4eb2a36 --- /dev/null +++ b/zod_bank/base/common_email.py @@ -0,0 +1,30 @@ +""" +Common send_mail function +""" +import logging + +from django.core.mail import EmailMultiAlternatives + + +def send_mail(subject, message, from_email, recipient_list, html_message=None, cc=None, + fail_silently=False): + """ + Send Email + :param subject: + :param message: + :param from_email: + :param recipient_list: + :param html_message: + :param cc: + :param fail_silently: + :return: + """ + try: + mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, cc) + if html_message: + mail.attach_alternative(html_message, 'text/html') + + return mail.send(fail_silently) + except Exception as e: + logging.error(e) + return False diff --git a/zod_bank/base/constants.py b/zod_bank/base/constants.py new file mode 100644 index 0000000..72f2b0c --- /dev/null +++ b/zod_bank/base/constants.py @@ -0,0 +1,99 @@ +""" +This module contains constants used throughout the project +""" +import os + +# GOOGLE_URL used for interact with google server to verify user existence. +#GOOGLE_URL = "https://www.googleapis.com/plus/v1/" + + +# Super Admin string constant for 'role' +SUPER_ADMIN = "Super Admin" + +# Define jwt_token_expiration time in minutes for now token will expire after 3 days +JWT_TOKEN_EXPIRATION = 3 * 24 * 60 + +# Define common file extention +FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg", "csv", "doc", "docx", "odt", "pdf", "rtf", "txt", "wks", "wp", + "wpd") + +# Define file size in bytes(5MB = 5 * 1024 * 1024) +FILE_SIZE = 5 * 1024 * 1024 + +# String constant for configurable date for allocation lock period +ALLOCATION_LOCK_DATE = 1 + +sort_dict = { + '1': 'name', + '2': '-name' +} +USER_TYPE = ( + ('1', 'junior'), + ('2', 'guardian'), + ('3', 'superuser') +) +GENDERS = ( + ('1', 'Male'), + ('2', 'Female') +) +# duplicate name used defined in constant PROJECT_NAME +PROJECT_NAME = 'Zod Bank' +GUARDIAN = 'guardian' +JUNIOR = 'junior' +SUPERUSER = 'superuser' +# numbers used as a constant +NUMBER = { + 'point_zero': 0.0, + 'zero': 0, + 'one': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'twelve': 12, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty_four': 24, + 'twenty_one': 21, + 'twenty_two': 22, + 'twenty_five': 25, + 'thirty': 30, + 'thirty_five': 35, + 'thirty_six': 36, + 'forty': 40, + 'fifty': 50, + 'fifty_nine': 59, + 'sixty': 60, + 'seventy_five': 75, + 'eighty': 80, + 'ninty_five': 95, + 'ninty_six': 96, + 'ninety_nine': 99, + 'hundred': 100, + 'one_one_nine': 119, + 'one_twenty': 120, + 'four_zero_four': 404, + 'five_hundred': 500, + 'minus_one': -1, + 'point_three': 0.3, + 'point_seven': 0.7 +} + +# Define the byte into kb +BYTE_IMAGE_SIZE = 1024 + +# validate file size +MAX_FILE_SIZE = 1024 * 1024 * 5 + + diff --git a/zod_bank/base/custom_exceptions.py b/zod_bank/base/custom_exceptions.py new file mode 100644 index 0000000..5815504 --- /dev/null +++ b/zod_bank/base/custom_exceptions.py @@ -0,0 +1,16 @@ +""" +module containing override conflict error class +""" +# third party imports +from django.utils.translation import ugettext_lazy as _ +from rest_framework import status +from rest_framework.exceptions import APIException + + +class ConflictError(APIException): + """ + Override conflict error + """ + status_code = status.HTTP_409_CONFLICT + default_detail = _('Not allowed request.') + default_code = 'conflict_error' diff --git a/zod_bank/base/image_constants.py b/zod_bank/base/image_constants.py new file mode 100644 index 0000000..0737698 --- /dev/null +++ b/zod_bank/base/image_constants.py @@ -0,0 +1,16 @@ +""" +This module contains constants used throughout the project +""" +from zod_bank.settings import BUCKET_NAME + +# Define S3 folder url +S3_FOLDER_DIR = { + 'user_image': 'user_image/', +} + +# S3 bucket url +S3_URL = "https://"+BUCKET_NAME+".s3.amazonaws.com/" + +S3_FOLDER_URL = { + 'user_image_file': S3_URL+S3_FOLDER_DIR['user_image'], +} diff --git a/zod_bank/base/messages.py b/zod_bank/base/messages.py new file mode 100644 index 0000000..718d6be --- /dev/null +++ b/zod_bank/base/messages.py @@ -0,0 +1,92 @@ +""" +This module contains all the messages used all across the project +""" + +ERROR_CODE_REQUIRED = { + # Error code for email address + "1000": ["Required email address not found."], + # Error code for password + "1001": ["Required password not found."], + # Error code for Required Post parameters + "1002": ["Required POST parameters not found."], + # Error code for Required Get parameters + "1003": ["Required GET parameters not found."], + # Error code for Required Headers + "1004": ["Required headers were not found."], + # Error code for Required Put parameters + "1005": ["Required PUT parameters not found."], + # Error code for Required query parameters + "1006": ["Required query parameters is not valid."], + # Error code for Required Head parameters + "1008": ["Required HEAD parameters not found."] +} + +# Error code +ERROR_CODE = { + "2000": "Email not found.", + "2001": "Your account has not been verified. Please check your email and verify it.", + "2002": "Invalid login credentials.", + "2003": "An account already exists with this email address.", + "2004": "User doesn't exist.", + "2005": "Your account has been activated.", + "2006": "Your account is not activated.", + "2007": "Your account already activated.", + "2008": "Invalid OTP.", + "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.", + "2010": "Invalid Link.", + "2011": "Your profile has not been completed yet.", + "2012": "Password and Confirm password should be same.", + "2013": "Invalid token.", + "2014": "Your old password doesn't match.", + "2015": "Invalid old password.", + "2016": "Invalid search.", + "2017": "{model} object with {pk} does not exist", + "2018": "Attached File not found", + "2019": "Either File extension or File size doesn't meet the requirements", + "2020": "Enter valid mobile number", + "2021": "Already register", + "2022":"Invalid Guardian code", + "2023":"Invalid user", + "2024":"Email not verified" +} +SUCCESS_CODE = { + # Success code for password + "3001": "Sign up successfully", + # Success code for Thank you + "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.", + # Success code for account activation + "3003": "Your account has been activated.", + # Success code for password reset + "3004": "Password reset link has been sent to your email address", + # Success code for link verified + "3005": "Your link has been verified, it's valid", + # Success code for password reset + "3006": "Your password has been reset successfully.", + # Success code for password update + "3007": "Your password has been updated successfully.", + # Success code for valid link + "3008": "You have a valid link.", + # Success code for logged out + "3009": "You have successfully logged out!", + # Success code for check all fields + "3010": "All fields are valid", + "3011": "Email OTP Verified successfully", + "3012": "Phone OTP Verified successfully", + "3013": "Valid Guardian code", + "3014": "Password has been updated successfully." +} + +STATUS_CODE_ERROR = { + # Status code for Invalid Input + "4001": ["Invalid input."], + # Status code for Authentication credentials + "4002": ["Authentication credentials were not provided."], + # Status code for Permission + "4003": ["You do not have permission to perform this action."], + # Status code for not found + "4004": ["Not found."], + # Status code for method not allowed + "4005": ["Method not allowed."] +} + + diff --git a/zod_bank/base/routers.py b/zod_bank/base/routers.py new file mode 100644 index 0000000..e2df0e1 --- /dev/null +++ b/zod_bank/base/routers.py @@ -0,0 +1,17 @@ +""" +Custom routers for job sourcing . +""" +# third party imports +from rest_framework.routers import DefaultRouter + + +class OptionalSlashRouter(DefaultRouter): + """ + optional slash router class + """ + def __init__(self): + """ + explicitly appending '/' in urls if '/' doesn't exists for making common url patterns . + """ + super(OptionalSlashRouter, self).__init__() + self.trailing_slash = '/?' diff --git a/zod_bank/base/search_and_filters.py b/zod_bank/base/search_and_filters.py new file mode 100644 index 0000000..52330ef --- /dev/null +++ b/zod_bank/base/search_and_filters.py @@ -0,0 +1,49 @@ +""" +This module contains search methods that can be used to search over a particular field in a specific model. +Update SEARCH_MAP dict for searching with model name and fields name +""" +# python imports +import operator +from functools import reduce + +# third party imports +from django.contrib.auth import get_user_model +from django.db.models import Q + +SEARCH_MAP = { + get_user_model(): {'search_fields': ['first_name__icontains', 'last_name__icontains', 'username__icontains']} +} + + +def search_query(search_by, search_term): + """ + :param search_by: search fields + :param search_term: search value + :return: return query + """ + query = [] + if search_by: + for key in search_by: + query.append({key: search_term}) + return query + + +def get_search_fields(model): + """ + :param model: model name + :return: dict of searching field name + """ + return SEARCH_MAP[model]['search_fields'] + + +def global_search(search_data, model_name): + """ + :param search_data: search value + :param model_name: model name + :return: query + """ + # get search fields for the above model + search_fields = get_search_fields(model_name) + # build query + query = search_query(search_fields, search_data) + return model_name.objects.filter(reduce(operator.or_, [Q(**x) for x in query])) diff --git a/zod_bank/base/upload_file.py b/zod_bank/base/upload_file.py new file mode 100644 index 0000000..14f9e02 --- /dev/null +++ b/zod_bank/base/upload_file.py @@ -0,0 +1,177 @@ +""" +This file used for file uploaded +""" +import datetime +# python imports +import logging +import mimetypes +import os + +import boto3 +from django.core.files.storage import FileSystemStorage +# django imports +from django.utils.crypto import get_random_string +from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK + +from base import constants +from base.constants import NUMBER +# local import +from resourcekit.settings import base_settings as settings +from resourcekit.settings.base_settings import BASE_DIR + + +def image_upload(folder, file_name, data): + """ + Function to upload files + :param folder:folder location string + :param file_name:file_name without ext string + :param data:data file obj + :return:Dictionary + """ + status = HTTP_400_BAD_REQUEST + img_name = None + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + bucket_name = settings.BUCKET_NAME + MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp') + fss = FileSystemStorage() + file = fss.save('tmp/' + str(file_name), data) + fss.url(file) + tmp_file = os.path.join(MEDIA_ROOT, str(file_name)) + s3_client.upload_file( + tmp_file, bucket_name, folder + str(file_name), + ExtraArgs={'ACL': 'public-read', 'ContentType': data.content_type} + ) + os.unlink(tmp_file) + img_name = file_name + status = HTTP_200_OK + except Exception as e: + error = e + logging.error(e) + return status, error, img_name + + +def file_delete(folder, file_name): + """ + To delete common file + :param folder: folder name str + :param file_name: file_name string type + """ + status = HTTP_400_BAD_REQUEST + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + s3_client.delete_object(Bucket=settings.BUCKET_NAME, Key=str(folder) + str(file_name)) + status = HTTP_200_OK + except Exception as e: + error = e + return status, error + + +def get_aws_obj(folder, file_name): + """ + To get aws file obj + :param folder: folder string type + :param file_name: file_name string type + """ + status = HTTP_400_BAD_REQUEST + obj = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + file_name = folder + file_name + obj = s3_client.get_object(Bucket=settings.BUCKET_NAME, Key=file_name) + status = HTTP_200_OK + except Exception as e: + logging.error(e) + return status, obj + + +def upload_image(post_data, folder): + """ + :param post_data: + :param folder: string type + :return: + """ + upload_obj = None + # Check Post data + if post_data: + date_now = datetime.datetime.now() + file_extension = os.path.splitext(str(post_data.name)) + file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower() + rand = get_random_string(NUMBER['twelve']) + image_name = str(rand) + date_now.strftime("%s") + "." + file_extension + upload_obj = image_upload(folder, image_name, post_data) + return upload_obj + + +def upload_voice_kit_image(post_data, folder, image_dir): + """ + :param post_data: + :param folder: string type + :param image_dir: image_dir + :return: + """ + upload_obj = None + # Check Post data + if post_data: + date_now = datetime.datetime.now() + file_extension = os.path.splitext(str(post_data)) + file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower() + rand = get_random_string(NUMBER['twelve']) + image_name = str(rand) + date_now.strftime("%s") + "." + file_extension + upload_obj = voice_kit_image_upload(folder, image_name, post_data, image_dir) + return upload_obj + + +def voice_kit_image_upload(folder, file_name, data, image_dir): + """ + Function to upload files + :param folder:folder location string + :param file_name:file_name without ext string + :param data:data file obj + :return:Dictionary + """ + status = HTTP_400_BAD_REQUEST + img_name = None + error = None + try: + s3_client = boto3.client('s3', + aws_access_key_id=settings.AWS_ACCESS_KEY, + aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, + region_name=settings.AWS_DEFAULT_REGION + ) + + bucket_name = settings.BUCKET_NAME + MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp') + fss = FileSystemStorage() + with open(image_dir+data, 'rb') as f: + file = fss.save('tmp/' + str(file_name), f) + fss.url(file) + tmp_file = os.path.join(MEDIA_ROOT, str(file_name)) + s3_client.upload_file( + tmp_file, bucket_name, folder + str(file_name), + ExtraArgs={'ACL': 'public-read', 'ContentType': mimetypes.guess_type(file_name)[0]} + ) + os.unlink(tmp_file) + img_name = file_name + status = HTTP_200_OK + except Exception as e: + error = e + logging.error(e) + return status, error, img_name + diff --git a/zod_bank/guardian/__init__.py b/zod_bank/guardian/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/guardian/admin.py b/zod_bank/guardian/admin.py new file mode 100644 index 0000000..f2c8fbc --- /dev/null +++ b/zod_bank/guardian/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Guardian +# Register your models here. +@admin.register(Guardian) +class GuardianAdmin(admin.ModelAdmin): + list_display = ['user', 'family_name'] + + def __str__(self): + return self.user__email \ No newline at end of file diff --git a/zod_bank/guardian/apps.py b/zod_bank/guardian/apps.py new file mode 100644 index 0000000..b69ed5d --- /dev/null +++ b/zod_bank/guardian/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CustodianConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'guardian' diff --git a/zod_bank/guardian/migrations/0001_initial.py b/zod_bank/guardian/migrations/0001_initial.py new file mode 100644 index 0000000..0038a3b --- /dev/null +++ b/zod_bank/guardian/migrations/0001_initial.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Guardian', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('country_code', models.IntegerField(blank=True, null=True)), + ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)), + ('family_name', models.CharField(blank=True, default=None, max_length=50, null=True)), + ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=15, null=True)), + ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)), + ('guardian_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('junior_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)), + ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('is_active', models.BooleanField(default=True)), + ('is_complete_profile', models.BooleanField(default=False)), + ('passcode', models.IntegerField(blank=True, default=None, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')), + ], + options={ + 'verbose_name': 'Guardian', + 'db_table': 'guardians', + }, + ), + ] diff --git a/zod_bank/guardian/migrations/__init__.py b/zod_bank/guardian/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/guardian/models.py b/zod_bank/guardian/models.py new file mode 100644 index 0000000..2819bf3 --- /dev/null +++ b/zod_bank/guardian/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.contrib.auth import get_user_model +from base.constants import GENDERS +from django.contrib.postgres.fields import ArrayField +User = get_user_model() +# Create your models here. + +class Guardian(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email') + country_code = models.IntegerField(blank=True, null=True) + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + family_name = models.CharField(max_length=50, null=True, blank=True, default=None) + gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) + dob = models.DateField(max_length=15, null=True, blank=True, default=None) + guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) + junior_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) + referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(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 class """ + db_table = 'guardians' + verbose_name = 'Guardian' + + def __str__(self): + return f'{self.user}' diff --git a/zod_bank/guardian/serializers.py b/zod_bank/guardian/serializers.py new file mode 100644 index 0000000..e55c766 --- /dev/null +++ b/zod_bank/guardian/serializers.py @@ -0,0 +1,91 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from .models import Guardian +from django.db import transaction +import random +from account.models import UserProfile +from junior.models import Junior +from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +class UserSerializer(serializers.ModelSerializer): + auth_token = serializers.SerializerMethodField('get_auth_token') + + + class Meta(object): + model = User + fields = ['email', 'password', 'auth_token'] + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj) + access_token = str(refresh.access_token) + return access_token + def create(self, validated_data): + email = validated_data.get('email') + user_type = self.context + password = validated_data.get('password') + try: + user = User.objects.create_user(username=email, email=email, password=password) + UserProfile.objects.create(user=user, user_type=user_type) + return user + except: + raise serializers.ValidationError({"details":ERROR_CODE['2021']}) + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance + +class CreateGuardianSerializer(serializers.ModelSerializer): + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + phone = serializers.CharField(max_length=20, required=False) + family_name = serializers.CharField(max_length=100, required=False) + country_code = serializers.IntegerField(required=False) + dob = serializers.DateField(required=False) + referral_code = serializers.CharField(max_length=100, required=False) + + class Meta(object): + model = Guardian + fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', + 'is_complete_profile'] + + def get_first_name(self,obj): + return obj.user.first_name + + def get_last_name(self,obj): + return obj.user.last_name + + def get_email(self,obj): + return obj.user.email + + def create(self, validated_data): + user = User.objects.filter(username=self.context['user']).last() + if user: + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + guardian, created = Guardian.objects.get_or_create(user=self.context['user']) + if created: + guardian.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + guardian.guardian_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + if guardian: + guardian.phone = validated_data.get('phone', guardian.phone) + guardian.gender = validated_data.get('gender',guardian.gender) + guardian.family_name = validated_data.get('family_name', guardian.family_name) + guardian.dob = validated_data.get('dob',guardian.dob) + guardian.passcode = validated_data.get('passcode', guardian.passcode) + complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, + guardian.dob, guardian.country_code, user.first_name, user.last_name]) + guardian.is_complete_profile = False + if complete_profile_field: + guardian.is_complete_profile = True + guardian.country_code = validated_data.get('country_code', guardian.country_code) + guardian.save() + return guardian + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/zod_bank/guardian/tasks.py b/zod_bank/guardian/tasks.py new file mode 100644 index 0000000..7a5dd90 --- /dev/null +++ b/zod_bank/guardian/tasks.py @@ -0,0 +1,6 @@ +"""task files""" +"""Django import""" +import random +def generate_otp(): + """generate random otp""" + return ''.join([str(random.randrange(9)) for _ in range(6)]) diff --git a/zod_bank/guardian/tests.py b/zod_bank/guardian/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/guardian/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/guardian/urls.py b/zod_bank/guardian/urls.py new file mode 100644 index 0000000..64aa80f --- /dev/null +++ b/zod_bank/guardian/urls.py @@ -0,0 +1,17 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +from .views import SignupViewset, UpdateGuardianProfile +"""Third party import""" +from rest_framework import routers + +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('sign-up', SignupViewset, basename='sign-up') +router.register('update-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/guardian/views.py b/zod_bank/guardian/views.py new file mode 100644 index 0000000..f43e4c8 --- /dev/null +++ b/zod_bank/guardian/views.py @@ -0,0 +1,44 @@ +from django.shortcuts import render +from rest_framework import (pagination, viewsets, status, generics, mixins) +from .serializers import CreateGuardianSerializer +from rest_framework.decorators import action +from rest_framework.response import Response +from django.views.decorators.csrf import csrf_exempt +# Create your views here. +from rest_framework import viewsets, status +from rest_framework.response import Response +from .serializers import UserSerializer +from django.contrib.auth.models import User +from rest_framework.permissions import IsAuthenticated +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from junior.models import Junior +from account.models import UserEmailOtp +from .tasks import generate_otp +from account.utils import send_otp_email +from account.utils import custom_response, custom_error_response +from base.messages import ERROR_CODE, SUCCESS_CODE +class SignupViewset(viewsets.ModelViewSet): + serializer_class = UserSerializer + + def create(self, request, *args, **kwargs): + serializer = UserSerializer(context=request.data['user_type'], data=request.data) + if serializer.is_valid(): + serializer.save() + otp = generate_otp() + UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + # send_otp_email(request.data['email'], otp) + return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class UpdateGuardianProfile(viewsets.ViewSet): + serializer_class = CreateGuardianSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + serializer = CreateGuardianSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/junior/__init__.py b/zod_bank/junior/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/junior/admin.py b/zod_bank/junior/admin.py new file mode 100644 index 0000000..07d6053 --- /dev/null +++ b/zod_bank/junior/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Junior +# Register your models here. +@admin.register(Junior) +class JuniorAdmin(admin.ModelAdmin): + list_display = ['auth'] + + def __str__(self): + return self.auth__email diff --git a/zod_bank/junior/apps.py b/zod_bank/junior/apps.py new file mode 100644 index 0000000..0232709 --- /dev/null +++ b/zod_bank/junior/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class JuniorConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'junior' diff --git a/zod_bank/junior/migrations/0001_initial.py b/zod_bank/junior/migrations/0001_initial.py new file mode 100644 index 0000000..e451c2e --- /dev/null +++ b/zod_bank/junior/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.2 on 2023-06-23 12:05 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Junior', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)), + ('country_code', models.IntegerField(blank=True, null=True)), + ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=10, null=True)), + ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)), + ('junior_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('guardian_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)), + ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)), + ('is_active', models.BooleanField(default=True)), + ('is_complete_profile', models.BooleanField(default=False)), + ('passcode', models.IntegerField(blank=True, default=None, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('auth', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')), + ], + options={ + 'verbose_name': 'Junior', + 'db_table': 'junior', + }, + ), + ] diff --git a/zod_bank/junior/migrations/__init__.py b/zod_bank/junior/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/junior/models.py b/zod_bank/junior/models.py new file mode 100644 index 0000000..43c9d74 --- /dev/null +++ b/zod_bank/junior/models.py @@ -0,0 +1,32 @@ +from django.db import models +from django.contrib.auth import get_user_model +from base.constants import GENDERS +from guardian.models import Guardian +from django.contrib.postgres.fields import ArrayField +User = get_user_model() +# Create your models here. + +class Junior(models.Model): + auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') + phone = models.CharField(max_length=31, null=True, blank=True, default=None) + country_code = models.IntegerField(blank=True, null=True) + gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) + dob = models.DateField(max_length=15, null=True, blank=True, default=None) + # image = models.ImageField(upload_to='images/') + junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) + guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) + referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + is_active = models.BooleanField(default=True) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(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 class """ + db_table = 'junior' + verbose_name = 'Junior' + + def __str__(self): + return f'{self.auth}' diff --git a/zod_bank/junior/serializers.py b/zod_bank/junior/serializers.py new file mode 100644 index 0000000..3e493e7 --- /dev/null +++ b/zod_bank/junior/serializers.py @@ -0,0 +1,73 @@ +from rest_framework import serializers +from django.contrib.auth.models import User +from .models import Guardian +from django.db import transaction +import random +from account.models import UserProfile +from junior.models import Junior +from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER +from rest_framework_simplejwt.tokens import RefreshToken +from base.messages import ERROR_CODE, SUCCESS_CODE +from django.contrib.postgres.fields import ArrayField + +class ListCharField(serializers.ListField): + child = serializers.CharField() + + def to_representation(self, data): + return data + + def to_internal_value(self, data): + if isinstance(data, list): + return data + raise serializers.ValidationError({"details":"Invalid input. Expected a list of strings."}) + + +class CreateJuniorSerializer(serializers.ModelSerializer): + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + phone = serializers.CharField(max_length=20, required=False) + country_code = serializers.IntegerField(required=False) + dob = serializers.DateField(required=False) + referral_code = serializers.CharField(max_length=100, required=False) + guardian_code = ListCharField(required=False) + + class Meta(object): + model = Junior + fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', + 'passcode', 'is_complete_profile', 'guardian_code'] + + def get_first_name(self,obj): + return obj.auth.first_name + + def get_last_name(self,obj): + return obj.auth.last_name + + def get_email(self,obj): + return obj.auth.email + + def create(self, validated_data): + user = User.objects.filter(username=self.context['user']).last() + if user: + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() + junior, created = Junior.objects.get_or_create(auth=self.context['user']) + if created: + junior.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + junior.junior_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + if junior: + junior.phone = validated_data.get('phone', junior.phone) + junior.gender = validated_data.get('gender',junior.gender) + junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) + junior.dob = validated_data.get('dob',junior.dob) + junior.passcode = validated_data.get('passcode', junior.passcode) + junior.is_complete_profile = validated_data.get('is_complete_profile', junior.is_complete_profile) + junior.country_code = validated_data.get('country_code', junior.country_code) + junior.save() + return junior + + def save(self, **kwargs): + with transaction.atomic(): + instance = super().save(**kwargs) + return instance diff --git a/zod_bank/junior/tests.py b/zod_bank/junior/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/zod_bank/junior/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/zod_bank/junior/urls.py b/zod_bank/junior/urls.py new file mode 100644 index 0000000..cce385d --- /dev/null +++ b/zod_bank/junior/urls.py @@ -0,0 +1,17 @@ +""" Urls files""" +"""Django import""" +from django.urls import path, include +from rest_framework.decorators import api_view +from .views import UpdateJuniorProfile, ValidateGuardianCode +"""Third party import""" +from rest_framework import routers + +"""Router""" +router = routers.SimpleRouter() + +"""API End points with router""" +router.register('profile-update', UpdateJuniorProfile, basename='profile-update') +router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/zod_bank/junior/views.py b/zod_bank/junior/views.py new file mode 100644 index 0000000..4eb3a35 --- /dev/null +++ b/zod_bank/junior/views.py @@ -0,0 +1,40 @@ +from django.shortcuts import render +from rest_framework import (pagination, viewsets, status, generics, mixins) +from rest_framework.decorators import action +from rest_framework.response import Response +from django.views.decorators.csrf import csrf_exempt +# Create your views here. +from rest_framework import viewsets, status +from rest_framework.response import Response +from .serializers import CreateJuniorSerializer +from django.contrib.auth.models import User +from rest_framework.permissions import IsAuthenticated +from base.constants import GUARDIAN, JUNIOR, SUPERUSER +from junior.models import Junior +from guardian.models import Guardian +from base.messages import ERROR_CODE, SUCCESS_CODE +from account.utils import custom_response, custom_error_response + +class UpdateJuniorProfile(viewsets.ViewSet): + serializer_class = CreateJuniorSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + serializer = CreateJuniorSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ValidateGuardianCode(viewsets.ViewSet): + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + guardian_code = request.data.get('guardian_code') + for code in guardian_code: + guardian_data = Guardian.objects.filter(guardian_code=code).exists() + if guardian_data: + return custom_response(SUCCESS_CODE['3028'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/manage.py b/zod_bank/manage.py new file mode 100755 index 0000000..169c7c2 --- /dev/null +++ b/zod_bank/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/zod_bank/nginx/django.conf b/zod_bank/nginx/django.conf new file mode 100644 index 0000000..488e2c0 --- /dev/null +++ b/zod_bank/nginx/django.conf @@ -0,0 +1,24 @@ +upstream web { + ip_hash; + server web:8000; + } + + # portal + server { + location / { + proxy_pass http://web/; + proxy_set_header Host $http_host; + } + listen 8000; + client_max_body_size 512M; + server_name localhost; + proxy_read_timeout 900; + proxy_connect_timeout 900; + proxy_send_timeout 900; + #proxy_set_header Host $http_host; + + location /static { + autoindex on; + alias /usr/src/app/zod_bank/static/; + } + } diff --git a/zod_bank/requirements.txt b/zod_bank/requirements.txt new file mode 100644 index 0000000..e78af44 --- /dev/null +++ b/zod_bank/requirements.txt @@ -0,0 +1,55 @@ +aliyun-python-sdk-core==2.13.36 +aliyun-python-sdk-dysmsapi==2.1.2 +amqp==5.1.1 +asgiref==3.7.2 +async-timeout==4.0.2 +billiard==4.1.0 +boto3==1.26.157 +botocore==1.29.157 +celery==5.3.1 +cffi==1.15.1 +channels==4.0.0 +channels-redis==4.1.0 +click==8.1.3 +click-didyoumean==0.3.0 +click-plugins==1.1.1 +click-repl==0.3.0 +cron-descriptor==1.4.0 +cryptography==41.0.1 +decouple==0.0.7 +Django==4.2.2 +django-celery-beat==2.5.0 +django-celery-results==2.5.1 +django-cors-headers==4.1.0 +django-dotenv==1.4.2 +django-extensions==3.2.3 +django-ses==3.5.0 +django-storages==1.13.2 +django-timezone-field==5.1 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.2.2 +drf-yasg==1.21.6 +inflection==0.5.1 +jmespath==0.10.0 +kombu==5.3.1 +msgpack==1.0.5 +packaging==23.1 +prompt-toolkit==3.0.38 +psycopg==3.1.9 +pycparser==2.21 +PyJWT==2.7.0 +python-crontab==2.7.1 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +pytz==2023.3 +PyYAML==6.0 +redis==4.5.5 +s3transfer==0.6.1 +six==1.16.0 +sqlparse==0.4.4 +typing_extensions==4.6.3 +tzdata==2023.3 +uritemplate==4.1.1 +urllib3==1.26.16 +vine==5.0.0 +wcwidth==0.2.6 diff --git a/zod_bank/zod_bank/__init__.py b/zod_bank/zod_bank/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zod_bank/zod_bank/asgi.py b/zod_bank/zod_bank/asgi.py new file mode 100644 index 0000000..967882b --- /dev/null +++ b/zod_bank/zod_bank/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for ZOD_Bank project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + +application = get_asgi_application() diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/zod_bank/settings.py new file mode 100644 index 0000000..0c8da76 --- /dev/null +++ b/zod_bank/zod_bank/settings.py @@ -0,0 +1,176 @@ +""" +Django settings for ZOD_Bank project. + +Generated by 'django-admin startproject' using Django 3.0.14. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +import os +from dotenv import load_dotenv +from datetime import timedelta +load_dotenv() +# OR, the same with increased verbosity: +load_dotenv(verbose=True) +env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env') +load_dotenv(dotenv_path=env_path) + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +# OR, the same with increased verbosity: +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '-pb+8w#)6qsh+w&tr+q$tholf7=54v%05e^9!lneiqqgtddg6q' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django_extensions', + 'storages', + 'drf_yasg', + 'corsheaders', + 'django.contrib.postgres', + 'rest_framework', + 'django_ses', + 'account', + 'junior', + 'guardian', +] +# CSRF_COOKIE_SECURE = False +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'zod_bank.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'zod_bank.wsgi.application' +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + # 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework_simplejwt.authentication.JWTAuthentication', +] +} +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), +} +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME':os.getenv('DB_NAME'), + 'USER':os.getenv('DB_USERNAME'), + 'PASSWORD':os.getenv('DB_PASSWORD'), + 'HOST':os.getenv('DB_HOST'), + 'PORT':os.getenv('DB_PORT'), + } +} +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# cors header settings +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +CORS_ORIGIN_ALLOW_ALL = True + +CORS_ALLOW_METHODS = ( + 'DELETE', + 'GET', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', +) + +CORS_ALLOW_HEADERS = ( + 'accept', + 'accept-encoding', + 'authorization', + 'content-type', + 'dnt', + 'origin', + 'account-agent', + 'x-csrftoken', + 'x-requested-with', +) + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + + + +STATIC_URL = '/static/' diff --git a/zod_bank/zod_bank/urls.py b/zod_bank/zod_bank/urls.py new file mode 100644 index 0000000..274451e --- /dev/null +++ b/zod_bank/zod_bank/urls.py @@ -0,0 +1,33 @@ +"""ZOD_Bank URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +# third-party import +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import path, include +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from django.urls import path + + +schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, ) + +urlpatterns = [ + path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), + path('admin/', admin.site.urls), + path('', include(('account.urls', 'account'), namespace='account')), + path('', include('guardian.urls')), + path('', include(('junior.urls', 'junior'), namespace='junior')), +] diff --git a/zod_bank/zod_bank/wsgi.py b/zod_bank/zod_bank/wsgi.py new file mode 100644 index 0000000..e6b5c4f --- /dev/null +++ b/zod_bank/zod_bank/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for ZOD_Bank project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') + +application = get_wsgi_application() From 89982f851f2932c4c95b000e8d7ed93e8f2d4f34 Mon Sep 17 00:00:00 2001 From: jain Date: Sat, 24 Jun 2023 18:17:32 +0530 Subject: [PATCH 003/372] forgot password and email verification --- zod_bank/account/serializers.py | 32 +++++++++++ .../templated_email/email_base.email | 54 +++++++++++++++++++ .../email_otp_verification.email | 23 ++++++++ .../email_reset_verification.email | 23 ++++++++ zod_bank/account/urls.py | 6 ++- zod_bank/account/utils.py | 15 ++++-- zod_bank/account/views.py | 50 +++++++++++++++-- zod_bank/base/messages.py | 5 +- zod_bank/guardian/views.py | 3 +- zod_bank/zod_bank/settings.py | 26 +++++++++ 10 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 zod_bank/account/templates/templated_email/email_base.email create mode 100644 zod_bank/account/templates/templated_email/email_otp_verification.email create mode 100644 zod_bank/account/templates/templated_email/email_reset_verification.email diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py index 03b1012..f70f084 100644 --- a/zod_bank/account/serializers.py +++ b/zod_bank/account/serializers.py @@ -13,6 +13,38 @@ from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken +class ResetPasswordSerializer(serializers.Serializer): + """Reset Password after verification""" + verification_code = serializers.CharField(max_length=10) + password = serializers.CharField(required=True) + class Meta(object): + """Meta info""" + model = User + + def create(self, validated_data): + verification_code = validated_data.pop('verification_code') + password = validated_data.pop('password') + print("verification_code===>",verification_code) + print("password===>", password) + user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last() + print("user_opt_details===>",user_opt_details) + if user_opt_details: + print("qqqqqqqqqq") + user_details = User.objects.filter(email=user_opt_details.email).last() + if user_details: + print("333333333==>",user_details.password) + user_details.set_password(password) + user_details.save() + return {'password':password} + return user_opt_details + + + + +class ForgotPasswordSerializer(serializers.Serializer): + """Forget password serializer""" + email = serializers.EmailField() + class SuperUserSerializer(serializers.ModelSerializer): user_type = serializers.SerializerMethodField('get_user_type') diff --git a/zod_bank/account/templates/templated_email/email_base.email b/zod_bank/account/templates/templated_email/email_base.email new file mode 100644 index 0000000..5721e28 --- /dev/null +++ b/zod_bank/account/templates/templated_email/email_base.email @@ -0,0 +1,54 @@ + +{% block subject %}DinDin{% endblock %} +{% load static %} + +{% block html %} + + + + + Zod Bank | OTP + + + + + + + + + + +
+ + + + + {% block plain %} + {% endblock %} + + + +
+
+

-

+

Cheers!

+

Zod Bank Team

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

+ Hi User, +

+ + + + +

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

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

+ Hi User, +

+ + + + +

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

+ + +{% endblock %} diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py index 1350cea..0b3ddb5 100644 --- a/zod_bank/account/urls.py +++ b/zod_bank/account/urls.py @@ -4,7 +4,8 @@ from django.urls import path, include from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers -from .views import UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp +from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, + ForgotPasswordAPIView, ResetPasswordAPIView) """Router""" router = routers.SimpleRouter() @@ -15,6 +16,9 @@ router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') + urlpatterns = [ path('api/v1/', include(router.urls)), + path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), + path('api/v1/reset-password/', ResetPasswordAPIView.as_view()) ] diff --git a/zod_bank/account/utils.py b/zod_bank/account/utils.py index c6ffe7f..2d86106 100644 --- a/zod_bank/account/utils.py +++ b/zod_bank/account/utils.py @@ -3,12 +3,19 @@ from django.conf import settings import random from rest_framework import viewsets, status from rest_framework.response import Response + +from templated_email import send_templated_mail def send_otp_email(recipient_email, otp): - subject = 'One-Time Password' - message = f'Your OTP is: {otp}' - from_email = settings.DEFAULT_FROM_EMAIL + from_email = settings.EMAIL_HOST_USER recipient_list = [recipient_email] - send_mail(subject, message, from_email, recipient_list) + send_templated_mail( + template_name='email_otp_verification.email', + from_email=from_email, + recipient_list=recipient_list, + context={ + 'otp': otp + } + ) return otp def custom_response(detail, data=None, response_status=status.HTTP_200_OK): diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 352b834..321b200 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -1,12 +1,13 @@ -from rest_framework import viewsets, status +from rest_framework import viewsets, status, views from rest_framework.decorators import action -from rest_framework.response import Response +import random from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User -from .serializers import SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer +from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, + ForgotPasswordSerializer, ResetPasswordSerializer) from django.views.decorators.csrf import csrf_exempt from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.views import TokenObtainPairView @@ -15,6 +16,49 @@ from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp from account.utils import custom_response, custom_error_response +from django.core.mail import EmailMessage +from django.core.mail import send_mail +from rest_framework.response import Response +from templated_email import send_templated_mail +import secrets + +class ResetPasswordAPIView(views.APIView): + def post(self, request): + print("request.data====>",request.data) + serializer = ResetPasswordSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + +class ForgotPasswordAPIView(views.APIView): + def post(self, request): + serializer = ForgotPasswordSerializer(data=request.data) + if serializer.is_valid(): + email = serializer.validated_data['email'] + try: + User.objects.get(email=email) + except User.DoesNotExist: + return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) + verification_code = ''.join([str(random.randrange(9)) for _ in range(6)]) + # Send the verification code to the user's email + subject = 'Password Reset Verification Code' + message = f'Your verification code is: {verification_code}' + from_email = 'infozodbank@gmail.com' + 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 + } + ) + UserEmailOtp.objects.create(email=email, otp=verification_code) + return custom_response(SUCCESS_CODE['3015'], {'verification_code': verification_code}, + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" def create(self, request, *args, **kwargs): diff --git a/zod_bank/base/messages.py b/zod_bank/base/messages.py index 718d6be..953d761 100644 --- a/zod_bank/base/messages.py +++ b/zod_bank/base/messages.py @@ -27,7 +27,7 @@ ERROR_CODE = { "2001": "Your account has not been verified. Please check your email and verify it.", "2002": "Invalid login credentials.", "2003": "An account already exists with this email address.", - "2004": "User doesn't exist.", + "2004": "User not found.", "2005": "Your account has been activated.", "2006": "Your account is not activated.", "2007": "Your account already activated.", @@ -73,7 +73,8 @@ SUCCESS_CODE = { "3011": "Email OTP Verified successfully", "3012": "Phone OTP Verified successfully", "3013": "Valid Guardian code", - "3014": "Password has been updated successfully." + "3014": "Password has been updated successfully.", + "3015": "Verification code sent on your email." } STATUS_CODE_ERROR = { diff --git a/zod_bank/guardian/views.py b/zod_bank/guardian/views.py index f43e4c8..2b40702 100644 --- a/zod_bank/guardian/views.py +++ b/zod_bank/guardian/views.py @@ -17,6 +17,7 @@ from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE + class SignupViewset(viewsets.ModelViewSet): serializer_class = UserSerializer @@ -26,7 +27,7 @@ class SignupViewset(viewsets.ModelViewSet): serializer.save() otp = generate_otp() UserEmailOtp.objects.create(email=request.data['email'], otp=otp) - # send_otp_email(request.data['email'], otp) + send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/zod_bank/settings.py index 0c8da76..9e47b30 100644 --- a/zod_bank/zod_bank/settings.py +++ b/zod_bank/zod_bank/settings.py @@ -171,6 +171,32 @@ CORS_ALLOW_HEADERS = ( # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ +# +# MAIL_MAILER='smtp' +# MAIL_HOST='smtp.gmail.com' +# MAIL_PORT=587 +# mail_username='infozodbank@gmail.com' +# MAIL_PASSWORD='ghwdmznwwslvchga' +# MAIL_ENCRYPTION='tls' +# mail_from_address='infozodbank@gmail.com' +# MAIL_FROM_NAME="${APP_NAME}" + +# MAIL_MAILER='smtp' +# MAIL_HOST='smtp.gmail.com' +# MAIL_PORT=587 +# mail_username='ankita.jain@kiwitech.com' +# MAIL_PASSWORD='jaijain0912@' +# MAIL_ENCRYPTION='tls' +# mail_from_address='infozodbank@gmail.com' +# MAIL_FROM_NAME="${APP_NAME}" + +# Email settings +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = 'infozodbank@gmail.com' +EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' # Replace with your Gmail email password or App password STATIC_URL = '/static/' From 9db620d8186985344cfeda40bac4a6ffaa681d22 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 26 Jun 2023 10:38:49 +0530 Subject: [PATCH 004/372] forgot, reset and change password --- zod_bank/account/serializers.py | 28 ++++++++++++++++++- .../email_reset_verification.email | 2 +- zod_bank/account/urls.py | 5 ++-- zod_bank/account/views.py | 23 +++++++++++---- zod_bank/base/messages.py | 2 +- zod_bank/guardian/urls.py | 2 +- zod_bank/junior/urls.py | 2 +- 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py index f70f084..94c774f 100644 --- a/zod_bank/account/serializers.py +++ b/zod_bank/account/serializers.py @@ -36,8 +36,34 @@ class ResetPasswordSerializer(serializers.Serializer): user_details.set_password(password) user_details.save() return {'password':password} - return user_opt_details + return user_opt_details + return '' +class ChangePasswordSerializer(serializers.Serializer): + """Update Password after verification""" + current_password = serializers.CharField(max_length=100) + new_password = serializers.CharField(required=True) + class Meta(object): + """Meta info""" + model = User + + def validate_current_password(self, value): + user = self.context + if self.context.password not in ('', None): + if user.check_password(value): + return value + raise serializers.ValidationError({"error":"Invalid Current password"}) + def create(self, validated_data): + new_password = validated_data.pop('new_password') + user_details = User.objects.filter(email=self.context).last() + print("user_details==>", user_details) + if user_details: + print("333333333==>",user_details.password) + user_details.set_password(new_password) + user_details.save() + return {'password':new_password} + return user_details + return '' diff --git a/zod_bank/account/templates/templated_email/email_reset_verification.email b/zod_bank/account/templates/templated_email/email_reset_verification.email index 0138d99..e2f8ebf 100644 --- a/zod_bank/account/templates/templated_email/email_reset_verification.email +++ b/zod_bank/account/templates/templated_email/email_reset_verification.email @@ -1,7 +1,7 @@ {% extends "templated_email/email_base.email" %} {% block subject %} - Reset Password Verification + Password Reset Verification Code {% endblock %} {% block plain %} diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py index 0b3ddb5..3c1fc2c 100644 --- a/zod_bank/account/urls.py +++ b/zod_bank/account/urls.py @@ -5,7 +5,7 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView) """Router""" router = routers.SimpleRouter() @@ -20,5 +20,6 @@ router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), - path('api/v1/reset-password/', ResetPasswordAPIView.as_view()) + path('api/v1/reset-password/', ResetPasswordAPIView.as_view()), + path('api/v1/change-password/', ChangePasswordAPIView.as_view()) ] diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 321b200..82039b9 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -7,21 +7,34 @@ from junior.models import Junior from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, - ForgotPasswordSerializer, ResetPasswordSerializer) + ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer) from django.views.decorators.csrf import csrf_exempt from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp - +from django.conf import settings from account.utils import custom_response, custom_error_response from django.core.mail import EmailMessage from django.core.mail import send_mail from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail import secrets + +class ChangePasswordAPIView(views.APIView): + permission_classes = [IsAuthenticated] + def post(self, request): + print("request.data====>",request.data) + print("request.user====>", request.user) + serializer = ChangePasswordSerializer(context=request.user, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + class ResetPasswordAPIView(views.APIView): def post(self, request): print("request.data====>",request.data) @@ -42,9 +55,7 @@ class ForgotPasswordAPIView(views.APIView): return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) verification_code = ''.join([str(random.randrange(9)) for _ in range(6)]) # Send the verification code to the user's email - subject = 'Password Reset Verification Code' - message = f'Your verification code is: {verification_code}' - from_email = 'infozodbank@gmail.com' + from_email = settings.EMAIL_HOST_USER recipient_list = [email] send_templated_mail( template_name='email_reset_verification.email', @@ -102,7 +113,7 @@ class UserLogin(viewsets.ViewSet): serializer = JuniorSerializer(junior_data) if user.is_superuser: serializer = SuperUserSerializer(user) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) except Exception as e: diff --git a/zod_bank/base/messages.py b/zod_bank/base/messages.py index 953d761..39f0282 100644 --- a/zod_bank/base/messages.py +++ b/zod_bank/base/messages.py @@ -55,7 +55,7 @@ SUCCESS_CODE = { # Success code for Thank you "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.", # Success code for account activation - "3003": "Your account has been activated.", + "3003": "Log in successfully", # Success code for password reset "3004": "Password reset link has been sent to your email address", # Success code for link verified diff --git a/zod_bank/guardian/urls.py b/zod_bank/guardian/urls.py index 64aa80f..0e3f898 100644 --- a/zod_bank/guardian/urls.py +++ b/zod_bank/guardian/urls.py @@ -11,7 +11,7 @@ router = routers.SimpleRouter() """API End points with router""" router.register('sign-up', SignupViewset, basename='sign-up') -router.register('update-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +router.register('complete-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/zod_bank/junior/urls.py b/zod_bank/junior/urls.py index cce385d..27a312e 100644 --- a/zod_bank/junior/urls.py +++ b/zod_bank/junior/urls.py @@ -10,7 +10,7 @@ from rest_framework import routers router = routers.SimpleRouter() """API End points with router""" -router.register('profile-update', UpdateJuniorProfile, basename='profile-update') +router.register('complete-junior-profile', UpdateJuniorProfile, basename='profile-update') router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') urlpatterns = [ path('api/v1/', include(router.urls)), From 9bd31f9e864fdeffb917ae678027cf629c1d01a8 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 26 Jun 2023 16:44:32 +0530 Subject: [PATCH 005/372] jira-9 changes in phone verification --- zod_bank/account/serializers.py | 2 +- zod_bank/account/urls.py | 2 +- zod_bank/account/views.py | 34 ++++++++++++-------- zod_bank/base/constants.py | 50 +---------------------------- zod_bank/base/custom_exceptions.py | 16 --------- zod_bank/base/search_and_filters.py | 49 ---------------------------- zod_bank/base/upload_file.py | 4 +-- zod_bank/guardian/models.py | 1 - zod_bank/guardian/urls.py | 2 +- zod_bank/junior/urls.py | 2 +- zod_bank/junior/views.py | 2 +- zod_bank/requirements.txt | 5 +++ 12 files changed, 33 insertions(+), 136 deletions(-) delete mode 100644 zod_bank/base/custom_exceptions.py delete mode 100644 zod_bank/base/search_and_filters.py diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py index 94c774f..2f18213 100644 --- a/zod_bank/account/serializers.py +++ b/zod_bank/account/serializers.py @@ -13,6 +13,7 @@ from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken + class ResetPasswordSerializer(serializers.Serializer): """Reset Password after verification""" verification_code = serializers.CharField(max_length=10) @@ -62,7 +63,6 @@ class ChangePasswordSerializer(serializers.Serializer): user_details.set_password(new_password) user_details.save() return {'password':new_password} - return user_details return '' diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py index 3c1fc2c..0df4826 100644 --- a/zod_bank/account/urls.py +++ b/zod_bank/account/urls.py @@ -11,7 +11,7 @@ router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') -router.register('superuser', UserLogin, basename='superuser') +router.register('admin', UserLogin, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 82039b9..e3a9550 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -9,8 +9,6 @@ from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer) from django.views.decorators.csrf import csrf_exempt -from rest_framework_simplejwt.serializers import TokenObtainPairSerializer -from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -21,14 +19,11 @@ from django.core.mail import send_mail from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail -import secrets class ChangePasswordAPIView(views.APIView): permission_classes = [IsAuthenticated] def post(self, request): - print("request.data====>",request.data) - print("request.user====>", request.user) serializer = ChangePasswordSerializer(context=request.user, data=request.data) if serializer.is_valid(): serializer.save() @@ -37,7 +32,6 @@ class ChangePasswordAPIView(views.APIView): class ResetPasswordAPIView(views.APIView): def post(self, request): - print("request.data====>",request.data) serializer = ResetPasswordSerializer(data=request.data) if serializer.is_valid(): serializer.save() @@ -65,7 +59,10 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - UserEmailOtp.objects.create(email=email, otp=verification_code) + user_data = UserEmailOtp.objects.get_or_create(email=email) + if user_data: + user_data.otp = verification_code + user_data.save() return custom_response(SUCCESS_CODE['3015'], {'verification_code': verification_code}, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) @@ -74,9 +71,15 @@ class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" def create(self, request, *args, **kwargs): otp = generate_otp() - UserPhoneOtp.objects.create(country_code=self.request.data['country_code'], - phone=self.request.data['phone'], otp=otp) - return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK) + phone_number = self.request.data['phone'] + if phone_number.isdigit() and len(phone_number) == 10: + phone_otp, created = UserPhoneOtp.objects.get_or_create(country_code=self.request.data['country_code'], + phone=self.request.data['phone']) + if phone_otp: + phone_otp.otp = otp + phone_otp.save() + return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2020'], response_status=status.HTTP_400_BAD_REQUEST) class UserPhoneVerification(viewsets.ModelViewSet): @@ -88,7 +91,7 @@ class UserPhoneVerification(viewsets.ModelViewSet): if phone_data: phone_data.is_verified = True phone_data.save() - return custom_response(SUCCESS_CODE['3027'], response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3012'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: @@ -132,8 +135,8 @@ class UserLogin(viewsets.ViewSet): email_verified.otp = otp email_verified.save() data.update({"email_otp":otp}) - return custom_response(ERROR_CODE['2024'], {"email_otp":otp, "is_email_verified": is_verified}, - response_status=status.HTTP_400_BAD_REQUEST) + return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, + response_status=status.HTTP_200_OK) data.update({"is_email_verified": is_verified}) return custom_response(None, data, response_status=status.HTTP_200_OK) @@ -159,7 +162,10 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): - UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + email_data = UserEmailOtp.objects.get_or_create(email=request.data['email']) + if email_data: + email_data.otp = otp + email_data.save() return custom_response(None, {'email_otp': otp}, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/base/constants.py b/zod_bank/base/constants.py index 72f2b0c..b66ed5f 100644 --- a/zod_bank/base/constants.py +++ b/zod_bank/base/constants.py @@ -14,8 +14,7 @@ SUPER_ADMIN = "Super Admin" JWT_TOKEN_EXPIRATION = 3 * 24 * 60 # Define common file extention -FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg", "csv", "doc", "docx", "odt", "pdf", "rtf", "txt", "wks", "wp", - "wpd") +FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg") # Define file size in bytes(5MB = 5 * 1024 * 1024) FILE_SIZE = 5 * 1024 * 1024 @@ -42,53 +41,6 @@ GUARDIAN = 'guardian' JUNIOR = 'junior' SUPERUSER = 'superuser' # numbers used as a constant -NUMBER = { - 'point_zero': 0.0, - 'zero': 0, - 'one': 1, - 'two': 2, - 'three': 3, - 'four': 4, - 'five': 5, - 'six': 6, - 'seven': 7, - 'eight': 8, - 'nine': 9, - 'ten': 10, - 'eleven': 11, - 'twelve': 12, - 'thirteen': 13, - 'fourteen': 14, - 'fifteen': 15, - 'sixteen': 16, - 'seventeen': 17, - 'eighteen': 18, - 'nineteen': 19, - 'twenty_four': 24, - 'twenty_one': 21, - 'twenty_two': 22, - 'twenty_five': 25, - 'thirty': 30, - 'thirty_five': 35, - 'thirty_six': 36, - 'forty': 40, - 'fifty': 50, - 'fifty_nine': 59, - 'sixty': 60, - 'seventy_five': 75, - 'eighty': 80, - 'ninty_five': 95, - 'ninty_six': 96, - 'ninety_nine': 99, - 'hundred': 100, - 'one_one_nine': 119, - 'one_twenty': 120, - 'four_zero_four': 404, - 'five_hundred': 500, - 'minus_one': -1, - 'point_three': 0.3, - 'point_seven': 0.7 -} # Define the byte into kb BYTE_IMAGE_SIZE = 1024 diff --git a/zod_bank/base/custom_exceptions.py b/zod_bank/base/custom_exceptions.py deleted file mode 100644 index 5815504..0000000 --- a/zod_bank/base/custom_exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -module containing override conflict error class -""" -# third party imports -from django.utils.translation import ugettext_lazy as _ -from rest_framework import status -from rest_framework.exceptions import APIException - - -class ConflictError(APIException): - """ - Override conflict error - """ - status_code = status.HTTP_409_CONFLICT - default_detail = _('Not allowed request.') - default_code = 'conflict_error' diff --git a/zod_bank/base/search_and_filters.py b/zod_bank/base/search_and_filters.py deleted file mode 100644 index 52330ef..0000000 --- a/zod_bank/base/search_and_filters.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This module contains search methods that can be used to search over a particular field in a specific model. -Update SEARCH_MAP dict for searching with model name and fields name -""" -# python imports -import operator -from functools import reduce - -# third party imports -from django.contrib.auth import get_user_model -from django.db.models import Q - -SEARCH_MAP = { - get_user_model(): {'search_fields': ['first_name__icontains', 'last_name__icontains', 'username__icontains']} -} - - -def search_query(search_by, search_term): - """ - :param search_by: search fields - :param search_term: search value - :return: return query - """ - query = [] - if search_by: - for key in search_by: - query.append({key: search_term}) - return query - - -def get_search_fields(model): - """ - :param model: model name - :return: dict of searching field name - """ - return SEARCH_MAP[model]['search_fields'] - - -def global_search(search_data, model_name): - """ - :param search_data: search value - :param model_name: model name - :return: query - """ - # get search fields for the above model - search_fields = get_search_fields(model_name) - # build query - query = search_query(search_fields, search_data) - return model_name.objects.filter(reduce(operator.or_, [Q(**x) for x in query])) diff --git a/zod_bank/base/upload_file.py b/zod_bank/base/upload_file.py index 14f9e02..2d47ad2 100644 --- a/zod_bank/base/upload_file.py +++ b/zod_bank/base/upload_file.py @@ -16,8 +16,8 @@ from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK from base import constants from base.constants import NUMBER # local import -from resourcekit.settings import base_settings as settings -from resourcekit.settings.base_settings import BASE_DIR +from zod_bank.settings import base_settings as settings +from zod_bank.settings.base_settings import BASE_DIR def image_upload(folder, file_name, data): diff --git a/zod_bank/guardian/models.py b/zod_bank/guardian/models.py index 2819bf3..03ce62a 100644 --- a/zod_bank/guardian/models.py +++ b/zod_bank/guardian/models.py @@ -13,7 +13,6 @@ class Guardian(models.Model): gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) - junior_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) is_active = models.BooleanField(default=True) diff --git a/zod_bank/guardian/urls.py b/zod_bank/guardian/urls.py index 0e3f898..5adfc55 100644 --- a/zod_bank/guardian/urls.py +++ b/zod_bank/guardian/urls.py @@ -11,7 +11,7 @@ router = routers.SimpleRouter() """API End points with router""" router.register('sign-up', SignupViewset, basename='sign-up') -router.register('complete-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/zod_bank/junior/urls.py b/zod_bank/junior/urls.py index 27a312e..1dc2e23 100644 --- a/zod_bank/junior/urls.py +++ b/zod_bank/junior/urls.py @@ -10,7 +10,7 @@ from rest_framework import routers router = routers.SimpleRouter() """API End points with router""" -router.register('complete-junior-profile', UpdateJuniorProfile, basename='profile-update') +router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/zod_bank/junior/views.py b/zod_bank/junior/views.py index 4eb3a35..7eb1063 100644 --- a/zod_bank/junior/views.py +++ b/zod_bank/junior/views.py @@ -25,7 +25,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): if serializer.is_valid(): serializer.save() return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ValidateGuardianCode(viewsets.ViewSet): permission_classes = [IsAuthenticated] diff --git a/zod_bank/requirements.txt b/zod_bank/requirements.txt index e78af44..da73f65 100644 --- a/zod_bank/requirements.txt +++ b/zod_bank/requirements.txt @@ -23,8 +23,12 @@ django-celery-results==2.5.1 django-cors-headers==4.1.0 django-dotenv==1.4.2 django-extensions==3.2.3 +django-phonenumber-field==7.1.0 +django-render-block==0.9.2 django-ses==3.5.0 +django-smtp-ssl==1.0 django-storages==1.13.2 +django-templated-email==3.0.1 django-timezone-field==5.1 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 @@ -34,6 +38,7 @@ jmespath==0.10.0 kombu==5.3.1 msgpack==1.0.5 packaging==23.1 +phonenumbers==8.13.15 prompt-toolkit==3.0.38 psycopg==3.1.9 pycparser==2.21 From 44b25dde3ea542e860898654ce0d4aad116b4ebd Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 11:44:00 +0530 Subject: [PATCH 006/372] Jira-13 sonar fixes --- zod_bank/account/admin.py | 8 ++++- zod_bank/account/utils.py | 4 +-- zod_bank/account/views.py | 28 ++++++++++++++--- zod_bank/guardian/admin.py | 7 ++++- zod_bank/guardian/apps.py | 3 ++ zod_bank/guardian/models.py | 11 ++++++- zod_bank/guardian/serializers.py | 53 +++++++++++++++++++++++--------- zod_bank/guardian/tests.py | 2 ++ zod_bank/guardian/urls.py | 6 ++-- zod_bank/guardian/views.py | 34 ++++++++++---------- zod_bank/junior/admin.py | 5 +++ zod_bank/junior/apps.py | 4 ++- zod_bank/junior/models.py | 13 ++++++-- zod_bank/junior/serializers.py | 35 ++++++++++++++++----- zod_bank/junior/tests.py | 2 ++ zod_bank/junior/urls.py | 4 ++- zod_bank/junior/views.py | 26 ++++++++-------- zod_bank/manage.py | 4 +++ zod_bank/zod_bank/settings.py | 28 +++-------------- 19 files changed, 188 insertions(+), 89 deletions(-) diff --git a/zod_bank/account/admin.py b/zod_bank/account/admin.py index fce7bde..7dbf869 100644 --- a/zod_bank/account/admin.py +++ b/zod_bank/account/admin.py @@ -1,10 +1,12 @@ +"""Account admin""" from django.contrib import admin -# Register your models here. +"""Import django app""" from .models import UserProfile, UserEmailOtp, UserPhoneOtp # Register your models here. @admin.register(UserProfile) class UserProfileAdmin(admin.ModelAdmin): + """User profile admin""" list_display = ['user'] def __str__(self): @@ -12,14 +14,18 @@ class UserProfileAdmin(admin.ModelAdmin): @admin.register(UserEmailOtp) class UserEmailOtpAdmin(admin.ModelAdmin): + """User Email otp admin""" list_display = ['email'] def __str__(self): + """Return object in email and otp format""" return self.email + '-' + self.otp @admin.register(UserPhoneOtp) class UserPhoneOtpAdmin(admin.ModelAdmin): + """User Phone otp admin""" list_display = ['phone'] def __str__(self): + """Return object in phone number and otp format""" return self.phone + '-' + self.otp diff --git a/zod_bank/account/utils.py b/zod_bank/account/utils.py index 2d86106..f20ef19 100644 --- a/zod_bank/account/utils.py +++ b/zod_bank/account/utils.py @@ -1,6 +1,6 @@ -from django.core.mail import send_mail +"""Account utils""" +"""Third party Django app""" from django.conf import settings -import random from rest_framework import viewsets, status from rest_framework.response import Response diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index e3a9550..3a6d6f6 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -1,6 +1,7 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random +import logging from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior @@ -8,7 +9,6 @@ from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer) -from django.views.decorators.csrf import csrf_exempt from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -94,7 +94,7 @@ class UserPhoneVerification(viewsets.ModelViewSet): return custom_response(SUCCESS_CODE['3012'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) - except Exception as e: + except Exception: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) @@ -114,12 +114,11 @@ class UserLogin(viewsets.ViewSet): junior_data = Junior.objects.filter(auth__username=username, is_complete_profile=True).last() if junior_data: serializer = JuniorSerializer(junior_data) - if user.is_superuser: - serializer = SuperUserSerializer(user) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) except Exception as e: + logging.error(e) user_profile_data = UserProfile.objects.filter(user__username=username).last() email_verified = UserEmailOtp.objects.filter(email=username).last() refresh = RefreshToken.for_user(user) @@ -140,6 +139,26 @@ class UserLogin(viewsets.ViewSet): data.update({"is_email_verified": is_verified}) return custom_response(None, data, response_status=status.HTTP_200_OK) + @action(methods=['post'], detail=False) + def admin_login(self, request): + username = request.data.get('username') + password = request.data.get('password') + user = authenticate(request, username=username, password=password) + try: + if user is not None: + login(request, user) + if user.is_superuser: + serializer = SuperUserSerializer(user) + return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) + except Exception as e: + logging.error(e) + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + data = {"auth_token": access_token, "user_role": '3'} + return custom_response(None, data, response_status=status.HTTP_200_OK) + class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer @@ -155,6 +174,7 @@ class UserEmailVerification(viewsets.ModelViewSet): else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: + logging.error(e) return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) class ReSendEmailOtp(viewsets.ModelViewSet): diff --git a/zod_bank/guardian/admin.py b/zod_bank/guardian/admin.py index f2c8fbc..a3bd40c 100644 --- a/zod_bank/guardian/admin.py +++ b/zod_bank/guardian/admin.py @@ -1,9 +1,14 @@ +"""Guardian admin""" +"""Third party Django app""" from django.contrib import admin +"""Import Django app""" from .models import Guardian # Register your models here. @admin.register(Guardian) class GuardianAdmin(admin.ModelAdmin): + """Junior Admin""" list_display = ['user', 'family_name'] def __str__(self): - return self.user__email \ No newline at end of file + """Return email id""" + return self.user__email diff --git a/zod_bank/guardian/apps.py b/zod_bank/guardian/apps.py index b69ed5d..fcaf209 100644 --- a/zod_bank/guardian/apps.py +++ b/zod_bank/guardian/apps.py @@ -1,6 +1,9 @@ +"""Guardian app file""" +"""Third party Django app""" from django.apps import AppConfig class CustodianConfig(AppConfig): + """Guardian config""" default_auto_field = 'django.db.models.BigAutoField' name = 'guardian' diff --git a/zod_bank/guardian/models.py b/zod_bank/guardian/models.py index 03ce62a..f135bae 100644 --- a/zod_bank/guardian/models.py +++ b/zod_bank/guardian/models.py @@ -1,23 +1,31 @@ +"""Guardian model file""" +"""Third party Django app""" from django.db import models from django.contrib.auth import get_user_model +"""Import Django app""" from base.constants import GENDERS -from django.contrib.postgres.fields import ArrayField User = get_user_model() # Create your models here. class Guardian(models.Model): + """Guardian model""" user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email') + """Contact details""" country_code = models.IntegerField(blank=True, null=True) phone = models.CharField(max_length=31, null=True, blank=True, default=None) + """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) + """Codes""" guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + """Profile activity""" is_active = models.BooleanField(default=True) is_complete_profile = models.BooleanField(default=False) passcode = models.IntegerField(null=True, blank=True, default=None) + """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -27,4 +35,5 @@ class Guardian(models.Model): verbose_name = 'Guardian' def __str__(self): + """Return email id""" return f'{self.user}' diff --git a/zod_bank/guardian/serializers.py b/zod_bank/guardian/serializers.py index e55c766..e2bd807 100644 --- a/zod_bank/guardian/serializers.py +++ b/zod_bank/guardian/serializers.py @@ -1,91 +1,116 @@ -from rest_framework import serializers -from django.contrib.auth.models import User -from .models import Guardian -from django.db import transaction +"""Serializer of Guardian""" +"""Third party Django app""" +import logging import random -from account.models import UserProfile -from junior.models import Junior -from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER +from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken +from django.db import transaction +from django.contrib.auth.models import User +"""Import Django app""" +from .models import Guardian +from account.models import UserProfile from base.messages import ERROR_CODE, SUCCESS_CODE class UserSerializer(serializers.ModelSerializer): + """User serializer""" auth_token = serializers.SerializerMethodField('get_auth_token') - class Meta(object): + """Meta info""" model = User fields = ['email', 'password', 'auth_token'] def get_auth_token(self, obj): + """generate auth token""" refresh = RefreshToken.for_user(obj) access_token = str(refresh.access_token) return access_token def create(self, validated_data): + """fetch data""" email = validated_data.get('email') user_type = self.context password = validated_data.get('password') try: + """Create user profile""" user = User.objects.create_user(username=email, email=email, password=password) UserProfile.objects.create(user=user, user_type=user_type) return user - except: + except Exception as e: + """Error handling""" + logging.error(e) raise serializers.ValidationError({"details":ERROR_CODE['2021']}) def save(self, **kwargs): + """save the data""" with transaction.atomic(): instance = super().save(**kwargs) return instance class CreateGuardianSerializer(serializers.ModelSerializer): + """Create guardian serializer""" + """Basic info""" first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') email = serializers.SerializerMethodField('get_email') + """Contact details""" phone = serializers.CharField(max_length=20, required=False) - family_name = serializers.CharField(max_length=100, required=False) country_code = serializers.IntegerField(required=False) + family_name = serializers.CharField(max_length=100, required=False) dob = serializers.DateField(required=False) referral_code = serializers.CharField(max_length=100, required=False) class Meta(object): + """Meta info""" model = Guardian - fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', - 'is_complete_profile'] + fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', + 'dob', 'referral_code', 'passcode', 'is_complete_profile'] def get_first_name(self,obj): + """first name of guardian""" return obj.user.first_name def get_last_name(self,obj): + """last name of guardian""" return obj.user.last_name def get_email(self,obj): + """emailof guardian""" return obj.user.email def create(self, validated_data): + """Create guardian profile""" user = User.objects.filter(username=self.context['user']).last() if user: + """Save first and last name of guardian""" user.first_name = self.context.get('first_name', user.first_name) user.last_name = self.context.get('last_name', user.last_name) user.save() + """Create guardian data""" guardian, created = Guardian.objects.get_or_create(user=self.context['user']) if created: + """Create referral code and guardian code""" guardian.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) guardian.guardian_code = ''.join([str(random.randrange(9)) for _ in range(4)]) if guardian: - guardian.phone = validated_data.get('phone', guardian.phone) + """update details according to the data get from request""" guardian.gender = validated_data.get('gender',guardian.gender) guardian.family_name = validated_data.get('family_name', guardian.family_name) guardian.dob = validated_data.get('dob',guardian.dob) + """Update country code and phone number""" + guardian.phone = validated_data.get('phone', guardian.phone) + guardian.country_code = validated_data.get('country_code', guardian.country_code) guardian.passcode = validated_data.get('passcode', guardian.passcode) + guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) + """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.dob, guardian.country_code, user.first_name, user.last_name]) guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True - guardian.country_code = validated_data.get('country_code', guardian.country_code) guardian.save() return guardian def save(self, **kwargs): + """Save the data into junior table""" with transaction.atomic(): instance = super().save(**kwargs) return instance diff --git a/zod_bank/guardian/tests.py b/zod_bank/guardian/tests.py index 7ce503c..3036e8b 100644 --- a/zod_bank/guardian/tests.py +++ b/zod_bank/guardian/tests.py @@ -1,3 +1,5 @@ +"""Test file of Guardian""" +"""Third party Django app""" from django.test import TestCase # Create your tests here. diff --git a/zod_bank/guardian/urls.py b/zod_bank/guardian/urls.py index 5adfc55..5399f2b 100644 --- a/zod_bank/guardian/urls.py +++ b/zod_bank/guardian/urls.py @@ -1,17 +1,19 @@ """ Urls files""" """Django import""" from django.urls import path, include -from rest_framework.decorators import api_view from .views import SignupViewset, UpdateGuardianProfile """Third party import""" from rest_framework import routers -"""Router""" +"""Define Router""" router = routers.SimpleRouter() """API End points with router""" +"""Sign up API""" router.register('sign-up', SignupViewset, basename='sign-up') +"""Create guardian profile API""" router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +"""Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/zod_bank/guardian/views.py b/zod_bank/guardian/views.py index 2b40702..72e5b94 100644 --- a/zod_bank/guardian/views.py +++ b/zod_bank/guardian/views.py @@ -1,45 +1,47 @@ -from django.shortcuts import render -from rest_framework import (pagination, viewsets, status, generics, mixins) -from .serializers import CreateGuardianSerializer -from rest_framework.decorators import action -from rest_framework.response import Response -from django.views.decorators.csrf import csrf_exempt -# Create your views here. -from rest_framework import viewsets, status -from rest_framework.response import Response -from .serializers import UserSerializer -from django.contrib.auth.models import User +"""Views of Guardian""" +"""Third party Django app""" from rest_framework.permissions import IsAuthenticated -from base.constants import GUARDIAN, JUNIOR, SUPERUSER -from junior.models import Junior +from rest_framework import viewsets, status +"""Import Django app""" +from .serializers import UserSerializer +from .serializers import CreateGuardianSerializer from account.models import UserEmailOtp from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE - +# Create your views here. class SignupViewset(viewsets.ModelViewSet): + """Signup view set""" serializer_class = UserSerializer def create(self, request, *args, **kwargs): + """Create user profile""" serializer = UserSerializer(context=request.data['user_type'], data=request.data) if serializer.is_valid(): serializer.save() + """Generate otp""" otp = generate_otp() UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + """Send email to the register user""" send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class UpdateGuardianProfile(viewsets.ViewSet): + """Update guardian profile""" serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): - serializer = CreateGuardianSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), - "last_name": request.data.get('last_name',' ')}, data=request.data) + """Create guardian profile""" + serializer = CreateGuardianSerializer(context={"user":request.user, + "first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, + data=request.data) if serializer.is_valid(): + """save serializer""" serializer.save() return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/junior/admin.py b/zod_bank/junior/admin.py index 07d6053..87cd7d8 100644 --- a/zod_bank/junior/admin.py +++ b/zod_bank/junior/admin.py @@ -1,9 +1,14 @@ +"""Junior admin""" +"""Third party Django app""" from django.contrib import admin +"""Import Django app""" from .models import Junior # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): + """Junior Admin""" list_display = ['auth'] def __str__(self): + """Return email id""" return self.auth__email diff --git a/zod_bank/junior/apps.py b/zod_bank/junior/apps.py index 0232709..f3df25e 100644 --- a/zod_bank/junior/apps.py +++ b/zod_bank/junior/apps.py @@ -1,6 +1,8 @@ +"""App file""" +"""Import AppConfig""" from django.apps import AppConfig - class JuniorConfig(AppConfig): + """Junior config""" default_auto_field = 'django.db.models.BigAutoField' name = 'junior' diff --git a/zod_bank/junior/models.py b/zod_bank/junior/models.py index 43c9d74..732bb66 100644 --- a/zod_bank/junior/models.py +++ b/zod_bank/junior/models.py @@ -1,25 +1,33 @@ +"""Junior model """ +"""Import django""" from django.db import models from django.contrib.auth import get_user_model -from base.constants import GENDERS -from guardian.models import Guardian from django.contrib.postgres.fields import ArrayField +"""Import django app""" +from base.constants import GENDERS User = get_user_model() # Create your models here. class Junior(models.Model): + """Junior model""" auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') + """Contact details""" phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_code = models.IntegerField(blank=True, null=True) + """Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) # image = models.ImageField(upload_to='images/') + """Codes""" junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + """Profile activity""" is_active = models.BooleanField(default=True) is_complete_profile = models.BooleanField(default=False) passcode = models.IntegerField(null=True, blank=True, default=None) + """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -29,4 +37,5 @@ class Junior(models.Model): verbose_name = 'Junior' def __str__(self): + """Return email id""" return f'{self.auth}' diff --git a/zod_bank/junior/serializers.py b/zod_bank/junior/serializers.py index 3e493e7..5d6df77 100644 --- a/zod_bank/junior/serializers.py +++ b/zod_bank/junior/serializers.py @@ -1,28 +1,29 @@ +"""Serializer file for junior""" +"""Import Django 3rd party app""" from rest_framework import serializers from django.contrib.auth.models import User -from .models import Guardian from django.db import transaction import random -from account.models import UserProfile +"""Import django app""" from junior.models import Junior -from base.constants import GENDERS, GUARDIAN, JUNIOR, SUPERUSER -from rest_framework_simplejwt.tokens import RefreshToken -from base.messages import ERROR_CODE, SUCCESS_CODE -from django.contrib.postgres.fields import ArrayField class ListCharField(serializers.ListField): + """Serializer for Array field""" child = serializers.CharField() def to_representation(self, data): + """to represent the data""" return data def to_internal_value(self, data): + """internal value""" if isinstance(data, list): return data raise serializers.ValidationError({"details":"Invalid input. Expected a list of strings."}) class CreateJuniorSerializer(serializers.ModelSerializer): + """Create junior serializer""" first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') email = serializers.SerializerMethodField('get_email') @@ -33,41 +34,59 @@ class CreateJuniorSerializer(serializers.ModelSerializer): guardian_code = ListCharField(required=False) class Meta(object): + """Meta info""" model = Junior fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'guardian_code'] def get_first_name(self,obj): + """first name of junior""" return obj.auth.first_name def get_last_name(self,obj): + """last name of junior""" return obj.auth.last_name def get_email(self,obj): + """email of junior""" return obj.auth.email def create(self, validated_data): + """Create junior profile""" user = User.objects.filter(username=self.context['user']).last() if user: + """Save first and last name of junior""" user.first_name = self.context.get('first_name', user.first_name) user.last_name = self.context.get('last_name', user.last_name) user.save() + """Create junior data""" junior, created = Junior.objects.get_or_create(auth=self.context['user']) if created: + """Create referral code and junior code""" junior.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) junior.junior_code = ''.join([str(random.randrange(9)) for _ in range(4)]) if junior: - junior.phone = validated_data.get('phone', junior.phone) + """update details according to the data get from request""" junior.gender = validated_data.get('gender',junior.gender) + """Update guardian code""" junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) junior.dob = validated_data.get('dob',junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) - junior.is_complete_profile = validated_data.get('is_complete_profile', junior.is_complete_profile) + """Update country code and phone number""" + junior.phone = validated_data.get('phone', junior.phone) junior.country_code = validated_data.get('country_code', junior.country_code) + junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) + """Complete profile of the junior if below all data are filled""" + complete_profile_field = all([junior.phone, junior.gender, junior.family_name, + junior.dob, junior.country_code, user.first_name, user.last_name]) + junior.is_complete_profile = False + if complete_profile_field: + junior.is_complete_profile = True junior.save() return junior def save(self, **kwargs): + """Save the data into junior table""" with transaction.atomic(): instance = super().save(**kwargs) return instance diff --git a/zod_bank/junior/tests.py b/zod_bank/junior/tests.py index 7ce503c..1a75974 100644 --- a/zod_bank/junior/tests.py +++ b/zod_bank/junior/tests.py @@ -1,3 +1,5 @@ +"""Junior test file""" +"""Import TestCase""" from django.test import TestCase # Create your tests here. diff --git a/zod_bank/junior/urls.py b/zod_bank/junior/urls.py index 1dc2e23..2b64fe4 100644 --- a/zod_bank/junior/urls.py +++ b/zod_bank/junior/urls.py @@ -1,7 +1,6 @@ """ Urls files""" """Django import""" from django.urls import path, include -from rest_framework.decorators import api_view from .views import UpdateJuniorProfile, ValidateGuardianCode """Third party import""" from rest_framework import routers @@ -10,8 +9,11 @@ from rest_framework import routers router = routers.SimpleRouter() """API End points with router""" +"""Create junior profile API""" router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') +"""validate guardian code API""" router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') +"""Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/zod_bank/junior/views.py b/zod_bank/junior/views.py index 7eb1063..4022ba7 100644 --- a/zod_bank/junior/views.py +++ b/zod_bank/junior/views.py @@ -1,36 +1,36 @@ -from django.shortcuts import render -from rest_framework import (pagination, viewsets, status, generics, mixins) -from rest_framework.decorators import action -from rest_framework.response import Response -from django.views.decorators.csrf import csrf_exempt -# Create your views here. +"""Junior view file""" from rest_framework import viewsets, status -from rest_framework.response import Response -from .serializers import CreateJuniorSerializer -from django.contrib.auth.models import User from rest_framework.permissions import IsAuthenticated -from base.constants import GUARDIAN, JUNIOR, SUPERUSER +"""Django app import""" from junior.models import Junior +from .serializers import CreateJuniorSerializer from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE from account.utils import custom_response, custom_error_response - +# Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): + """Update junior profile""" serializer_class = CreateJuniorSerializer permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): - serializer = CreateJuniorSerializer(context={"user":request.user,"first_name":request.data.get('first_name', ''), - "last_name": request.data.get('last_name',' ')}, data=request.data) + """Use CreateJuniorSerializer""" + serializer = CreateJuniorSerializer(context={"user":request.user, + "first_name":request.data.get('first_name', ''), + "last_name": request.data.get('last_name',' ')}, + data=request.data) if serializer.is_valid(): + """save serializer""" serializer.save() return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ValidateGuardianCode(viewsets.ViewSet): + """Check guardian code exist or not""" permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): + """check guardian code""" guardian_code = request.data.get('guardian_code') for code in guardian_code: guardian_data = Guardian.objects.filter(guardian_code=code).exists() diff --git a/zod_bank/manage.py b/zod_bank/manage.py index 169c7c2..fe9e065 100755 --- a/zod_bank/manage.py +++ b/zod_bank/manage.py @@ -1,14 +1,18 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" +"""Django import""" import os import sys def main(): + """Main function""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings') try: + """Import execute from command line function""" from django.core.management import execute_from_command_line except ImportError as exc: + """Show Exception error""" raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/zod_bank/settings.py index 9e47b30..79eaeeb 100644 --- a/zod_bank/zod_bank/settings.py +++ b/zod_bank/zod_bank/settings.py @@ -57,7 +57,7 @@ INSTALLED_APPS = [ 'junior', 'guardian', ] -# CSRF_COOKIE_SECURE = False + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -168,27 +168,9 @@ CORS_ALLOW_HEADERS = ( 'x-requested-with', ) -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.0/howto/static-files/ +"""Static files (CSS, JavaScript, Images) +https://docs.djangoproject.com/en/3.0/howto/static-files/""" -# -# MAIL_MAILER='smtp' -# MAIL_HOST='smtp.gmail.com' -# MAIL_PORT=587 -# mail_username='infozodbank@gmail.com' -# MAIL_PASSWORD='ghwdmznwwslvchga' -# MAIL_ENCRYPTION='tls' -# mail_from_address='infozodbank@gmail.com' -# MAIL_FROM_NAME="${APP_NAME}" - -# MAIL_MAILER='smtp' -# MAIL_HOST='smtp.gmail.com' -# MAIL_PORT=587 -# mail_username='ankita.jain@kiwitech.com' -# MAIL_PASSWORD='jaijain0912@' -# MAIL_ENCRYPTION='tls' -# mail_from_address='infozodbank@gmail.com' -# MAIL_FROM_NAME="${APP_NAME}" # Email settings EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' @@ -196,7 +178,7 @@ EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_USE_TLS = True EMAIL_HOST_USER = 'infozodbank@gmail.com' -EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' # Replace with your Gmail email password or App password - +# Replace with your Gmail email password or App password +EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' STATIC_URL = '/static/' From aaa173063675cef229aeb826050fb54c80b672b8 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 11:46:20 +0530 Subject: [PATCH 007/372] Jira-13 migrate file --- .../0002_remove_guardian_junior_code.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py diff --git a/zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py b/zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py new file mode 100644 index 0000000..6996d0a --- /dev/null +++ b/zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.2 on 2023-06-27 06:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='guardian', + name='junior_code', + ), + ] From 232f082c77109f7314b0e0a7bd6aa23abaebf064 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 14:59:00 +0530 Subject: [PATCH 008/372] jira-7 changes in requirment.txt file --- zod_bank/Dockerfile | 11 +++++++++++ zod_bank/account/views.py | 20 +++++++++++--------- zod_bank/base/messages.py | 5 +++-- zod_bank/docker-compose.yml | 18 ++++++++++++++++++ zod_bank/junior/views.py | 4 ++-- zod_bank/requirements.txt | 1 + 6 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 zod_bank/Dockerfile create mode 100644 zod_bank/docker-compose.yml diff --git a/zod_bank/Dockerfile b/zod_bank/Dockerfile new file mode 100644 index 0000000..77c6fa8 --- /dev/null +++ b/zod_bank/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9 +ENV PYTHONUNBUFFERED 1 +RUN mkdir /usr/src/app +WORKDIR /usr/src/app +COPY . . +RUN apt-get update +RUN apt-get install wkhtmltopdf -y +RUN apt install -y gdal-bin python3-gdal +RUN pip install -r requirements.txt +WORKDIR /usr/src/app + diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 3a6d6f6..c6f65ff 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -13,6 +13,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp from django.conf import settings +from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from django.core.mail import EmailMessage from django.core.mail import send_mail @@ -27,7 +28,7 @@ class ChangePasswordAPIView(views.APIView): serializer = ChangePasswordSerializer(context=request.user, data=request.data) if serializer.is_valid(): serializer.save() - return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK) + 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): @@ -59,11 +60,11 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - user_data = UserEmailOtp.objects.get_or_create(email=email) + user_data, created = UserEmailOtp.objects.get_or_create(email=email) if user_data: user_data.otp = verification_code user_data.save() - return custom_response(SUCCESS_CODE['3015'], {'verification_code': verification_code}, + 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) @@ -86,8 +87,8 @@ class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" def list(self, request, *args, **kwargs): try: - phone_data = UserPhoneOtp.objects.filter(phone=request.data['phone'], - otp=request.data['otp']).last() + phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'), + otp=self.request.GET.get('otp')).last() if phone_data: phone_data.is_verified = True phone_data.save() @@ -165,8 +166,8 @@ class UserEmailVerification(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): try: - email_data = UserEmailOtp.objects.filter(email=request.data['email'], - otp=request.data['otp']).last() + email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'), + otp=self.request.GET.get('otp')).last() if email_data: email_data.is_verified = True email_data.save() @@ -182,11 +183,12 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): - email_data = UserEmailOtp.objects.get_or_create(email=request.data['email']) + email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) if email_data: email_data.otp = otp email_data.save() - return custom_response(None, {'email_otp': otp}, response_status=status.HTTP_200_OK) + send_otp_email(request.data['email'], otp) + return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/base/messages.py b/zod_bank/base/messages.py index 39f0282..37507fd 100644 --- a/zod_bank/base/messages.py +++ b/zod_bank/base/messages.py @@ -63,7 +63,7 @@ SUCCESS_CODE = { # Success code for password reset "3006": "Your password has been reset successfully.", # Success code for password update - "3007": "Your password has been updated successfully.", + "3007": "Your password has been changed successfully.", # Success code for valid link "3008": "You have a valid link.", # Success code for logged out @@ -74,7 +74,8 @@ SUCCESS_CODE = { "3012": "Phone OTP Verified successfully", "3013": "Valid Guardian code", "3014": "Password has been updated successfully.", - "3015": "Verification code sent on your email." + "3015": "Verification code sent on your email.", + "3016": "Send otp on your Email successfully" } STATUS_CODE_ERROR = { diff --git a/zod_bank/docker-compose.yml b/zod_bank/docker-compose.yml new file mode 100644 index 0000000..700bf1b --- /dev/null +++ b/zod_bank/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + nginx: + image: nginx:latest + container_name: nginx + ports: + - "8000:8000" + volumes: + - ./nginx:/etc/nginx/conf.d + - .:/usr/src/app + depends_on: + - web + web: + build: . + container_name: django + command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" + volumes: + - .:/usr/src/app diff --git a/zod_bank/junior/views.py b/zod_bank/junior/views.py index 4022ba7..153bb41 100644 --- a/zod_bank/junior/views.py +++ b/zod_bank/junior/views.py @@ -31,10 +31,10 @@ class ValidateGuardianCode(viewsets.ViewSet): def list(self, request, *args, **kwargs): """check guardian code""" - guardian_code = request.data.get('guardian_code') + guardian_code = self.request.GET.get('guardian_code').split(',') for code in guardian_code: guardian_data = Guardian.objects.filter(guardian_code=code).exists() if guardian_data: - return custom_response(SUCCESS_CODE['3028'], response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/requirements.txt b/zod_bank/requirements.txt index da73f65..a875fbf 100644 --- a/zod_bank/requirements.txt +++ b/zod_bank/requirements.txt @@ -33,6 +33,7 @@ django-timezone-field==5.1 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 drf-yasg==1.21.6 +gunicorn==20.1.0 inflection==0.5.1 jmespath==0.10.0 kombu==5.3.1 From d4eaaada7429daba596b5983b96d11ea40d4d344 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 15:17:46 +0530 Subject: [PATCH 009/372] auth token changes --- zod_bank/account/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index c6f65ff..80a1e1a 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -166,12 +166,15 @@ class UserEmailVerification(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): try: + user_obj = User.objects.filter(username=self.request.GET.get('email')).last() email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'), otp=self.request.GET.get('otp')).last() if email_data: email_data.is_verified = True email_data.save() - return custom_response(SUCCESS_CODE['3011'], response_status=status.HTTP_200_OK) + refresh = RefreshToken.for_user(user_obj) + access_token = str(refresh.access_token) + return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: From 4537f1f4facf7f115770267c080f84cdbdfa270e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 16:45:42 +0530 Subject: [PATCH 010/372] changes --- zod_bank/Dockerfile => Dockerfile | 0 .../docker-compose.yml => docker-compose.yml | 0 zod_bank/requirements.txt => requirements.txt | 0 zod_bank/account/urls.py | 3 ++- zod_bank/account/views.py | 4 ++++ zod_bank/zod_bank/settings.py | 20 ++++++++++++------- 6 files changed, 19 insertions(+), 8 deletions(-) rename zod_bank/Dockerfile => Dockerfile (100%) rename zod_bank/docker-compose.yml => docker-compose.yml (100%) rename zod_bank/requirements.txt => requirements.txt (100%) diff --git a/zod_bank/Dockerfile b/Dockerfile similarity index 100% rename from zod_bank/Dockerfile rename to Dockerfile diff --git a/zod_bank/docker-compose.yml b/docker-compose.yml similarity index 100% rename from zod_bank/docker-compose.yml rename to docker-compose.yml diff --git a/zod_bank/requirements.txt b/requirements.txt similarity index 100% rename from zod_bank/requirements.txt rename to requirements.txt diff --git a/zod_bank/account/urls.py b/zod_bank/account/urls.py index 0df4826..8462378 100644 --- a/zod_bank/account/urls.py +++ b/zod_bank/account/urls.py @@ -5,13 +5,14 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, GoogleLoginAPIViewset) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') +router.register('google-login', GoogleLoginAPIViewset, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 80a1e1a..5e73a14 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -21,6 +21,10 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail +class GoogleLoginAPIViewset(viewsets.ModelViewSet): + """Google Login""" + serializer_class = SocialSignInSerializer + class ChangePasswordAPIView(views.APIView): permission_classes = [IsAuthenticated] diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/zod_bank/settings.py index 79eaeeb..e1527a4 100644 --- a/zod_bank/zod_bank/settings.py +++ b/zod_bank/zod_bank/settings.py @@ -173,12 +173,18 @@ https://docs.djangoproject.com/en/3.0/howto/static-files/""" # Email settings -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'smtp.gmail.com' -EMAIL_PORT = 587 -EMAIL_USE_TLS = True -EMAIL_HOST_USER = 'infozodbank@gmail.com' -# Replace with your Gmail email password or App password -EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' +# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +# EMAIL_HOST = 'smtp.gmail.com' +# EMAIL_PORT = 587 +# EMAIL_USE_TLS = True +# EMAIL_HOST_USER = 'infozodbank@gmail.com' +# # Replace with your Gmail email password or App password +# EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' + +EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +EMAIL_HOST = os.getenv('EMAIL_HOST') +EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') +EMAIL_PORT = os.getenv('EMAIL_PORT') STATIC_URL = '/static/' From 8ff142ce2ffcf22e2a08a3ddf19f9f138c29254e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 16:53:38 +0530 Subject: [PATCH 011/372] changes docker file position --- zod_bank/Dockerfile => Dockerfile | 0 zod_bank/docker-compose.yml => docker-compose.yml | 0 zod_bank/requirements.txt => requirements.txt | 0 zod_bank/account/views.py | 1 - 4 files changed, 1 deletion(-) rename zod_bank/Dockerfile => Dockerfile (100%) rename zod_bank/docker-compose.yml => docker-compose.yml (100%) rename zod_bank/requirements.txt => requirements.txt (100%) diff --git a/zod_bank/Dockerfile b/Dockerfile similarity index 100% rename from zod_bank/Dockerfile rename to Dockerfile diff --git a/zod_bank/docker-compose.yml b/docker-compose.yml similarity index 100% rename from zod_bank/docker-compose.yml rename to docker-compose.yml diff --git a/zod_bank/requirements.txt b/requirements.txt similarity index 100% rename from zod_bank/requirements.txt rename to requirements.txt diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 80a1e1a..ccb52d5 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -194,4 +194,3 @@ class ReSendEmailOtp(viewsets.ModelViewSet): 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) - From 019d028cb64d582db7ff57554a838e7d6b1ae21e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 17:22:59 +0530 Subject: [PATCH 012/372] jira-5 google login --- zod_bank/account/serializers.py | 15 +++++++++++++++ zod_bank/account/views.py | 21 +++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/zod_bank/account/serializers.py b/zod_bank/account/serializers.py index 2f18213..4ee88c4 100644 --- a/zod_bank/account/serializers.py +++ b/zod_bank/account/serializers.py @@ -13,6 +13,21 @@ from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken +class GoogleSignInSerializer(serializers.Serializer): + """Google login Serializer""" + + def create(self, validated_data): + """Create or update user model""" + with transaction.atomic(): + print() + if User.objects.filter(email__iexact=self.validated_data['email']).exists(): + return User.objects.get(email__iexact=self.validated_data['email']) + + if not User.objects.filter(email__iexact=self.validated_data['email']).exists(): + instance = User.objects.create(username=self.validated_data['email'], + email=self.validated_data['email']) + return instance + class ResetPasswordSerializer(serializers.Serializer): """Reset Password after verification""" diff --git a/zod_bank/account/views.py b/zod_bank/account/views.py index 5e73a14..a8f25ab 100644 --- a/zod_bank/account/views.py +++ b/zod_bank/account/views.py @@ -8,7 +8,8 @@ from junior.models import Junior from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, - ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer) + ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, + GoogleSignInSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -23,7 +24,23 @@ from templated_email import send_templated_mail class GoogleLoginAPIViewset(viewsets.ModelViewSet): """Google Login""" - serializer_class = SocialSignInSerializer + serializer_class = GoogleSignInSerializer + + def create(self, request, *args, **kwargs): + """ + Override default behaviour of create method + """ + provider_type = [] + serializer = self.get_serializer(data=request.data) + if serializer.is_valid(raise_exception=True): + provider = self.get_provider_view(request.data.get('provider')) + # if User is not authenticated then send error message + if not provider.is_authenticated(request): + return custom_error_response({}, status.HTTP_400_BAD_REQUEST) + + user = serializer.save() + if User.objects.filter(email__iexact=user.email).exists(): + class ChangePasswordAPIView(views.APIView): From ad582d77d7206c8be59ce1d31bd55d28e60f7db6 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 17:27:19 +0530 Subject: [PATCH 013/372] changes --- {zod_bank/account => account}/__init__.py | 0 {zod_bank/account => account}/admin.py | 0 {zod_bank/account => account}/apps.py | 0 {zod_bank/account => account}/migrations/0001_initial.py | 0 {zod_bank/account => account}/migrations/__init__.py | 0 {zod_bank/account => account}/models.py | 0 {zod_bank/account => account}/serializers.py | 0 .../templates/templated_email/email_base.email | 0 .../templates/templated_email/email_otp_verification.email | 0 .../templates/templated_email/email_reset_verification.email | 0 {zod_bank/account => account}/tests.py | 0 {zod_bank/account => account}/urls.py | 0 {zod_bank/account => account}/utils.py | 0 {zod_bank/account => account}/views.py | 0 {zod_bank/base => base}/__init__.py | 0 {zod_bank/base => base}/common_email.py | 0 {zod_bank/base => base}/constants.py | 0 {zod_bank/base => base}/image_constants.py | 0 {zod_bank/base => base}/messages.py | 0 {zod_bank/base => base}/routers.py | 0 {zod_bank/base => base}/upload_file.py | 0 {zod_bank/guardian => guardian}/__init__.py | 0 {zod_bank/guardian => guardian}/admin.py | 0 {zod_bank/guardian => guardian}/apps.py | 0 {zod_bank/guardian => guardian}/migrations/0001_initial.py | 0 .../migrations/0002_remove_guardian_junior_code.py | 0 {zod_bank/guardian => guardian}/migrations/__init__.py | 0 {zod_bank/guardian => guardian}/models.py | 0 {zod_bank/guardian => guardian}/serializers.py | 0 {zod_bank/guardian => guardian}/tasks.py | 0 {zod_bank/guardian => guardian}/tests.py | 0 {zod_bank/guardian => guardian}/urls.py | 0 {zod_bank/guardian => guardian}/views.py | 0 {zod_bank/junior => junior}/__init__.py | 0 {zod_bank/junior => junior}/admin.py | 0 {zod_bank/junior => junior}/apps.py | 0 {zod_bank/junior => junior}/migrations/0001_initial.py | 0 {zod_bank/junior => junior}/migrations/__init__.py | 0 {zod_bank/junior => junior}/models.py | 0 {zod_bank/junior => junior}/serializers.py | 0 {zod_bank/junior => junior}/tests.py | 0 {zod_bank/junior => junior}/urls.py | 0 {zod_bank/junior => junior}/views.py | 0 zod_bank/manage.py => manage.py | 0 {zod_bank/nginx => nginx}/django.conf | 0 zod_bank/{zod_bank => }/__init__.py | 0 zod_bank/{zod_bank => }/asgi.py | 0 zod_bank/{zod_bank => }/settings.py | 0 zod_bank/{zod_bank => }/urls.py | 0 zod_bank/{zod_bank => }/wsgi.py | 0 50 files changed, 0 insertions(+), 0 deletions(-) rename {zod_bank/account => account}/__init__.py (100%) rename {zod_bank/account => account}/admin.py (100%) rename {zod_bank/account => account}/apps.py (100%) rename {zod_bank/account => account}/migrations/0001_initial.py (100%) rename {zod_bank/account => account}/migrations/__init__.py (100%) rename {zod_bank/account => account}/models.py (100%) rename {zod_bank/account => account}/serializers.py (100%) rename {zod_bank/account => account}/templates/templated_email/email_base.email (100%) rename {zod_bank/account => account}/templates/templated_email/email_otp_verification.email (100%) rename {zod_bank/account => account}/templates/templated_email/email_reset_verification.email (100%) rename {zod_bank/account => account}/tests.py (100%) rename {zod_bank/account => account}/urls.py (100%) rename {zod_bank/account => account}/utils.py (100%) rename {zod_bank/account => account}/views.py (100%) rename {zod_bank/base => base}/__init__.py (100%) rename {zod_bank/base => base}/common_email.py (100%) rename {zod_bank/base => base}/constants.py (100%) rename {zod_bank/base => base}/image_constants.py (100%) rename {zod_bank/base => base}/messages.py (100%) rename {zod_bank/base => base}/routers.py (100%) rename {zod_bank/base => base}/upload_file.py (100%) rename {zod_bank/guardian => guardian}/__init__.py (100%) rename {zod_bank/guardian => guardian}/admin.py (100%) rename {zod_bank/guardian => guardian}/apps.py (100%) rename {zod_bank/guardian => guardian}/migrations/0001_initial.py (100%) rename {zod_bank/guardian => guardian}/migrations/0002_remove_guardian_junior_code.py (100%) rename {zod_bank/guardian => guardian}/migrations/__init__.py (100%) rename {zod_bank/guardian => guardian}/models.py (100%) rename {zod_bank/guardian => guardian}/serializers.py (100%) rename {zod_bank/guardian => guardian}/tasks.py (100%) rename {zod_bank/guardian => guardian}/tests.py (100%) rename {zod_bank/guardian => guardian}/urls.py (100%) rename {zod_bank/guardian => guardian}/views.py (100%) rename {zod_bank/junior => junior}/__init__.py (100%) rename {zod_bank/junior => junior}/admin.py (100%) rename {zod_bank/junior => junior}/apps.py (100%) rename {zod_bank/junior => junior}/migrations/0001_initial.py (100%) rename {zod_bank/junior => junior}/migrations/__init__.py (100%) rename {zod_bank/junior => junior}/models.py (100%) rename {zod_bank/junior => junior}/serializers.py (100%) rename {zod_bank/junior => junior}/tests.py (100%) rename {zod_bank/junior => junior}/urls.py (100%) rename {zod_bank/junior => junior}/views.py (100%) rename zod_bank/manage.py => manage.py (100%) rename {zod_bank/nginx => nginx}/django.conf (100%) rename zod_bank/{zod_bank => }/__init__.py (100%) rename zod_bank/{zod_bank => }/asgi.py (100%) rename zod_bank/{zod_bank => }/settings.py (100%) rename zod_bank/{zod_bank => }/urls.py (100%) rename zod_bank/{zod_bank => }/wsgi.py (100%) diff --git a/zod_bank/account/__init__.py b/account/__init__.py similarity index 100% rename from zod_bank/account/__init__.py rename to account/__init__.py diff --git a/zod_bank/account/admin.py b/account/admin.py similarity index 100% rename from zod_bank/account/admin.py rename to account/admin.py diff --git a/zod_bank/account/apps.py b/account/apps.py similarity index 100% rename from zod_bank/account/apps.py rename to account/apps.py diff --git a/zod_bank/account/migrations/0001_initial.py b/account/migrations/0001_initial.py similarity index 100% rename from zod_bank/account/migrations/0001_initial.py rename to account/migrations/0001_initial.py diff --git a/zod_bank/account/migrations/__init__.py b/account/migrations/__init__.py similarity index 100% rename from zod_bank/account/migrations/__init__.py rename to account/migrations/__init__.py diff --git a/zod_bank/account/models.py b/account/models.py similarity index 100% rename from zod_bank/account/models.py rename to account/models.py diff --git a/zod_bank/account/serializers.py b/account/serializers.py similarity index 100% rename from zod_bank/account/serializers.py rename to account/serializers.py diff --git a/zod_bank/account/templates/templated_email/email_base.email b/account/templates/templated_email/email_base.email similarity index 100% rename from zod_bank/account/templates/templated_email/email_base.email rename to account/templates/templated_email/email_base.email diff --git a/zod_bank/account/templates/templated_email/email_otp_verification.email b/account/templates/templated_email/email_otp_verification.email similarity index 100% rename from zod_bank/account/templates/templated_email/email_otp_verification.email rename to account/templates/templated_email/email_otp_verification.email diff --git a/zod_bank/account/templates/templated_email/email_reset_verification.email b/account/templates/templated_email/email_reset_verification.email similarity index 100% rename from zod_bank/account/templates/templated_email/email_reset_verification.email rename to account/templates/templated_email/email_reset_verification.email diff --git a/zod_bank/account/tests.py b/account/tests.py similarity index 100% rename from zod_bank/account/tests.py rename to account/tests.py diff --git a/zod_bank/account/urls.py b/account/urls.py similarity index 100% rename from zod_bank/account/urls.py rename to account/urls.py diff --git a/zod_bank/account/utils.py b/account/utils.py similarity index 100% rename from zod_bank/account/utils.py rename to account/utils.py diff --git a/zod_bank/account/views.py b/account/views.py similarity index 100% rename from zod_bank/account/views.py rename to account/views.py diff --git a/zod_bank/base/__init__.py b/base/__init__.py similarity index 100% rename from zod_bank/base/__init__.py rename to base/__init__.py diff --git a/zod_bank/base/common_email.py b/base/common_email.py similarity index 100% rename from zod_bank/base/common_email.py rename to base/common_email.py diff --git a/zod_bank/base/constants.py b/base/constants.py similarity index 100% rename from zod_bank/base/constants.py rename to base/constants.py diff --git a/zod_bank/base/image_constants.py b/base/image_constants.py similarity index 100% rename from zod_bank/base/image_constants.py rename to base/image_constants.py diff --git a/zod_bank/base/messages.py b/base/messages.py similarity index 100% rename from zod_bank/base/messages.py rename to base/messages.py diff --git a/zod_bank/base/routers.py b/base/routers.py similarity index 100% rename from zod_bank/base/routers.py rename to base/routers.py diff --git a/zod_bank/base/upload_file.py b/base/upload_file.py similarity index 100% rename from zod_bank/base/upload_file.py rename to base/upload_file.py diff --git a/zod_bank/guardian/__init__.py b/guardian/__init__.py similarity index 100% rename from zod_bank/guardian/__init__.py rename to guardian/__init__.py diff --git a/zod_bank/guardian/admin.py b/guardian/admin.py similarity index 100% rename from zod_bank/guardian/admin.py rename to guardian/admin.py diff --git a/zod_bank/guardian/apps.py b/guardian/apps.py similarity index 100% rename from zod_bank/guardian/apps.py rename to guardian/apps.py diff --git a/zod_bank/guardian/migrations/0001_initial.py b/guardian/migrations/0001_initial.py similarity index 100% rename from zod_bank/guardian/migrations/0001_initial.py rename to guardian/migrations/0001_initial.py diff --git a/zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py b/guardian/migrations/0002_remove_guardian_junior_code.py similarity index 100% rename from zod_bank/guardian/migrations/0002_remove_guardian_junior_code.py rename to guardian/migrations/0002_remove_guardian_junior_code.py diff --git a/zod_bank/guardian/migrations/__init__.py b/guardian/migrations/__init__.py similarity index 100% rename from zod_bank/guardian/migrations/__init__.py rename to guardian/migrations/__init__.py diff --git a/zod_bank/guardian/models.py b/guardian/models.py similarity index 100% rename from zod_bank/guardian/models.py rename to guardian/models.py diff --git a/zod_bank/guardian/serializers.py b/guardian/serializers.py similarity index 100% rename from zod_bank/guardian/serializers.py rename to guardian/serializers.py diff --git a/zod_bank/guardian/tasks.py b/guardian/tasks.py similarity index 100% rename from zod_bank/guardian/tasks.py rename to guardian/tasks.py diff --git a/zod_bank/guardian/tests.py b/guardian/tests.py similarity index 100% rename from zod_bank/guardian/tests.py rename to guardian/tests.py diff --git a/zod_bank/guardian/urls.py b/guardian/urls.py similarity index 100% rename from zod_bank/guardian/urls.py rename to guardian/urls.py diff --git a/zod_bank/guardian/views.py b/guardian/views.py similarity index 100% rename from zod_bank/guardian/views.py rename to guardian/views.py diff --git a/zod_bank/junior/__init__.py b/junior/__init__.py similarity index 100% rename from zod_bank/junior/__init__.py rename to junior/__init__.py diff --git a/zod_bank/junior/admin.py b/junior/admin.py similarity index 100% rename from zod_bank/junior/admin.py rename to junior/admin.py diff --git a/zod_bank/junior/apps.py b/junior/apps.py similarity index 100% rename from zod_bank/junior/apps.py rename to junior/apps.py diff --git a/zod_bank/junior/migrations/0001_initial.py b/junior/migrations/0001_initial.py similarity index 100% rename from zod_bank/junior/migrations/0001_initial.py rename to junior/migrations/0001_initial.py diff --git a/zod_bank/junior/migrations/__init__.py b/junior/migrations/__init__.py similarity index 100% rename from zod_bank/junior/migrations/__init__.py rename to junior/migrations/__init__.py diff --git a/zod_bank/junior/models.py b/junior/models.py similarity index 100% rename from zod_bank/junior/models.py rename to junior/models.py diff --git a/zod_bank/junior/serializers.py b/junior/serializers.py similarity index 100% rename from zod_bank/junior/serializers.py rename to junior/serializers.py diff --git a/zod_bank/junior/tests.py b/junior/tests.py similarity index 100% rename from zod_bank/junior/tests.py rename to junior/tests.py diff --git a/zod_bank/junior/urls.py b/junior/urls.py similarity index 100% rename from zod_bank/junior/urls.py rename to junior/urls.py diff --git a/zod_bank/junior/views.py b/junior/views.py similarity index 100% rename from zod_bank/junior/views.py rename to junior/views.py diff --git a/zod_bank/manage.py b/manage.py similarity index 100% rename from zod_bank/manage.py rename to manage.py diff --git a/zod_bank/nginx/django.conf b/nginx/django.conf similarity index 100% rename from zod_bank/nginx/django.conf rename to nginx/django.conf diff --git a/zod_bank/zod_bank/__init__.py b/zod_bank/__init__.py similarity index 100% rename from zod_bank/zod_bank/__init__.py rename to zod_bank/__init__.py diff --git a/zod_bank/zod_bank/asgi.py b/zod_bank/asgi.py similarity index 100% rename from zod_bank/zod_bank/asgi.py rename to zod_bank/asgi.py diff --git a/zod_bank/zod_bank/settings.py b/zod_bank/settings.py similarity index 100% rename from zod_bank/zod_bank/settings.py rename to zod_bank/settings.py diff --git a/zod_bank/zod_bank/urls.py b/zod_bank/urls.py similarity index 100% rename from zod_bank/zod_bank/urls.py rename to zod_bank/urls.py diff --git a/zod_bank/zod_bank/wsgi.py b/zod_bank/wsgi.py similarity index 100% rename from zod_bank/zod_bank/wsgi.py rename to zod_bank/wsgi.py From 7b6d34f3343fb4a14d83e2177ee4fa07d4630308 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 17:37:12 +0530 Subject: [PATCH 014/372] setting changes --- zod_bank/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 79eaeeb..c9afada 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -182,3 +182,4 @@ EMAIL_HOST_USER = 'infozodbank@gmail.com' EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static') From d208e5252c7961e7420cfe4563240b735e01afb0 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 19:32:57 +0530 Subject: [PATCH 015/372] jira-13 update country name --- .../migrations/0003_guardian_country_name.py | 18 ++++++++++++++++++ guardian/models.py | 1 + guardian/serializers.py | 1 + junior/migrations/0002_junior_country_name.py | 18 ++++++++++++++++++ junior/models.py | 1 + junior/serializers.py | 1 + 6 files changed, 40 insertions(+) create mode 100644 guardian/migrations/0003_guardian_country_name.py create mode 100644 junior/migrations/0002_junior_country_name.py diff --git a/guardian/migrations/0003_guardian_country_name.py b/guardian/migrations/0003_guardian_country_name.py new file mode 100644 index 0000000..ea9858b --- /dev/null +++ b/guardian/migrations/0003_guardian_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-27 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0002_remove_guardian_junior_code'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=30, null=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index f135bae..d9228b7 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -13,6 +13,7 @@ class Guardian(models.Model): """Contact details""" country_code = models.IntegerField(blank=True, null=True) phone = models.CharField(max_length=31, null=True, blank=True, default=None) + country_name = models.CharField(max_length=30, null=True, blank=True, default=None) """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) diff --git a/guardian/serializers.py b/guardian/serializers.py index e2bd807..c4dcfca 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -99,6 +99,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.phone = validated_data.get('phone', guardian.phone) guardian.country_code = validated_data.get('country_code', guardian.country_code) guardian.passcode = validated_data.get('passcode', guardian.passcode) + guardian.country_name = validated_data.get('country_name', guardian.country_name) guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, diff --git a/junior/migrations/0002_junior_country_name.py b/junior/migrations/0002_junior_country_name.py new file mode 100644 index 0000000..0dd74bd --- /dev/null +++ b/junior/migrations/0002_junior_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-27 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=30, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 732bb66..b389e3a 100644 --- a/junior/models.py +++ b/junior/models.py @@ -14,6 +14,7 @@ class Junior(models.Model): """Contact details""" phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_code = models.IntegerField(blank=True, null=True) + country_name = models.CharField(max_length=30, null=True, blank=True, default=None) """Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) diff --git a/junior/serializers.py b/junior/serializers.py index 5d6df77..54d6fac 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -72,6 +72,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) junior.dob = validated_data.get('dob',junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) + junior.country_name = validated_data.get('country_name', junior.country_name) """Update country code and phone number""" junior.phone = validated_data.get('phone', junior.phone) junior.country_code = validated_data.get('country_code', junior.country_code) From 7b02b7dfbc429016682aab95745e37cbc19cd0d0 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 20:16:35 +0530 Subject: [PATCH 016/372] changes in setting.py file --- account/serializers.py | 3 ++- account/urls.py | 4 ++-- account/views.py | 39 +++++++++++++++++++++------------------ zod_bank/settings.py | 3 ++- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 4ee88c4..afd923c 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -15,11 +15,12 @@ from rest_framework_simplejwt.tokens import RefreshToken class GoogleSignInSerializer(serializers.Serializer): """Google login Serializer""" + email = serializers.EmailField() def create(self, validated_data): """Create or update user model""" with transaction.atomic(): - print() + print("validated_data====>",validated_data) if User.objects.filter(email__iexact=self.validated_data['email']).exists(): return User.objects.get(email__iexact=self.validated_data['email']) diff --git a/account/urls.py b/account/urls.py index 8462378..eab8be4 100644 --- a/account/urls.py +++ b/account/urls.py @@ -5,14 +5,14 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, GoogleLoginAPIViewset) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') -router.register('google-login', GoogleLoginAPIViewset, basename='admin') +# router.register('google-login', GoogleLoginAPIViewset, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') diff --git a/account/views.py b/account/views.py index 4fe28a5..69e4e95 100644 --- a/account/views.py +++ b/account/views.py @@ -22,24 +22,27 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail -class GoogleLoginAPIViewset(viewsets.ModelViewSet): - """Google Login""" - serializer_class = GoogleSignInSerializer - - def create(self, request, *args, **kwargs): - """ - Override default behaviour of create method - """ - provider_type = [] - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(raise_exception=True): - provider = self.get_provider_view(request.data.get('provider')) - # if User is not authenticated then send error message - if not provider.is_authenticated(request): - return custom_error_response({}, status.HTTP_400_BAD_REQUEST) - - user = serializer.save() - if User.objects.filter(email__iexact=user.email).exists(): +# class GoogleLoginAPIViewset(viewsets.ModelViewSet): +# """Google Login""" +# serializer_class = GoogleSignInSerializer +# +# def create(self, request, *args, **kwargs): +# """ +# Override default behaviour of create method +# """ +# provider_type = [] +# serializer = self.get_serializer(data=request.data) +# if serializer.is_valid(raise_exception=True): +# # provider = self.get_provider_view(request.data.get('provider')) +# # if User is not authenticated then send error message +# # if not provider.is_authenticated(request): +# # return custom_error_response({}, status.HTTP_400_BAD_REQUEST) +# +# user = serializer.save() +# if User.objects.filter(email__iexact=user.email).exists(): +# print("ppppppppppppp") +# return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK) +# return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index e1527a4..5ef382e 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -187,4 +187,5 @@ EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') EMAIL_PORT = os.getenv('EMAIL_PORT') -STATIC_URL = '/static/' +STATIC_URL = 'static/' +STATIC_ROOT = 'static' From d849153becd2708df25e404d4092af5fbf0b9c0b Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 20:43:22 +0530 Subject: [PATCH 017/372] changes in setting.py file --- zod_bank/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index bd7d3f3..5ef382e 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -189,4 +189,3 @@ EMAIL_PORT = os.getenv('EMAIL_PORT') STATIC_URL = 'static/' STATIC_ROOT = 'static' - From 4a982f1bafc04e0090508dfdb55fad5e7df99371 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 20:44:42 +0530 Subject: [PATCH 018/372] changes in setting.py file --- zod_bank/settings.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 5ef382e..1c758b7 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -173,19 +173,19 @@ https://docs.djangoproject.com/en/3.0/howto/static-files/""" # Email settings -# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -# EMAIL_HOST = 'smtp.gmail.com' -# EMAIL_PORT = 587 -# EMAIL_USE_TLS = True -# EMAIL_HOST_USER = 'infozodbank@gmail.com' -# # Replace with your Gmail email password or App password -# EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.gmail.com' +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = 'infozodbank@gmail.com' +# Replace with your Gmail email password or App password +EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' -EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') -EMAIL_HOST = os.getenv('EMAIL_HOST') -EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') -EMAIL_PORT = os.getenv('EMAIL_PORT') +# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +# EMAIL_HOST = os.getenv('EMAIL_HOST') +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') +# EMAIL_PORT = os.getenv('EMAIL_PORT') STATIC_URL = 'static/' STATIC_ROOT = 'static' From f0a2a4bd4b1a9e94ac12eed23261ad31d795e6cc Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 27 Jun 2023 21:34:40 +0530 Subject: [PATCH 019/372] jira-13 update profile --- guardian/serializers.py | 12 +++++++----- junior/serializers.py | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index c4dcfca..f2c0c46 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -62,7 +62,8 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Meta info""" model = Guardian fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', - 'dob', 'referral_code', 'passcode', 'is_complete_profile'] + 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used', + 'country_name'] def get_first_name(self,obj): """first name of guardian""" @@ -81,9 +82,10 @@ class CreateGuardianSerializer(serializers.ModelSerializer): user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of guardian""" - user.first_name = self.context.get('first_name', user.first_name) - user.last_name = self.context.get('last_name', user.last_name) - user.save() + if self.context.get('first_name') != '' and self.context.get('last_name') != '': + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() """Create guardian data""" guardian, created = Guardian.objects.get_or_create(user=self.context['user']) if created: @@ -102,7 +104,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.country_name = validated_data.get('country_name', guardian.country_name) guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, + complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name]) guardian.is_complete_profile = False if complete_profile_field: diff --git a/junior/serializers.py b/junior/serializers.py index 54d6fac..a741203 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -37,7 +37,8 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', - 'passcode', 'is_complete_profile', 'guardian_code'] + 'passcode', 'is_complete_profile', 'guardian_code', 'referral_code_used', + 'country_name'] def get_first_name(self,obj): """first name of junior""" @@ -56,9 +57,10 @@ class CreateJuniorSerializer(serializers.ModelSerializer): user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of junior""" - user.first_name = self.context.get('first_name', user.first_name) - user.last_name = self.context.get('last_name', user.last_name) - user.save() + if self.context.get('first_name') != '' and self.context.get('last_name') != '': + user.first_name = self.context.get('first_name', user.first_name) + user.last_name = self.context.get('last_name', user.last_name) + user.save() """Create junior data""" junior, created = Junior.objects.get_or_create(auth=self.context['user']) if created: @@ -78,7 +80,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.country_code = validated_data.get('country_code', junior.country_code) junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([junior.phone, junior.gender, junior.family_name, + complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.dob, junior.country_code, user.first_name, user.last_name]) junior.is_complete_profile = False if complete_profile_field: From 9d7f265f408dec10296737cf0847025e481f2ec7 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 11:40:04 +0530 Subject: [PATCH 020/372] jira-9 and jira-12 sendgrid and upload images in alibaba bucket --- account/serializers.py | 41 ++++++++++++++---- account/urls.py | 6 +-- account/utils.py | 2 +- account/views.py | 16 ++++++- base/messages.py | 9 ++-- guardian/migrations/0004_guardian_image.py | 18 ++++++++ guardian/models.py | 2 + guardian/serializers.py | 15 ++++++- guardian/utils.py | 13 ++++++ .../Screenshot_from_2023-06-28_14-22-20.png | Bin 0 -> 144986 bytes .../Screenshot_from_2023-06-28_15-16-09.png | Bin 0 -> 226054 bytes ...nshot_from_2023-06-28_15-16-09_MkRvErJ.png | Bin 0 -> 226054 bytes ...nshot_from_2023-06-28_15-16-09_o1it3PP.png | Bin 0 -> 226054 bytes junior/migrations/0003_junior_image.py | 18 ++++++++ junior/models.py | 2 +- junior/serializers.py | 18 +++++++- zod_bank/settings.py | 35 +++++++++------ 17 files changed, 160 insertions(+), 35 deletions(-) create mode 100644 guardian/migrations/0004_guardian_image.py create mode 100644 guardian/utils.py create mode 100644 images/Screenshot_from_2023-06-28_14-22-20.png create mode 100644 images/Screenshot_from_2023-06-28_15-16-09.png create mode 100644 images/Screenshot_from_2023-06-28_15-16-09_MkRvErJ.png create mode 100644 images/Screenshot_from_2023-06-28_15-16-09_o1it3PP.png create mode 100644 junior/migrations/0003_junior_image.py diff --git a/account/serializers.py b/account/serializers.py index afd923c..5f03fc2 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -12,6 +12,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken +from guardian.utils import upload_image_to_alibaba class GoogleSignInSerializer(serializers.Serializer): """Google login Serializer""" @@ -20,7 +21,6 @@ class GoogleSignInSerializer(serializers.Serializer): def create(self, validated_data): """Create or update user model""" with transaction.atomic(): - print("validated_data====>",validated_data) if User.objects.filter(email__iexact=self.validated_data['email']).exists(): return User.objects.get(email__iexact=self.validated_data['email']) @@ -29,7 +29,34 @@ class GoogleSignInSerializer(serializers.Serializer): email=self.validated_data['email']) return instance +class UpdateGuardianImageSerializer(serializers.ModelSerializer): + """Reset Password after verification""" + class Meta(object): + """Meta info""" + model = Guardian + fields = '__all__' + def update(self, instance, validated_data): + """update image """ + instance.image = validated_data.get('image', instance.image) + instance.save() + return instance + +class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): + """Reset Password after verification""" + class Meta(object): + """Meta info""" + model = Junior + fields = '__all__' + + def update(self, instance, validated_data): + """update image """ + image = validated_data.get('image', instance.image) + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + instance.image = image_url + instance.save() + return instance class ResetPasswordSerializer(serializers.Serializer): """Reset Password after verification""" verification_code = serializers.CharField(max_length=10) @@ -41,15 +68,10 @@ class ResetPasswordSerializer(serializers.Serializer): def create(self, validated_data): verification_code = validated_data.pop('verification_code') password = validated_data.pop('password') - print("verification_code===>",verification_code) - print("password===>", password) user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last() - print("user_opt_details===>",user_opt_details) if user_opt_details: - print("qqqqqqqqqq") user_details = User.objects.filter(email=user_opt_details.email).last() if user_details: - print("333333333==>",user_details.password) user_details.set_password(password) user_details.save() return {'password':password} @@ -69,13 +91,14 @@ class ChangePasswordSerializer(serializers.Serializer): if self.context.password not in ('', None): if user.check_password(value): return value - raise serializers.ValidationError({"error":"Invalid Current password"}) + raise serializers.ValidationError({"details":ERROR_CODE['2015']}) def create(self, validated_data): new_password = validated_data.pop('new_password') + current_password = validated_data.pop('current_password') + if new_password == current_password: + raise serializers.ValidationError({"details": ERROR_CODE['2026']}) user_details = User.objects.filter(email=self.context).last() - print("user_details==>", user_details) if user_details: - print("333333333==>",user_details.password) user_details.set_password(new_password) user_details.save() return {'password':new_password} diff --git a/account/urls.py b/account/urls.py index eab8be4..b9501e6 100644 --- a/account/urls.py +++ b/account/urls.py @@ -5,7 +5,7 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage) """Router""" router = routers.SimpleRouter() @@ -17,10 +17,10 @@ router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') - urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), path('api/v1/reset-password/', ResetPasswordAPIView.as_view()), - path('api/v1/change-password/', ChangePasswordAPIView.as_view()) + path('api/v1/change-password/', ChangePasswordAPIView.as_view()), + path('api/v1/update-profile-image/', UpdateProfileImage.as_view()) ] diff --git a/account/utils.py b/account/utils.py index f20ef19..bfc2399 100644 --- a/account/utils.py +++ b/account/utils.py @@ -6,7 +6,7 @@ from rest_framework.response import Response from templated_email import send_templated_mail def send_otp_email(recipient_email, otp): - from_email = settings.EMAIL_HOST_USER + from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [recipient_email] send_templated_mail( template_name='email_otp_verification.email', diff --git a/account/views.py b/account/views.py index 69e4e95..7ed3918 100644 --- a/account/views.py +++ b/account/views.py @@ -9,7 +9,7 @@ from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, - GoogleSignInSerializer) + GoogleSignInSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -44,7 +44,19 @@ from templated_email import send_templated_mail # return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK) # return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) - +class UpdateProfileImage(views.APIView): + permission_classes = [IsAuthenticated] + def put(self, request, format=None): + if request.data['user_type'] == '1': + junior_query = Junior.objects.filter(auth=request.user).last() + serializer = UpdateJuniorProfileImageSerializer(junior_query, data=request.data, partial=True) + else: + guardian_query = Guardian.objects.filter(user=request.user).last() + serializer = UpdateGuardianImageSerializer(guardian_query, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ChangePasswordAPIView(views.APIView): permission_classes = [IsAuthenticated] diff --git a/base/messages.py b/base/messages.py index 37507fd..f977ee7 100644 --- a/base/messages.py +++ b/base/messages.py @@ -35,7 +35,7 @@ ERROR_CODE = { "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.", "2010": "Invalid Link.", "2011": "Your profile has not been completed yet.", - "2012": "Password and Confirm password should be same.", + "2012": "Phone number already used", "2013": "Invalid token.", "2014": "Your old password doesn't match.", "2015": "Invalid old password.", @@ -47,7 +47,9 @@ ERROR_CODE = { "2021": "Already register", "2022":"Invalid Guardian code", "2023":"Invalid user", - "2024":"Email not verified" + "2024":"Email not verified", + "2025":"Invalid input. Expected a list of strings.", + "2026" : "New password should not same as old password" } SUCCESS_CODE = { # Success code for password @@ -75,7 +77,8 @@ SUCCESS_CODE = { "3013": "Valid Guardian code", "3014": "Password has been updated successfully.", "3015": "Verification code sent on your email.", - "3016": "Send otp on your Email successfully" + "3016": "Send otp on your Email successfully", + "3017": "Profile image update successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/migrations/0004_guardian_image.py b/guardian/migrations/0004_guardian_image.py new file mode 100644 index 0000000..519ed02 --- /dev/null +++ b/guardian/migrations/0004_guardian_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-28 06:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0003_guardian_country_name'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='image', + field=models.ImageField(blank=True, default=None, null=True, upload_to='images/'), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index d9228b7..0512ee4 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -14,6 +14,8 @@ class Guardian(models.Model): country_code = models.IntegerField(blank=True, null=True) phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_name = models.CharField(max_length=30, null=True, blank=True, default=None) + """Image info""" + image = models.ImageField(upload_to='images/', null=True, blank=True, default=None) """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) diff --git a/guardian/serializers.py b/guardian/serializers.py index f2c0c46..1be4638 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -10,6 +10,8 @@ from django.contrib.auth.models import User from .models import Guardian from account.models import UserProfile from base.messages import ERROR_CODE, SUCCESS_CODE +from .utils import upload_image_to_alibaba +from junior.models import Junior class UserSerializer(serializers.ModelSerializer): """User serializer""" auth_token = serializers.SerializerMethodField('get_auth_token') @@ -57,13 +59,14 @@ class CreateGuardianSerializer(serializers.ModelSerializer): family_name = serializers.CharField(max_length=100, required=False) dob = serializers.DateField(required=False) referral_code = serializers.CharField(max_length=100, required=False) + image = serializers.ImageField(required=False) class Meta(object): """Meta info""" model = Guardian fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used', - 'country_name'] + 'country_name', 'image'] def get_first_name(self,obj): """first name of guardian""" @@ -79,6 +82,11 @@ class CreateGuardianSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create guardian profile""" + phone_number = validated_data.pop('phone', None) + guardian_data = Guardian.objects.filter(phone=phone_number) + junior_data = Junior.objects.filter(phone=phone_number) + if guardian_data or junior_data: + raise serializers.ValidationError({"details": ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of guardian""" @@ -109,6 +117,11 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True + image = validated_data.pop('image', None) + if image: + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + guardian.image = image_url guardian.save() return guardian diff --git a/guardian/utils.py b/guardian/utils.py new file mode 100644 index 0000000..0e46c6c --- /dev/null +++ b/guardian/utils.py @@ -0,0 +1,13 @@ +import oss2 +from django.conf import settings +import tempfile + +def upload_image_to_alibaba(image, filename): + # Save the image object to a temporary file + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + temp_file.write(image.read()) + auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET) + 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) + return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{filename}" diff --git a/images/Screenshot_from_2023-06-28_14-22-20.png b/images/Screenshot_from_2023-06-28_14-22-20.png new file mode 100644 index 0000000000000000000000000000000000000000..217186aea78b85d9b90d47ad82fa62163145be13 GIT binary patch literal 144986 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

qlZ)pW&#G;is-UE2m+6d?q&8-E#zW1PmH0c*&8 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�%?LP3o_bte9X2-iJxBmA7;NYWs2=U{Fk$woU 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 LIahYp@4^27zKv*M literal 0 HcmV?d00001 diff --git a/images/Screenshot_from_2023-06-28_15-16-09.png b/images/Screenshot_from_2023-06-28_15-16-09.png new file mode 100644 index 0000000000000000000000000000000000000000..e79b16aedeef85453fd3219184b892d0e782fc1b GIT binary patch literal 226054 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

g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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
g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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
g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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 Date: Thu, 29 Jun 2023 11:49:40 +0530 Subject: [PATCH 021/372] jira-13 image upload changes --- .../migrations/0005_alter_guardian_image.py | 18 ++++++++++++++++++ guardian/models.py | 2 +- guardian/serializers.py | 2 +- .../Screenshot_from_2023-06-28_14-22-20.png | Bin 144986 -> 0 bytes .../Screenshot_from_2023-06-28_15-16-09.png | Bin 226054 -> 0 bytes ...nshot_from_2023-06-28_15-16-09_MkRvErJ.png | Bin 226054 -> 0 bytes ...nshot_from_2023-06-28_15-16-09_o1it3PP.png | Bin 226054 -> 0 bytes junior/migrations/0004_alter_junior_image.py | 18 ++++++++++++++++++ junior/models.py | 2 +- junior/serializers.py | 3 ++- 10 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 guardian/migrations/0005_alter_guardian_image.py delete mode 100644 images/Screenshot_from_2023-06-28_14-22-20.png delete mode 100644 images/Screenshot_from_2023-06-28_15-16-09.png delete mode 100644 images/Screenshot_from_2023-06-28_15-16-09_MkRvErJ.png delete mode 100644 images/Screenshot_from_2023-06-28_15-16-09_o1it3PP.png create mode 100644 junior/migrations/0004_alter_junior_image.py diff --git a/guardian/migrations/0005_alter_guardian_image.py b/guardian/migrations/0005_alter_guardian_image.py new file mode 100644 index 0000000..5899e64 --- /dev/null +++ b/guardian/migrations/0005_alter_guardian_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-29 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0004_guardian_image'), + ] + + operations = [ + migrations.AlterField( + model_name='guardian', + name='image', + field=models.ImageField(blank=True, default=None, null=True, upload_to=''), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 0512ee4..9bb92eb 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -15,7 +15,7 @@ class Guardian(models.Model): phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_name = models.CharField(max_length=30, null=True, blank=True, default=None) """Image info""" - image = models.ImageField(upload_to='images/', null=True, blank=True, default=None) + image = models.ImageField(null=True, blank=True, default=None) """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) diff --git a/guardian/serializers.py b/guardian/serializers.py index 1be4638..a7651b3 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -85,7 +85,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): phone_number = validated_data.pop('phone', None) guardian_data = Guardian.objects.filter(phone=phone_number) junior_data = Junior.objects.filter(phone=phone_number) - if guardian_data or junior_data: + if phone_number and (guardian_data or junior_data): raise serializers.ValidationError({"details": ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: diff --git a/images/Screenshot_from_2023-06-28_14-22-20.png b/images/Screenshot_from_2023-06-28_14-22-20.png deleted file mode 100644 index 217186aea78b85d9b90d47ad82fa62163145be13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144986 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
g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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
g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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
g~Rs37!VK?+W$}cOR%Gw4fcB%i&p^s&aLZ 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",phone_number,'==>',type(phone_number)) guardian_data = Guardian.objects.filter(phone=phone_number) junior_data = Junior.objects.filter(phone=phone_number) - if junior_data or guardian_data: + if phone_number and (junior_data or guardian_data): raise serializers.ValidationError({"details":ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: From 4864d3b499aaaf595f0dc9205931d7be1ffab378 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 13:03:40 +0530 Subject: [PATCH 022/372] add requirement file --- requirements.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/requirements.txt b/requirements.txt index a875fbf..2175525 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ aliyun-python-sdk-core==2.13.36 aliyun-python-sdk-dysmsapi==2.1.2 +aliyun-python-sdk-kms==2.16.1 +aliyun-python-sdk-sts==3.1.1 amqp==5.1.1 asgiref==3.7.2 async-timeout==4.0.2 @@ -7,13 +9,16 @@ billiard==4.1.0 boto3==1.26.157 botocore==1.29.157 celery==5.3.1 +certifi==2023.5.7 cffi==1.15.1 channels==4.0.0 channels-redis==4.1.0 +charset-normalizer==3.1.0 click==8.1.3 click-didyoumean==0.3.0 click-plugins==1.1.1 click-repl==0.3.0 +crcmod==1.7 cron-descriptor==1.4.0 cryptography==41.0.1 decouple==0.0.7 @@ -34,15 +39,19 @@ djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 drf-yasg==1.21.6 gunicorn==20.1.0 +idna==3.4 inflection==0.5.1 jmespath==0.10.0 kombu==5.3.1 msgpack==1.0.5 +oss2==2.18.0 packaging==23.1 phonenumbers==8.13.15 +Pillow==9.5.0 prompt-toolkit==3.0.38 psycopg==3.1.9 pycparser==2.21 +pycryptodome==3.18.0 PyJWT==2.7.0 python-crontab==2.7.1 python-dateutil==2.8.2 @@ -50,6 +59,7 @@ python-dotenv==1.0.0 pytz==2023.3 PyYAML==6.0 redis==4.5.5 +requests==2.31.0 s3transfer==0.6.1 six==1.16.0 sqlparse==0.4.4 From ea2bd635ca57e783f85362a5db637e2a1553e1fa Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 18:03:27 +0530 Subject: [PATCH 023/372] jira-4 login after email verified --- .../migrations/0002_useremailotp_user_type.py | 18 +++++++++++++ account/models.py | 1 + account/views.py | 25 +++++++++++++------ .../migrations/0006_guardian_is_verified.py | 18 +++++++++++++ guardian/models.py | 9 ++++--- guardian/serializers.py | 4 +++ guardian/views.py | 7 +++--- junior/migrations/0005_junior_is_verified.py | 18 +++++++++++++ junior/models.py | 1 + 9 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 account/migrations/0002_useremailotp_user_type.py create mode 100644 guardian/migrations/0006_guardian_is_verified.py create mode 100644 junior/migrations/0005_junior_is_verified.py diff --git a/account/migrations/0002_useremailotp_user_type.py b/account/migrations/0002_useremailotp_user_type.py new file mode 100644 index 0000000..9b8821a --- /dev/null +++ b/account/migrations/0002_useremailotp_user_type.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-29 12:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='useremailotp', + name='user_type', + field=models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True), + ), + ] diff --git a/account/models.py b/account/models.py index ff8bb5d..43ab6e5 100644 --- a/account/models.py +++ b/account/models.py @@ -59,6 +59,7 @@ class UserEmailOtp(models.Model): """otp details""" otp = models.CharField(max_length=10) is_verified = models.BooleanField(default=False) + user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None) # OTP validity created_at = models.DateTimeField(auto_now_add=True) diff --git a/account/views.py b/account/views.py index 7ed3918..afceade 100644 --- a/account/views.py +++ b/account/views.py @@ -141,27 +141,28 @@ class UserLogin(viewsets.ViewSet): def login(self, request): username = request.data.get('username') password = request.data.get('password') + print("username==>",username,'==>',password) user = authenticate(request, username=username, password=password) + try: if user is not None: login(request, user) - guardian_data = Guardian.objects.filter(user__username=username, is_complete_profile=True).last() + guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() if guardian_data: - serializer = GuardianSerializer(guardian_data) - junior_data = Junior.objects.filter(auth__username=username, is_complete_profile=True).last() + serializer = GuardianSerializer(guardian_data).data + junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() if junior_data: - serializer = JuniorSerializer(junior_data) - return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) + serializer = JuniorSerializer(junior_data).data + 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) except Exception as e: logging.error(e) - user_profile_data = UserProfile.objects.filter(user__username=username).last() email_verified = UserEmailOtp.objects.filter(email=username).last() refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) data = {"auth_token":access_token, "is_profile_complete": False, - "user_role": user_profile_data.user_type, + "user_role": email_verified.user_type, } is_verified = False if email_verified: @@ -208,6 +209,16 @@ class UserEmailVerification(viewsets.ModelViewSet): if email_data: 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() + 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() + if guardian_data: + guardian_data.is_verified = True + guardian_data.save() refresh = RefreshToken.for_user(user_obj) access_token = str(refresh.access_token) return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, response_status=status.HTTP_200_OK) diff --git a/guardian/migrations/0006_guardian_is_verified.py b/guardian/migrations/0006_guardian_is_verified.py new file mode 100644 index 0000000..5d4082e --- /dev/null +++ b/guardian/migrations/0006_guardian_is_verified.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-29 12:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0005_alter_guardian_image'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='is_verified', + field=models.BooleanField(default=False), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 9bb92eb..3040c82 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -20,14 +20,15 @@ class Guardian(models.Model): family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) + """Profile activity""" + is_active = models.BooleanField(default=True) + is_verified = models.BooleanField(default=False) + is_complete_profile = models.BooleanField(default=False) + passcode = models.IntegerField(null=True, blank=True, default=None) """Codes""" guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) - """Profile activity""" - is_active = models.BooleanField(default=True) - is_complete_profile = models.BooleanField(default=False) - passcode = models.IntegerField(null=True, blank=True, default=None) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/guardian/serializers.py b/guardian/serializers.py index a7651b3..0136eb4 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -35,6 +35,10 @@ class UserSerializer(serializers.ModelSerializer): """Create user profile""" user = User.objects.create_user(username=email, email=email, password=password) UserProfile.objects.create(user=user, user_type=user_type) + if user_type == '1': + Junior.objects.create(auth=user) + if user_type == '2': + Guardian.objects.create(user=user) return user except Exception as e: """Error handling""" diff --git a/guardian/views.py b/guardian/views.py index 72e5b94..049cc1f 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -3,8 +3,9 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status """Import Django app""" -from .serializers import UserSerializer -from .serializers import CreateGuardianSerializer +from .serializers import UserSerializer, CreateGuardianSerializer +from .models import Guardian +from junior.models import Junior from account.models import UserEmailOtp from .tasks import generate_otp from account.utils import send_otp_email @@ -22,7 +23,7 @@ class SignupViewset(viewsets.ModelViewSet): serializer.save() """Generate otp""" otp = generate_otp() - UserEmailOtp.objects.create(email=request.data['email'], otp=otp) + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=request.data['user_type']) """Send email to the register user""" send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, diff --git a/junior/migrations/0005_junior_is_verified.py b/junior/migrations/0005_junior_is_verified.py new file mode 100644 index 0000000..a5ed1e6 --- /dev/null +++ b/junior/migrations/0005_junior_is_verified.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-29 12:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0004_alter_junior_image'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='is_verified', + field=models.BooleanField(default=False), + ), + ] diff --git a/junior/models.py b/junior/models.py index e7bf4cd..762032b 100644 --- a/junior/models.py +++ b/junior/models.py @@ -28,6 +28,7 @@ class Junior(models.Model): is_active = models.BooleanField(default=True) is_complete_profile = models.BooleanField(default=False) passcode = models.IntegerField(null=True, blank=True, default=None) + is_verified = models.BooleanField(default=False) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) From be6725c66d69864bb3dfaf45729875537cd4277d Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 18:14:49 +0530 Subject: [PATCH 024/372] jira-9 changes in setting --- zod_bank/settings.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 763cf47..79debb9 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -181,13 +181,21 @@ https://docs.djangoproject.com/en/3.0/howto/static-files/""" # # Replace with your Gmail email password or App password # EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' -EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') -EMAIL_HOST = os.getenv('EMAIL_HOST') -EMAIL_PORT = os.getenv('EMAIL_PORT') -EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') -EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address -EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password -EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') +# EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') +# EMAIL_HOST = os.getenv('EMAIL_HOST') +# EMAIL_PORT = os.getenv('EMAIL_PORT') +# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password +# EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') + +EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST="smtp.sendgrid.net" +EMAIL_PORT="587" +EMAIL_USE_TLS="True" +EMAIL_HOST_USER="apikey" # Replace with your Gmail email address +EMAIL_HOST_PASSWORD="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" +EMAIL_FROM_ADDRESS="zodbank@yopmail.com" ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') From 3d3ccfb146843f7d859d4b2ac5ac549c5f3ec3ad Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 18:16:11 +0530 Subject: [PATCH 025/372] jira-9 changes in setting --- zod_bank/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 79debb9..7f3e0b0 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -204,5 +204,5 @@ ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') -STATIC_URL = 'static/' -STATIC_ROOT = 'static' +STATIC_URL = '/static/' +# STATIC_ROOT = 'static' From 88a9e925ed1fe4587aed79303e8276fb16baf85f Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 18:20:07 +0530 Subject: [PATCH 026/372] jira-9 changes in setting --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 7f3e0b0..17cf48e 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -205,4 +205,4 @@ ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') STATIC_URL = '/static/' -# STATIC_ROOT = 'static' +STATIC_ROOT = 'static' From 70c136dde4e8d4c139aabb2e4de5cbed6b4e5a8e Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 18:21:15 +0530 Subject: [PATCH 027/372] jira-9 changes in setting --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 17cf48e..79debb9 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -204,5 +204,5 @@ ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') -STATIC_URL = '/static/' +STATIC_URL = 'static/' STATIC_ROOT = 'static' From c2713b8214e0a4da33b9e0c7a40318a26f9db038 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 20:26:35 +0530 Subject: [PATCH 028/372] jira-7 email --- account/views.py | 3 +-- guardian/serializers.py | 17 ++++++++--------- guardian/views.py | 2 +- junior/serializers.py | 5 ++--- nginx/django.conf | 2 +- zod_bank/settings.py | 24 ++++++++++-------------- 6 files changed, 23 insertions(+), 30 deletions(-) diff --git a/account/views.py b/account/views.py index afceade..38ba10a 100644 --- a/account/views.py +++ b/account/views.py @@ -141,7 +141,6 @@ class UserLogin(viewsets.ViewSet): def login(self, request): username = request.data.get('username') password = request.data.get('password') - print("username==>",username,'==>',password) user = authenticate(request, username=username, password=password) try: @@ -162,7 +161,7 @@ class UserLogin(viewsets.ViewSet): refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) data = {"auth_token":access_token, "is_profile_complete": False, - "user_role": email_verified.user_type, + "user_type": email_verified.user_type, } is_verified = False if email_verified: diff --git a/guardian/serializers.py b/guardian/serializers.py index 0136eb4..75291b1 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -8,7 +8,7 @@ from django.db import transaction from django.contrib.auth.models import User """Import Django app""" from .models import Guardian -from account.models import UserProfile +from account.models import UserProfile, UserEmailOtp from base.messages import ERROR_CODE, SUCCESS_CODE from .utils import upload_image_to_alibaba from junior.models import Junior @@ -43,13 +43,12 @@ class UserSerializer(serializers.ModelSerializer): except Exception as e: """Error handling""" logging.error(e) - raise serializers.ValidationError({"details":ERROR_CODE['2021']}) - - def save(self, **kwargs): - """save the data""" - with transaction.atomic(): - instance = super().save(**kwargs) - return instance + otp = UserEmailOtp.objects.filter(email=email).last() + otp_verified = False + if otp and otp.is_verified: + otp_verified = True + raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":otp_verified, "code": "400", "status":"failed", + }) class CreateGuardianSerializer(serializers.ModelSerializer): """Create guardian serializer""" @@ -86,7 +85,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create guardian profile""" - phone_number = validated_data.pop('phone', None) + phone_number = validated_data.get('phone', None) guardian_data = Guardian.objects.filter(phone=phone_number) junior_data = Junior.objects.filter(phone=phone_number) if phone_number and (guardian_data or junior_data): diff --git a/guardian/views.py b/guardian/views.py index 049cc1f..e8dda1e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -23,7 +23,7 @@ class SignupViewset(viewsets.ModelViewSet): serializer.save() """Generate otp""" otp = generate_otp() - UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=request.data['user_type']) + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type'])) """Send email to the register user""" send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, diff --git a/junior/serializers.py b/junior/serializers.py index c05f2a8..2fcf7cc 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -58,9 +58,8 @@ class CreateJuniorSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create junior profile""" - image = validated_data.pop('image', None) - phone_number = validated_data.pop('phone', None) - print("phone_number====>",phone_number,'==>',type(phone_number)) + image = validated_data.get('image', None) + phone_number = validated_data.get('phone', None) guardian_data = Guardian.objects.filter(phone=phone_number) junior_data = Junior.objects.filter(phone=phone_number) if phone_number and (junior_data or guardian_data): diff --git a/nginx/django.conf b/nginx/django.conf index 488e2c0..af79e46 100644 --- a/nginx/django.conf +++ b/nginx/django.conf @@ -19,6 +19,6 @@ upstream web { location /static { autoindex on; - alias /usr/src/app/zod_bank/static/; + alias /usr/src/app/static/; } } diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 79debb9..840de8e 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -172,15 +172,6 @@ CORS_ALLOW_HEADERS = ( https://docs.djangoproject.com/en/3.0/howto/static-files/""" -# Email settings For temporary use -# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -# EMAIL_HOST = 'smtp.gmail.com' -# EMAIL_PORT = 587 -# EMAIL_USE_TLS = True -# EMAIL_HOST_USER = 'infozodbank@gmail.com' -# # Replace with your Gmail email password or App password -# EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga' - # EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') # EMAIL_HOST = os.getenv('EMAIL_HOST') # EMAIL_PORT = os.getenv('EMAIL_PORT') @@ -197,12 +188,17 @@ EMAIL_HOST_USER="apikey" # Replace with your Gmail email address EMAIL_HOST_PASSWORD="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" EMAIL_FROM_ADDRESS="zodbank@yopmail.com" -ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') -ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') -ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') -ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') -ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') +# ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') +# ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') +# ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') +# ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') +# ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') +ALIYUN_OSS_ACCESS_KEY_ID="LTAI5t7w1gq1CswJtvxtEZTd" +ALIYUN_OSS_ACCESS_KEY_SECRET="6yknAFpP2gVMhCWAJwbAjCEw2eehpf" +ALIYUN_OSS_BUCKET_NAME="zod-dev" +ALIYUN_OSS_ENDPOINT="oss-me-central-1.aliyuncs.com" +ALIYUN_OSS_REGION="Global" STATIC_URL = 'static/' STATIC_ROOT = 'static' From d254c5e5fa3803225afd453aa90f60a4cbb63aeb Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 29 Jun 2023 21:39:36 +0530 Subject: [PATCH 029/372] email changes --- account/views.py | 2 +- guardian/serializers.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/account/views.py b/account/views.py index 38ba10a..067b147 100644 --- a/account/views.py +++ b/account/views.py @@ -86,7 +86,7 @@ class ForgotPasswordAPIView(views.APIView): return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) verification_code = ''.join([str(random.randrange(9)) for _ in range(6)]) # Send the verification code to the user's email - from_email = settings.EMAIL_HOST_USER + from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [email] send_templated_mail( template_name='email_reset_verification.email', diff --git a/guardian/serializers.py b/guardian/serializers.py index 75291b1..a896e62 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -47,7 +47,8 @@ class UserSerializer(serializers.ModelSerializer): otp_verified = False if otp and otp.is_verified: otp_verified = True - raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":otp_verified, "code": "400", "status":"failed", + raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":bool(otp_verified), + "code": 400, "status":"failed", }) class CreateGuardianSerializer(serializers.ModelSerializer): From c57968b7c0fe86a33e7b97aea82432500a29ebe1 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 11:17:18 +0530 Subject: [PATCH 030/372] reset password msg --- account/serializers.py | 2 ++ base/messages.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/account/serializers.py b/account/serializers.py index 5f03fc2..36571dc 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -72,6 +72,8 @@ class ResetPasswordSerializer(serializers.Serializer): if user_opt_details: user_details = User.objects.filter(email=user_opt_details.email).last() if user_details: + if user_details.check_password(password): + raise serializers.ValidationError({"details":ERROR_CODE['2001'],"code":"400", "status":"failed"}) user_details.set_password(password) user_details.save() return {'password':password} diff --git a/base/messages.py b/base/messages.py index f977ee7..7d48f97 100644 --- a/base/messages.py +++ b/base/messages.py @@ -24,7 +24,7 @@ ERROR_CODE_REQUIRED = { # Error code ERROR_CODE = { "2000": "Email not found.", - "2001": "Your account has not been verified. Please check your email and verify it.", + "2001": "This is your existing password. Please choose other one", "2002": "Invalid login credentials.", "2003": "An account already exists with this email address.", "2004": "User not found.", From 7376fc555bc60fe29a4110b7d15b9f9d15f6f4fb Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 15:21:00 +0530 Subject: [PATCH 031/372] jira-5 google login --- account/serializers.py | 7 +++++ account/urls.py | 5 +-- account/views.py | 69 +++++++++++++++++++++++++++++++++++++++++- zod_bank/settings.py | 16 ++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 36571dc..e7a9e33 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -29,6 +29,13 @@ class GoogleSignInSerializer(serializers.Serializer): email=self.validated_data['email']) return instance +class GoogleLoginSerializer1(serializers.Serializer): + access_token = serializers.CharField(max_length=5000, required=True) + + class Meta: + """meta class""" + fields = ('access_token',) + class UpdateGuardianImageSerializer(serializers.ModelSerializer): """Reset Password after verification""" class Meta(object): diff --git a/account/urls.py b/account/urls.py index b9501e6..010398a 100644 --- a/account/urls.py +++ b/account/urls.py @@ -5,14 +5,15 @@ from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, - ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage) + ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, + GoogleLoginViewSet1) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') -# router.register('google-login', GoogleLoginAPIViewset, basename='admin') +router.register('google-login', GoogleLoginViewSet1, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') diff --git a/account/views.py b/account/views.py index 067b147..4335b67 100644 --- a/account/views.py +++ b/account/views.py @@ -9,7 +9,7 @@ from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, - GoogleSignInSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) + GoogleLoginSerializer1, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -22,6 +22,73 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail +import google.oauth2.credentials +import google.auth.transport.requests +from rest_framework import status +from rest_framework.response import Response +import requests +from django.conf import settings +# from apps.accounts.utility import get_token + + +class GoogleLoginMixin: + def google_login(self, request): + access_token = request.data.get('access_token') + user_type = request.data.get('user_type') + if not access_token: + return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) + + try: + # Validate the access token and obtain the user's email and name + credentials = google.oauth2.credentials.Credentials.from_authorized_user_info( + info={ + 'access_token': access_token, + 'token_uri': 'https://oauth2.googleapis.com/token', + # 'token_uri': 'https://auth.googleapis.com/token', + 'client_id': settings.GOOGLE_CLIENT_ID, + 'client_secret': settings.GOOGLE_CLIENT_SECRET, + 'refresh_token': None, + } + ) + print("credentials===>",credentials, '===>',credentials.token) + user_info_endpoint = f'https://www.googleapis.com/oauth2/v3/userinfo?access_token={access_token}' + # user_info_endpoint = f'https://www.googleapis.com/auth/userinfo?access_token={access_token}' + headers = {'Authorization': f'Bearer {credentials.token}'} + response = requests.get(user_info_endpoint, headers=headers) + response.raise_for_status() + user_info = response.json() + email = user_info['email'] + name = user_info['name'] + profile_picture = user_info['picture'] + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + + # Check if the user exists in your database or create a new user + # ... + if User.objects.filter(email__iexact=email).exists(): + print("00000000000") + return custom_response(SUCCESS_CODE['3003'], response_status=status.HTTP_200_OK) + + if not User.objects.filter(email__iexact=email).exists(): + print("999999999999999") + user_obj = User.objects.create(username=email, + email=email) + if str(user_type) == '1': + Junior.objects.create(auth=user_obj) + + + # Return a JSON response with the user's email and name + return Response({'token': "get_token()", 'name': name, 'email': email, 'profile_picture': profile_picture}) + +class GoogleLoginViewSet1(GoogleLoginMixin, viewsets.GenericViewSet): + serializer_class = GoogleLoginSerializer1 + + def create(self, request): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + print("88888888888888888888888888") + return self.google_login(request) + # class GoogleLoginAPIViewset(viewsets.ModelViewSet): # """Google Login""" # serializer_class = GoogleSignInSerializer diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 840de8e..27d33b6 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'account', 'junior', 'guardian', + # 'social_django' ] MIDDLEWARE = [ @@ -171,6 +172,21 @@ CORS_ALLOW_HEADERS = ( """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" +AUTHENTICATION_BACKENDS = [ + 'social_core.backends.google.GoogleOAuth2', + 'django.contrib.auth.backends.ModelBackend', +] + +LOGIN_URL = 'login' +LOGIN_REDIRECT_URL = 'home' +LOGOUT_URL = 'logout' +LOGOUT_REDIRECT_URL = 'login' + +# SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' +# SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' + +GOOGLE_CLIENT_ID = "182276566528-hlbjncs19fo502jposod6kft2p9k4grk.apps.googleusercontent.com" +GOOGLE_CLIENT_SECRET = "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" # EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') # EMAIL_HOST = os.getenv('EMAIL_HOST') From 6cbe824d9ea5061e2667228c8a5d1cf0259162b5 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 15:24:49 +0530 Subject: [PATCH 032/372] jira-13 alibaba bucket name change --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 840de8e..1f1f2bf 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -196,7 +196,7 @@ EMAIL_FROM_ADDRESS="zodbank@yopmail.com" ALIYUN_OSS_ACCESS_KEY_ID="LTAI5t7w1gq1CswJtvxtEZTd" ALIYUN_OSS_ACCESS_KEY_SECRET="6yknAFpP2gVMhCWAJwbAjCEw2eehpf" -ALIYUN_OSS_BUCKET_NAME="zod-dev" +ALIYUN_OSS_BUCKET_NAME="zod-qa" ALIYUN_OSS_ENDPOINT="oss-me-central-1.aliyuncs.com" ALIYUN_OSS_REGION="Global" From 245162b913b23673baeb00cfc6985baaf3c8e0e5 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 15:51:12 +0530 Subject: [PATCH 033/372] jira-274 user can use same number multiple time --- guardian/serializers.py | 10 +++++----- junior/serializers.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index a896e62..ba72ef5 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -86,11 +86,11 @@ class CreateGuardianSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create guardian profile""" - phone_number = validated_data.get('phone', None) - guardian_data = Guardian.objects.filter(phone=phone_number) - junior_data = Junior.objects.filter(phone=phone_number) - if phone_number and (guardian_data or junior_data): - raise serializers.ValidationError({"details": ERROR_CODE['2012']}) + # phone_number = validated_data.get('phone', None) + # guardian_data = Guardian.objects.filter(phone=phone_number) + # junior_data = Junior.objects.filter(phone=phone_number) + # if phone_number and (guardian_data or junior_data): + # raise serializers.ValidationError({"details": ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of guardian""" diff --git a/junior/serializers.py b/junior/serializers.py index 2fcf7cc..34596e9 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -59,11 +59,11 @@ class CreateJuniorSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create junior profile""" image = validated_data.get('image', None) - phone_number = validated_data.get('phone', None) - guardian_data = Guardian.objects.filter(phone=phone_number) - junior_data = Junior.objects.filter(phone=phone_number) - if phone_number and (junior_data or guardian_data): - raise serializers.ValidationError({"details":ERROR_CODE['2012']}) + # phone_number = validated_data.get('phone', None) + # guardian_data = Guardian.objects.filter(phone=phone_number) + # junior_data = Junior.objects.filter(phone=phone_number) + # if phone_number and (junior_data or guardian_data): + # raise serializers.ValidationError({"details":ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of junior""" From 79ac140ddd03fec4025dc2e861f893b147a27488 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 16:22:41 +0530 Subject: [PATCH 034/372] jira-3 google login --- account/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index 4335b67..93cf17f 100644 --- a/account/views.py +++ b/account/views.py @@ -74,7 +74,9 @@ class GoogleLoginMixin: user_obj = User.objects.create(username=email, email=email) if str(user_type) == '1': - Junior.objects.create(auth=user_obj) + Junior.objects.create(auth=user_obj, is_verified=True) + if str(user_type) == '2': + Guardian.objects.create(user=user_obj, is_verified=True) # Return a JSON response with the user's email and name From 66bbe066862988b3f238fdb9b5f65f7a71de13e6 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 16:24:45 +0530 Subject: [PATCH 035/372] jira-6 change country name length --- .../0007_alter_guardian_country_name.py | 18 ++++++++++++++++++ guardian/models.py | 2 +- .../0006_alter_junior_country_name.py | 18 ++++++++++++++++++ junior/models.py | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 guardian/migrations/0007_alter_guardian_country_name.py create mode 100644 junior/migrations/0006_alter_junior_country_name.py diff --git a/guardian/migrations/0007_alter_guardian_country_name.py b/guardian/migrations/0007_alter_guardian_country_name.py new file mode 100644 index 0000000..2cbfd73 --- /dev/null +++ b/guardian/migrations/0007_alter_guardian_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-30 10:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0006_guardian_is_verified'), + ] + + operations = [ + migrations.AlterField( + model_name='guardian', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=100, null=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 3040c82..1ecf47f 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -13,7 +13,7 @@ class Guardian(models.Model): """Contact details""" country_code = models.IntegerField(blank=True, null=True) phone = models.CharField(max_length=31, null=True, blank=True, default=None) - country_name = models.CharField(max_length=30, null=True, blank=True, default=None) + country_name = models.CharField(max_length=100, null=True, blank=True, default=None) """Image info""" image = models.ImageField(null=True, blank=True, default=None) """Personal info""" diff --git a/junior/migrations/0006_alter_junior_country_name.py b/junior/migrations/0006_alter_junior_country_name.py new file mode 100644 index 0000000..e3db0ba --- /dev/null +++ b/junior/migrations/0006_alter_junior_country_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-06-30 10:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0005_junior_is_verified'), + ] + + operations = [ + migrations.AlterField( + model_name='junior', + name='country_name', + field=models.CharField(blank=True, default=None, max_length=100, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 762032b..331673f 100644 --- a/junior/models.py +++ b/junior/models.py @@ -14,7 +14,7 @@ class Junior(models.Model): """Contact details""" phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_code = models.IntegerField(blank=True, null=True) - country_name = models.CharField(max_length=30, null=True, blank=True, default=None) + country_name = models.CharField(max_length=100, null=True, blank=True, default=None) """Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) From 36cedf8c69feeb020ceb9d8c04a5c147b56c4117 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 17:20:47 +0530 Subject: [PATCH 036/372] textual changes --- base/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index 7d48f97..db53373 100644 --- a/base/messages.py +++ b/base/messages.py @@ -57,7 +57,7 @@ SUCCESS_CODE = { # Success code for Thank you "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.", # Success code for account activation - "3003": "Log in successfully", + "3003": "Log in successful", # Success code for password reset "3004": "Password reset link has been sent to your email address", # Success code for link verified From d2498f82ad3862ef76e06fa16bae69135a89cb64 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 30 Jun 2023 21:25:43 +0530 Subject: [PATCH 037/372] jira-5 google login and apple login --- account/serializers.py | 21 +++++----- account/urls.py | 7 ++-- account/utils.py | 57 ++++++++++++++++++++++++++- account/views.py | 88 +++++++++++++++++++++++++++--------------- zod_bank/settings.py | 20 +++++----- 5 files changed, 139 insertions(+), 54 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index e7a9e33..f25510d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -13,7 +13,7 @@ from rest_framework.decorators import action from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken from guardian.utils import upload_image_to_alibaba - +from .utils import get_token class GoogleSignInSerializer(serializers.Serializer): """Google login Serializer""" email = serializers.EmailField() @@ -139,11 +139,13 @@ class GuardianSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - refresh = RefreshToken.for_user(obj.user) - access_token = str(refresh.access_token) - return access_token + return get_token()['access'] + def get_refresh_token(self, obj): + return get_token()['refresh'] + def get_user_type(self, obj): """user type""" @@ -164,7 +166,7 @@ class GuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', + fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'created_at', 'updated_at', 'user_type'] @@ -176,11 +178,12 @@ class JuniorSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - refresh = RefreshToken.for_user(obj.auth) - access_token = str(refresh.access_token) - return access_token + return get_token()['access'] + def get_refresh_token(self, obj): + return get_token()['refresh'] def get_user_type(self, obj): return JUNIOR @@ -197,7 +200,7 @@ class JuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'updated_at', 'user_type'] diff --git a/account/urls.py b/account/urls.py index 010398a..e0e1c6c 100644 --- a/account/urls.py +++ b/account/urls.py @@ -6,14 +6,14 @@ from rest_framework.decorators import api_view from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, - GoogleLoginViewSet1) + GoogleLoginViewSet, SigninWithApple) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') -router.register('google-login', GoogleLoginViewSet1, basename='admin') +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') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') @@ -23,5 +23,6 @@ urlpatterns = [ path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), path('api/v1/reset-password/', ResetPasswordAPIView.as_view()), path('api/v1/change-password/', ChangePasswordAPIView.as_view()), - path('api/v1/update-profile-image/', UpdateProfileImage.as_view()) + path('api/v1/update-profile-image/', UpdateProfileImage.as_view()), + path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'), ] diff --git a/account/utils.py b/account/utils.py index bfc2399..0848c03 100644 --- a/account/utils.py +++ b/account/utils.py @@ -3,8 +3,13 @@ from django.conf import settings from rest_framework import viewsets, status from rest_framework.response import Response - from templated_email import send_templated_mail +import jwt +from datetime import datetime +from calendar import timegm +from uuid import uuid4 +import secrets + def send_otp_email(recipient_email, otp): from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [recipient_email] @@ -36,3 +41,53 @@ def custom_error_response(detail, response_status): if not detail: detail = {} return Response({"error": detail, "status": "failed", "code": response_status}) + + +def get_user_data(attrs): + """ + used to decode token + """ + user_data = jwt.decode(jwt=attrs['token'], options={'verify_signature': False}, + algorithms=['RS256']) + return user_data + + +def generate_jwt_token(token_type: str, now_time: int, data: dict = dict): + """ + used to generate jwt token + """ + if type(data) == type: + data = {} + data.update({ + 'token_type': token_type, + 'iss': 'your_site_url', + 'iat': timegm(datetime.utcnow().utctimetuple()), + 'jti': uuid4().hex + }) + TOKEN_TYPE = ["access", "refresh"] + if token_type == TOKEN_TYPE[1]: + exp = now_time + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] + else: + exp = now_time + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] + + data.update({ + "exp": timegm(exp.utctimetuple()) + }) + + + signing_key = secrets.token_hex(32) + + return jwt.encode(payload=data, key=signing_key, + algorithm='HS256') + + +def get_token(data: dict = dict): + """ create access and refresh token """ + now_time = datetime.utcnow() + access = generate_jwt_token('access', now_time, data) + refresh = generate_jwt_token('refresh', now_time, data) + + return { + 'access': access, + 'refresh': refresh + } \ No newline at end of file diff --git a/account/views.py b/account/views.py index 93cf17f..106dfce 100644 --- a/account/views.py +++ b/account/views.py @@ -2,6 +2,7 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging +import jwt from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior @@ -13,22 +14,17 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp -from django.conf import settings from account.utils import send_otp_email from account.utils import custom_response, custom_error_response -from django.core.mail import EmailMessage -from django.core.mail import send_mail -from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail - import google.oauth2.credentials import google.auth.transport.requests from rest_framework import status from rest_framework.response import Response import requests from django.conf import settings -# from apps.accounts.utility import get_token +from .utils import get_token class GoogleLoginMixin: @@ -65,9 +61,18 @@ class GoogleLoginMixin: # Check if the user exists in your database or create a new user # ... - if User.objects.filter(email__iexact=email).exists(): + user_data = User.objects.filter(email__iexact=email) + if user_data.exists(): print("00000000000") - return custom_response(SUCCESS_CODE['3003'], response_status=status.HTTP_200_OK) + if str(user_type) == '1': + junior_query = Junior.objects.filter(auth=user_data.last()).last() + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.filter(user=user_data.last()).last() + print("guardian_query==>",guardian_query,'====>',type(guardian_query)) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) if not User.objects.filter(email__iexact=email).exists(): print("999999999999999") @@ -80,9 +85,11 @@ class GoogleLoginMixin: # Return a JSON response with the user's email and name - return Response({'token': "get_token()", 'name': name, 'email': email, 'profile_picture': profile_picture}) + return custom_response(SUCCESS_CODE['3003'], {'auth_token': get_token(), 'name': name, 'email': email, + 'profile_picture': profile_picture, "user_type":user_type}, + response_status=status.HTTP_200_OK) -class GoogleLoginViewSet1(GoogleLoginMixin, viewsets.GenericViewSet): +class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): serializer_class = GoogleLoginSerializer1 def create(self, request): @@ -91,27 +98,46 @@ class GoogleLoginViewSet1(GoogleLoginMixin, viewsets.GenericViewSet): print("88888888888888888888888888") return self.google_login(request) -# class GoogleLoginAPIViewset(viewsets.ModelViewSet): -# """Google Login""" -# serializer_class = GoogleSignInSerializer -# -# def create(self, request, *args, **kwargs): -# """ -# Override default behaviour of create method -# """ -# provider_type = [] -# serializer = self.get_serializer(data=request.data) -# if serializer.is_valid(raise_exception=True): -# # provider = self.get_provider_view(request.data.get('provider')) -# # if User is not authenticated then send error message -# # if not provider.is_authenticated(request): -# # return custom_error_response({}, status.HTTP_400_BAD_REQUEST) -# -# user = serializer.save() -# if User.objects.filter(email__iexact=user.email).exists(): -# print("ppppppppppppp") -# return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK) -# return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) +class SigninWithApple(views.APIView): + """This API is for sign in with Apple for app.""" + def post(self, request): + token = request.data.get("identityToken") + user_type = request.data.get("user_type") + if not token: + return Response({"message": "data should contain `identityToken`"}) + decoded_data = jwt.decode(token, options={"verify_signature": False}) + print("decoded_data===>",decoded_data) + user_data = {"email": decoded_data.get('email'),"username": decoded_data.get('email'), + "first_name": request.data.get("fullName").get("givenName"),"is_active": True, + "last_name": request.data.get("fullName").get("familyName"),} + if user_data['email'] and not user_data['first_name']: + user_data['first_name'] = user_data['email'].split("@")[0] + user_data['last_name'] = user_data['email'].split("@")[0] + if decoded_data.get("email"): + try: + user = User.objects.get(email=decoded_data.get("email")) + if str(user_type) == '1': + junior_query = Junior.objects.filter(auth=user).last() + print("junior_query==>", junior_query, '====>', type(junior_query)) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.filter(user=user).last() + print("guardian_query==>", guardian_query, '====>', type(guardian_query)) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + + except User.DoesNotExist: + user = User.objects.create(**user_data) + if str(user_type) == '1': + junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + class UpdateProfileImage(views.APIView): permission_classes = [IsAuthenticated] diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 27d33b6..fc88b1c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -96,7 +96,7 @@ REST_FRAMEWORK = { ] } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), } # Database @@ -172,15 +172,15 @@ CORS_ALLOW_HEADERS = ( """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" -AUTHENTICATION_BACKENDS = [ - 'social_core.backends.google.GoogleOAuth2', - 'django.contrib.auth.backends.ModelBackend', -] - -LOGIN_URL = 'login' -LOGIN_REDIRECT_URL = 'home' -LOGOUT_URL = 'logout' -LOGOUT_REDIRECT_URL = 'login' +# AUTHENTICATION_BACKENDS = [ +# 'social_core.backends.google.GoogleOAuth2', +# 'django.contrib.auth.backends.ModelBackend', +# ] +# +# LOGIN_URL = 'login' +# LOGIN_REDIRECT_URL = 'home' +# LOGOUT_URL = 'logout' +# LOGOUT_REDIRECT_URL = 'login' # SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' # SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' From 014b5fe183cb58ea6f57ac4b549d8bb831d67999 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 12:58:06 +0530 Subject: [PATCH 038/372] jira-5 apple login with first and last name --- account/views.py | 95 +++++++++++++++++++++++++----------------------- base/messages.py | 11 +++--- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/account/views.py b/account/views.py index 106dfce..f645080 100644 --- a/account/views.py +++ b/account/views.py @@ -31,6 +31,7 @@ class GoogleLoginMixin: def google_login(self, request): access_token = request.data.get('access_token') user_type = request.data.get('user_type') + # decoded_data = jwt.decode(access_token, options={"verify_signature": False}) if not access_token: return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) @@ -40,7 +41,6 @@ class GoogleLoginMixin: info={ 'access_token': access_token, 'token_uri': 'https://oauth2.googleapis.com/token', - # 'token_uri': 'https://auth.googleapis.com/token', 'client_id': settings.GOOGLE_CLIENT_ID, 'client_secret': settings.GOOGLE_CLIENT_SECRET, 'refresh_token': None, @@ -48,16 +48,17 @@ class GoogleLoginMixin: ) print("credentials===>",credentials, '===>',credentials.token) user_info_endpoint = f'https://www.googleapis.com/oauth2/v3/userinfo?access_token={access_token}' - # user_info_endpoint = f'https://www.googleapis.com/auth/userinfo?access_token={access_token}' headers = {'Authorization': f'Bearer {credentials.token}'} response = requests.get(user_info_endpoint, headers=headers) response.raise_for_status() user_info = response.json() + print("user_info===>",user_info,'==>',type(user_info)) email = user_info['email'] - name = user_info['name'] + first_name = user_info['given_name'] + last_name = user_info['family_name'] profile_picture = user_info['picture'] except Exception as e: - return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) # Check if the user exists in your database or create a new user # ... @@ -76,19 +77,20 @@ class GoogleLoginMixin: if not User.objects.filter(email__iexact=email).exists(): print("999999999999999") - user_obj = User.objects.create(username=email, - email=email) + user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name) if str(user_type) == '1': - Junior.objects.create(auth=user_obj, is_verified=True) + junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, + image=profile_picture) + serializer = JuniorSerializer(junior_query) if str(user_type) == '2': - Guardian.objects.create(user=user_obj, is_verified=True) - - + guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, + image=profile_picture) + serializer = GuardianSerializer(guardian_query) # Return a JSON response with the user's email and name - return custom_response(SUCCESS_CODE['3003'], {'auth_token': get_token(), 'name': name, 'email': email, - 'profile_picture': profile_picture, "user_type":user_type}, + return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) + class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): serializer_class = GoogleLoginSerializer1 @@ -101,42 +103,45 @@ class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): class SigninWithApple(views.APIView): """This API is for sign in with Apple for app.""" def post(self, request): - token = request.data.get("identityToken") + token = request.data.get("access_token") user_type = request.data.get("user_type") if not token: - return Response({"message": "data should contain `identityToken`"}) - decoded_data = jwt.decode(token, options={"verify_signature": False}) - print("decoded_data===>",decoded_data) - user_data = {"email": decoded_data.get('email'),"username": decoded_data.get('email'), - "first_name": request.data.get("fullName").get("givenName"),"is_active": True, - "last_name": request.data.get("fullName").get("familyName"),} - if user_data['email'] and not user_data['first_name']: - user_data['first_name'] = user_data['email'].split("@")[0] - user_data['last_name'] = user_data['email'].split("@")[0] - if decoded_data.get("email"): - try: - user = User.objects.get(email=decoded_data.get("email")) - if str(user_type) == '1': - junior_query = Junior.objects.filter(auth=user).last() - print("junior_query==>", junior_query, '====>', type(junior_query)) - serializer = JuniorSerializer(junior_query) - if str(user_type) == '2': - guardian_query = Guardian.objects.filter(user=user).last() - print("guardian_query==>", guardian_query, '====>', type(guardian_query)) - serializer = GuardianSerializer(guardian_query) - return custom_response(SUCCESS_CODE['3003'], serializer.data, - response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2027'], response_status=status.HTTP_400_BAD_REQUEST) + try: + decoded_data = jwt.decode(token, options={"verify_signature": False}) + print("decoded_data===>",decoded_data) + user_data = {"email": decoded_data.get('email'),"username": decoded_data.get('email'), + "first_name": request.data.get("fullName").get("givenName"),"is_active": True, + "last_name": request.data.get("fullName").get("familyName"),} + if user_data['email'] and not user_data['first_name']: + user_data['first_name'] = user_data['email'].split("@")[0] + user_data['last_name'] = user_data['email'].split("@")[0] + if decoded_data.get("email"): + try: + user = User.objects.get(email=decoded_data.get("email")) + if str(user_type) == '1': + junior_query = Junior.objects.filter(auth=user).last() + print("junior_query==>", junior_query, '====>', type(junior_query)) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.filter(user=user).last() + print("guardian_query==>", guardian_query, '====>', type(guardian_query)) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) - except User.DoesNotExist: - user = User.objects.create(**user_data) - if str(user_type) == '1': - junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True) - serializer = JuniorSerializer(junior_query) - if str(user_type) == '2': - guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True) - serializer = GuardianSerializer(guardian_query) - return custom_response(SUCCESS_CODE['3003'], serializer.data, - response_status=status.HTTP_200_OK) + except User.DoesNotExist: + user = User.objects.create(**user_data) + if str(user_type) == '1': + junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True) + serializer = JuniorSerializer(junior_query) + if str(user_type) == '2': + guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True) + serializer = GuardianSerializer(guardian_query) + return custom_response(SUCCESS_CODE['3003'], serializer.data, + response_status=status.HTTP_200_OK) + except Exception as e: + logging.error(e) class UpdateProfileImage(views.APIView): diff --git a/base/messages.py b/base/messages.py index 7d48f97..83e221b 100644 --- a/base/messages.py +++ b/base/messages.py @@ -45,11 +45,12 @@ ERROR_CODE = { "2019": "Either File extension or File size doesn't meet the requirements", "2020": "Enter valid mobile number", "2021": "Already register", - "2022":"Invalid Guardian code", - "2023":"Invalid user", - "2024":"Email not verified", - "2025":"Invalid input. Expected a list of strings.", - "2026" : "New password should not same as old password" + "2022": "Invalid Guardian code", + "2023": "Invalid user", + "2024": "Email not verified", + "2025": "Invalid input. Expected a list of strings.", + "2026": "New password should not same as old password", + "2027": "data should contain `identityToken`" } SUCCESS_CODE = { # Success code for password From 28cbdf9cfa689e22f7a5274132db2c8a19865b3e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 14:57:07 +0530 Subject: [PATCH 039/372] jira-5 social login --- account/serializers.py | 4 ++-- account/views.py | 18 ++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index f25510d..2388b5f 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -167,7 +167,7 @@ class GuardianSerializer(serializers.ModelSerializer): """Meta info""" model = Guardian fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', - 'referral_code', 'is_active', 'is_complete_profile', 'passcode', + 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type'] @@ -201,7 +201,7 @@ class JuniorSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type'] class EmailVerificationSerializer(serializers.ModelSerializer): diff --git a/account/views.py b/account/views.py index f645080..37bc7c8 100644 --- a/account/views.py +++ b/account/views.py @@ -31,7 +31,6 @@ class GoogleLoginMixin: def google_login(self, request): access_token = request.data.get('access_token') user_type = request.data.get('user_type') - # decoded_data = jwt.decode(access_token, options={"verify_signature": False}) if not access_token: return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) @@ -46,13 +45,11 @@ class GoogleLoginMixin: 'refresh_token': None, } ) - print("credentials===>",credentials, '===>',credentials.token) user_info_endpoint = f'https://www.googleapis.com/oauth2/v3/userinfo?access_token={access_token}' headers = {'Authorization': f'Bearer {credentials.token}'} response = requests.get(user_info_endpoint, headers=headers) response.raise_for_status() user_info = response.json() - print("user_info===>",user_info,'==>',type(user_info)) email = user_info['email'] first_name = user_info['given_name'] last_name = user_info['family_name'] @@ -64,19 +61,16 @@ class GoogleLoginMixin: # ... user_data = User.objects.filter(email__iexact=email) if user_data.exists(): - print("00000000000") if str(user_type) == '1': junior_query = Junior.objects.filter(auth=user_data.last()).last() serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.filter(user=user_data.last()).last() - print("guardian_query==>",guardian_query,'====>',type(guardian_query)) serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) if not User.objects.filter(email__iexact=email).exists(): - print("999999999999999") 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, @@ -97,7 +91,6 @@ class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): def create(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - print("88888888888888888888888888") return self.google_login(request) class SigninWithApple(views.APIView): @@ -109,23 +102,15 @@ class SigninWithApple(views.APIView): return custom_error_response(ERROR_CODE['2027'], response_status=status.HTTP_400_BAD_REQUEST) try: decoded_data = jwt.decode(token, options={"verify_signature": False}) - print("decoded_data===>",decoded_data) - user_data = {"email": decoded_data.get('email'),"username": decoded_data.get('email'), - "first_name": request.data.get("fullName").get("givenName"),"is_active": True, - "last_name": request.data.get("fullName").get("familyName"),} - if user_data['email'] and not user_data['first_name']: - user_data['first_name'] = user_data['email'].split("@")[0] - user_data['last_name'] = user_data['email'].split("@")[0] + user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True} if decoded_data.get("email"): try: user = User.objects.get(email=decoded_data.get("email")) if str(user_type) == '1': junior_query = Junior.objects.filter(auth=user).last() - print("junior_query==>", junior_query, '====>', type(junior_query)) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.filter(user=user).last() - print("guardian_query==>", guardian_query, '====>', type(guardian_query)) serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -142,6 +127,7 @@ class SigninWithApple(views.APIView): response_status=status.HTTP_200_OK) except Exception as e: logging.error(e) + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class UpdateProfileImage(views.APIView): From a27c486c4368057a7156e0a6cd9ae37a3509d6e0 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 15:53:25 +0530 Subject: [PATCH 040/372] remove unused code --- account/serializers.py | 15 +-------------- account/views.py | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 2388b5f..e2aee49 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -14,22 +14,9 @@ from django.contrib.auth import authenticate, login from rest_framework_simplejwt.tokens import RefreshToken from guardian.utils import upload_image_to_alibaba from .utils import get_token -class GoogleSignInSerializer(serializers.Serializer): - """Google login Serializer""" - email = serializers.EmailField() - def create(self, validated_data): - """Create or update user model""" - with transaction.atomic(): - if User.objects.filter(email__iexact=self.validated_data['email']).exists(): - return User.objects.get(email__iexact=self.validated_data['email']) - if not User.objects.filter(email__iexact=self.validated_data['email']).exists(): - instance = User.objects.create(username=self.validated_data['email'], - email=self.validated_data['email']) - return instance - -class GoogleLoginSerializer1(serializers.Serializer): +class GoogleLoginSerializer(serializers.Serializer): access_token = serializers.CharField(max_length=5000, required=True) class Meta: diff --git a/account/views.py b/account/views.py index 37bc7c8..f992a05 100644 --- a/account/views.py +++ b/account/views.py @@ -10,7 +10,7 @@ from account.models import UserProfile, UserPhoneOtp, UserEmailOtp from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, - GoogleLoginSerializer1, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) + GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -86,7 +86,7 @@ class GoogleLoginMixin: class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): - serializer_class = GoogleLoginSerializer1 + serializer_class = GoogleLoginSerializer def create(self, request): serializer = self.get_serializer(data=request.data) From 22f1d01b9478e5cb5df1a7e1e41a88f8bc4d5390 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 15:59:37 +0530 Subject: [PATCH 041/372] requirement.txt file --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.txt b/requirements.txt index 2175525..1103c6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ async-timeout==4.0.2 billiard==4.1.0 boto3==1.26.157 botocore==1.29.157 +cachetools==5.3.1 celery==5.3.1 certifi==2023.5.7 cffi==1.15.1 @@ -38,6 +39,7 @@ django-timezone-field==5.1 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 drf-yasg==1.21.6 +google-auth==2.21.0 gunicorn==20.1.0 idna==3.4 inflection==0.5.1 @@ -50,6 +52,8 @@ phonenumbers==8.13.15 Pillow==9.5.0 prompt-toolkit==3.0.38 psycopg==3.1.9 +pyasn1==0.5.0 +pyasn1-modules==0.3.0 pycparser==2.21 pycryptodome==3.18.0 PyJWT==2.7.0 @@ -60,6 +64,7 @@ pytz==2023.3 PyYAML==6.0 redis==4.5.5 requests==2.31.0 +rsa==4.9 s3transfer==0.6.1 six==1.16.0 sqlparse==0.4.4 From 1c7d6bac85ce0891a1c75ca9d0fccaef9c1baa08 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 17:17:58 +0530 Subject: [PATCH 042/372] changes in settings and env file --- zod_bank/settings.py | 64 ++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index fc88b1c..6930469 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -172,49 +172,37 @@ CORS_ALLOW_HEADERS = ( """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" -# AUTHENTICATION_BACKENDS = [ -# 'social_core.backends.google.GoogleOAuth2', -# 'django.contrib.auth.backends.ModelBackend', -# ] -# -# LOGIN_URL = 'login' -# LOGIN_REDIRECT_URL = 'home' -# LOGOUT_URL = 'logout' -# LOGOUT_REDIRECT_URL = 'login' -# SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' -# SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' +GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') +GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') -GOOGLE_CLIENT_ID = "182276566528-hlbjncs19fo502jposod6kft2p9k4grk.apps.googleusercontent.com" -GOOGLE_CLIENT_SECRET = "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" +EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') +EMAIL_HOST = os.getenv('EMAIL_HOST') +EMAIL_PORT = os.getenv('EMAIL_PORT') +EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address +EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password +EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') -# EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') -# EMAIL_HOST = os.getenv('EMAIL_HOST') -# EMAIL_PORT = os.getenv('EMAIL_PORT') -# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') -# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address -# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password -# EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') +# EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" +# EMAIL_HOST="smtp.sendgrid.net" +# EMAIL_PORT="587" +# EMAIL_USE_TLS="True" +# EMAIL_HOST_USER="apikey" # Replace with your Gmail email address +# EMAIL_HOST_PASSWORD="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" +# EMAIL_FROM_ADDRESS="zodbank@yopmail.com" -EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" -EMAIL_HOST="smtp.sendgrid.net" -EMAIL_PORT="587" -EMAIL_USE_TLS="True" -EMAIL_HOST_USER="apikey" # Replace with your Gmail email address -EMAIL_HOST_PASSWORD="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" -EMAIL_FROM_ADDRESS="zodbank@yopmail.com" +ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') +ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') +ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') +ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') +ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') -# ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') -# ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') -# ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') -# ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') -# ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') - -ALIYUN_OSS_ACCESS_KEY_ID="LTAI5t7w1gq1CswJtvxtEZTd" -ALIYUN_OSS_ACCESS_KEY_SECRET="6yknAFpP2gVMhCWAJwbAjCEw2eehpf" -ALIYUN_OSS_BUCKET_NAME="zod-dev" -ALIYUN_OSS_ENDPOINT="oss-me-central-1.aliyuncs.com" -ALIYUN_OSS_REGION="Global" +# ALIYUN_OSS_ACCESS_KEY_ID="LTAI5t7w1gq1CswJtvxtEZTd" +# ALIYUN_OSS_ACCESS_KEY_SECRET="6yknAFpP2gVMhCWAJwbAjCEw2eehpf" +# ALIYUN_OSS_BUCKET_NAME="zod-dev" +# ALIYUN_OSS_ENDPOINT="oss-me-central-1.aliyuncs.com" +# ALIYUN_OSS_REGION="Global" STATIC_URL = 'static/' STATIC_ROOT = 'static' From 376bb89f20ea83ba40218b1adfcdb98e47e2c5db Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 3 Jul 2023 18:10:53 +0530 Subject: [PATCH 043/372] changes in setting --- zod_bank/settings.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 6930469..71b561c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -176,21 +176,21 @@ https://docs.djangoproject.com/en/3.0/howto/static-files/""" GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') -EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') -EMAIL_HOST = os.getenv('EMAIL_HOST') -EMAIL_PORT = os.getenv('EMAIL_PORT') -EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') -EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address -EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password -EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') +# EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') +# EMAIL_HOST = os.getenv('EMAIL_HOST') +# EMAIL_PORT = os.getenv('EMAIL_PORT') +# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') +# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address +# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password +# EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') -# EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" -# EMAIL_HOST="smtp.sendgrid.net" -# EMAIL_PORT="587" -# EMAIL_USE_TLS="True" -# EMAIL_HOST_USER="apikey" # Replace with your Gmail email address -# EMAIL_HOST_PASSWORD="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" -# EMAIL_FROM_ADDRESS="zodbank@yopmail.com" +EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST="smtp.sendgrid.net" +EMAIL_PORT="587" +EMAIL_USE_TLS="True" +EMAIL_HOST_USER="apikey" # Replace with your Gmail email address +EMAIL_HOST_PASSWORD="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" +EMAIL_FROM_ADDRESS="zodbank@yopmail.com" ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') @@ -198,11 +198,6 @@ ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') -# ALIYUN_OSS_ACCESS_KEY_ID="LTAI5t7w1gq1CswJtvxtEZTd" -# ALIYUN_OSS_ACCESS_KEY_SECRET="6yknAFpP2gVMhCWAJwbAjCEw2eehpf" -# ALIYUN_OSS_BUCKET_NAME="zod-dev" -# ALIYUN_OSS_ENDPOINT="oss-me-central-1.aliyuncs.com" -# ALIYUN_OSS_REGION="Global" STATIC_URL = 'static/' STATIC_ROOT = 'static' From 5e5161a77ed00bae811778fea65a091a3dd28987 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 4 Jul 2023 18:26:00 +0530 Subject: [PATCH 044/372] list of the task and task table --- account/serializers.py | 19 +++--- base/constants.py | 10 +++ base/messages.py | 6 +- guardian/admin.py | 11 +++- guardian/migrations/0008_juniortask.py | 36 +++++++++++ guardian/models.py | 29 ++++++++- guardian/serializers.py | 84 +++++++++++++++++++++++++- guardian/urls.py | 6 +- guardian/views.py | 56 +++++++++++++---- junior/serializers.py | 58 ++++++++++++++++++ junior/urls.py | 4 +- junior/views.py | 15 ++++- zod_bank/settings.py | 1 + 13 files changed, 304 insertions(+), 31 deletions(-) create mode 100644 guardian/migrations/0008_juniortask.py diff --git a/account/serializers.py b/account/serializers.py index e2aee49..c328e37 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -126,12 +126,11 @@ class GuardianSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') - refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - return get_token()['access'] - def get_refresh_token(self, obj): - return get_token()['refresh'] + refresh = RefreshToken.for_user(obj.user) + access_token = str(refresh.access_token) + return access_token def get_user_type(self, obj): @@ -153,7 +152,7 @@ class GuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', + fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type'] @@ -165,12 +164,12 @@ class JuniorSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') - refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - return get_token()['access'] - def get_refresh_token(self, obj): - return get_token()['refresh'] + print("obj===>",obj,'===>',type(obj)) + refresh = RefreshToken.for_user(obj.auth) + access_token = str(refresh.access_token) + return access_token def get_user_type(self, obj): return JUNIOR @@ -187,7 +186,7 @@ class JuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type'] diff --git a/base/constants.py b/base/constants.py index b66ed5f..4e7bdcd 100644 --- a/base/constants.py +++ b/base/constants.py @@ -35,6 +35,16 @@ GENDERS = ( ('1', 'Male'), ('2', 'Female') ) +TASK_STATUS = ( + ('1', 'pending'), + ('2', 'in-progress'), + ('3', 'rejected'), + ('4', 'requested'), + ('5', 'completed') +) + +PENDING = 'pending' +TASK_POINTS = 5 # duplicate name used defined in constant PROJECT_NAME PROJECT_NAME = 'Zod Bank' GUARDIAN = 'guardian' diff --git a/base/messages.py b/base/messages.py index ffdc1c5..b6fd487 100644 --- a/base/messages.py +++ b/base/messages.py @@ -50,7 +50,8 @@ ERROR_CODE = { "2024": "Email not verified", "2025": "Invalid input. Expected a list of strings.", "2026": "New password should not same as old password", - "2027": "data should contain `identityToken`" + "2027": "data should contain `identityToken`", + "2028": "You are not authorized person to sign up on this platform" } SUCCESS_CODE = { # Success code for password @@ -79,7 +80,8 @@ SUCCESS_CODE = { "3014": "Password has been updated successfully.", "3015": "Verification code sent on your email.", "3016": "Send otp on your Email successfully", - "3017": "Profile image update successfully" + "3017": "Profile image update successfully", + "3018": "Created task successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/admin.py b/guardian/admin.py index a3bd40c..6ece960 100644 --- a/guardian/admin.py +++ b/guardian/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Guardian +from .models import Guardian, JuniorTask # Register your models here. @admin.register(Guardian) class GuardianAdmin(admin.ModelAdmin): @@ -12,3 +12,12 @@ class GuardianAdmin(admin.ModelAdmin): def __str__(self): """Return email id""" return self.user__email + +@admin.register(JuniorTask) +class TaskAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['task_name', 'task_status'] + + def __str__(self): + """Return email id""" + return str(self.task_name) + str(self.points) diff --git a/guardian/migrations/0008_juniortask.py b/guardian/migrations/0008_juniortask.py new file mode 100644 index 0000000..44a10d7 --- /dev/null +++ b/guardian/migrations/0008_juniortask.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.2 on 2023-07-04 09:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0006_alter_junior_country_name'), + ('guardian', '0007_alter_guardian_country_name'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorTask', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('task_name', models.CharField(max_length=100)), + ('task_description', models.CharField(max_length=500)), + ('points', models.IntegerField(default=5)), + ('due_date', models.DateField(blank=True, null=True)), + ('image', models.ImageField(blank=True, default=None, null=True, upload_to='')), + ('task_status', models.CharField(blank=True, choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default='pending', max_length=15, null=True)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('guardian', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian', to='guardian.guardian', verbose_name='Guardian')), + ('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior', to='junior.junior', verbose_name='Junior')), + ], + options={ + 'verbose_name': 'Junior Task', + 'db_table': 'junior_task', + }, + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 1ecf47f..501e080 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -3,7 +3,8 @@ from django.db import models from django.contrib.auth import get_user_model """Import Django app""" -from base.constants import GENDERS +from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS +from junior.models import Junior User = get_user_model() # Create your models here. @@ -41,3 +42,29 @@ class Guardian(models.Model): def __str__(self): """Return email id""" return f'{self.user}' + +class JuniorTask(models.Model): + """Guardian model""" + guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian') + task_name = models.CharField(max_length=100) + task_description = models.CharField(max_length=500) + points = models.IntegerField(default=TASK_POINTS) + due_date = models.DateField(auto_now_add=False, null=True, blank=True) + image = models.ImageField(null=True, blank=True, default=None) + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') + task_status = models.CharField(choices=TASK_STATUS, max_length=15, null=True, blank=True, default=PENDING) + is_active = models.BooleanField(default=True) + """Profile created and updated time""" + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta class """ + db_table = 'junior_task' + verbose_name = 'Junior Task' + + def __str__(self): + """Return email id""" + return f'{self.task_name}' + + diff --git a/guardian/serializers.py b/guardian/serializers.py index ba72ef5..d8429d1 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -6,9 +6,13 @@ from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User +from django.core.validators import URLValidator +from django.core.exceptions import ValidationError """Import Django app""" -from .models import Guardian +from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp +from account.serializers import JuniorSerializer +from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from .utils import upload_image_to_alibaba from junior.models import Junior @@ -134,3 +138,81 @@ class CreateGuardianSerializer(serializers.ModelSerializer): with transaction.atomic(): instance = super().save(**kwargs) return instance + + + + +class ImageField(serializers.Field): + def to_representation(self, value): + return value + def to_internal_value(self, data): + # If data is a valid URL, return it as is + if validators.URLValidator()(data): + return data + # else: + # raise serializers.ValidationError("Enter a valid URL.") + + + +class TaskSerializer(serializers.ModelSerializer): + class Meta(object): + model = JuniorTask + fields = ['task_name','task_description','points', 'due_date', 'junior', 'image'] + def validate_image(self, value): + if 'http' in str(value): + print("999999999999") + return value + else: + print("00000000000000000") + filename = f"images/{value.name}" + image_url = upload_image_to_alibaba(value, filename) + return image_url + def create(self, validated_data): + print("validated_data NOW===>", validated_data, '===>', type(validated_data)) + validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() + # images = self.context['image'] + # if 'http' in str(images): + # print("999999999999") + # validated_data['image'] = images + # else: + # print("00000000000000000") + # filename = f"images/{images.name}" + # image_url = upload_image_to_alibaba(images, filename) + # validated_data['image'] = image_url + + print("validated_data NOW===>", validated_data, '===>', type(validated_data)) + instance = JuniorTask.objects.create(**validated_data) + return instance + +class GuardianDetailSerializer(serializers.ModelSerializer): + """junior serializer""" + + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + + def get_auth(self, obj): + """user email address""" + return obj.user.username + + def get_first_name(self, obj): + """user first name""" + return obj.user.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.user.last_name + class Meta(object): + """Meta info""" + model = Guardian + fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'updated_at'] +class TaskDetailsSerializer(serializers.ModelSerializer): + + guardian = GuardianDetailSerializer() + junior = JuniorDetailSerializer() + class Meta(object): + model = JuniorTask + fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date', 'image','junior', + 'task_status', 'is_active'] diff --git a/guardian/urls.py b/guardian/urls.py index 5399f2b..54470cb 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 +from .views import SignupViewset, UpdateGuardianProfile, TaskListAPIView, CreateTaskAPIView """Third party import""" from rest_framework import routers @@ -13,6 +13,10 @@ router = routers.SimpleRouter() router.register('sign-up', SignupViewset, basename='sign-up') """Create guardian profile API""" router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') +"""Create Task API""" +router.register('create-task', CreateTaskAPIView, basename='create-task') +"""Task list API""" +router.register('task-list', TaskListAPIView, basename='task-list') """Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/views.py b/guardian/views.py index e8dda1e..f764d36 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -3,8 +3,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status """Import Django app""" -from .serializers import UserSerializer, CreateGuardianSerializer -from .models import Guardian +from .serializers import UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer +from .models import Guardian, JuniorTask from junior.models import Junior from account.models import UserEmailOtp from .tasks import generate_otp @@ -18,17 +18,20 @@ class SignupViewset(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): """Create user profile""" - serializer = UserSerializer(context=request.data['user_type'], data=request.data) - if serializer.is_valid(): - serializer.save() - """Generate otp""" - otp = generate_otp() - UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type'])) - """Send email to the register user""" - send_otp_email(request.data['email'], otp) - return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, - response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + if request.data['user_type'] in ['1', '2']: + serializer = UserSerializer(context=request.data['user_type'], data=request.data) + if serializer.is_valid(): + serializer.save() + """Generate otp""" + otp = generate_otp() + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type'])) + """Send email to the register user""" + send_otp_email(request.data['email'], otp) + return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST) class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" @@ -46,3 +49,30 @@ class UpdateGuardianProfile(viewsets.ViewSet): serializer.save() return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + + +class TaskListAPIView(viewsets.ModelViewSet): + """Update guardian profile""" + serializer_class = TaskDetailsSerializer + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + """Create guardian profile""" + queryset = JuniorTask.objects.filter(guardian__user=request.user) + print("queryset==>",queryset) + serializer = TaskDetailsSerializer(queryset, many=True) + print("serializer.data===>",serializer.data) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + +class CreateTaskAPIView(viewsets.ModelViewSet): + """create task for junior""" + serializer_class = TaskSerializer + + def create(self, request, *args, **kwargs): + print("request.data===>",request.data) + image = request.data['image'] + serializer = TaskSerializer(context={"user":request.user, "image":image}, data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/serializers.py b/junior/serializers.py index 34596e9..b8c6864 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -59,6 +59,11 @@ class CreateJuniorSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create junior profile""" image = validated_data.get('image', None) + guardian_code = validated_data.get('guardian_code',None) + print("guardian_code===>",guardian_code,'==>',type(guardian_code)) + + + # phone_number = validated_data.get('phone', None) # guardian_data = Guardian.objects.filter(phone=phone_number) # junior_data = Junior.objects.filter(phone=phone_number) @@ -82,6 +87,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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""" + if guardian_code: + junior.guardian_code = guardian_code junior.dob = validated_data.get('dob',junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) junior.country_name = validated_data.get('country_name', junior.country_name) @@ -107,3 +115,53 @@ class CreateJuniorSerializer(serializers.ModelSerializer): with transaction.atomic(): instance = super().save(**kwargs) return instance + +class JuniorDetailSerializer(serializers.ModelSerializer): + """junior serializer""" + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + + def get_auth(self, obj): + """user email address""" + return obj.auth.username + + def get_first_name(self, obj): + """user first name""" + return obj.auth.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.auth.last_name + + class Meta(object): + """Meta info""" + model = Junior + fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'updated_at'] + +class JuniorDetailListSerializer(serializers.ModelSerializer): + """junior serializer""" + + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + + + + def get_auth(self, obj): + return obj.auth.username + + def get_first_name(self, obj): + return obj.auth.first_name + + def get_last_name(self, obj): + return obj.auth.last_name + + class Meta(object): + """Meta info""" + model = Junior + fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'updated_at'] \ No newline at end of file diff --git a/junior/urls.py b/junior/urls.py index 2b64fe4..5eb0f4c 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -1,7 +1,7 @@ """ Urls files""" """Django import""" from django.urls import path, include -from .views import UpdateJuniorProfile, ValidateGuardianCode +from .views import UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView """Third party import""" from rest_framework import routers @@ -13,6 +13,8 @@ router = routers.SimpleRouter() router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') """validate guardian code API""" router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') +"""junior list API""" +router.register('junior-list', JuniorListAPIView, basename='junior-list') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 153bb41..fd2555e 100644 --- a/junior/views.py +++ b/junior/views.py @@ -3,7 +3,7 @@ from rest_framework import viewsets, status from rest_framework.permissions import IsAuthenticated """Django app import""" from junior.models import Junior -from .serializers import CreateJuniorSerializer +from .serializers import CreateJuniorSerializer, JuniorDetailListSerializer from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE from account.utils import custom_response, custom_error_response @@ -38,3 +38,16 @@ class ValidateGuardianCode(viewsets.ViewSet): return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) + +class JuniorListAPIView(viewsets.ModelViewSet): + """Junior list of assosicated guardian""" + + serializer_class = JuniorDetailListSerializer + + def list(self, request, *args, **kwargs): + """ junior list""" + guardian_data = Guardian.objects.filter(user__email=request.user).last() + queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + print("queryset===>",queryset) + serializer = JuniorDetailListSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 71b561c..7a05e1a 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -201,3 +201,4 @@ ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') STATIC_URL = 'static/' STATIC_ROOT = 'static' + From 0d143a39ac062327d990daa9b7b0d3641f5431ee Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 4 Jul 2023 19:25:51 +0530 Subject: [PATCH 045/372] jira-20 create task --- .../migrations/0009_alter_juniortask_image.py | 18 +++++++++++ .../0010_alter_juniortask_task_status.py | 18 +++++++++++ guardian/models.py | 4 +-- guardian/serializers.py | 32 +++---------------- guardian/urls.py | 6 ++-- guardian/views.py | 27 ++++++++++++++-- 6 files changed, 70 insertions(+), 35 deletions(-) create mode 100644 guardian/migrations/0009_alter_juniortask_image.py create mode 100644 guardian/migrations/0010_alter_juniortask_task_status.py diff --git a/guardian/migrations/0009_alter_juniortask_image.py b/guardian/migrations/0009_alter_juniortask_image.py new file mode 100644 index 0000000..496fee1 --- /dev/null +++ b/guardian/migrations/0009_alter_juniortask_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-04 12:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0008_juniortask'), + ] + + operations = [ + migrations.AlterField( + model_name='juniortask', + name='image', + field=models.URLField(blank=True, default=None, null=True), + ), + ] diff --git a/guardian/migrations/0010_alter_juniortask_task_status.py b/guardian/migrations/0010_alter_juniortask_task_status.py new file mode 100644 index 0000000..da84f2c --- /dev/null +++ b/guardian/migrations/0010_alter_juniortask_task_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-04 13:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0009_alter_juniortask_image'), + ] + + operations = [ + migrations.AlterField( + model_name='juniortask', + name='task_status', + field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default='pending', max_length=15), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 501e080..3752b02 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -50,9 +50,9 @@ class JuniorTask(models.Model): task_description = models.CharField(max_length=500) points = models.IntegerField(default=TASK_POINTS) due_date = models.DateField(auto_now_add=False, null=True, blank=True) - image = models.ImageField(null=True, blank=True, default=None) + image = models.URLField(null=True, blank=True, default=None) junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') - task_status = models.CharField(choices=TASK_STATUS, max_length=15, null=True, blank=True, default=PENDING) + task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) is_active = models.BooleanField(default=True) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) diff --git a/guardian/serializers.py b/guardian/serializers.py index d8429d1..be0272d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -141,46 +141,22 @@ class CreateGuardianSerializer(serializers.ModelSerializer): - -class ImageField(serializers.Field): - def to_representation(self, value): - return value - def to_internal_value(self, data): - # If data is a valid URL, return it as is - if validators.URLValidator()(data): - return data - # else: - # raise serializers.ValidationError("Enter a valid URL.") - - - class TaskSerializer(serializers.ModelSerializer): class Meta(object): model = JuniorTask fields = ['task_name','task_description','points', 'due_date', 'junior', 'image'] - def validate_image(self, value): - if 'http' in str(value): - print("999999999999") - return value - else: - print("00000000000000000") - filename = f"images/{value.name}" - image_url = upload_image_to_alibaba(value, filename) - return image_url def create(self, validated_data): - print("validated_data NOW===>", validated_data, '===>', type(validated_data)) validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() - # images = self.context['image'] + images = self.context['image'] + validated_data['image'] = images + print("images===>",images) + print("validated_data===>",validated_data) # if 'http' in str(images): - # print("999999999999") # validated_data['image'] = images # else: - # print("00000000000000000") # filename = f"images/{images.name}" # image_url = upload_image_to_alibaba(images, filename) # validated_data['image'] = image_url - - print("validated_data NOW===>", validated_data, '===>', type(validated_data)) instance = JuniorTask.objects.create(**validated_data) return instance diff --git a/guardian/urls.py b/guardian/urls.py index 54470cb..6acd172 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, TaskListAPIView, CreateTaskAPIView +from .views import SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView """Third party import""" from rest_framework import routers @@ -15,7 +15,9 @@ 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') -"""Task list API""" +"""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') """Define Url pattern""" urlpatterns = [ diff --git a/guardian/views.py b/guardian/views.py index f764d36..22ffaa0 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -11,6 +11,7 @@ from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE +from .utils import upload_image_to_alibaba # Create your views here. class SignupViewset(viewsets.ModelViewSet): """Signup view set""" @@ -51,7 +52,7 @@ class UpdateGuardianProfile(viewsets.ViewSet): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) -class TaskListAPIView(viewsets.ModelViewSet): +class AllTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] @@ -64,14 +65,34 @@ class TaskListAPIView(viewsets.ModelViewSet): print("serializer.data===>",serializer.data) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +class TaskListAPIView(viewsets.ModelViewSet): + """Update guardian profile""" + serializer_class = TaskDetailsSerializer + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + """Create guardian profile""" + print("request.GET.get(status)==>",self.request.GET.get('status')) + queryset = JuniorTask.objects.filter(guardian__user=request.user, task_status=self.request.GET.get('status')) + print("queryset==>",queryset) + serializer = TaskDetailsSerializer(queryset, many=True) + print("serializer.data===>",serializer.data) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" serializer_class = TaskSerializer def create(self, request, *args, **kwargs): - print("request.data===>",request.data) image = request.data['image'] - serializer = TaskSerializer(context={"user":request.user, "image":image}, data=request.data) + data = request.data + if 'https' in str(image): + image_data = image + else: + filename = f"images/{image}" + image_url = upload_image_to_alibaba(image, filename) + image_data = image_url + data.pop('image') + serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) From a80f603614813007a362cee43ca08c6bf128658d Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 07:57:53 +0530 Subject: [PATCH 046/372] jira-15 top junior API and default images field --- base/constants.py | 6 +- guardian/admin.py | 2 +- ...t_image_juniortask_is_approved_and_more.py | 28 ++++++ guardian/models.py | 2 + guardian/serializers.py | 70 +++++++++++++++ guardian/urls.py | 7 +- guardian/utils.py | 1 + guardian/views.py | 90 ++++++++++++++++++- junior/serializers.py | 43 ++++++++- zod_bank/settings.py | 3 +- 10 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 guardian/migrations/0011_juniortask_default_image_juniortask_is_approved_and_more.py diff --git a/base/constants.py b/base/constants.py index 4e7bdcd..f455316 100644 --- a/base/constants.py +++ b/base/constants.py @@ -43,7 +43,11 @@ TASK_STATUS = ( ('5', 'completed') ) -PENDING = 'pending' +PENDING = 1 +IN_PROGRESS = 2 +REJECTED = 3 +REQUESTED = 4 +COMPLETED = 5 TASK_POINTS = 5 # duplicate name used defined in constant PROJECT_NAME PROJECT_NAME = 'Zod Bank' diff --git a/guardian/admin.py b/guardian/admin.py index 6ece960..45f5c95 100644 --- a/guardian/admin.py +++ b/guardian/admin.py @@ -16,7 +16,7 @@ class GuardianAdmin(admin.ModelAdmin): @admin.register(JuniorTask) class TaskAdmin(admin.ModelAdmin): """Junior Admin""" - list_display = ['task_name', 'task_status'] + list_display = ['task_name', 'task_status', 'junior', 'points'] def __str__(self): """Return email id""" diff --git a/guardian/migrations/0011_juniortask_default_image_juniortask_is_approved_and_more.py b/guardian/migrations/0011_juniortask_default_image_juniortask_is_approved_and_more.py new file mode 100644 index 0000000..7d10d3c --- /dev/null +++ b/guardian/migrations/0011_juniortask_default_image_juniortask_is_approved_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.2 on 2023-07-05 11:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0010_alter_juniortask_task_status'), + ] + + operations = [ + migrations.AddField( + model_name='juniortask', + name='default_image', + field=models.ImageField(blank=True, default=None, null=True, upload_to=''), + ), + migrations.AddField( + model_name='juniortask', + name='is_approved', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='juniortask', + name='task_status', + field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default=1, max_length=15), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 3752b02..c2ac60b 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -50,10 +50,12 @@ class JuniorTask(models.Model): task_description = models.CharField(max_length=500) points = models.IntegerField(default=TASK_POINTS) due_date = models.DateField(auto_now_add=False, null=True, blank=True) + default_image = models.ImageField(null=True, blank=True, default=None) image = models.URLField(null=True, blank=True, default=None) junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) is_active = models.BooleanField(default=True) + is_approved = models.BooleanField(default=False) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/guardian/serializers.py b/guardian/serializers.py index be0272d..fb091fc 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -129,7 +129,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer): if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) + print("image_url=====>",image_url) guardian.image = image_url + print("guardian.image=====>", guardian.image) guardian.save() return guardian @@ -192,3 +194,71 @@ class TaskDetailsSerializer(serializers.ModelSerializer): model = JuniorTask fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date', 'image','junior', 'task_status', 'is_active'] + + + + + +class TopJuniorSerializer(serializers.ModelSerializer): + total_points = serializers.SerializerMethodField() + + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + + def get_auth(self, obj): + return obj.auth.username + + def get_first_name(self, obj): + return obj.auth.first_name + + def get_last_name(self, obj): + return obj.auth.last_name + + class Meta: + model = Junior + fields = ['id', 'email', 'first_name', 'last_name', 'phone', 'country_code', 'country_name', 'gender', 'dob', 'image', 'junior_code', 'guardian_code', 'referral_code', 'referral_code_used', 'is_active', 'is_complete_profile', 'passcode', 'is_verified', 'created_at', 'updated_at', 'total_points'] + + def get_total_points(self, obj): + junior_ids_with_total_points = self.context.get('junior_ids_with_total_points') + if junior_ids_with_total_points: + junior_id = obj.id + for item in junior_ids_with_total_points: + if item['junior'] == junior_id: + return item['total_points'] + return 0 + +# +# class TopJuniorSerializer(serializers.ModelSerializer): +# total_points = serializers.SerializerMethodField() +# +# email = serializers.SerializerMethodField('get_auth') +# first_name = serializers.SerializerMethodField('get_first_name') +# last_name = serializers.SerializerMethodField('get_last_name') +# +# +# def get_auth(self, obj): +# return obj.auth.username +# +# def get_first_name(self, obj): +# return obj.auth.first_name +# +# def get_last_name(self, obj): +# print("onbj==>",obj) +# return obj.auth.last_name +# +# class Meta: +# model = Junior +# fields = ['id', 'email', 'first_name', 'last_name', 'phone', 'country_code', 'country_name', 'gender', 'dob', 'image', 'junior_code', 'guardian_code', 'referral_code', 'referral_code_used', 'is_active', 'is_complete_profile', 'passcode', 'is_verified', 'created_at', 'updated_at', 'total_points'] +# +# def get_total_points(self, obj): +# total_highest_points = self.context.get('total_highest_points') +# if total_highest_points: +# print("total_highest_points==>",total_highest_points) +# junior_id = obj.id +# print("junior_id==>", junior_id) +# total_points = next((item['total_points'] for item in total_highest_points if item['junior'] == junior_id), 0) +# print("total_points==>", total_points) +# return total_points +# return 0 +# diff --git a/guardian/urls.py b/guardian/urls.py index 6acd172..b69b041 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -1,7 +1,8 @@ """ Urls files""" """Django import""" from django.urls import path, include -from .views import SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView +from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, + SearchTaskListAPIView, TopJuniorListAPIView) """Third party import""" from rest_framework import routers @@ -19,6 +20,10 @@ router.register('create-task', CreateTaskAPIView, basename='create-task') 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""" +router.register('top-junior', TopJuniorListAPIView, basename='top-junior') +"""Search Task list on the bases of status, due date, and task title API""" +router.register('filter-task', SearchTaskListAPIView, basename='filter-task') """Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/utils.py b/guardian/utils.py index 0e46c6c..a4e1ffc 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -11,3 +11,4 @@ def upload_image_to_alibaba(image, filename): # Upload the temporary file to Alibaba OSS bucket.put_object_from_file(filename, temp_file.name) return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{filename}" + diff --git a/guardian/views.py b/guardian/views.py index 22ffaa0..9a4ab78 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -2,16 +2,20 @@ """Third party Django app""" from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status +from django.db.models import Max """Import Django app""" -from .serializers import UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer +from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, + TopJuniorSerializer) from .models import Guardian, JuniorTask from junior.models import Junior +from junior.serializers import JuniorDetailSerializer from account.models import UserEmailOtp from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE from .utils import upload_image_to_alibaba +from django.db.models import Sum # Create your views here. class SignupViewset(viewsets.ModelViewSet): """Signup view set""" @@ -73,7 +77,12 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" print("request.GET.get(status)==>",self.request.GET.get('status')) - queryset = JuniorTask.objects.filter(guardian__user=request.user, task_status=self.request.GET.get('status')) + status_value = self.request.GET.get('status') + if status_value == 0: + queryset = JuniorTask.objects.filter(guardian__user=request.user) + else: + queryset = JuniorTask.objects.filter(guardian__user=request.user, + task_status=status_value) print("queryset==>",queryset) serializer = TaskDetailsSerializer(queryset, many=True) print("serializer.data===>",serializer.data) @@ -97,3 +106,80 @@ class CreateTaskAPIView(viewsets.ModelViewSet): serializer.save() return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + + +class SearchTaskListAPIView(viewsets.ModelViewSet): + """Update guardian profile""" + serializer_class = TaskDetailsSerializer + permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + """Create guardian profile""" + due_date = self.request.GET.get('due_date') + print("request.GET.get(status)==>",self.request.GET.get('status')) + if self.request.GET.get('status'): + queryset = JuniorTask.objects.filter(guardian__user=request.user, + task_status=self.request.GET.get('status')).order_by('due_date') + if due_date: + queryset = JuniorTask.objects.filter(guardian__user=request.user, + due_date=due_date).order_by('due_date').order_by('created_at') + + print("queryset==>",queryset) + serializer = TaskDetailsSerializer(queryset, many=True) + # print("serializer.data===>",serializer.data) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class TopJuniorListAPIView(viewsets.ModelViewSet): + """Top juniors list""" + serializer_class = TopJuniorSerializer + # permission_classes = [IsAuthenticated] + + def list(self, request, *args, **kwargs): + """fetch junior list those complete their task""" + junior_ids_with_total_points = JuniorTask.objects.filter(task_status=1) \ + .values('junior') \ + .annotate(total_points=Sum('points')) \ + .order_by('-total_points') + + junior_ids = [item['junior'] for item in junior_ids_with_total_points] + + juniors = Junior.objects.filter(id__in=junior_ids) \ + .annotate(max_points=Max('junior_task__points')) \ + .order_by('-max_points', 'id') + + serializer = self.get_serializer(juniors, many=True, context={'junior_ids_with_total_points': junior_ids_with_total_points}) + return custom_response(serializer.data, response_status=status.HTTP_200_OK) + +# +# class TopJuniorListAPIView(viewsets.ModelViewSet): +# """Top juniors list""" +# serializer_class = TopJuniorSerializer +# # permission_classes = [IsAuthenticated] +# +# def list(self, request, *args, **kwargs): +# """fetch junior list those complete their task""" +# total_highest_points = list(JuniorTask.objects.filter(task_status=1) +# .values('junior') +# .annotate(total_points=Sum('points')) +# .order_by('-total_points')) +# print("total_highest_points===>",total_highest_points,'===>',type(total_highest_points)) +# junior_ids = [item['junior'] for item in total_highest_points] +# print("junior_ids====>", junior_ids) +# juniors = Junior.objects.filter(id__in=junior_ids) +# # a = [] +# # for i in junior_ids: +# # juniors = Junior.objects.filter(id=i) +# # a.append(juniors) +# print("juniors====>", juniors) +# # print('a===>',a,'==>',type(a)) +# serializer = self.get_serializer(juniors, context={'total_highest_points': total_highest_points}, many=True) +# print("serializer====>",type(serializer.data)) +# # Find the junior with the highest points +# # highest_points_junior = max(serializer.data, key=lambda x: x['total_points']) +# +# return custom_response(serializer.data, response_status=status.HTTP_200_OK) +# # serializer = self.get_serializer(total_highest_points, many=True) +# # return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) + + diff --git a/junior/serializers.py b/junior/serializers.py index b8c6864..8e871cd 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -8,7 +8,8 @@ import random from junior.models import Junior from guardian.utils import upload_image_to_alibaba from base.messages import ERROR_CODE, SUCCESS_CODE -from guardian.models import Guardian +from guardian.models import Guardian, JuniorTask +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED class ListCharField(serializers.ListField): """Serializer for Array field""" @@ -147,7 +148,13 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): email = serializers.SerializerMethodField('get_auth') first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') - + assigned_task = serializers.SerializerMethodField('get_assigned_task') + points = serializers.SerializerMethodField('get_points') + in_progress_task = serializers.SerializerMethodField('get_in_progress_task') + completed_task = serializers.SerializerMethodField('get_completed_task') + requested_task = serializers.SerializerMethodField('get_requested_task') + rejected_task = serializers.SerializerMethodField('get_rejected_task') + pending_task = serializers.SerializerMethodField('get_pending_task') def get_auth(self, obj): @@ -159,9 +166,39 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): def get_last_name(self, obj): return obj.auth.last_name + def get_assigned_task(self, obj): + print("obj===>",obj,'type==>',type(obj)) + data = JuniorTask.objects.filter(junior=obj).count() + return data + + def get_points(self, obj): + data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True)) + return data + + def get_in_progress_task(self, obj): + data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count() + return data + + def get_completed_task(self, obj): + data = JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).count() + return data + + + def get_requested_task(self, obj): + data = JuniorTask.objects.filter(junior=obj, task_status=REQUESTED).count() + return data + + def get_rejected_task(self, obj): + data = JuniorTask.objects.filter(junior=obj, task_status=REJECTED).count() + return data + + def get_pending_task(self, obj): + data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count() + return data class Meta(object): """Meta info""" model = Junior fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', - 'updated_at'] \ No newline at end of file + 'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task', + 'requested_task', 'rejected_task'] diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 7a05e1a..60c3e70 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -190,7 +190,8 @@ EMAIL_PORT="587" EMAIL_USE_TLS="True" EMAIL_HOST_USER="apikey" # Replace with your Gmail email address EMAIL_HOST_PASSWORD="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" -EMAIL_FROM_ADDRESS="zodbank@yopmail.com" +EMAIL_FROM_ADDRESS="support@zodbank.com" +# EMAIL_FROM_ADDRESS="zodbank@yopmail.com" ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') From cf966a52500a186385ff83b78177bdc01241adf8 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 12:21:54 +0530 Subject: [PATCH 047/372] jira-15 dashboard API --- account/serializers.py | 2 +- base/messages.py | 2 +- guardian/admin.py | 2 +- .../0012_alter_juniortask_default_image.py | 18 +++++++ .../migrations/0013_alter_guardian_image.py | 18 +++++++ guardian/models.py | 4 +- guardian/serializers.py | 14 +++--- guardian/utils.py | 6 ++- guardian/views.py | 47 +++++++++---------- zod_bank/settings.py | 5 +- 10 files changed, 76 insertions(+), 42 deletions(-) create mode 100644 guardian/migrations/0012_alter_juniortask_default_image.py create mode 100644 guardian/migrations/0013_alter_guardian_image.py diff --git a/account/serializers.py b/account/serializers.py index c328e37..70808f3 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -87,7 +87,7 @@ class ChangePasswordSerializer(serializers.Serializer): if self.context.password not in ('', None): if user.check_password(value): return value - raise serializers.ValidationError({"details":ERROR_CODE['2015']}) + raise serializers.ValidationError(ERROR_CODE['2015']) def create(self, validated_data): new_password = validated_data.pop('new_password') current_password = validated_data.pop('current_password') diff --git a/base/messages.py b/base/messages.py index b6fd487..dc6392a 100644 --- a/base/messages.py +++ b/base/messages.py @@ -81,7 +81,7 @@ SUCCESS_CODE = { "3015": "Verification code sent on your email.", "3016": "Send otp on your Email successfully", "3017": "Profile image update successfully", - "3018": "Created task successfully" + "3018": "Task created successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/admin.py b/guardian/admin.py index 45f5c95..97edbdb 100644 --- a/guardian/admin.py +++ b/guardian/admin.py @@ -16,7 +16,7 @@ class GuardianAdmin(admin.ModelAdmin): @admin.register(JuniorTask) class TaskAdmin(admin.ModelAdmin): """Junior Admin""" - list_display = ['task_name', 'task_status', 'junior', 'points'] + list_display = ['id', 'task_name', 'task_status', 'junior', 'due_date', 'points', 'created_at', 'updated_at'] def __str__(self): """Return email id""" diff --git a/guardian/migrations/0012_alter_juniortask_default_image.py b/guardian/migrations/0012_alter_juniortask_default_image.py new file mode 100644 index 0000000..eefae1b --- /dev/null +++ b/guardian/migrations/0012_alter_juniortask_default_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-06 05:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0011_juniortask_default_image_juniortask_is_approved_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='juniortask', + name='default_image', + field=models.URLField(blank=True, default=None, null=True), + ), + ] diff --git a/guardian/migrations/0013_alter_guardian_image.py b/guardian/migrations/0013_alter_guardian_image.py new file mode 100644 index 0000000..0a16630 --- /dev/null +++ b/guardian/migrations/0013_alter_guardian_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-06 06:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0012_alter_juniortask_default_image'), + ] + + operations = [ + migrations.AlterField( + model_name='guardian', + name='image', + field=models.URLField(blank=True, default=None, null=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index c2ac60b..00c5d42 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -16,7 +16,7 @@ class Guardian(models.Model): phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_name = models.CharField(max_length=100, null=True, blank=True, default=None) """Image info""" - image = models.ImageField(null=True, blank=True, default=None) + image = models.URLField(null=True, blank=True, default=None) """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) @@ -50,7 +50,7 @@ class JuniorTask(models.Model): task_description = models.CharField(max_length=500) points = models.IntegerField(default=TASK_POINTS) due_date = models.DateField(auto_now_add=False, null=True, blank=True) - default_image = models.ImageField(null=True, blank=True, default=None) + default_image = models.URLField(null=True, blank=True, default=None) image = models.URLField(null=True, blank=True, default=None) junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) diff --git a/guardian/serializers.py b/guardian/serializers.py index fb091fc..a55c320 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -129,9 +129,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer): if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) - print("image_url=====>",image_url) + print("image_url=====>",image_url,'===>',type(image_url)) guardian.image = image_url - print("guardian.image=====>", guardian.image) + print("guardian.image=====>", guardian.image,'===>',type(guardian.image)) guardian.save() return guardian @@ -146,13 +146,11 @@ class CreateGuardianSerializer(serializers.ModelSerializer): class TaskSerializer(serializers.ModelSerializer): class Meta(object): model = JuniorTask - fields = ['task_name','task_description','points', 'due_date', 'junior', 'image'] + fields = ['task_name','task_description','points', 'due_date', 'junior', 'default_image'] def create(self, validated_data): validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() images = self.context['image'] - validated_data['image'] = images - print("images===>",images) - print("validated_data===>",validated_data) + validated_data['default_image'] = images # if 'http' in str(images): # validated_data['image'] = images # else: @@ -192,8 +190,8 @@ class TaskDetailsSerializer(serializers.ModelSerializer): junior = JuniorDetailSerializer() class Meta(object): model = JuniorTask - fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date', 'image','junior', - 'task_status', 'is_active'] + fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image', + 'junior', 'task_status', 'is_active', 'created_at','updated_at'] diff --git a/guardian/utils.py b/guardian/utils.py index a4e1ffc..46a8071 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -10,5 +10,9 @@ def upload_image_to_alibaba(image, filename): 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) - return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{filename}" + print("filename====>",filename,'===>',type(filename)) + new_filename = filename.replace(' ', '%20') + print() + print("filename====>", filename, '===>', type(filename)) + return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" diff --git a/guardian/views.py b/guardian/views.py index 9a4ab78..900734e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -2,6 +2,7 @@ """Third party Django app""" from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status +from rest_framework.pagination import PageNumberPagination from django.db.models import Max """Import Django app""" from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, @@ -64,35 +65,34 @@ class AllTaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" queryset = JuniorTask.objects.filter(guardian__user=request.user) - print("queryset==>",queryset) serializer = TaskDetailsSerializer(queryset, many=True) - print("serializer.data===>",serializer.data) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] + pagination_class = PageNumberPagination def list(self, request, *args, **kwargs): """Create guardian profile""" - print("request.GET.get(status)==>",self.request.GET.get('status')) status_value = self.request.GET.get('status') if status_value == 0: - queryset = JuniorTask.objects.filter(guardian__user=request.user) + queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('created_at') else: queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=status_value) - print("queryset==>",queryset) - serializer = TaskDetailsSerializer(queryset, many=True) - print("serializer.data===>",serializer.data) + task_status=status_value).order_by('due_date','created_at') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" serializer_class = TaskSerializer def create(self, request, *args, **kwargs): - image = request.data['image'] + image = request.data['default_image'] data = request.data if 'https' in str(image): image_data = image @@ -100,7 +100,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): filename = f"images/{image}" image_url = upload_image_to_alibaba(image, filename) image_data = image_url - data.pop('image') + data.pop('default_image') serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): serializer.save() @@ -112,21 +112,17 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] + pagination_class = PageNumberPagination def list(self, request, *args, **kwargs): """Create guardian profile""" - due_date = self.request.GET.get('due_date') - print("request.GET.get(status)==>",self.request.GET.get('status')) - if self.request.GET.get('status'): - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=self.request.GET.get('status')).order_by('due_date') - if due_date: - queryset = JuniorTask.objects.filter(guardian__user=request.user, - due_date=due_date).order_by('due_date').order_by('created_at') - - print("queryset==>",queryset) - serializer = TaskDetailsSerializer(queryset, many=True) - # print("serializer.data===>",serializer.data) + title = self.request.GET.get('title') + if title: + queryset = JuniorTask.objects.filter(guardian__user=request.user, task_name__icontains=title)\ + .order_by('due_date','created_at') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -144,11 +140,10 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): junior_ids = [item['junior'] for item in junior_ids_with_total_points] - juniors = Junior.objects.filter(id__in=junior_ids) \ - .annotate(max_points=Max('junior_task__points')) \ - .order_by('-max_points', 'id') + juniors = Junior.objects.filter(id__in=junior_ids) - serializer = self.get_serializer(juniors, many=True, context={'junior_ids_with_total_points': junior_ids_with_total_points}) + serializer = self.get_serializer(juniors, many=True, context={'junior_ids_with_total_points': + junior_ids_with_total_points}) return custom_response(serializer.data, response_status=status.HTTP_200_OK) # diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 60c3e70..3a897cb 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -92,8 +92,9 @@ REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', - 'rest_framework_simplejwt.authentication.JWTAuthentication', -] + 'rest_framework_simplejwt.authentication.JWTAuthentication',], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 5, # Set the default pagination size } SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), From 2edd8c4e0065e0ce20226774fa73c0f06fd51772 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 15:52:30 +0530 Subject: [PATCH 048/372] jira-15 and email otp validation --- account/serializers.py | 10 ++++++++-- account/urls.py | 4 ++-- account/views.py | 26 ++++++++++++++++++++++++++ base/messages.py | 3 ++- guardian/views.py | 6 +++++- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 70808f3..9380ba8 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -135,7 +135,10 @@ class GuardianSerializer(serializers.ModelSerializer): def get_user_type(self, obj): """user type""" - return GUARDIAN + email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last() + if email_verified and email_verified.user_type != None: + return email_verified.user_type + return '2' def get_auth(self, obj): """user email address""" @@ -172,7 +175,10 @@ class JuniorSerializer(serializers.ModelSerializer): return access_token def get_user_type(self, obj): - return JUNIOR + email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last() + if email_verified and email_verified.user_type != None: + return email_verified.user_type + return '1' def get_auth(self, obj): return obj.auth.username diff --git a/account/urls.py b/account/urls.py index e0e1c6c..74098c2 100644 --- a/account/urls.py +++ b/account/urls.py @@ -14,8 +14,8 @@ router = routers.SimpleRouter() router.register('user', UserLogin, basename='user') router.register('admin', UserLogin, basename='admin') 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') +# router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') +# router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') urlpatterns = [ diff --git a/account/views.py b/account/views.py index f992a05..3c006d7 100644 --- a/account/views.py +++ b/account/views.py @@ -1,3 +1,5 @@ +from datetime import datetime, timedelta + from rest_framework import viewsets, status, views from rest_framework.decorators import action import random @@ -182,9 +184,16 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) + + expiry = datetime.today() + timedelta(days=1) + print("expiry===>", expiry, '===>', type(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) @@ -246,6 +255,7 @@ class UserLogin(viewsets.ViewSet): email_verified = UserEmailOtp.objects.filter(email=username).last() refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) + print("email_verified.user_type==>",email_verified.user_type) data = {"auth_token":access_token, "is_profile_complete": False, "user_type": email_verified.user_type, } @@ -292,6 +302,15 @@ class UserEmailVerification(viewsets.ModelViewSet): email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'), otp=self.request.GET.get('otp')).last() if email_data: + input_datetime_str = str(email_data.expired_at) + input_format = "%Y-%m-%d %H:%M:%S.%f%z" + output_format = "%Y-%m-%d %H:%M:%S.%f" + input_datetime = datetime.strptime(input_datetime_str, input_format) + output_datetime_str = input_datetime.strftime(output_format) + format_str = "%Y-%m-%d %H:%M:%S.%f" + datetime_obj = datetime.strptime(output_datetime_str, format_str) + if datetime.today() > datetime_obj: + return custom_error_response(ERROR_CODE["2029"], response_status=status.HTTP_400_BAD_REQUEST) email_data.is_verified = True email_data.save() if email_data.user_type == '1': @@ -315,12 +334,19 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" + serializer_class = EmailVerificationSerializer def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): + expiry = datetime.today() + timedelta(days=1) + print("expiry===>", expiry, '===>', type(expiry)) email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) + if created: + email_data.expired_at = expiry + email_data.save() if email_data: email_data.otp = otp + email_data.expired_at = expiry email_data.save() send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK) diff --git a/base/messages.py b/base/messages.py index dc6392a..f024a3d 100644 --- a/base/messages.py +++ b/base/messages.py @@ -51,7 +51,8 @@ ERROR_CODE = { "2025": "Invalid input. Expected a list of strings.", "2026": "New password should not same as old password", "2027": "data should contain `identityToken`", - "2028": "You are not authorized person to sign up on this platform" + "2028": "You are not authorized person to sign up on this platform", + "2029": "Validity of otp verification is expired" } SUCCESS_CODE = { # Success code for password diff --git a/guardian/views.py b/guardian/views.py index 900734e..6b7bf9b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -4,6 +4,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.db.models import Max +from datetime import datetime, timedelta """Import Django app""" from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer) @@ -30,7 +31,10 @@ class SignupViewset(viewsets.ModelViewSet): serializer.save() """Generate otp""" otp = generate_otp() - UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type'])) + expiry = datetime.today() + timedelta(days=1) + print("expiry===>", expiry, '===>', type(expiry)) + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, + user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, From 3b642fcc5f7717f02a014b6114b18ae3cada9c0c Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 16:08:43 +0530 Subject: [PATCH 049/372] changes in requirement file --- account/serializers.py | 1 - account/views.py | 3 --- guardian/serializers.py | 38 -------------------------------------- guardian/views.py | 33 --------------------------------- junior/serializers.py | 10 ---------- junior/views.py | 1 - requirements.txt | 5 +++++ 7 files changed, 5 insertions(+), 86 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 9380ba8..5d688b4 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -169,7 +169,6 @@ class JuniorSerializer(serializers.ModelSerializer): auth_token = serializers.SerializerMethodField('get_auth_token') def get_auth_token(self, obj): - print("obj===>",obj,'===>',type(obj)) refresh = RefreshToken.for_user(obj.auth) access_token = str(refresh.access_token) return access_token diff --git a/account/views.py b/account/views.py index 3c006d7..fd0cef2 100644 --- a/account/views.py +++ b/account/views.py @@ -186,7 +186,6 @@ class ForgotPasswordAPIView(views.APIView): ) expiry = datetime.today() + timedelta(days=1) - print("expiry===>", expiry, '===>', type(expiry)) user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry @@ -255,7 +254,6 @@ class UserLogin(viewsets.ViewSet): email_verified = UserEmailOtp.objects.filter(email=username).last() refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) - print("email_verified.user_type==>",email_verified.user_type) data = {"auth_token":access_token, "is_profile_complete": False, "user_type": email_verified.user_type, } @@ -339,7 +337,6 @@ class ReSendEmailOtp(viewsets.ModelViewSet): otp = generate_otp() if User.objects.filter(email=request.data['email']): expiry = datetime.today() + timedelta(days=1) - print("expiry===>", expiry, '===>', type(expiry)) email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) if created: email_data.expired_at = expiry diff --git a/guardian/serializers.py b/guardian/serializers.py index a55c320..f85affc 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -6,8 +6,6 @@ from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User -from django.core.validators import URLValidator -from django.core.exceptions import ValidationError """Import Django app""" from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp @@ -129,9 +127,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) - print("image_url=====>",image_url,'===>',type(image_url)) guardian.image = image_url - print("guardian.image=====>", guardian.image,'===>',type(guardian.image)) guardian.save() return guardian @@ -226,37 +222,3 @@ class TopJuniorSerializer(serializers.ModelSerializer): return item['total_points'] return 0 -# -# class TopJuniorSerializer(serializers.ModelSerializer): -# total_points = serializers.SerializerMethodField() -# -# email = serializers.SerializerMethodField('get_auth') -# first_name = serializers.SerializerMethodField('get_first_name') -# last_name = serializers.SerializerMethodField('get_last_name') -# -# -# def get_auth(self, obj): -# return obj.auth.username -# -# def get_first_name(self, obj): -# return obj.auth.first_name -# -# def get_last_name(self, obj): -# print("onbj==>",obj) -# return obj.auth.last_name -# -# class Meta: -# model = Junior -# fields = ['id', 'email', 'first_name', 'last_name', 'phone', 'country_code', 'country_name', 'gender', 'dob', 'image', 'junior_code', 'guardian_code', 'referral_code', 'referral_code_used', 'is_active', 'is_complete_profile', 'passcode', 'is_verified', 'created_at', 'updated_at', 'total_points'] -# -# def get_total_points(self, obj): -# total_highest_points = self.context.get('total_highest_points') -# if total_highest_points: -# print("total_highest_points==>",total_highest_points) -# junior_id = obj.id -# print("junior_id==>", junior_id) -# total_points = next((item['total_points'] for item in total_highest_points if item['junior'] == junior_id), 0) -# print("total_points==>", total_points) -# return total_points -# return 0 -# diff --git a/guardian/views.py b/guardian/views.py index 6b7bf9b..5418565 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -32,7 +32,6 @@ class SignupViewset(viewsets.ModelViewSet): """Generate otp""" otp = generate_otp() expiry = datetime.today() + timedelta(days=1) - print("expiry===>", expiry, '===>', type(expiry)) 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""" @@ -150,35 +149,3 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): junior_ids_with_total_points}) return custom_response(serializer.data, response_status=status.HTTP_200_OK) -# -# class TopJuniorListAPIView(viewsets.ModelViewSet): -# """Top juniors list""" -# serializer_class = TopJuniorSerializer -# # permission_classes = [IsAuthenticated] -# -# def list(self, request, *args, **kwargs): -# """fetch junior list those complete their task""" -# total_highest_points = list(JuniorTask.objects.filter(task_status=1) -# .values('junior') -# .annotate(total_points=Sum('points')) -# .order_by('-total_points')) -# print("total_highest_points===>",total_highest_points,'===>',type(total_highest_points)) -# junior_ids = [item['junior'] for item in total_highest_points] -# print("junior_ids====>", junior_ids) -# juniors = Junior.objects.filter(id__in=junior_ids) -# # a = [] -# # for i in junior_ids: -# # juniors = Junior.objects.filter(id=i) -# # a.append(juniors) -# print("juniors====>", juniors) -# # print('a===>',a,'==>',type(a)) -# serializer = self.get_serializer(juniors, context={'total_highest_points': total_highest_points}, many=True) -# print("serializer====>",type(serializer.data)) -# # Find the junior with the highest points -# # highest_points_junior = max(serializer.data, key=lambda x: x['total_points']) -# -# return custom_response(serializer.data, response_status=status.HTTP_200_OK) -# # serializer = self.get_serializer(total_highest_points, many=True) -# # return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) - - diff --git a/junior/serializers.py b/junior/serializers.py index 8e871cd..5802a9c 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -61,15 +61,6 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Create junior profile""" image = validated_data.get('image', None) guardian_code = validated_data.get('guardian_code',None) - print("guardian_code===>",guardian_code,'==>',type(guardian_code)) - - - - # phone_number = validated_data.get('phone', None) - # guardian_data = Guardian.objects.filter(phone=phone_number) - # junior_data = Junior.objects.filter(phone=phone_number) - # if phone_number and (junior_data or guardian_data): - # raise serializers.ValidationError({"details":ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of junior""" @@ -167,7 +158,6 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): return obj.auth.last_name def get_assigned_task(self, obj): - print("obj===>",obj,'type==>',type(obj)) data = JuniorTask.objects.filter(junior=obj).count() return data diff --git a/junior/views.py b/junior/views.py index fd2555e..89c177f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -48,6 +48,5 @@ class JuniorListAPIView(viewsets.ModelViewSet): """ junior list""" guardian_data = Guardian.objects.filter(user__email=request.user).last() queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - print("queryset===>",queryset) serializer = JuniorDetailListSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) diff --git a/requirements.txt b/requirements.txt index 1103c6a..5dd7f76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,8 @@ click==8.1.3 click-didyoumean==0.3.0 click-plugins==1.1.1 click-repl==0.3.0 +coreapi==2.3.3 +coreschema==0.0.4 crcmod==1.7 cron-descriptor==1.4.0 cryptography==41.0.1 @@ -43,8 +45,11 @@ google-auth==2.21.0 gunicorn==20.1.0 idna==3.4 inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.1.2 jmespath==0.10.0 kombu==5.3.1 +MarkupSafe==2.1.3 msgpack==1.0.5 oss2==2.18.0 packaging==23.1 From c7a5bf68a0853ff654f08de0f14778a7857dd9dc Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 16:17:56 +0530 Subject: [PATCH 050/372] jira-15 remove commented code --- guardian/serializers.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index f85affc..0241874 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -147,12 +147,6 @@ class TaskSerializer(serializers.ModelSerializer): validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() images = self.context['image'] validated_data['default_image'] = images - # if 'http' in str(images): - # validated_data['image'] = images - # else: - # filename = f"images/{images.name}" - # image_url = upload_image_to_alibaba(images, filename) - # validated_data['image'] = image_url instance = JuniorTask.objects.create(**validated_data) return instance From 34034dcdb84ded897b42aadbfeecdf4cf11241c0 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 16:20:07 +0530 Subject: [PATCH 051/372] jira-15 remove commented code --- guardian/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/guardian/utils.py b/guardian/utils.py index 46a8071..66a36ca 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -10,9 +10,6 @@ def upload_image_to_alibaba(image, filename): 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) - print("filename====>",filename,'===>',type(filename)) new_filename = filename.replace(' ', '%20') - print() - print("filename====>", filename, '===>', type(filename)) return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" From 2b783512a5e4e19e4dd15ebb8e88e48e70597342 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 19:53:20 +0530 Subject: [PATCH 052/372] update profile --- account/serializers.py | 32 +++++++++-------- account/views.py | 24 +++++++++---- guardian/views.py | 37 +++++++++++++++----- junior/migrations/0007_alter_junior_image.py | 18 ++++++++++ junior/models.py | 2 +- 5 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 junior/migrations/0007_alter_junior_image.py diff --git a/account/serializers.py b/account/serializers.py index 5d688b4..2074c25 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -23,12 +23,28 @@ class GoogleLoginSerializer(serializers.Serializer): """meta class""" fields = ('access_token',) + +class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): + """Reset Password after verification""" + class Meta(object): + """Meta info""" + model = Junior + fields = ['image'] + + def update(self, instance, validated_data): + """update image """ + junior_image = validated_data.get('image', instance.image) + instance.image = junior_image + instance.save() + return instance + + class UpdateGuardianImageSerializer(serializers.ModelSerializer): """Reset Password after verification""" class Meta(object): """Meta info""" model = Guardian - fields = '__all__' + fields = ['image'] def update(self, instance, validated_data): """update image """ @@ -36,21 +52,7 @@ class UpdateGuardianImageSerializer(serializers.ModelSerializer): instance.save() return instance -class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): - """Reset Password after verification""" - class Meta(object): - """Meta info""" - model = Junior - fields = '__all__' - def update(self, instance, validated_data): - """update image """ - image = validated_data.get('image', instance.image) - filename = f"images/{image.name}" - image_url = upload_image_to_alibaba(image, filename) - instance.image = image_url - instance.save() - return instance class ResetPasswordSerializer(serializers.Serializer): """Reset Password after verification""" verification_code = serializers.CharField(max_length=10) diff --git a/account/views.py b/account/views.py index fd0cef2..33ebbbc 100644 --- a/account/views.py +++ b/account/views.py @@ -4,7 +4,9 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging +from django.utils import timezone import jwt +from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior @@ -135,12 +137,22 @@ class SigninWithApple(views.APIView): class UpdateProfileImage(views.APIView): permission_classes = [IsAuthenticated] def put(self, request, format=None): - if request.data['user_type'] == '1': + if str(request.data['user_type']) == '1': junior_query = Junior.objects.filter(auth=request.user).last() - serializer = UpdateJuniorProfileImageSerializer(junior_query, data=request.data, partial=True) - else: + image = request.data['image'] + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + image_data = image_url + serializer = UpdateJuniorProfileImageSerializer(junior_query, + data={'image':image_data}, partial=True) + if str(request.data['user_type']) == '2': guardian_query = Guardian.objects.filter(user=request.user).last() - serializer = UpdateGuardianImageSerializer(guardian_query, data=request.data, partial=True) + image = request.data['image'] + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + image_data = image_url + serializer = UpdateGuardianImageSerializer(guardian_query, + data={'image':image_data}, partial=True) if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK) @@ -185,7 +197,7 @@ class ForgotPasswordAPIView(views.APIView): } ) - expiry = datetime.today() + timedelta(days=1) + expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry @@ -336,7 +348,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): - expiry = datetime.today() + timedelta(days=1) + 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 diff --git a/guardian/views.py b/guardian/views.py index 5418565..3238cdd 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -3,7 +3,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination -from django.db.models import Max +from django.contrib.auth.models import User +from django.utils import timezone from datetime import datetime, timedelta """Import Django app""" from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, @@ -22,7 +23,6 @@ from django.db.models import Sum class SignupViewset(viewsets.ModelViewSet): """Signup view set""" serializer_class = UserSerializer - def create(self, request, *args, **kwargs): """Create user profile""" if request.data['user_type'] in ['1', '2']: @@ -31,7 +31,7 @@ class SignupViewset(viewsets.ModelViewSet): serializer.save() """Generate otp""" otp = generate_otp() - expiry = datetime.today() + timedelta(days=1) + expiry = timezone.now() + timezone.timedelta(days=1) 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""" @@ -110,24 +110,45 @@ class CreateTaskAPIView(viewsets.ModelViewSet): 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) - class SearchTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination + def get_queryset(self): + """Get the queryset for the view""" + title = self.request.GET.get('title') + junior_queryset = JuniorTask.objects.filter(guardian__user=self.request.user, task_name__icontains=title)\ + .order_by('due_date', 'created_at') + return junior_queryset + def list(self, request, *args, **kwargs): """Create guardian profile""" - title = self.request.GET.get('title') - if title: - queryset = JuniorTask.objects.filter(guardian__user=request.user, task_name__icontains=title)\ - .order_by('due_date','created_at') + queryset = self.get_queryset() + paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# class SearchTaskListAPIView(viewsets.ModelViewSet): +# """Update guardian profile""" +# serializer_class = TaskDetailsSerializer +# permission_classes = [IsAuthenticated] +# pagination_class = PageNumberPagination +# +# def list(self, request, *args, **kwargs): +# """Create guardian profile""" +# title = self.request.GET.get('title') +# junior_queryset = JuniorTask.objects.filter(guardian__user=request.user, task_name__icontains=title)\ +# .order_by('due_date','created_at') +# paginator = self.pagination_class() +# queryset = paginator.paginate_queryset(junior_queryset, request) +# serializer = TaskDetailsSerializer(queryset, many=True) +# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" diff --git a/junior/migrations/0007_alter_junior_image.py b/junior/migrations/0007_alter_junior_image.py new file mode 100644 index 0000000..be695a0 --- /dev/null +++ b/junior/migrations/0007_alter_junior_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-06 12:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0006_alter_junior_country_name'), + ] + + operations = [ + migrations.AlterField( + model_name='junior', + name='image', + field=models.URLField(blank=True, default=None, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 331673f..2534b50 100644 --- a/junior/models.py +++ b/junior/models.py @@ -18,7 +18,7 @@ class Junior(models.Model): """Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) - image = models.ImageField(null=True, blank=True, default=None) + image = models.URLField(null=True, blank=True, default=None) """Codes""" junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) From c6ffb1f039751c65eb6ba1ab15361bebe4afd4e3 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 6 Jul 2023 19:59:03 +0530 Subject: [PATCH 053/372] update profile --- account/serializers.py | 4 ++-- guardian/views.py | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 2074c25..28a58b2 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -25,7 +25,7 @@ class GoogleLoginSerializer(serializers.Serializer): class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): - """Reset Password after verification""" + """update junior image""" class Meta(object): """Meta info""" model = Junior @@ -40,7 +40,7 @@ class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): class UpdateGuardianImageSerializer(serializers.ModelSerializer): - """Reset Password after verification""" + """update guardian image""" class Meta(object): """Meta info""" model = Guardian diff --git a/guardian/views.py b/guardian/views.py index 3238cdd..2a3eeaf 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -133,23 +133,6 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) -# class SearchTaskListAPIView(viewsets.ModelViewSet): -# """Update guardian profile""" -# serializer_class = TaskDetailsSerializer -# permission_classes = [IsAuthenticated] -# pagination_class = PageNumberPagination -# -# def list(self, request, *args, **kwargs): -# """Create guardian profile""" -# title = self.request.GET.get('title') -# junior_queryset = JuniorTask.objects.filter(guardian__user=request.user, task_name__icontains=title)\ -# .order_by('due_date','created_at') -# paginator = self.pagination_class() -# queryset = paginator.paginate_queryset(junior_queryset, request) -# serializer = TaskDetailsSerializer(queryset, many=True) -# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - - class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer From ab435ef384b9704daf9f3094d5939f3258a0ae37 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 7 Jul 2023 16:33:51 +0530 Subject: [PATCH 054/372] jira-15 dashboard api profile, default image, top leaderboard API --- account/admin.py | 11 +++- account/migrations/0003_defaulttaskimages.py | 26 +++++++++ account/models.py | 16 ++++++ account/serializers.py | 20 ++++++- account/urls.py | 6 ++- account/views.py | 57 ++++++++++++++++++-- guardian/serializers.py | 37 ++++++++++--- guardian/views.py | 11 ++-- junior/serializers.py | 30 +++++++++++ junior/views.py | 4 +- 10 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 account/migrations/0003_defaulttaskimages.py diff --git a/account/admin.py b/account/admin.py index 7dbf869..96c2e4d 100644 --- a/account/admin.py +++ b/account/admin.py @@ -2,8 +2,17 @@ from django.contrib import admin """Import django app""" -from .models import UserProfile, UserEmailOtp, UserPhoneOtp +from .models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages # Register your models here. + +@admin.register(DefaultTaskImages) +class DefaultTaskImagesAdmin(admin.ModelAdmin): + """User profile admin""" + list_display = ['task_name', 'image_url'] + + def __str__(self): + return self.image_url + @admin.register(UserProfile) class UserProfileAdmin(admin.ModelAdmin): """User profile admin""" diff --git a/account/migrations/0003_defaulttaskimages.py b/account/migrations/0003_defaulttaskimages.py new file mode 100644 index 0000000..ec2e030 --- /dev/null +++ b/account/migrations/0003_defaulttaskimages.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.2 on 2023-07-07 10:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0002_useremailotp_user_type'), + ] + + operations = [ + migrations.CreateModel( + name='DefaultTaskImages', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('task_name', models.CharField(max_length=15)), + ('image_url', models.URLField(blank=True, default=None, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'db_table': 'default_task_image', + }, + ), + ] diff --git a/account/models.py b/account/models.py index 43ab6e5..3eedfde 100644 --- a/account/models.py +++ b/account/models.py @@ -74,3 +74,19 @@ class UserEmailOtp(models.Model): def __str__(self): """return phone as an object""" return self.email + +class DefaultTaskImages(models.Model): + """Default images upload in oss bucket""" + + task_name = models.CharField(max_length=15) + image_url = models.URLField(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 = 'default_task_image' + + def __str__(self): + """return phone as an object""" + return self.task_name diff --git a/account/serializers.py b/account/serializers.py index 28a58b2..b4b6899 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.contrib.auth.models import User from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserEmailOtp, UserPhoneOtp +from account.models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages from base.constants import GUARDIAN, JUNIOR, SUPERUSER from django.db import transaction from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR @@ -204,3 +204,21 @@ class EmailVerificationSerializer(serializers.ModelSerializer): model = UserEmailOtp fields = '__all__' + + +class DefaultTaskImagesSerializer(serializers.ModelSerializer): + """Update Password after verification""" + class Meta(object): + """Meta info""" + model = DefaultTaskImages + fields = ['task_name', 'image_url'] + def create(self, validated_data): + data = DefaultTaskImages.objects.create(**validated_data) + return data + +class DefaultTaskImagesDetailsSerializer(serializers.ModelSerializer): + """Update Password after verification""" + class Meta(object): + """Meta info""" + model = DefaultTaskImages + fields = '__all__' diff --git a/account/urls.py b/account/urls.py index 74098c2..c9929be 100644 --- a/account/urls.py +++ b/account/urls.py @@ -6,7 +6,8 @@ from rest_framework.decorators import api_view from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, - GoogleLoginViewSet, SigninWithApple) + GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, + DefaultImageAPIViewSet) """Router""" router = routers.SimpleRouter() @@ -18,6 +19,9 @@ router.register('google-login', GoogleLoginViewSet, basename='admin') # router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') +router.register('profile', ProfileAPIViewSet, basename='profile') +router.register('upload-default-task-image', UploadImageAPIViewSet, basename='upload-default-task-image') +router.register('default-task-image', DefaultImageAPIViewSet, basename='default-task-image') urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), diff --git a/account/views.py b/account/views.py index 33ebbbc..15b8cff 100644 --- a/account/views.py +++ b/account/views.py @@ -10,11 +10,12 @@ from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserPhoneOtp, UserEmailOtp +from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, - GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer) + GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, + DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -29,7 +30,8 @@ from rest_framework.response import Response import requests from django.conf import settings from .utils import get_token - +from junior.serializers import JuniorProfileSerializer +from guardian.serializers import GuardianProfileSerializer class GoogleLoginMixin: def google_login(self, request): @@ -159,6 +161,7 @@ class UpdateProfileImage(views.APIView): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ChangePasswordAPIView(views.APIView): + serializer_class = ChangePasswordSerializer permission_classes = [IsAuthenticated] def post(self, request): serializer = ChangePasswordSerializer(context=request.user, data=request.data) @@ -212,6 +215,7 @@ class ForgotPasswordAPIView(views.APIView): class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" + queryset = UserPhoneOtp.objects.all() def create(self, request, *args, **kwargs): otp = generate_otp() phone_number = self.request.data['phone'] @@ -227,6 +231,7 @@ class SendPhoneOtp(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" + queryset = UserPhoneOtp.objects.all() def list(self, request, *args, **kwargs): try: phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'), @@ -305,6 +310,7 @@ class UserLogin(viewsets.ViewSet): class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer + queryset = UserEmailOtp.objects.all() def list(self, request, *args, **kwargs): try: @@ -344,6 +350,7 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" + queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer def create(self, request, *args, **kwargs): otp = generate_otp() @@ -361,3 +368,47 @@ class ReSendEmailOtp(viewsets.ModelViewSet): 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) + +class ProfileAPIViewSet(viewsets.ModelViewSet): + """Profile viewset""" + queryset = User.objects.all() + serializer_class = [JuniorProfileSerializer, GuardianProfileSerializer] + def list(self, request, *args, **kwargs): + """profile view""" + if str(self.request.GET.get('user_type')) == '1': + junior_data = Junior.objects.filter(auth=self.request.user).last() + if junior_data: + serializer = JuniorProfileSerializer(junior_data) + if str(self.request.GET.get('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) + + +class UploadImageAPIViewSet(viewsets.ModelViewSet): + """Profile viewset""" + queryset = DefaultTaskImages.objects.all() + serializer_class = DefaultTaskImagesSerializer + def create(self, request, *args, **kwargs): + """profile view""" + image_data = request.data['image_url'] + filename = f"default_task_images/{image_data.name}" + image = upload_image_to_alibaba(image_data, filename) + image_data = image + request.data['image_url'] = image_data + serializer = DefaultTaskImagesSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + +class DefaultImageAPIViewSet(viewsets.ModelViewSet): + """Profile viewset""" + queryset = DefaultTaskImages.objects.all() + serializer_class = DefaultTaskImagesDetailsSerializer + def list(self, request, *args, **kwargs): + """profile view""" + queryset = DefaultTaskImages.objects.all() + serializer = DefaultTaskImagesSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) diff --git a/guardian/serializers.py b/guardian/serializers.py index 0241874..0a01cd1 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -38,9 +38,11 @@ class UserSerializer(serializers.ModelSerializer): user = User.objects.create_user(username=email, email=email, password=password) UserProfile.objects.create(user=user, user_type=user_type) if user_type == '1': - Junior.objects.create(auth=user) + Junior.objects.create(auth=user, junior_code=''.join([str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join([str(random.randrange(9)) for _ in range(6)])) if user_type == '2': - Guardian.objects.create(user=user) + Guardian.objects.create(user=user, guardian_code=''.join([str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join([str(random.randrange(9)) for _ in range(6)])) return user except Exception as e: """Error handling""" @@ -88,11 +90,6 @@ class CreateGuardianSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create guardian profile""" - # phone_number = validated_data.get('phone', None) - # guardian_data = Guardian.objects.filter(phone=phone_number) - # junior_data = Junior.objects.filter(phone=phone_number) - # if phone_number and (guardian_data or junior_data): - # raise serializers.ValidationError({"details": ERROR_CODE['2012']}) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of guardian""" @@ -176,7 +173,7 @@ class GuardianDetailSerializer(serializers.ModelSerializer): 'updated_at'] class TaskDetailsSerializer(serializers.ModelSerializer): - guardian = GuardianDetailSerializer() + # guardian = GuardianDetailSerializer() junior = JuniorDetailSerializer() class Meta(object): model = JuniorTask @@ -216,3 +213,27 @@ class TopJuniorSerializer(serializers.ModelSerializer): return item['total_points'] return 0 +class GuardianProfileSerializer(serializers.ModelSerializer): + """junior serializer""" + + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + + def get_auth(self, obj): + """user email address""" + return obj.user.username + + def get_first_name(self, obj): + """user first name""" + return obj.user.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.user.last_name + class Meta(object): + """Meta info""" + model = Guardian + fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'updated_at'] \ No newline at end of file diff --git a/guardian/views.py b/guardian/views.py index 2a3eeaf..b6fc025 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -22,6 +22,7 @@ from django.db.models import Sum # Create your views here. class SignupViewset(viewsets.ModelViewSet): """Signup view set""" + queryset = User.objects.all() serializer_class = UserSerializer def create(self, request, *args, **kwargs): """Create user profile""" @@ -44,6 +45,7 @@ class SignupViewset(viewsets.ModelViewSet): class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" + queryset = Guardian.objects.all() serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] @@ -63,6 +65,7 @@ class UpdateGuardianProfile(viewsets.ViewSet): class AllTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer + queryset = JuniorTask.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -76,6 +79,7 @@ class TaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination + queryset = JuniorTask.objects.all() def list(self, request, *args, **kwargs): """Create guardian profile""" @@ -93,6 +97,7 @@ class TaskListAPIView(viewsets.ModelViewSet): class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" serializer_class = TaskSerializer + queryset = JuniorTask.objects.all() def create(self, request, *args, **kwargs): image = request.data['default_image'] @@ -103,7 +108,6 @@ class CreateTaskAPIView(viewsets.ModelViewSet): filename = f"images/{image}" image_url = upload_image_to_alibaba(image, filename) image_data = image_url - data.pop('default_image') serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): serializer.save() @@ -115,6 +119,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination + queryset = JuniorTask.objects.all() def get_queryset(self): """Get the queryset for the view""" @@ -136,8 +141,8 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer - # permission_classes = [IsAuthenticated] - + permission_classes = [IsAuthenticated] + queryset = JuniorTask.objects.all() def list(self, request, *args, **kwargs): """fetch junior list those complete their task""" junior_ids_with_total_points = JuniorTask.objects.filter(task_status=1) \ diff --git a/junior/serializers.py b/junior/serializers.py index 5802a9c..a62e7ae 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -192,3 +192,33 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): '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'] + +class JuniorProfileSerializer(serializers.ModelSerializer): + """junior serializer""" + email = serializers.SerializerMethodField('get_auth') + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + notification_count = serializers.SerializerMethodField('get_notification_count') + + def get_auth(self, obj): + """user email address""" + return obj.auth.username + + def get_first_name(self, obj): + """user first name""" + return obj.auth.first_name + + def get_last_name(self, obj): + """user last name""" + return obj.auth.last_name + + def get_notification_count(self, obj): + """user email address""" + return 0 + + class Meta(object): + """Meta info""" + model = Junior + fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'updated_at', 'notification_count'] diff --git a/junior/views.py b/junior/views.py index 89c177f..0658b2c 100644 --- a/junior/views.py +++ b/junior/views.py @@ -10,6 +10,7 @@ from account.utils import custom_response, custom_error_response # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" + queryset = Junior.objects.all() serializer_class = CreateJuniorSerializer permission_classes = [IsAuthenticated] @@ -27,6 +28,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): class ValidateGuardianCode(viewsets.ViewSet): """Check guardian code exist or not""" + queryset = Guardian.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -43,7 +45,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - + queryset = Junior.objects.all() def list(self, request, *args, **kwargs): """ junior list""" guardian_data = Guardian.objects.filter(user__email=request.user).last() From 9e8cde3516025a4e36d998a994446a3055eeacb2 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 7 Jul 2023 17:02:48 +0530 Subject: [PATCH 055/372] swagger --- zod_bank/settings.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 3a897cb..6960233 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -112,6 +112,30 @@ DATABASES = { 'PORT':os.getenv('DB_PORT'), } } + +SWAGGER_SETTINGS = { + "exclude_namespaces": [], + "api_version": '0.1', + "api_path": "", + "enabled_methods": [ + 'get', + 'post', + 'put', + 'patch', + 'delete' + ], + "api_key": '', + "is_authenticated": True, + "is_superuser": False, + + 'SECURITY_DEFINITIONS': { + "api_key": { + "type": "apiKey", + "name": "Authorization", + "in": "header", + }, + }, +} # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators From 413434496c64faf42be621a842374bb30f724c10 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 7 Jul 2023 17:57:29 +0530 Subject: [PATCH 056/372] jira-15 country_name --- account/serializers.py | 8 ++++---- guardian/serializers.py | 2 +- junior/serializers.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index b4b6899..b04df3e 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -157,9 +157,9 @@ class GuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob', - 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', - 'created_at', 'updated_at', 'user_type'] + fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', + 'gender', 'dob', 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', + 'created_at', 'updated_at', 'user_type', 'country_name'] class JuniorSerializer(serializers.ModelSerializer): @@ -195,7 +195,7 @@ class JuniorSerializer(serializers.ModelSerializer): model = Junior fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', - 'updated_at', 'user_type'] + 'updated_at', 'user_type', 'country_name'] class EmailVerificationSerializer(serializers.ModelSerializer): """Email verification serializer""" diff --git a/guardian/serializers.py b/guardian/serializers.py index 0a01cd1..6df46c6 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -234,6 +234,6 @@ class GuardianProfileSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 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'] \ No newline at end of file diff --git a/junior/serializers.py b/junior/serializers.py index a62e7ae..7506f81 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -219,6 +219,6 @@ class JuniorProfileSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 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'] From 3723e46be1e91a5e6024ed8a4125a681d3b77320 Mon Sep 17 00:00:00 2001 From: jain Date: Sun, 9 Jul 2023 17:56:47 +0530 Subject: [PATCH 057/372] jira-15 changes in profile API --- guardian/serializers.py | 22 +++++++++++++++++++++- guardian/views.py | 3 ++- junior/serializers.py | 16 ++++++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 6df46c6..1b6fa7e 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -219,6 +219,9 @@ class GuardianProfileSerializer(serializers.ModelSerializer): email = serializers.SerializerMethodField('get_auth') first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') + total_count = serializers.SerializerMethodField('get_total_count') + complete_field_count = serializers.SerializerMethodField('get_complete_field_count') + notification_count = serializers.SerializerMethodField('get_notification_count') def get_auth(self, obj): """user email address""" @@ -231,9 +234,26 @@ class GuardianProfileSerializer(serializers.ModelSerializer): def get_last_name(self, obj): """user last name""" return obj.user.last_name + + def get_total_count(self, obj): + """total fields count""" + return 9 + + def get_complete_field_count(self, obj): + """total filled fields count""" + total_field_list = [obj.user.first_name, obj.user.last_name, obj.user.email, obj.country_name, obj.country_code, + obj.phone, obj.gender, obj.dob, obj.image] + total_complete_field = [data for data in total_field_list if data != '' and data is not None] + return len(total_complete_field) + + def get_notification_count(self, obj): + """total notification count""" + return 0 + class Meta(object): """Meta info""" model = Guardian 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', + 'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code', + 'is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at'] \ No newline at end of file diff --git a/guardian/views.py b/guardian/views.py index b6fc025..eba6248 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -84,7 +84,8 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') - if status_value == 0: + print("status_value==>",status_value,'===>',type(status_value)) + if str(status_value) == '0': queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('created_at') else: queryset = JuniorTask.objects.filter(guardian__user=request.user, diff --git a/junior/serializers.py b/junior/serializers.py index 7506f81..b87c799 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -199,6 +199,8 @@ class JuniorProfileSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') notification_count = serializers.SerializerMethodField('get_notification_count') + total_count = serializers.SerializerMethodField('get_total_count') + complete_field_count = serializers.SerializerMethodField('get_complete_field_count') def get_auth(self, obj): """user email address""" @@ -213,12 +215,22 @@ class JuniorProfileSerializer(serializers.ModelSerializer): return obj.auth.last_name def get_notification_count(self, obj): - """user email address""" + """total notification count""" return 0 + def get_total_count(self, obj): + """total fields count""" + return 9 + + def get_complete_field_count(self, obj): + """total filled fields count""" + field_list = [obj.auth.first_name, obj.auth.last_name, obj.auth.email, obj.country_name, obj.country_code, + obj.phone, obj.gender, obj.dob, obj.image] + complete_field = [data for data in field_list if data is not None and data != ''] + return len(complete_field) class Meta(object): """Meta info""" model = Junior 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'] + 'updated_at', 'notification_count', 'total_count', 'complete_field_count'] From 1b60d4344e03e6be6086b2ba34d64041db99e6e6 Mon Sep 17 00:00:00 2001 From: jain Date: Sun, 9 Jul 2023 19:13:05 +0530 Subject: [PATCH 058/372] jira-15 top leadership API --- guardian/serializers.py | 32 ++++---------------------- guardian/views.py | 18 ++++----------- junior/admin.py | 11 ++++++++- junior/migrations/0008_juniorpoints.py | 28 ++++++++++++++++++++++ junior/models.py | 18 +++++++++++++++ 5 files changed, 65 insertions(+), 42 deletions(-) create mode 100644 junior/migrations/0008_juniorpoints.py diff --git a/guardian/serializers.py b/guardian/serializers.py index 1b6fa7e..a0a85a6 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -13,7 +13,7 @@ from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from .utils import upload_image_to_alibaba -from junior.models import Junior +from junior.models import Junior, JuniorPoints class UserSerializer(serializers.ModelSerializer): """User serializer""" auth_token = serializers.SerializerMethodField('get_auth_token') @@ -181,37 +181,13 @@ class TaskDetailsSerializer(serializers.ModelSerializer): 'junior', 'task_status', 'is_active', 'created_at','updated_at'] - - - class TopJuniorSerializer(serializers.ModelSerializer): - total_points = serializers.SerializerMethodField() - - email = serializers.SerializerMethodField('get_auth') - first_name = serializers.SerializerMethodField('get_first_name') - last_name = serializers.SerializerMethodField('get_last_name') - - def get_auth(self, obj): - return obj.auth.username - - def get_first_name(self, obj): - return obj.auth.first_name - - def get_last_name(self, obj): - return obj.auth.last_name + junior = JuniorDetailSerializer() class Meta: - model = Junior - fields = ['id', 'email', 'first_name', 'last_name', 'phone', 'country_code', 'country_name', 'gender', 'dob', 'image', 'junior_code', 'guardian_code', 'referral_code', 'referral_code_used', 'is_active', 'is_complete_profile', 'passcode', 'is_verified', 'created_at', 'updated_at', 'total_points'] + model = JuniorPoints + fields = ['id', 'junior', 'total_task_points', 'created_at', 'updated_at'] - def get_total_points(self, obj): - junior_ids_with_total_points = self.context.get('junior_ids_with_total_points') - if junior_ids_with_total_points: - junior_id = obj.id - for item in junior_ids_with_total_points: - if item['junior'] == junior_id: - return item['total_points'] - return 0 class GuardianProfileSerializer(serializers.ModelSerializer): """junior serializer""" diff --git a/guardian/views.py b/guardian/views.py index eba6248..e0f2bef 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer) from .models import Guardian, JuniorTask -from junior.models import Junior +from junior.models import Junior, JuniorPoints from junior.serializers import JuniorDetailSerializer from account.models import UserEmailOtp from .tasks import generate_otp @@ -139,23 +139,15 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] - queryset = JuniorTask.objects.all() + queryset = JuniorPoints.objects.all() def list(self, request, *args, **kwargs): """fetch junior list those complete their task""" - junior_ids_with_total_points = JuniorTask.objects.filter(task_status=1) \ - .values('junior') \ - .annotate(total_points=Sum('points')) \ - .order_by('-total_points') - - junior_ids = [item['junior'] for item in junior_ids_with_total_points] - - juniors = Junior.objects.filter(id__in=junior_ids) - - serializer = self.get_serializer(juniors, many=True, context={'junior_ids_with_total_points': - junior_ids_with_total_points}) + junior_total_points = self.get_queryset().order_by('-total_task_points') + serializer = self.get_serializer(junior_total_points, many=True) return custom_response(serializer.data, response_status=status.HTTP_200_OK) diff --git a/junior/admin.py b/junior/admin.py index 87cd7d8..bb9ea49 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior +from .models import Junior, JuniorPoints # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): @@ -12,3 +12,12 @@ class JuniorAdmin(admin.ModelAdmin): def __str__(self): """Return email id""" return self.auth__email + +@admin.register(JuniorPoints) +class JuniorPointsAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['junior', 'total_task_points'] + + def __str__(self): + """Return email id""" + return self.junior.auth.email diff --git a/junior/migrations/0008_juniorpoints.py b/junior/migrations/0008_juniorpoints.py new file mode 100644 index 0000000..d16b7e2 --- /dev/null +++ b/junior/migrations/0008_juniorpoints.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.2 on 2023-07-09 12:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0007_alter_junior_image'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorPoints', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('total_task_points', models.IntegerField(blank=True, default=0, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('junior', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='junior_points', to='junior.junior')), + ], + options={ + 'verbose_name': 'Junior Task Points', + 'db_table': 'junior_task_points', + }, + ), + ] diff --git a/junior/models.py b/junior/models.py index 2534b50..167983f 100644 --- a/junior/models.py +++ b/junior/models.py @@ -41,3 +41,21 @@ class Junior(models.Model): def __str__(self): """Return email id""" return f'{self.auth}' + +class JuniorPoints(models.Model): + """Junior model""" + junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points') + """Contact details""" + total_task_points = models.IntegerField(blank=True, null=True, default=0) + """Profile created and updated time""" + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta class """ + db_table = 'junior_task_points' + verbose_name = 'Junior Task Points' + + def __str__(self): + """Return email id""" + return f'{self.junior.auth}' From 3098de36b8129c6346bc7f873316ed41564d4bc9 Mon Sep 17 00:00:00 2001 From: jain Date: Sun, 9 Jul 2023 19:25:59 +0530 Subject: [PATCH 059/372] jira-15 top leadership API --- guardian/serializers.py | 8 +++++++- guardian/views.py | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index a0a85a6..845f331 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -183,10 +183,16 @@ class TaskDetailsSerializer(serializers.ModelSerializer): class TopJuniorSerializer(serializers.ModelSerializer): junior = JuniorDetailSerializer() + position = serializers.SerializerMethodField() class Meta: model = JuniorPoints - fields = ['id', 'junior', 'total_task_points', 'created_at', 'updated_at'] + fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at'] + + def get_position(self, obj): + queryset = self.context['view'].get_queryset() + position = list(queryset).index(obj) + 1 + return position class GuardianProfileSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index e0f2bef..ff6e332 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -143,11 +143,18 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer - permission_classes = [IsAuthenticated] + # permission_classes = [IsAuthenticated] queryset = JuniorPoints.objects.all() + + def get_serializer_context(self): + context = super().get_serializer_context() + context.update({'view': self}) + return context + def list(self, request, *args, **kwargs): """fetch junior list those complete their task""" junior_total_points = self.get_queryset().order_by('-total_task_points') serializer = self.get_serializer(junior_total_points, many=True) return custom_response(serializer.data, response_status=status.HTTP_200_OK) + From 026bfb6da71a74b64102b3f5712320c6a6cf8e71 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 12:10:15 +0530 Subject: [PATCH 060/372] sonar fixes --- account/apps.py | 3 +++ account/models.py | 4 +++- account/serializers.py | 22 +++++++++------------- account/tests.py | 2 ++ account/urls.py | 14 +++++++++++--- account/utils.py | 15 ++++++++++++--- account/views.py | 11 ++++++++--- guardian/models.py | 9 ++++++++- guardian/serializers.py | 25 ++++++++++++++++--------- guardian/utils.py | 7 +++++++ guardian/views.py | 2 +- junior/serializers.py | 13 +++++++------ manage.py | 3 +++ zod_bank/settings.py | 15 ++++----------- 14 files changed, 94 insertions(+), 51 deletions(-) diff --git a/account/apps.py b/account/apps.py index 2b08f1a..dc67efe 100644 --- a/account/apps.py +++ b/account/apps.py @@ -1,6 +1,9 @@ +"""Account app file""" +"""Import Django""" from django.apps import AppConfig class AccountConfig(AppConfig): + """default configurations""" default_auto_field = 'django.db.models.BigAutoField' name = 'account' diff --git a/account/models.py b/account/models.py index 3eedfde..ac23e79 100644 --- a/account/models.py +++ b/account/models.py @@ -1,6 +1,8 @@ +"""Account module file""" +"""Django import""" from django.db import models -import random from django.contrib.auth.models import User +"""App import""" from base.constants import USER_TYPE # Create your models here. diff --git a/account/serializers.py b/account/serializers.py index b04df3e..db11233 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,25 +1,21 @@ +"""Account serializer""" +"""Django Imoprt""" from rest_framework import serializers from django.contrib.auth.models import User +from rest_framework_simplejwt.tokens import RefreshToken +"""App import""" from guardian.models import Guardian from junior.models import Junior from account.models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages from base.constants import GUARDIAN, JUNIOR, SUPERUSER -from django.db import transaction from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR -from django.core.exceptions import ObjectDoesNotExist -from django.contrib.auth import authenticate -from rest_framework import viewsets, status -from rest_framework.decorators import action -from django.contrib.auth import authenticate, login -from rest_framework_simplejwt.tokens import RefreshToken -from guardian.utils import upload_image_to_alibaba -from .utils import get_token class GoogleLoginSerializer(serializers.Serializer): + """google login serializer""" access_token = serializers.CharField(max_length=5000, required=True) - class Meta: + class Meta(object): """meta class""" fields = ('access_token',) @@ -86,9 +82,8 @@ class ChangePasswordSerializer(serializers.Serializer): def validate_current_password(self, value): user = self.context - if self.context.password not in ('', None): - if user.check_password(value): - return value + if self.context.password not in ('', None) and user.check_password(value): + return value raise serializers.ValidationError(ERROR_CODE['2015']) def create(self, validated_data): new_password = validated_data.pop('new_password') @@ -109,6 +104,7 @@ class ForgotPasswordSerializer(serializers.Serializer): email = serializers.EmailField() class SuperUserSerializer(serializers.ModelSerializer): + """Super admin serializer""" user_type = serializers.SerializerMethodField('get_user_type') def get_user_type(self, obj): diff --git a/account/tests.py b/account/tests.py index 7ce503c..f2435b9 100644 --- a/account/tests.py +++ b/account/tests.py @@ -1,3 +1,5 @@ +"""Test cases file of account""" +"""Django import""" from django.test import TestCase # Create your tests here. diff --git a/account/urls.py b/account/urls.py index c9929be..ffb2db6 100644 --- a/account/urls.py +++ b/account/urls.py @@ -1,9 +1,9 @@ """ Urls files""" """Django import""" from django.urls import path, include -from rest_framework.decorators import api_view """Third party import""" from rest_framework import routers +"""Import view functions""" from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, @@ -13,15 +13,23 @@ router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') +"""super admin login""" router.register('admin', UserLogin, 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') +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""" router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp') +"""Profile end point""" router.register('profile', ProfileAPIViewSet, basename='profile') +"""Upload default task image end point""" router.register('upload-default-task-image', UploadImageAPIViewSet, basename='upload-default-task-image') +"""Fetch default task image end point""" router.register('default-task-image', DefaultImageAPIViewSet, basename='default-task-image') +"""Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()), diff --git a/account/utils.py b/account/utils.py index 0848c03..4412e62 100644 --- a/account/utils.py +++ b/account/utils.py @@ -1,8 +1,9 @@ """Account utils""" -"""Third party Django app""" +"""Import django""" from django.conf import settings from rest_framework import viewsets, status from rest_framework.response import Response +"""Third party Django app""" from templated_email import send_templated_mail import jwt from datetime import datetime @@ -11,6 +12,7 @@ from uuid import uuid4 import secrets def send_otp_email(recipient_email, otp): + """Send otp on email with template""" from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [recipient_email] send_templated_mail( @@ -26,8 +28,8 @@ def send_otp_email(recipient_email, otp): def custom_response(detail, data=None, response_status=status.HTTP_200_OK): """Custom response code""" if not data: + """when data is none""" data = None - return Response({"data": data, "message": detail, "status": "success", "code": response_status}) @@ -39,6 +41,7 @@ def custom_error_response(detail, response_status): :return: Json response """ if not detail: + """when details is empty""" detail = {} return Response({"error": detail, "status": "failed", "code": response_status}) @@ -58,16 +61,20 @@ def generate_jwt_token(token_type: str, now_time: int, data: dict = dict): """ if type(data) == type: data = {} + """Update data dictionary""" data.update({ 'token_type': token_type, 'iss': 'your_site_url', 'iat': timegm(datetime.utcnow().utctimetuple()), 'jti': uuid4().hex }) + """Access and Refresh token""" TOKEN_TYPE = ["access", "refresh"] if token_type == TOKEN_TYPE[1]: + """Refresh token""" exp = now_time + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] else: + """access token""" exp = now_time + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] data.update({ @@ -84,10 +91,12 @@ def generate_jwt_token(token_type: str, now_time: int, data: dict = dict): def get_token(data: dict = dict): """ create access and refresh token """ now_time = datetime.utcnow() + """generate access token""" access = generate_jwt_token('access', now_time, data) + """generate refresh token""" refresh = generate_jwt_token('refresh', now_time, data) return { 'access': access, 'refresh': refresh - } \ No newline at end of file + } diff --git a/account/views.py b/account/views.py index 15b8cff..1324869 100644 --- a/account/views.py +++ b/account/views.py @@ -1,17 +1,20 @@ +"""Account view """ +"""Django import""" from datetime import datetime, timedelta - from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging from django.utils import timezone import jwt +"""App Import""" from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages from django.contrib.auth.models import User +"""Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, @@ -29,12 +32,13 @@ from rest_framework import status from rest_framework.response import Response import requests from django.conf import settings -from .utils import get_token from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer class GoogleLoginMixin: + """google login mixin""" def google_login(self, request): + """google login function""" access_token = request.data.get('access_token') user_type = request.data.get('user_type') if not access_token: @@ -341,7 +345,8 @@ class UserEmailVerification(viewsets.ModelViewSet): guardian_data.save() refresh = RefreshToken.for_user(user_obj) access_token = str(refresh.access_token) - return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, + response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: diff --git a/guardian/models.py b/guardian/models.py index 00c5d42..1bb4d2c 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model """Import Django app""" from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS from junior.models import Junior +"""Add user model""" User = get_user_model() # Create your models here. @@ -44,16 +45,22 @@ class Guardian(models.Model): return f'{self.user}' class JuniorTask(models.Model): - """Guardian model""" + """Junior Task details model""" guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian') + """task details""" task_name = models.CharField(max_length=100) task_description = models.CharField(max_length=500) + """points of the task""" points = models.IntegerField(default=TASK_POINTS) due_date = models.DateField(auto_now_add=False, null=True, blank=True) + """Images of task""" default_image = models.URLField(null=True, blank=True, default=None) image = models.URLField(null=True, blank=True, default=None) + """associated junior with the task""" junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') + """task status""" task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) + """task stage""" is_active = models.BooleanField(default=True) is_approved = models.BooleanField(default=False) """Profile created and updated time""" diff --git a/guardian/serializers.py b/guardian/serializers.py index 845f331..08c2861 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -114,17 +114,18 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.passcode = validated_data.get('passcode', guardian.passcode) guardian.country_name = validated_data.get('country_name', guardian.country_name) guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) - """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_name, - guardian.dob, guardian.country_code, user.first_name, user.last_name]) - guardian.is_complete_profile = False - if complete_profile_field: - guardian.is_complete_profile = True image = validated_data.pop('image', None) if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) guardian.image = image_url + """Complete profile of the junior if below all data are filled""" + complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_name, + guardian.dob, guardian.country_code, user.first_name, user.last_name, + user.email, guardian.image]) + guardian.is_complete_profile = False + if complete_profile_field: + guardian.is_complete_profile = True guardian.save() return guardian @@ -137,10 +138,13 @@ class CreateGuardianSerializer(serializers.ModelSerializer): class TaskSerializer(serializers.ModelSerializer): + """Task serializer""" class Meta(object): + """Meta info""" model = JuniorTask fields = ['task_name','task_description','points', 'due_date', 'junior', 'default_image'] def create(self, validated_data): + """create default task image data""" validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() images = self.context['image'] validated_data['default_image'] = images @@ -173,23 +177,26 @@ class GuardianDetailSerializer(serializers.ModelSerializer): 'updated_at'] class TaskDetailsSerializer(serializers.ModelSerializer): - # guardian = GuardianDetailSerializer() junior = JuniorDetailSerializer() class Meta(object): + """Meta info""" model = JuniorTask fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image', 'junior', 'task_status', 'is_active', 'created_at','updated_at'] class TopJuniorSerializer(serializers.ModelSerializer): + """Top junior serializer""" junior = JuniorDetailSerializer() position = serializers.SerializerMethodField() - class Meta: + class Meta(object): + """Meta info""" model = JuniorPoints fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at'] def get_position(self, obj): + """get position of junior""" queryset = self.context['view'].get_queryset() position = list(queryset).index(obj) + 1 return position @@ -238,4 +245,4 @@ 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', - 'updated_at'] \ No newline at end of file + 'updated_at'] diff --git a/guardian/utils.py b/guardian/utils.py index 66a36ca..ff8472c 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -1,15 +1,22 @@ +"""Utiles file of guardian""" +"""Django import""" import oss2 from django.conf import settings import tempfile def upload_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.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') return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" diff --git a/guardian/views.py b/guardian/views.py index ff6e332..9c43628 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -143,7 +143,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer - # permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated] queryset = JuniorPoints.objects.all() def get_serializer_context(self): diff --git a/junior/serializers.py b/junior/serializers.py index b87c799..81ea0cb 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -89,16 +89,17 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.phone = validated_data.get('phone', junior.phone) junior.country_code = validated_data.get('country_code', junior.country_code) junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) - """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([junior.phone, junior.gender, junior.country_name, - junior.dob, junior.country_code, user.first_name, user.last_name]) - junior.is_complete_profile = False - if complete_profile_field: - junior.is_complete_profile = True if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) junior.image = image_url + """Complete profile of the junior if below all data are filled""" + complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, + junior.dob, junior.country_code, user.first_name, user.last_name, + user.email]) + junior.is_complete_profile = False + if complete_profile_field: + junior.is_complete_profile = True junior.save() return junior diff --git a/manage.py b/manage.py index fe9e065..a19d33b 100755 --- a/manage.py +++ b/manage.py @@ -1,7 +1,9 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" """Django import""" +"""Import OS module""" import os +"""Import sys module""" import sys @@ -18,6 +20,7 @@ def main(): "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc + """execute command line function""" execute_from_command_line(sys.argv) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 6960233..6f43fdc 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -93,8 +93,8 @@ REST_FRAMEWORK = { # 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication',], - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 5, # Set the default pagination size + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 5, } SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), @@ -201,22 +201,15 @@ https://docs.djangoproject.com/en/3.0/howto/static-files/""" GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') -# EMAIL_BACKEND = os.getenv('EMAIL_BACKEND') -# EMAIL_HOST = os.getenv('EMAIL_HOST') -# EMAIL_PORT = os.getenv('EMAIL_PORT') -# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS') -# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER') # Replace with your Gmail email address -# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD') # Replace with your Gmail email password or App password -# EMAIL_FROM_ADDRESS = os.getenv('EMAIL_FROM_ADDRESS') EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" EMAIL_HOST="smtp.sendgrid.net" EMAIL_PORT="587" EMAIL_USE_TLS="True" -EMAIL_HOST_USER="apikey" # Replace with your Gmail email address +EMAIL_HOST_USER="apikey" EMAIL_HOST_PASSWORD="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" EMAIL_FROM_ADDRESS="support@zodbank.com" -# EMAIL_FROM_ADDRESS="zodbank@yopmail.com" + ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') From c8925aad8eff38c70bd5aaa239b76a11faf073cb Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 14:53:43 +0530 Subject: [PATCH 061/372] jira-15 leader board API and profile API --- guardian/serializers.py | 16 ++++++++-------- guardian/views.py | 10 ++++++++-- junior/admin.py | 2 +- .../migrations/0009_juniorpoints_position.py | 18 ++++++++++++++++++ junior/models.py | 2 ++ junior/serializers.py | 10 ++++++++-- 6 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 junior/migrations/0009_juniorpoints_position.py diff --git a/guardian/serializers.py b/guardian/serializers.py index 08c2861..f5f8588 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -120,7 +120,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): image_url = upload_image_to_alibaba(image, filename) guardian.image = image_url """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_name, + complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name, user.email, guardian.image]) guardian.is_complete_profile = False @@ -188,18 +188,18 @@ class TaskDetailsSerializer(serializers.ModelSerializer): class TopJuniorSerializer(serializers.ModelSerializer): """Top junior serializer""" junior = JuniorDetailSerializer() - position = serializers.SerializerMethodField() + position = serializers.IntegerField() - class Meta(object): + class Meta: """Meta info""" model = JuniorPoints fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at'] - def get_position(self, obj): - """get position of junior""" - queryset = self.context['view'].get_queryset() - position = list(queryset).index(obj) + 1 - return position + def to_representation(self, instance): + """Convert instance to representation""" + representation = super().to_representation(instance) + representation['position'] = instance.position + return representation class GuardianProfileSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index 9c43628..360f77d 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -143,7 +143,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer - permission_classes = [IsAuthenticated] + # permission_classes = [IsAuthenticated] queryset = JuniorPoints.objects.all() def get_serializer_context(self): @@ -152,8 +152,14 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): return context def list(self, request, *args, **kwargs): - """fetch junior list those complete their task""" + """Fetch junior list of those who complete their tasks""" junior_total_points = self.get_queryset().order_by('-total_task_points') + + # Update the position field for each JuniorPoints object + for index, junior in enumerate(junior_total_points): + junior.position = index + 1 + junior.save() + serializer = self.get_serializer(junior_total_points, many=True) return custom_response(serializer.data, response_status=status.HTTP_200_OK) diff --git a/junior/admin.py b/junior/admin.py index bb9ea49..a2acfb6 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -16,7 +16,7 @@ class JuniorAdmin(admin.ModelAdmin): @admin.register(JuniorPoints) class JuniorPointsAdmin(admin.ModelAdmin): """Junior Points Admin""" - list_display = ['junior', 'total_task_points'] + list_display = ['junior', 'total_task_points', 'position'] def __str__(self): """Return email id""" diff --git a/junior/migrations/0009_juniorpoints_position.py b/junior/migrations/0009_juniorpoints_position.py new file mode 100644 index 0000000..2f5b31d --- /dev/null +++ b/junior/migrations/0009_juniorpoints_position.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-10 07:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0008_juniorpoints'), + ] + + operations = [ + migrations.AddField( + model_name='juniorpoints', + name='position', + field=models.IntegerField(blank=True, default=99999, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 167983f..505a144 100644 --- a/junior/models.py +++ b/junior/models.py @@ -47,6 +47,8 @@ class JuniorPoints(models.Model): junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points') """Contact details""" total_task_points = models.IntegerField(blank=True, null=True, default=0) + """position of the junior""" + position = models.IntegerField(blank=True, null=True, default=99999) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/junior/serializers.py b/junior/serializers.py index 81ea0cb..12e522f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from django.db import transaction import random """Import django app""" -from junior.models import Junior +from junior.models import Junior, JuniorPoints from guardian.utils import upload_image_to_alibaba from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.models import Guardian, JuniorTask @@ -147,6 +147,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): requested_task = serializers.SerializerMethodField('get_requested_task') rejected_task = serializers.SerializerMethodField('get_rejected_task') pending_task = serializers.SerializerMethodField('get_pending_task') + position = serializers.SerializerMethodField('get_position') def get_auth(self, obj): @@ -162,6 +163,11 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): data = JuniorTask.objects.filter(junior=obj).count() return data + def get_position(self, obj): + data = JuniorPoints.objects.filter(junior=obj).last() + if data: + return data.position + return 99999 def get_points(self, obj): data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True)) return data @@ -192,7 +198,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): fields = ['id', 'email', 'first_name', 'last_name', 'country_code', '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'] + 'requested_task', 'rejected_task', 'position'] class JuniorProfileSerializer(serializers.ModelSerializer): """junior serializer""" From 0d4c9aae78afd1035f976d8ebc99db1e6cbb394e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 16:50:50 +0530 Subject: [PATCH 062/372] delete user account API --- account/migrations/0004_userdelete.py | 31 ++++++++++++++++++ account/models.py | 21 ++++++++++++ account/serializers.py | 47 ++++++++++++++++++++++++++- account/urls.py | 4 ++- account/views.py | 19 ++++++++++- base/messages.py | 5 +-- junior/admin.py | 2 +- 7 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 account/migrations/0004_userdelete.py diff --git a/account/migrations/0004_userdelete.py b/account/migrations/0004_userdelete.py new file mode 100644 index 0000000..10bd2aa --- /dev/null +++ b/account/migrations/0004_userdelete.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.2 on 2023-07-10 09:24 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('account', '0003_defaulttaskimages'), + ] + + operations = [ + migrations.CreateModel( + name='UserDelete', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('old_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Original Email')), + ('d_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Dummy Email')), + ('is_active', models.BooleanField(default=True)), + ('reason', models.TextField(blank=True, max_length=500, null=True, verbose_name='Reason for Leaving')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='delete_information_set', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'user_delete_information', + }, + ), + ] diff --git a/account/models.py b/account/models.py index ac23e79..31c592a 100644 --- a/account/models.py +++ b/account/models.py @@ -92,3 +92,24 @@ class DefaultTaskImages(models.Model): def __str__(self): """return phone as an object""" return self.task_name + +class UserDelete(models.Model): + """ + User delete information + """ + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='delete_information_set') + """Old email""" + old_email = models.EmailField(blank=True, null=True, verbose_name='Original Email') + """Dummy email""" + d_email = models.EmailField(blank=True, null=True, verbose_name='Dummy Email') + is_active = models.BooleanField(default=True) + """reason for leaving""" + reason = models.TextField(max_length=500, blank=True, null=True, verbose_name='Reason for Leaving') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_delete_information' + + def __str__(self): + return self.user.email diff --git a/account/serializers.py b/account/serializers.py index db11233..4007e98 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,12 +1,13 @@ """Account serializer""" """Django Imoprt""" +import random from rest_framework import serializers from django.contrib.auth.models import User from rest_framework_simplejwt.tokens import RefreshToken """App import""" from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages +from account.models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserDelete from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR @@ -218,3 +219,47 @@ class DefaultTaskImagesDetailsSerializer(serializers.ModelSerializer): """Meta info""" model = DefaultTaskImages fields = '__all__' + +class UserDeleteSerializer(serializers.ModelSerializer): + """User Delete Serializer""" + class Meta(object): + """Meta Information""" + model = UserDelete + fields = ['reason'] + def create(self, validated_data): + user = self.context['user'] + user_type = str(self.context['user_type']) + data = validated_data.get('reason') + random_num = random.randint(0,10000) + user_tb = User.objects.filter(id=user.id).last() + user_type_data = UserEmailOtp.objects.filter(email=user.email).last() + if user_type == '1' and user_type_data.user_type == '1': + junior_data = Junior.objects.filter(auth__email=user_tb.email).first() + if junior_data: + junior_data.is_active = False + junior_data.is_verified = False + junior_data.guardian_code = '{}' + junior_data.save() + elif user_type == '2' and user_type_data.user_type == '2': + guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() + if guardian_data: + guardian_data.is_active = False + guardian_data.is_verified = False + guardian_data.save() + jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + for data in jun_data: + data.guardian_code.remove(guardian_data.guardian_code) + data.save() + else: + raise serializers.ValidationError({"details":ERROR_CODE['2030'],"code":"400", "status":"failed"}) + + user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.password = 'None' + d_email = user_tb.email + o_mail = user.email + instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, + is_active=True, reason=data) + user_tb.save() + + return instance diff --git a/account/urls.py b/account/urls.py index ffb2db6..b64f99d 100644 --- a/account/urls.py +++ b/account/urls.py @@ -7,7 +7,7 @@ from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, - DefaultImageAPIViewSet) + DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet) """Router""" router = routers.SimpleRouter() @@ -29,6 +29,8 @@ router.register('profile', ProfileAPIViewSet, basename='profile') router.register('upload-default-task-image', UploadImageAPIViewSet, basename='upload-default-task-image') """Fetch default task image end point""" router.register('default-task-image', DefaultImageAPIViewSet, basename='default-task-image') +"""Delete user account""" +router.register('delete', DeleteUserProfileAPIViewSet, basename='delete') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/account/views.py b/account/views.py index 1324869..5657b4a 100644 --- a/account/views.py +++ b/account/views.py @@ -18,7 +18,7 @@ from django.contrib.auth.models import User from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, - DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer) + DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -417,3 +417,20 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet): queryset = DefaultTaskImages.objects.all() serializer = DefaultTaskImagesSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): + """ Delete user API view set """ + + @action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer, + permission_classes=[IsAuthenticated]) + def account(self, request): + print("request.data===>",request.data) + print("request.user===>", request.user) + user_type = str(request.data['user_type']) + serializer = self.get_serializer(data=request.data, context={'request': request, 'user': request.user, + 'user_type':user_type}) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3005'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/messages.py b/base/messages.py index f024a3d..b00bfbf 100644 --- a/base/messages.py +++ b/base/messages.py @@ -52,7 +52,8 @@ ERROR_CODE = { "2026": "New password should not same as old password", "2027": "data should contain `identityToken`", "2028": "You are not authorized person to sign up on this platform", - "2029": "Validity of otp verification is expired" + "2029": "Validity of otp verification is expired", + "2030": "Use correct user type and token" } SUCCESS_CODE = { # Success code for password @@ -64,7 +65,7 @@ SUCCESS_CODE = { # Success code for password reset "3004": "Password reset link has been sent to your email address", # Success code for link verified - "3005": "Your link has been verified, it's valid", + "3005": "Your account is deleted successfully.", # Success code for password reset "3006": "Your password has been reset successfully.", # Success code for password update diff --git a/junior/admin.py b/junior/admin.py index a2acfb6..3764f48 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -7,7 +7,7 @@ from .models import Junior, JuniorPoints @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): """Junior Admin""" - list_display = ['auth'] + list_display = ['auth', 'guardian_code'] def __str__(self): """Return email id""" From 4fb7a2fc6c6e369f455c2fcc8ee4d6aac3e81934 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 17:51:30 +0530 Subject: [PATCH 063/372] jira-14 dashboard API --- guardian/serializers.py | 21 +++++++++++---------- guardian/views.py | 14 +++++++++++--- junior/serializers.py | 20 +++++++++++--------- junior/views.py | 17 +++++++++++++---- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index f5f8588..14c4a6d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -67,7 +67,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): family_name = serializers.CharField(max_length=100, required=False) dob = serializers.DateField(required=False) referral_code = serializers.CharField(max_length=100, required=False) - image = serializers.ImageField(required=False) + image = serializers.URLField(required=False) class Meta(object): """Meta info""" @@ -93,10 +93,15 @@ class CreateGuardianSerializer(serializers.ModelSerializer): user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of guardian""" - if self.context.get('first_name') != '' and self.context.get('last_name') != '': - user.first_name = self.context.get('first_name', user.first_name) - user.last_name = self.context.get('last_name', user.last_name) - user.save() + if self.context.get('first_name') != '' and self.context.get('first_name') is not None: + user.first_name = self.context.get('first_name') + else: + user.first_name = user.first_name + if self.context.get('last_name') != '' and self.context.get('last_name') is not None: + user.last_name = self.context.get('last_name') + else: + user.last_name = user.last_name + user.save() """Create guardian data""" guardian, created = Guardian.objects.get_or_create(user=self.context['user']) if created: @@ -114,11 +119,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.passcode = validated_data.get('passcode', guardian.passcode) guardian.country_name = validated_data.get('country_name', guardian.country_name) guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) - image = validated_data.pop('image', None) - if image: - filename = f"images/{image.name}" - image_url = upload_image_to_alibaba(image, filename) - guardian.image = image_url + guardian.image = validated_data.get('image', guardian.image) """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name, diff --git a/guardian/views.py b/guardian/views.py index 360f77d..4d9517f 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -51,10 +51,18 @@ class UpdateGuardianProfile(viewsets.ViewSet): def create(self, request, *args, **kwargs): """Create guardian profile""" + data = request.data + image = request.data.get('image') + image_url = '' + if image: + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + data = {"image":image_url} serializer = CreateGuardianSerializer(context={"user":request.user, - "first_name":request.data.get('first_name', ''), - "last_name": request.data.get('last_name',' ')}, - data=request.data) + "first_name":request.data.get('first_name'), + "last_name": request.data.get('last_name'), + "image":image_url}, + data=data) if serializer.is_valid(): """save serializer""" serializer.save() diff --git a/junior/serializers.py b/junior/serializers.py index 12e522f..28bbdc4 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -36,7 +36,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): dob = serializers.DateField(required=False) referral_code = serializers.CharField(max_length=100, required=False) guardian_code = ListCharField(required=False) - image = serializers.ImageField(required=False) + image = serializers.URLField(required=False) class Meta(object): """Meta info""" @@ -64,10 +64,15 @@ class CreateJuniorSerializer(serializers.ModelSerializer): user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of junior""" - if self.context.get('first_name') != '' and self.context.get('last_name') != '': - user.first_name = self.context.get('first_name', user.first_name) - user.last_name = self.context.get('last_name', user.last_name) - user.save() + if self.context.get('first_name') != '' and self.context.get('first_name') is not None: + user.first_name = self.context.get('first_name') + else: + user.first_name = user.first_name + if self.context.get('last_name') != '' and self.context.get('last_name') is not None: + user.last_name = self.context.get('last_name') + else: + user.last_name = user.last_name + user.save() """Create junior data""" junior, created = Junior.objects.get_or_create(auth=self.context['user']) if created: @@ -89,10 +94,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.phone = validated_data.get('phone', junior.phone) junior.country_code = validated_data.get('country_code', junior.country_code) junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) - if image: - filename = f"images/{image.name}" - image_url = upload_image_to_alibaba(image, filename) - junior.image = image_url + junior.image = validated_data.get('image', junior.image) """Complete profile of the junior if below all data are filled""" complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, junior.dob, junior.country_code, user.first_name, user.last_name, diff --git a/junior/views.py b/junior/views.py index 0658b2c..fa3b57c 100644 --- a/junior/views.py +++ b/junior/views.py @@ -7,6 +7,7 @@ from .serializers import CreateJuniorSerializer, JuniorDetailListSerializer from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE from account.utils import custom_response, custom_error_response +from guardian.utils import upload_image_to_alibaba # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" @@ -16,10 +17,18 @@ class UpdateJuniorProfile(viewsets.ViewSet): def create(self, request, *args, **kwargs): """Use CreateJuniorSerializer""" - serializer = CreateJuniorSerializer(context={"user":request.user, - "first_name":request.data.get('first_name', ''), - "last_name": request.data.get('last_name',' ')}, - data=request.data) + request_data = request.data + image = request.data.get('image') + image_url = '' + if image: + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + request_data = {"image": image_url} + serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url, + "first_name": request.data.get('first_name'), + "last_name": request.data.get('last_name') + }, + data=request_data) if serializer.is_valid(): """save serializer""" serializer.save() From 03fb774a7b41dc51131f634ab2289113d5dc52de Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 18:01:14 +0530 Subject: [PATCH 064/372] jira-25 setting API --- guardian/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/guardian/views.py b/guardian/views.py index 4d9517f..94ff8f4 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -117,6 +117,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): filename = f"images/{image}" image_url = upload_image_to_alibaba(image, filename) image_data = image_url + data.pop('default_image') serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): serializer.save() From 1ae81d41349d3a234a20863794a51af9ec03ae4f Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 19:16:45 +0530 Subject: [PATCH 065/372] jira-25 setting API --- account/admin.py | 17 ++--- account/migrations/0005_usernotification.py | 31 ++++++++ account/models.py | 27 +++++++ account/serializers.py | 84 ++++++++++++++------- account/urls.py | 7 +- account/views.py | 36 +++++++-- base/messages.py | 3 +- guardian/serializers.py | 4 +- guardian/views.py | 3 +- 9 files changed, 164 insertions(+), 48 deletions(-) create mode 100644 account/migrations/0005_usernotification.py diff --git a/account/admin.py b/account/admin.py index 96c2e4d..2614bdb 100644 --- a/account/admin.py +++ b/account/admin.py @@ -2,9 +2,16 @@ from django.contrib import admin """Import django app""" -from .models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages +from .models import UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserNotification # Register your models here. +@admin.register(UserNotification) +class UserNotificationAdmin(admin.ModelAdmin): + """User profile admin""" + list_display = ['user', 'push_notification', 'email_notification', 'sms_notification'] + + def __str__(self): + return self.image_url @admin.register(DefaultTaskImages) class DefaultTaskImagesAdmin(admin.ModelAdmin): """User profile admin""" @@ -13,14 +20,6 @@ class DefaultTaskImagesAdmin(admin.ModelAdmin): def __str__(self): return self.image_url -@admin.register(UserProfile) -class UserProfileAdmin(admin.ModelAdmin): - """User profile admin""" - list_display = ['user'] - - def __str__(self): - return self.user__email - @admin.register(UserEmailOtp) class UserEmailOtpAdmin(admin.ModelAdmin): """User Email otp admin""" diff --git a/account/migrations/0005_usernotification.py b/account/migrations/0005_usernotification.py new file mode 100644 index 0000000..397b633 --- /dev/null +++ b/account/migrations/0005_usernotification.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.2 on 2023-07-10 12:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('account', '0004_userdelete'), + ] + + operations = [ + migrations.CreateModel( + name='UserNotification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('push_notification', models.BooleanField(default=True)), + ('email_notification', models.BooleanField(default=True)), + ('sms_notification', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_notification', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'user_notification', + }, + ), + ] diff --git a/account/models.py b/account/models.py index 31c592a..36b2286 100644 --- a/account/models.py +++ b/account/models.py @@ -72,6 +72,8 @@ class UserEmailOtp(models.Model): class Meta(object): """ Meta information """ db_table = 'user_email_otp' + verbose_name = 'User Email OTP' + verbose_name_plural = 'User Email OTP' def __str__(self): """return phone as an object""" @@ -113,3 +115,28 @@ class UserDelete(models.Model): def __str__(self): return self.user.email + + +class UserNotification(models.Model): + """ + User notification details + """ + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_notification') + """Push Notification""" + push_notification = models.BooleanField(default=True) + """Email Notification""" + email_notification = models.BooleanField(default=True) + """SMS Notification""" + sms_notification = models.BooleanField(default=True) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_notification' + verbose_name = 'User Notification' + verbose_name_plural = 'User Notification' + + def __str__(self): + return self.user.email diff --git a/account/serializers.py b/account/serializers.py index 4007e98..e4ad809 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -7,7 +7,7 @@ from rest_framework_simplejwt.tokens import RefreshToken """App import""" from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserDelete +from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR @@ -230,36 +230,64 @@ class UserDeleteSerializer(serializers.ModelSerializer): user = self.context['user'] user_type = str(self.context['user_type']) data = validated_data.get('reason') + passwd = self.context['password'] random_num = random.randint(0,10000) user_tb = User.objects.filter(id=user.id).last() - user_type_data = UserEmailOtp.objects.filter(email=user.email).last() - if user_type == '1' and user_type_data.user_type == '1': - junior_data = Junior.objects.filter(auth__email=user_tb.email).first() - if junior_data: - junior_data.is_active = False - junior_data.is_verified = False - junior_data.guardian_code = '{}' - junior_data.save() - elif user_type == '2' and user_type_data.user_type == '2': - guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() - if guardian_data: - guardian_data.is_active = False - guardian_data.is_verified = False - guardian_data.save() - jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - for data in jun_data: - data.guardian_code.remove(guardian_data.guardian_code) - data.save() + if user_tb.check_password(passwd): + user_type_data = UserEmailOtp.objects.filter(email=user.email).last() + if user_type == '1' and user_type_data.user_type == '1': + junior_data = Junior.objects.filter(auth__email=user_tb.email).first() + if junior_data: + junior_data.is_active = False + junior_data.is_verified = False + junior_data.guardian_code = '{}' + junior_data.save() + elif user_type == '2' and user_type_data.user_type == '2': + guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() + if guardian_data: + guardian_data.is_active = False + guardian_data.is_verified = False + guardian_data.save() + jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + for data in jun_data: + data.guardian_code.remove(guardian_data.guardian_code) + data.save() + else: + raise serializers.ValidationError({"details":ERROR_CODE['2030'],"code":"400", "status":"failed"}) + + user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.password = 'None' + d_email = user_tb.email + o_mail = user.email + instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, + is_active=True, reason=data) + user_tb.save() + + return instance else: - raise serializers.ValidationError({"details":ERROR_CODE['2030'],"code":"400", "status":"failed"}) + raise serializers.ValidationError({"details": ERROR_CODE['2031'], "code": "400", "status": "failed"}) - user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.password = 'None' - d_email = user_tb.email - o_mail = user.email - instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, - is_active=True, reason=data) - user_tb.save() +class UserNotificationSerializer(serializers.ModelSerializer): + """User Notification serializer""" + class Meta(object): + """Meta info""" + model = UserNotification + fields = '__all__' + + +class UpdateUserNotificationSerializer(serializers.ModelSerializer): + """Update User Notification serializer""" + class Meta(object): + """Meta info""" + model = UserNotification + fields = ['push_notification', 'email_notification', 'sms_notification'] + + def create(self, validated_data): + instance = UserNotification.objects.filter(user=self.context).last() + instance.push_notification = validated_data.get('push_notification',instance.push_notification) + instance.email_notification = validated_data.get('email_notification', instance.email_notification) + instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) + instance.save() return instance diff --git a/account/urls.py b/account/urls.py index b64f99d..6bfc8dd 100644 --- a/account/urls.py +++ b/account/urls.py @@ -7,7 +7,8 @@ from rest_framework import routers from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, - DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet) + DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, + UpdateUserNotificationAPIViewSet) """Router""" router = routers.SimpleRouter() @@ -31,6 +32,10 @@ router.register('upload-default-task-image', UploadImageAPIViewSet, basename='up router.register('default-task-image', DefaultImageAPIViewSet, basename='default-task-image') """Delete user account""" router.register('delete', DeleteUserProfileAPIViewSet, basename='delete') +"""user account notification""" +router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification') +"""update user account notification""" +router.register('update-user-notification', UpdateUserNotificationAPIViewSet, basename='update-user-notification') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/account/views.py b/account/views.py index 5657b4a..1ad142d 100644 --- a/account/views.py +++ b/account/views.py @@ -12,13 +12,14 @@ from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages +from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User """Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, - DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer) + DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, + UserNotificationSerializer, UpdateUserNotificationSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -425,12 +426,37 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): @action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer, permission_classes=[IsAuthenticated]) def account(self, request): - print("request.data===>",request.data) - print("request.user===>", request.user) user_type = str(request.data['user_type']) + password = request.data['password'] serializer = self.get_serializer(data=request.data, context={'request': request, 'user': request.user, - 'user_type':user_type}) + 'user_type':user_type, + 'password':password}) if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3005'], response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + + +class UserNotificationAPIViewSet(viewsets.ModelViewSet): + """Profile viewset""" + queryset = UserNotification.objects.all() + serializer_class = UserNotificationSerializer + def list(self, request, *args, **kwargs): + """profile view""" + queryset = self.queryset.filter(user=request.user) + serializer = UserNotificationSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): + """Profile viewset""" + serializer_class = UpdateUserNotificationSerializer + + def create(self, request, *args, **kwargs): + """profile view""" + serializer = UpdateUserNotificationSerializer(data=request.data, + context=request.user) + if serializer.is_valid(): + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/messages.py b/base/messages.py index b00bfbf..788c7bc 100644 --- a/base/messages.py +++ b/base/messages.py @@ -53,7 +53,8 @@ ERROR_CODE = { "2027": "data should contain `identityToken`", "2028": "You are not authorized person to sign up on this platform", "2029": "Validity of otp verification is expired", - "2030": "Use correct user type and token" + "2030": "Use correct user type and token", + "2031":"Invalid password" } SUCCESS_CODE = { # Success code for password diff --git a/guardian/serializers.py b/guardian/serializers.py index 14c4a6d..dd79ed7 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -8,7 +8,7 @@ from django.db import transaction from django.contrib.auth.models import User """Import Django app""" from .models import Guardian, JuniorTask -from account.models import UserProfile, UserEmailOtp +from account.models import UserProfile, UserEmailOtp, UserNotification from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -36,7 +36,7 @@ class UserSerializer(serializers.ModelSerializer): try: """Create user profile""" user = User.objects.create_user(username=email, email=email, password=password) - UserProfile.objects.create(user=user, user_type=user_type) + UserNotification.objects.create(user=user) if user_type == '1': Junior.objects.create(auth=user, junior_code=''.join([str(random.randrange(9)) for _ in range(6)]), referral_code=''.join([str(random.randrange(9)) for _ in range(6)])) diff --git a/guardian/views.py b/guardian/views.py index 94ff8f4..e7f10bf 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -12,7 +12,7 @@ from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializ from .models import Guardian, JuniorTask from junior.models import Junior, JuniorPoints from junior.serializers import JuniorDetailSerializer -from account.models import UserEmailOtp +from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response @@ -92,7 +92,6 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') - print("status_value==>",status_value,'===>',type(status_value)) if str(status_value) == '0': queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('created_at') else: From c0f20f732dae06848da21a4cd72892f715b42d02 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 10 Jul 2023 21:18:22 +0530 Subject: [PATCH 066/372] swagger points --- account/serializers.py | 10 ++++++++-- account/views.py | 11 +++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index e4ad809..47472c0 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -7,7 +7,7 @@ from rest_framework_simplejwt.tokens import RefreshToken """App import""" from guardian.models import Guardian from junior.models import Junior -from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification +from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR @@ -233,7 +233,7 @@ class UserDeleteSerializer(serializers.ModelSerializer): passwd = self.context['password'] random_num = random.randint(0,10000) user_tb = User.objects.filter(id=user.id).last() - if user_tb.check_password(passwd): + if user_tb and user_tb.check_password(passwd): user_type_data = UserEmailOtp.objects.filter(email=user.email).last() if user_type == '1' and user_type_data.user_type == '1': junior_data = Junior.objects.filter(auth__email=user_tb.email).first() @@ -291,3 +291,9 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) instance.save() return instance + +class UserPhoneOtpSerializer(serializers.ModelSerializer): + """User Phone serializers""" + class Meta(object): + model = UserPhoneOtp + fields = '__all__' diff --git a/account/views.py b/account/views.py index 1ad142d..ded9a74 100644 --- a/account/views.py +++ b/account/views.py @@ -19,7 +19,7 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, - UserNotificationSerializer, UpdateUserNotificationSerializer) + UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp @@ -221,6 +221,7 @@ class ForgotPasswordAPIView(views.APIView): class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" queryset = UserPhoneOtp.objects.all() + serializer_class = UserPhoneOtpSerializer def create(self, request, *args, **kwargs): otp = generate_otp() phone_number = self.request.data['phone'] @@ -237,6 +238,7 @@ class SendPhoneOtp(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" queryset = UserPhoneOtp.objects.all() + serializer_class = UserPhoneOtpSerializer def list(self, request, *args, **kwargs): try: phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'), @@ -378,7 +380,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" queryset = User.objects.all() - serializer_class = [JuniorProfileSerializer, GuardianProfileSerializer] + serializer_class = JuniorProfileSerializer def list(self, request, *args, **kwargs): """profile view""" if str(self.request.GET.get('user_type')) == '1': @@ -438,7 +440,7 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): class UserNotificationAPIViewSet(viewsets.ModelViewSet): - """Profile viewset""" + """notification viewset""" queryset = UserNotification.objects.all() serializer_class = UserNotificationSerializer def list(self, request, *args, **kwargs): @@ -449,7 +451,8 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): - """Profile viewset""" + """Update notification viewset""" + queryset = UserNotification.objects.all() serializer_class = UpdateUserNotificationSerializer def create(self, request, *args, **kwargs): From 41761f22e82c2ba9a205450aaa62340f30229cef Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 11:52:07 +0530 Subject: [PATCH 067/372] sonar issue --- account/admin.py | 9 ++++++++- account/serializers.py | 21 ++++----------------- account/utils.py | 21 +++++++++++++++++++++ account/views.py | 2 -- guardian/serializers.py | 4 ---- junior/serializers.py | 5 ----- 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/account/admin.py b/account/admin.py index 2614bdb..e1decdd 100644 --- a/account/admin.py +++ b/account/admin.py @@ -2,9 +2,16 @@ from django.contrib import admin """Import django app""" -from .models import UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserNotification +from .models import UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserNotification, UserDelete # Register your models here. +@admin.register(UserDelete) +class UserDeleteAdmin(admin.ModelAdmin): + """User profile admin""" + list_display = ['user', 'old_email', 'd_email'] + + def __str__(self): + return self.user @admin.register(UserNotification) class UserNotificationAdmin(admin.ModelAdmin): """User profile admin""" diff --git a/account/serializers.py b/account/serializers.py index 47472c0..21a8d41 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -10,7 +10,7 @@ from junior.models import Junior from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR - +from .utils import junior_account_update, guardian_account_update class GoogleLoginSerializer(serializers.Serializer): """google login serializer""" @@ -236,25 +236,11 @@ class UserDeleteSerializer(serializers.ModelSerializer): if user_tb and user_tb.check_password(passwd): user_type_data = UserEmailOtp.objects.filter(email=user.email).last() if user_type == '1' and user_type_data.user_type == '1': - junior_data = Junior.objects.filter(auth__email=user_tb.email).first() - if junior_data: - junior_data.is_active = False - junior_data.is_verified = False - junior_data.guardian_code = '{}' - junior_data.save() + junior_account_update(user_tb) elif user_type == '2' and user_type_data.user_type == '2': - guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() - if guardian_data: - guardian_data.is_active = False - guardian_data.is_verified = False - guardian_data.save() - jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - for data in jun_data: - data.guardian_code.remove(guardian_data.guardian_code) - data.save() + guardian_account_update(user_tb) else: raise serializers.ValidationError({"details":ERROR_CODE['2030'],"code":"400", "status":"failed"}) - user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.password = 'None' @@ -295,5 +281,6 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): class UserPhoneOtpSerializer(serializers.ModelSerializer): """User Phone serializers""" class Meta(object): + """Meta info""" model = UserPhoneOtp fields = '__all__' diff --git a/account/utils.py b/account/utils.py index 4412e62..af9f86b 100644 --- a/account/utils.py +++ b/account/utils.py @@ -10,7 +10,28 @@ from datetime import datetime from calendar import timegm from uuid import uuid4 import secrets +from junior.models import Junior +from guardian.models import Guardian +def junior_account_update(user_tb): + """junior account delete""" + junior_data = Junior.objects.filter(auth__email=user_tb.email).first() + if junior_data: + junior_data.is_active = False + junior_data.is_verified = False + junior_data.guardian_code = '{}' + junior_data.save() +def guardian_account_update(user_tb): + """update guardian account after delete the user account""" + guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() + if guardian_data: + guardian_data.is_active = False + guardian_data.is_verified = False + guardian_data.save() + jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + for data in jun_data: + data.guardian_code.remove(guardian_data.guardian_code) + data.save() def send_otp_email(recipient_email, otp): """Send otp on email with template""" from_email = settings.EMAIL_FROM_ADDRESS diff --git a/account/views.py b/account/views.py index ded9a74..b5c0d26 100644 --- a/account/views.py +++ b/account/views.py @@ -109,8 +109,6 @@ class SigninWithApple(views.APIView): def post(self, request): token = request.data.get("access_token") user_type = request.data.get("user_type") - if not token: - return custom_error_response(ERROR_CODE['2027'], response_status=status.HTTP_400_BAD_REQUEST) 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} diff --git a/guardian/serializers.py b/guardian/serializers.py index dd79ed7..2c1c3dc 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -95,12 +95,8 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Save first and last name of guardian""" if self.context.get('first_name') != '' and self.context.get('first_name') is not None: user.first_name = self.context.get('first_name') - else: - user.first_name = user.first_name if self.context.get('last_name') != '' and self.context.get('last_name') is not None: user.last_name = self.context.get('last_name') - else: - user.last_name = user.last_name user.save() """Create guardian data""" guardian, created = Guardian.objects.get_or_create(user=self.context['user']) diff --git a/junior/serializers.py b/junior/serializers.py index 28bbdc4..df7a40a 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -59,19 +59,14 @@ class CreateJuniorSerializer(serializers.ModelSerializer): def create(self, validated_data): """Create junior profile""" - image = validated_data.get('image', None) guardian_code = validated_data.get('guardian_code',None) user = User.objects.filter(username=self.context['user']).last() if user: """Save first and last name of junior""" if self.context.get('first_name') != '' and self.context.get('first_name') is not None: user.first_name = self.context.get('first_name') - else: - user.first_name = user.first_name if self.context.get('last_name') != '' and self.context.get('last_name') is not None: user.last_name = self.context.get('last_name') - else: - user.last_name = user.last_name user.save() """Create junior data""" junior, created = Junior.objects.get_or_create(auth=self.context['user']) From 8b4db3e2e931e2ce68aaa3e569cdc1c3683cb401 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 13:14:20 +0530 Subject: [PATCH 068/372] jira-26 changes in notification API --- account/serializers.py | 11 +++++++---- guardian/views.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 21a8d41..82b957e 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -272,10 +272,13 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): def create(self, validated_data): instance = UserNotification.objects.filter(user=self.context).last() - instance.push_notification = validated_data.get('push_notification',instance.push_notification) - instance.email_notification = validated_data.get('email_notification', instance.email_notification) - instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) - instance.save() + if instance: + instance.push_notification = validated_data.get('push_notification',instance.push_notification) + instance.email_notification = validated_data.get('email_notification', instance.email_notification) + instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) + instance.save() + else: + instance = UserNotification.objects.create(user=self.context) return instance class UserPhoneOtpSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index e7f10bf..f55dd47 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -151,7 +151,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer - # permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated] queryset = JuniorPoints.objects.all() def get_serializer_context(self): From efa121640f0eb38f042ccd1bc24c0f25e988c527 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 16:33:03 +0530 Subject: [PATCH 069/372] Support email --- .../templated_email/support_mail.email | 22 +++++++++++++++++++ account/urls.py | 5 ++++- account/utils.py | 16 ++++++++++++++ account/views.py | 20 +++++++++++++++-- base/messages.py | 7 ++++-- guardian/views.py | 2 +- zod_bank/settings.py | 1 + 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 account/templates/templated_email/support_mail.email diff --git a/account/templates/templated_email/support_mail.email b/account/templates/templated_email/support_mail.email new file mode 100644 index 0000000..50467a9 --- /dev/null +++ b/account/templates/templated_email/support_mail.email @@ -0,0 +1,22 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + {{subject}} +{% endblock %} + +{% block plain %} + + +

+ Hi {{name}}, +

+ + + + +

+ {{name}} have some queries and need some support. Please support them by using their email address {{sender}}.

Queries are:-
{{ message }} +

+ + +{% endblock %} diff --git a/account/urls.py b/account/urls.py index 6bfc8dd..3d4642f 100644 --- a/account/urls.py +++ b/account/urls.py @@ -8,7 +8,7 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet) + UpdateUserNotificationAPIViewSet, SendSupportEmail) """Router""" router = routers.SimpleRouter() @@ -36,6 +36,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') +"""Support Email API""" +# router.register('support', SupportAPIViewSet, basename='update-user-notification') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), @@ -44,4 +46,5 @@ urlpatterns = [ path('api/v1/change-password/', ChangePasswordAPIView.as_view()), path('api/v1/update-profile-image/', UpdateProfileImage.as_view()), path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'), + path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email') ] diff --git a/account/utils.py b/account/utils.py index af9f86b..ad91468 100644 --- a/account/utils.py +++ b/account/utils.py @@ -46,6 +46,22 @@ def send_otp_email(recipient_email, otp): ) return otp +def send_support_email(name, sender, subject, message): + """Send otp on email with template""" + to_email = [settings.EMAIL_FROM_ADDRESS] + from_email = settings.DEFAULT_ADDRESS + send_templated_mail( + template_name='support_mail.email', + from_email=from_email, + recipient_list=to_email, + context={ + 'name': name.title(), + 'sender': sender, + 'subject': subject, + 'message': message + } + ) + return name def custom_response(detail, data=None, response_status=status.HTTP_200_OK): """Custom response code""" if not data: diff --git a/account/views.py b/account/views.py index b5c0d26..12b0043 100644 --- a/account/views.py +++ b/account/views.py @@ -5,6 +5,7 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging +from django.views.decorators.csrf import csrf_exempt from django.utils import timezone import jwt """App Import""" @@ -23,8 +24,7 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.tasks import generate_otp -from account.utils import send_otp_email -from account.utils import custom_response, custom_error_response +from account.utils import send_otp_email, send_support_email, custom_response, custom_error_response from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail import google.oauth2.credentials @@ -461,3 +461,19 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): serializer.save() return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + + +class SendSupportEmail(views.APIView): + def post(self, request): + name = request.data.get('name') + sender = request.data.get('email') + subject = request.data.get('subject') + message = request.data.get('message') + if name and sender and subject and message: + try: + send_support_email(name, sender, subject, message) + return custom_response(SUCCESS_CODE['3019'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2033'], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/messages.py b/base/messages.py index 788c7bc..50ca7f0 100644 --- a/base/messages.py +++ b/base/messages.py @@ -54,7 +54,9 @@ ERROR_CODE = { "2028": "You are not authorized person to sign up on this platform", "2029": "Validity of otp verification is expired", "2030": "Use correct user type and token", - "2031":"Invalid password" + "2031": "Invalid password", + "2032": "Failed to send email", + "2033": "Missing required fields" } SUCCESS_CODE = { # Success code for password @@ -84,7 +86,8 @@ SUCCESS_CODE = { "3015": "Verification code sent on your email.", "3016": "Send otp on your Email successfully", "3017": "Profile image update successfully", - "3018": "Task created successfully" + "3018": "Task created successfully", + "3019": "Support Email sent successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/views.py b/guardian/views.py index f55dd47..f8295a3 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -93,7 +93,7 @@ class TaskListAPIView(viewsets.ModelViewSet): """Create guardian profile""" status_value = self.request.GET.get('status') if str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('created_at') + queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') else: queryset = JuniorTask.objects.filter(guardian__user=request.user, task_status=status_value).order_by('due_date','created_at') diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 6f43fdc..595305a 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -209,6 +209,7 @@ EMAIL_USE_TLS="True" EMAIL_HOST_USER="apikey" EMAIL_HOST_PASSWORD="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" EMAIL_FROM_ADDRESS="support@zodbank.com" +DEFAULT_ADDRESS="zodbank@yopmail.com" ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') From 68ce67d38748797460aa80ee25bdc4fe2480d90d Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 16:34:33 +0530 Subject: [PATCH 070/372] Support email --- account/urls.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/account/urls.py b/account/urls.py index 3d4642f..1a0d392 100644 --- a/account/urls.py +++ b/account/urls.py @@ -36,8 +36,6 @@ 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') -"""Support Email API""" -# router.register('support', SupportAPIViewSet, basename='update-user-notification') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), From 7e7367e3a4551b70bb9388fa97ce49c1aaebac9c Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 18:23:40 +0530 Subject: [PATCH 071/372] jira-25 setting API --- ...006_alter_useremailotp_options_and_more.py | 21 ++++++++++ account/serializers.py | 29 +++++-------- account/utils.py | 42 +++++++++++++++++++ account/views.py | 25 +++++++---- base/constants.py | 6 ++- .../migrations/0014_guardian_signup_method.py | 18 ++++++++ guardian/models.py | 4 +- guardian/serializers.py | 2 +- .../migrations/0010_junior_signup_method.py | 18 ++++++++ junior/models.py | 4 +- junior/serializers.py | 2 +- 11 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 account/migrations/0006_alter_useremailotp_options_and_more.py create mode 100644 guardian/migrations/0014_guardian_signup_method.py create mode 100644 junior/migrations/0010_junior_signup_method.py diff --git a/account/migrations/0006_alter_useremailotp_options_and_more.py b/account/migrations/0006_alter_useremailotp_options_and_more.py new file mode 100644 index 0000000..2321b9d --- /dev/null +++ b/account/migrations/0006_alter_useremailotp_options_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-11 11:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0005_usernotification'), + ] + + operations = [ + migrations.AlterModelOptions( + name='useremailotp', + options={'verbose_name': 'User Email OTP', 'verbose_name_plural': 'User Email OTP'}, + ), + migrations.AlterModelOptions( + name='usernotification', + options={'verbose_name': 'User Notification', 'verbose_name_plural': 'User Notification'}, + ), + ] diff --git a/account/serializers.py b/account/serializers.py index 82b957e..88d544d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -10,7 +10,7 @@ from junior.models import Junior from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR -from .utils import junior_account_update, guardian_account_update +from .utils import delete_user_account_condition_social, delete_user_account_condition class GoogleLoginSerializer(serializers.Serializer): """google login serializer""" @@ -231,26 +231,17 @@ class UserDeleteSerializer(serializers.ModelSerializer): user_type = str(self.context['user_type']) data = validated_data.get('reason') passwd = self.context['password'] - random_num = random.randint(0,10000) + signup_method = self.context['signup_method'] + random_num = random.randint(0, 10000) user_tb = User.objects.filter(id=user.id).last() - if user_tb and user_tb.check_password(passwd): - user_type_data = UserEmailOtp.objects.filter(email=user.email).last() - if user_type == '1' and user_type_data.user_type == '1': - junior_account_update(user_tb) - elif user_type == '2' and user_type_data.user_type == '2': - guardian_account_update(user_tb) - else: - raise serializers.ValidationError({"details":ERROR_CODE['2030'],"code":"400", "status":"failed"}) - user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.password = 'None' - d_email = user_tb.email - o_mail = user.email - instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, - is_active=True, reason=data) - user_tb.save() - + user_type_datas = UserEmailOtp.objects.filter(email=user.email).last() + if user_tb and user_tb.check_password(passwd) and signup_method == '1': + user_type_data = user_type_datas.user_type + instance = delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num) return instance + elif user_tb and passwd is None and signup_method in ['2','3']: + inst = delete_user_account_condition_social(user, user_type, user_tb, data, random_num) + return inst else: raise serializers.ValidationError({"details": ERROR_CODE['2031'], "code": "400", "status": "failed"}) diff --git a/account/utils.py b/account/utils.py index ad91468..fe70145 100644 --- a/account/utils.py +++ b/account/utils.py @@ -10,8 +10,50 @@ from datetime import datetime from calendar import timegm from uuid import uuid4 import secrets +from rest_framework import serializers from junior.models import Junior from guardian.models import Guardian +from account.models import UserDelete +from base.messages import ERROR_CODE + + +def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num): + """delete user account""" + if user_type == '1' and user_type_data == '1': + junior_account_update(user_tb) + elif user_type == '2' and user_type_data == '2': + guardian_account_update(user_tb) + else: + raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"}) + user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.password = 'None' + d_email = user_tb.email + o_mail = user.email + user_tb.save() + instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, + is_active=True, reason=data) + + return instance + +def delete_user_account_condition_social(user, user_type,user_tb, data, random_num): + """delete user account""" + if user_type == '1': + junior_account_update(user_tb) + elif user_type == '2': + guardian_account_update(user_tb) + else: + raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"}) + user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() + user_tb.password = 'None' + d_email = user_tb.email + o_mail = user.email + user_tb.save() + instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, + is_active=True, reason=data) + + return instance def junior_account_update(user_tb): """junior account delete""" junior_data = Junior.objects.filter(auth__email=user_tb.email).first() diff --git a/account/views.py b/account/views.py index 12b0043..67b5164 100644 --- a/account/views.py +++ b/account/views.py @@ -85,11 +85,11 @@ class GoogleLoginMixin: 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, - image=profile_picture) + image=profile_picture, signup_method='2') serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, - image=profile_picture) + image=profile_picture,signup_method='2') serializer = GuardianSerializer(guardian_query) # Return a JSON response with the user's email and name return custom_response(SUCCESS_CODE['3003'], serializer.data, @@ -127,10 +127,12 @@ class SigninWithApple(views.APIView): except User.DoesNotExist: user = User.objects.create(**user_data) if str(user_type) == '1': - junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True) + junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True, + signup_method='3') serializer = JuniorSerializer(junior_query) if str(user_type) == '2': - guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True) + guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, + signup_method='3') serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -385,11 +387,13 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): junior_data = Junior.objects.filter(auth=self.request.user).last() if junior_data: serializer = JuniorProfileSerializer(junior_data) - if str(self.request.GET.get('user_type')) == '2': + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + elif str(self.request.GET.get('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_response(None, serializer.data, response_status=status.HTTP_200_OK) + class UploadImageAPIViewSet(viewsets.ModelViewSet): @@ -427,10 +431,13 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): permission_classes=[IsAuthenticated]) def account(self, request): user_type = str(request.data['user_type']) - password = request.data['password'] + password = request.data.get('password') + signup_method = str(request.data.get('signup_method')) + print("signup_method===>",signup_method,'==>',type(signup_method)) serializer = self.get_serializer(data=request.data, context={'request': request, 'user': request.user, - 'user_type':user_type, - 'password':password}) + 'user_type': user_type, + 'password': password, + 'signup_method':signup_method}) if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3005'], response_status=status.HTTP_200_OK) diff --git a/base/constants.py b/base/constants.py index f455316..36dcf1f 100644 --- a/base/constants.py +++ b/base/constants.py @@ -42,7 +42,11 @@ TASK_STATUS = ( ('4', 'requested'), ('5', 'completed') ) - +SIGNUP_METHODS = ( + ('1', 'manual'), + ('2', 'google'), + ('3', 'apple') +) PENDING = 1 IN_PROGRESS = 2 REJECTED = 3 diff --git a/guardian/migrations/0014_guardian_signup_method.py b/guardian/migrations/0014_guardian_signup_method.py new file mode 100644 index 0000000..c0c1fe7 --- /dev/null +++ b/guardian/migrations/0014_guardian_signup_method.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-11 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0013_alter_guardian_image'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='signup_method', + field=models.CharField(choices=[('1', 'manual'), ('2', 'google'), ('3', 'apple')], default='1', max_length=31), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 1bb4d2c..921138a 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -3,7 +3,7 @@ from django.db import models from django.contrib.auth import get_user_model """Import Django app""" -from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS +from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS from junior.models import Junior """Add user model""" User = get_user_model() @@ -27,6 +27,8 @@ class Guardian(models.Model): is_verified = models.BooleanField(default=False) is_complete_profile = models.BooleanField(default=False) passcode = models.IntegerField(null=True, blank=True, default=None) + """Sign up method""" + signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') """Codes""" guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) diff --git a/guardian/serializers.py b/guardian/serializers.py index 2c1c3dc..338e58f 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -241,5 +241,5 @@ class GuardianProfileSerializer(serializers.ModelSerializer): model = Guardian 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', + 'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method', 'updated_at'] diff --git a/junior/migrations/0010_junior_signup_method.py b/junior/migrations/0010_junior_signup_method.py new file mode 100644 index 0000000..a87d77a --- /dev/null +++ b/junior/migrations/0010_junior_signup_method.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-11 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0009_juniorpoints_position'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='signup_method', + field=models.CharField(choices=[('1', 'manual'), ('2', 'google'), ('3', 'apple')], default='1', max_length=31), + ), + ] diff --git a/junior/models.py b/junior/models.py index 505a144..c150d20 100644 --- a/junior/models.py +++ b/junior/models.py @@ -4,7 +4,7 @@ from django.db import models from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField """Import django app""" -from base.constants import GENDERS +from base.constants import GENDERS, SIGNUP_METHODS User = get_user_model() # Create your models here. @@ -19,6 +19,8 @@ class Junior(models.Model): gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) image = models.URLField(null=True, blank=True, default=None) + """Sign up method""" + signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') """Codes""" junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) diff --git a/junior/serializers.py b/junior/serializers.py index df7a40a..bcae23f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -237,4 +237,4 @@ class JuniorProfileSerializer(serializers.ModelSerializer): model = Junior 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'] + 'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method'] From 18cb975d94c31cadb0a2a03f0836f026b281bfc4 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 22:34:59 +0530 Subject: [PATCH 072/372] logout api and sonar --- account/admin.py | 1 + account/models.py | 4 ++++ account/urls.py | 5 +++-- account/utils.py | 10 +++++----- account/views.py | 20 +++++++++++++++++++- base/constants.py | 4 ++++ base/messages.py | 3 ++- guardian/serializers.py | 7 ++++--- guardian/utils.py | 2 ++ junior/serializers.py | 6 +++--- zod_bank/settings.py | 5 ++++- 11 files changed, 51 insertions(+), 16 deletions(-) diff --git a/account/admin.py b/account/admin.py index e1decdd..7b59c84 100644 --- a/account/admin.py +++ b/account/admin.py @@ -11,6 +11,7 @@ class UserDeleteAdmin(admin.ModelAdmin): list_display = ['user', 'old_email', 'd_email'] def __str__(self): + """Return delete user""" return self.user @admin.register(UserNotification) class UserNotificationAdmin(admin.ModelAdmin): diff --git a/account/models.py b/account/models.py index 36b2286..fd18fc3 100644 --- a/account/models.py +++ b/account/models.py @@ -140,3 +140,7 @@ class UserNotification(models.Model): def __str__(self): return self.user.email + +# class RevokedToken(models.Model): +# token = models.CharField(max_length=255, unique=True) +# date_revoked = models.DateTimeField(auto_now_add=True) \ No newline at end of file diff --git a/account/urls.py b/account/urls.py index 1a0d392..f2a1d62 100644 --- a/account/urls.py +++ b/account/urls.py @@ -8,7 +8,7 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet, SendSupportEmail) + UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView) """Router""" router = routers.SimpleRouter() @@ -44,5 +44,6 @@ urlpatterns = [ path('api/v1/change-password/', ChangePasswordAPIView.as_view()), path('api/v1/update-profile-image/', UpdateProfileImage.as_view()), path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'), - path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email') + path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email'), + path('api/v1/logout/', LogoutAPIView.as_view(), name='logout') ] diff --git a/account/utils.py b/account/utils.py index fe70145..17f1bf5 100644 --- a/account/utils.py +++ b/account/utils.py @@ -47,13 +47,13 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.password = 'None' - d_email = user_tb.email - o_mail = user.email + dummy_email = user_tb.email + old_mail = user.email user_tb.save() - instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, - is_active=True, reason=data) + instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail, + is_active=True, reason=data) - return instance + return instance_data def junior_account_update(user_tb): """junior account delete""" junior_data = Junior.objects.filter(auth__email=user_tb.email).first() diff --git a/account/views.py b/account/views.py index 67b5164..c2739d6 100644 --- a/account/views.py +++ b/account/views.py @@ -8,6 +8,7 @@ import logging from django.views.decorators.csrf import csrf_exempt from django.utils import timezone import jwt +from django.contrib.auth import logout """App Import""" from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login @@ -30,8 +31,8 @@ from templated_email import send_templated_mail import google.oauth2.credentials import google.auth.transport.requests from rest_framework import status -from rest_framework.response import Response import requests +from rest_framework.response import Response from django.conf import settings from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer @@ -97,9 +98,11 @@ class GoogleLoginMixin: class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet): + """Google login viewset""" serializer_class = GoogleLoginSerializer def create(self, request): + """create method""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) return self.google_login(request) @@ -142,6 +145,7 @@ class SigninWithApple(views.APIView): class UpdateProfileImage(views.APIView): + """Update profile image""" permission_classes = [IsAuthenticated] def put(self, request, format=None): if str(request.data['user_type']) == '1': @@ -166,6 +170,7 @@ class UpdateProfileImage(views.APIView): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ChangePasswordAPIView(views.APIView): + """change password""" serializer_class = ChangePasswordSerializer permission_classes = [IsAuthenticated] def post(self, request): @@ -176,6 +181,7 @@ class ChangePasswordAPIView(views.APIView): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ResetPasswordAPIView(views.APIView): + """Reset password""" def post(self, request): serializer = ResetPasswordSerializer(data=request.data) if serializer.is_valid(): @@ -184,6 +190,7 @@ class ResetPasswordAPIView(views.APIView): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) class ForgotPasswordAPIView(views.APIView): + """Forgot password""" def post(self, request): serializer = ForgotPasswordSerializer(data=request.data) if serializer.is_valid(): @@ -255,6 +262,7 @@ class UserPhoneVerification(viewsets.ModelViewSet): class UserLogin(viewsets.ViewSet): + """User login""" @action(methods=['post'], detail=False) def login(self, request): username = request.data.get('username') @@ -471,6 +479,7 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): class SendSupportEmail(views.APIView): + """support email api""" def post(self, request): name = request.data.get('name') sender = request.data.get('email') @@ -484,3 +493,12 @@ class SendSupportEmail(views.APIView): return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) else: return custom_error_response(ERROR_CODE['2033'], response_status=status.HTTP_400_BAD_REQUEST) + + +class LogoutAPIView(views.APIView): + permission_classes = (IsAuthenticated,) + + def post(self, request): + logout(request) + request.session.flush() + return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK) diff --git a/base/constants.py b/base/constants.py index 36dcf1f..c1f48c3 100644 --- a/base/constants.py +++ b/base/constants.py @@ -26,15 +26,18 @@ sort_dict = { '1': 'name', '2': '-name' } +"""user type""" USER_TYPE = ( ('1', 'junior'), ('2', 'guardian'), ('3', 'superuser') ) +"""gender""" GENDERS = ( ('1', 'Male'), ('2', 'Female') ) +"""Task status""" TASK_STATUS = ( ('1', 'pending'), ('2', 'in-progress'), @@ -42,6 +45,7 @@ TASK_STATUS = ( ('4', 'requested'), ('5', 'completed') ) +"""sign up method""" SIGNUP_METHODS = ( ('1', 'manual'), ('2', 'google'), diff --git a/base/messages.py b/base/messages.py index 50ca7f0..7810bfc 100644 --- a/base/messages.py +++ b/base/messages.py @@ -87,7 +87,8 @@ SUCCESS_CODE = { "3016": "Send otp on your Email successfully", "3017": "Profile image update successfully", "3018": "Task created successfully", - "3019": "Support Email sent successfully" + "3019": "Support Email sent successfully", + "3020": "Logged out successfully." } STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index 338e58f..5b737c6 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -119,7 +119,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name, - user.email, guardian.image]) + user.email, guardian.image, guardian.passcode]) guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True @@ -173,6 +173,7 @@ class GuardianDetailSerializer(serializers.ModelSerializer): 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at'] class TaskDetailsSerializer(serializers.ModelSerializer): + """Task detail serializer""" junior = JuniorDetailSerializer() class Meta(object): @@ -187,7 +188,7 @@ class TopJuniorSerializer(serializers.ModelSerializer): junior = JuniorDetailSerializer() position = serializers.IntegerField() - class Meta: + class Meta(object): """Meta info""" model = JuniorPoints fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at'] @@ -228,7 +229,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer): def get_complete_field_count(self, obj): """total filled fields count""" total_field_list = [obj.user.first_name, obj.user.last_name, obj.user.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image] + obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] total_complete_field = [data for data in total_field_list if data != '' and data is not None] return len(total_complete_field) diff --git a/guardian/utils.py b/guardian/utils.py index ff8472c..f565e4b 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -1,7 +1,9 @@ """Utiles file of guardian""" """Django import""" import oss2 +"""Import setting""" from django.conf import settings +"""Import tempfile""" import tempfile def upload_image_to_alibaba(image, filename): diff --git a/junior/serializers.py b/junior/serializers.py index bcae23f..13aa594 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -93,7 +93,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, junior.dob, junior.country_code, user.first_name, user.last_name, - user.email]) + user.email, junior.passcode]) junior.is_complete_profile = False if complete_profile_field: junior.is_complete_profile = True @@ -224,12 +224,12 @@ class JuniorProfileSerializer(serializers.ModelSerializer): def get_total_count(self, obj): """total fields count""" - return 9 + return 10 def get_complete_field_count(self, obj): """total filled fields count""" field_list = [obj.auth.first_name, obj.auth.last_name, obj.auth.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image] + obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] complete_field = [data for data in field_list if data is not None and data != ''] return len(complete_field) class Meta(object): diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 595305a..9f4341c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -154,6 +154,10 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +JWT_AUTH = { + # Other JWT authentication settings + 'JWT_AUTHENTICATION': 'your_app.authentication.CustomJWTAuthentication', +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ @@ -167,7 +171,6 @@ USE_I18N = True USE_L10N = True USE_TZ = True - # cors header settings SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') From 33101811ff52e5bcffd0c2f4dc27d08d6b0ab77b Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 11 Jul 2023 22:35:59 +0530 Subject: [PATCH 073/372] logout api and sonar --- account/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/account/models.py b/account/models.py index fd18fc3..60184c5 100644 --- a/account/models.py +++ b/account/models.py @@ -141,6 +141,3 @@ class UserNotification(models.Model): def __str__(self): return self.user.email -# class RevokedToken(models.Model): -# token = models.CharField(max_length=255, unique=True) -# date_revoked = models.DateTimeField(auto_now_add=True) \ No newline at end of file From a041028840530a6d8b92e0bfaf19ca272515a6d5 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 12 Jul 2023 16:24:30 +0530 Subject: [PATCH 074/372] jira-17 add junior and send invite mail --- .../junior_approval_mail.email | 23 +++++++ .../junior_notification_email.email | 24 +++++++ account/utils.py | 8 +++ account/views.py | 1 + base/constants.py | 7 ++- base/messages.py | 3 +- junior/migrations/0011_junior_relationship.py | 18 ++++++ junior/migrations/0012_junior_is_invited.py | 18 ++++++ junior/models.py | 7 ++- junior/serializers.py | 62 ++++++++++++++++++- junior/urls.py | 4 +- junior/utils.py | 51 +++++++++++++++ junior/views.py | 17 ++++- 13 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 account/templates/templated_email/junior_approval_mail.email create mode 100644 account/templates/templated_email/junior_notification_email.email create mode 100644 junior/migrations/0011_junior_relationship.py create mode 100644 junior/migrations/0012_junior_is_invited.py create mode 100644 junior/utils.py diff --git a/account/templates/templated_email/junior_approval_mail.email b/account/templates/templated_email/junior_approval_mail.email new file mode 100644 index 0000000..fd3c19f --- /dev/null +++ b/account/templates/templated_email/junior_approval_mail.email @@ -0,0 +1,23 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + Approval Email +{% endblock %} + +{% block plain %} + + +

+ Hi {{full_name}}, +

+ + + + +

+ Please approve {{full_name}} for accessing the Zod bank platform as a junior. + +

+ + +{% endblock %} diff --git a/account/templates/templated_email/junior_notification_email.email b/account/templates/templated_email/junior_notification_email.email new file mode 100644 index 0000000..ea9c214 --- /dev/null +++ b/account/templates/templated_email/junior_notification_email.email @@ -0,0 +1,24 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + Invitation Email +{% endblock %} + +{% block plain %} + + +

+ Hi {{full_name}}, +

+ + + + +

+ You are receiving this email for join the ZOD bank platform. Please use {{ url }} link to join the platform. +
Your credentials are:- username = {{email}} and password {{password}}

Below are the steps to complete the account and how to use this platform. + +

+ + +{% endblock %} diff --git a/account/utils.py b/account/utils.py index 17f1bf5..3b74d24 100644 --- a/account/utils.py +++ b/account/utils.py @@ -6,6 +6,7 @@ from rest_framework.response import Response """Third party Django app""" from templated_email import send_templated_mail import jwt +import string from datetime import datetime from calendar import timegm from uuid import uuid4 @@ -179,3 +180,10 @@ def get_token(data: dict = dict): 'access': access, 'refresh': refresh } + + +def generate_alphanumeric_code(length): + alphabet = string.ascii_letters + string.digits + code = ''.join(secrets.choice(alphabet) for _ in range(length)) + return code + diff --git a/account/views.py b/account/views.py index c2739d6..aab69fb 100644 --- a/account/views.py +++ b/account/views.py @@ -297,6 +297,7 @@ class UserLogin(viewsets.ViewSet): email_verified.otp = otp email_verified.save() data.update({"email_otp":otp}) + send_otp_email(username, otp) return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, response_status=status.HTTP_200_OK) data.update({"is_email_verified": is_verified}) diff --git a/base/constants.py b/base/constants.py index c1f48c3..c2458f8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -51,6 +51,11 @@ SIGNUP_METHODS = ( ('2', 'google'), ('3', 'apple') ) +"""relationship""" +RELATIONSHIP = ( + ('1', 'parent'), + ('2', 'legal_guardian') +) PENDING = 1 IN_PROGRESS = 2 REJECTED = 3 @@ -69,5 +74,3 @@ BYTE_IMAGE_SIZE = 1024 # validate file size MAX_FILE_SIZE = 1024 * 1024 * 5 - - diff --git a/base/messages.py b/base/messages.py index 7810bfc..dfdf2cd 100644 --- a/base/messages.py +++ b/base/messages.py @@ -88,7 +88,8 @@ SUCCESS_CODE = { "3017": "Profile image update successfully", "3018": "Task created successfully", "3019": "Support Email sent successfully", - "3020": "Logged out successfully." + "3020": "Logged out successfully.", + "3021": "Add junior successfully" } STATUS_CODE_ERROR = { diff --git a/junior/migrations/0011_junior_relationship.py b/junior/migrations/0011_junior_relationship.py new file mode 100644 index 0000000..45defb0 --- /dev/null +++ b/junior/migrations/0011_junior_relationship.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-12 06:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0010_junior_signup_method'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='relationship', + field=models.CharField(blank=True, choices=[('1', 'parent'), ('2', 'legal_guardian')], default='1', max_length=31, null=True), + ), + ] diff --git a/junior/migrations/0012_junior_is_invited.py b/junior/migrations/0012_junior_is_invited.py new file mode 100644 index 0000000..f36f9b4 --- /dev/null +++ b/junior/migrations/0012_junior_is_invited.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-12 10:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0011_junior_relationship'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='is_invited', + field=models.BooleanField(default=False), + ), + ] diff --git a/junior/models.py b/junior/models.py index c150d20..58b1ae3 100644 --- a/junior/models.py +++ b/junior/models.py @@ -4,7 +4,7 @@ from django.db import models from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField """Import django app""" -from base.constants import GENDERS, SIGNUP_METHODS +from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP User = get_user_model() # Create your models here. @@ -19,6 +19,9 @@ class Junior(models.Model): gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) dob = models.DateField(max_length=15, null=True, blank=True, default=None) image = models.URLField(null=True, blank=True, default=None) + """relationship""" + relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, + default='1') """Sign up method""" signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') """Codes""" @@ -26,6 +29,8 @@ class Junior(models.Model): guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) + """invited junior""" + is_invited = models.BooleanField(default=False) """Profile activity""" is_active = models.BooleanField(default=True) is_complete_profile = models.BooleanField(default=False) diff --git a/junior/serializers.py b/junior/serializers.py index 13aa594..ce38851 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -4,12 +4,19 @@ from rest_framework import serializers from django.contrib.auth.models import User from django.db import transaction import random +from django.utils import timezone +from rest_framework_simplejwt.tokens import RefreshToken + """Import django app""" +from account.utils import send_otp_email from junior.models import Junior, JuniorPoints -from guardian.utils import upload_image_to_alibaba +from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE -from guardian.models import Guardian, JuniorTask from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED +from guardian.models import Guardian, JuniorTask +from account.models import UserEmailOtp +from junior.utils import junior_notification_email, junior_approval_mail + class ListCharField(serializers.ListField): """Serializer for Array field""" @@ -238,3 +245,54 @@ 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'] + +class AddJuniorSerializer(serializers.ModelSerializer): + """Add junior serializer""" + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + """auth token""" + refresh = RefreshToken.for_user(obj) + access_token = str(refresh.access_token) + return access_token + + class Meta(object): + """Meta info""" + model = Junior + fields = ['gender','dob', 'relationship', 'auth_token'] + + + def create(self, validated_data): + """ create junior""" + with transaction.atomic(): + email = self.context['email'] + guardian = self.context['user'] + full_name = self.context['first_name'] + ' ' + self.context['last_name'] + guardian_data = Guardian.objects.filter(user__username=guardian).last() + user_data = User.objects.create(username=email, email=email, + first_name=self.context['first_name'], + last_name=self.context['last_name']) + + password = User.objects.make_random_password() + user_data.set_password(password) + user_data.save() + junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), + dob=validated_data.get('dob'), is_invited=True, + relationship=validated_data.get('relationship'), + junior_code=''.join([str(random.randrange(9)) for _ in range(4)]), + referral_code=''.join([str(random.randrange(9)) for _ in range(4)]), + referral_code_used=guardian_data.referral_code) + """Generate otp""" + otp_value = generate_otp() + expiry_time = timezone.now() + timezone.timedelta(days=1) + UserEmailOtp.objects.create(email=email, otp=otp_value, + user_type='1', expired_at=expiry_time) + """Send email to the register user""" + send_otp_email(email, otp_value) + """Notification email""" + junior_notification_email(email, full_name, email, password) + junior_approval_mail(guardian, full_name) + return junior_data + + + diff --git a/junior/urls.py b/junior/urls.py index 5eb0f4c..3d2fc55 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -1,7 +1,7 @@ """ Urls files""" """Django import""" from django.urls import path, include -from .views import UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView +from .views import UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView """Third party import""" from rest_framework import routers @@ -15,6 +15,8 @@ router.register('create-junior-profile', UpdateJuniorProfile, basename='profile- router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') """junior list API""" router.register('junior-list', JuniorListAPIView, basename='junior-list') +"""Add junior list API""" +router.register('add-junior', AddJuniorAPIView, basename='add-junior') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/utils.py b/junior/utils.py new file mode 100644 index 0000000..7d19a2d --- /dev/null +++ b/junior/utils.py @@ -0,0 +1,51 @@ +"""Account utils""" +"""Import django""" +from django.conf import settings +from rest_framework import viewsets, status +from rest_framework.response import Response +"""Third party Django app""" +from templated_email import send_templated_mail +import jwt +import string +from datetime import datetime +from calendar import timegm +from uuid import uuid4 +import secrets +from rest_framework import serializers +from junior.models import Junior +from guardian.models import Guardian +from account.models import UserDelete +from base.messages import ERROR_CODE + + + +def junior_notification_email(recipient_email, full_name, email, password): + """Notification email""" + from_email = settings.EMAIL_FROM_ADDRESS + recipient_list = [recipient_email] + send_templated_mail( + template_name='junior_notification_email.email', + from_email=from_email, + recipient_list=recipient_list, + context={ + 'full_name': full_name, + 'url':'link', + 'email': email, + 'password': password + } + ) + return full_name + +def junior_approval_mail(guardian, full_name): + """junior approval mail""" + from_email = settings.EMAIL_FROM_ADDRESS + recipient_list = [guardian] + send_templated_mail( + template_name='junior_approval_mail.email', + from_email=from_email, + recipient_list=recipient_list, + context={ + 'full_name': full_name + } + ) + return full_name diff --git a/junior/views.py b/junior/views.py index fa3b57c..d0e689f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -3,7 +3,7 @@ from rest_framework import viewsets, status from rest_framework.permissions import IsAuthenticated """Django app import""" from junior.models import Junior -from .serializers import CreateJuniorSerializer, JuniorDetailListSerializer +from .serializers import CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE from account.utils import custom_response, custom_error_response @@ -61,3 +61,18 @@ class JuniorListAPIView(viewsets.ModelViewSet): queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) serializer = JuniorDetailListSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + +class AddJuniorAPIView(viewsets.ModelViewSet): + """Add Junior by guardian""" + queryset = Junior.objects.all() + serializer_class = AddJuniorSerializer + permission_classes = [IsAuthenticated] + def create(self, request, *args, **kwargs): + """ junior list""" + info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name']} + serializer = AddJuniorSerializer(data=request.data, context=info) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) From 81e314878e848e4c4f6ba89c5d23133a6a081263 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 12 Jul 2023 16:43:16 +0530 Subject: [PATCH 075/372] passcode add --- account/views.py | 1 + guardian/serializers.py | 6 +++--- junior/serializers.py | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/account/views.py b/account/views.py index c2739d6..aab69fb 100644 --- a/account/views.py +++ b/account/views.py @@ -297,6 +297,7 @@ class UserLogin(viewsets.ViewSet): email_verified.otp = otp email_verified.save() data.update({"email_otp":otp}) + send_otp_email(username, otp) return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, response_status=status.HTTP_200_OK) data.update({"is_email_verified": is_verified}) diff --git a/guardian/serializers.py b/guardian/serializers.py index 5b737c6..9ba837f 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -119,7 +119,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name, - user.email, guardian.image, guardian.passcode]) + user.email, guardian.image]) guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True @@ -229,7 +229,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer): def get_complete_field_count(self, obj): """total filled fields count""" total_field_list = [obj.user.first_name, obj.user.last_name, obj.user.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] + obj.phone, obj.gender, obj.dob, obj.image] total_complete_field = [data for data in total_field_list if data != '' and data is not None] return len(total_complete_field) @@ -243,4 +243,4 @@ 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'] + 'updated_at', 'passcode'] diff --git a/junior/serializers.py b/junior/serializers.py index 13aa594..cf936cb 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -93,7 +93,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, junior.dob, junior.country_code, user.first_name, user.last_name, - user.email, junior.passcode]) + user.email]) junior.is_complete_profile = False if complete_profile_field: junior.is_complete_profile = True @@ -224,12 +224,12 @@ class JuniorProfileSerializer(serializers.ModelSerializer): def get_total_count(self, obj): """total fields count""" - return 10 + return 9 def get_complete_field_count(self, obj): """total filled fields count""" field_list = [obj.auth.first_name, obj.auth.last_name, obj.auth.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] + obj.phone, obj.gender, obj.dob, obj.image] complete_field = [data for data in field_list if data is not None and data != ''] return len(complete_field) class Meta(object): @@ -237,4 +237,5 @@ class JuniorProfileSerializer(serializers.ModelSerializer): model = Junior 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'] + 'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method', + 'passcode'] From 1fc02d17e61b4ecf33aa6071359dbb5519572df5 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 12 Jul 2023 19:32:10 +0530 Subject: [PATCH 076/372] jira-16 filter, invite and remove API --- account/serializers.py | 10 +++--- account/views.py | 26 ++++++++++++--- base/messages.py | 6 ++-- guardian/serializers.py | 10 +++--- guardian/views.py | 2 +- junior/serializers.py | 31 ++++++++++++----- junior/urls.py | 8 ++++- junior/views.py | 74 +++++++++++++++++++++++++++++++++++++++-- zod_bank/settings.py | 4 --- 9 files changed, 137 insertions(+), 34 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 88d544d..d75039a 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -26,7 +26,7 @@ class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['image'] + fields = ['id', 'image'] def update(self, instance, validated_data): """update image """ @@ -41,7 +41,7 @@ class UpdateGuardianImageSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['image'] + fields = ['id','image'] def update(self, instance, validated_data): """update image """ @@ -192,7 +192,7 @@ class JuniorSerializer(serializers.ModelSerializer): model = Junior fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', - 'updated_at', 'user_type', 'country_name'] + 'updated_at', 'user_type', 'country_name','is_invited'] class EmailVerificationSerializer(serializers.ModelSerializer): """Email verification serializer""" @@ -208,7 +208,7 @@ class DefaultTaskImagesSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = DefaultTaskImages - fields = ['task_name', 'image_url'] + fields = ['id', 'task_name', 'image_url'] def create(self, validated_data): data = DefaultTaskImages.objects.create(**validated_data) return data @@ -225,7 +225,7 @@ class UserDeleteSerializer(serializers.ModelSerializer): class Meta(object): """Meta Information""" model = UserDelete - fields = ['reason'] + fields = ['id','reason'] def create(self, validated_data): user = self.context['user'] user_type = str(self.context['user_type']) diff --git a/account/views.py b/account/views.py index aab69fb..6b93ac8 100644 --- a/account/views.py +++ b/account/views.py @@ -86,11 +86,21 @@ class GoogleLoginMixin: 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, - image=profile_picture, signup_method='2') + image=profile_picture, signup_method='2', + junior_code=''.join( + [str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join( + [str(random.randrange(9)) for _ in range(6)]) + ) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, - image=profile_picture,signup_method='2') + image=profile_picture,signup_method='2', + guardian_code=''.join( + [str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join( + [str(random.randrange(9)) for _ in range(6)]) + ) serializer = GuardianSerializer(guardian_query) # Return a JSON response with the user's email and name return custom_response(SUCCESS_CODE['3003'], serializer.data, @@ -131,11 +141,18 @@ class SigninWithApple(views.APIView): user = User.objects.create(**user_data) if str(user_type) == '1': junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True, - signup_method='3') + signup_method='3',junior_code=''.join( + [str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join( + [str(random.randrange(9)) for _ in range(6)])) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, - signup_method='3') + signup_method='3', + guardian_code=''.join( + [str(random.randrange(9)) for _ in range(6)]), + referral_code=''.join( + [str(random.randrange(9)) for _ in range(6)])) serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -442,7 +459,6 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): user_type = str(request.data['user_type']) password = request.data.get('password') signup_method = str(request.data.get('signup_method')) - print("signup_method===>",signup_method,'==>',type(signup_method)) serializer = self.get_serializer(data=request.data, context={'request': request, 'user': request.user, 'user_type': user_type, 'password': password, diff --git a/base/messages.py b/base/messages.py index dfdf2cd..f811439 100644 --- a/base/messages.py +++ b/base/messages.py @@ -56,7 +56,8 @@ ERROR_CODE = { "2030": "Use correct user type and token", "2031": "Invalid password", "2032": "Failed to send email", - "2033": "Missing required fields" + "2033": "Missing required fields", + "2034": "Junior is not associated" } SUCCESS_CODE = { # Success code for password @@ -89,7 +90,8 @@ SUCCESS_CODE = { "3018": "Task created successfully", "3019": "Support Email sent successfully", "3020": "Logged out successfully.", - "3021": "Add junior successfully" + "3021": "Add junior successfully", + "3022": "Remove junior successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index 5b737c6..b52bc44 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -21,7 +21,7 @@ class UserSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = User - fields = ['email', 'password', 'auth_token'] + fields = ['id', 'email', 'password', 'auth_token'] def get_auth_token(self, obj): """generate auth token""" @@ -72,7 +72,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', + fields = ['id', 'first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used', 'country_name', 'image'] @@ -119,7 +119,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, guardian.dob, guardian.country_code, user.first_name, user.last_name, - user.email, guardian.image, guardian.passcode]) + user.email, guardian.image]) guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True @@ -139,7 +139,7 @@ class TaskSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = JuniorTask - fields = ['task_name','task_description','points', 'due_date', 'junior', 'default_image'] + fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image'] def create(self, validated_data): """create default task image data""" validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() @@ -229,7 +229,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer): def get_complete_field_count(self, obj): """total filled fields count""" total_field_list = [obj.user.first_name, obj.user.last_name, obj.user.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] + obj.phone, obj.gender, obj.dob, obj.image] total_complete_field = [data for data in total_field_list if data != '' and data is not None] return len(total_complete_field) diff --git a/guardian/views.py b/guardian/views.py index f8295a3..5d28bfe 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -169,6 +169,6 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): junior.save() serializer = self.get_serializer(junior_total_points, many=True) - return custom_response(serializer.data, response_status=status.HTTP_200_OK) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) diff --git a/junior/serializers.py b/junior/serializers.py index ce38851..dbe7c95 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -48,9 +48,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', + fields = ['id', 'first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'guardian_code', 'referral_code_used', - 'country_name', 'image'] + 'country_name', 'image', 'is_invited'] def get_first_name(self,obj): """first name of junior""" @@ -100,7 +100,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Complete profile of the junior if below all data are filled""" complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, junior.dob, junior.country_code, user.first_name, user.last_name, - user.email, junior.passcode]) + user.email]) junior.is_complete_profile = False if complete_profile_field: junior.is_complete_profile = True @@ -135,7 +135,7 @@ class JuniorDetailSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at'] class JuniorDetailListSerializer(serializers.ModelSerializer): @@ -202,7 +202,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): fields = ['id', 'email', 'first_name', 'last_name', 'country_code', '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'] + 'requested_task', 'rejected_task', 'position', 'is_invited'] class JuniorProfileSerializer(serializers.ModelSerializer): """junior serializer""" @@ -231,12 +231,12 @@ class JuniorProfileSerializer(serializers.ModelSerializer): def get_total_count(self, obj): """total fields count""" - return 10 + return 9 def get_complete_field_count(self, obj): """total filled fields count""" field_list = [obj.auth.first_name, obj.auth.last_name, obj.auth.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image, obj.passcode] + obj.phone, obj.gender, obj.dob, obj.image] complete_field = [data for data in field_list if data is not None and data != ''] return len(complete_field) class Meta(object): @@ -244,7 +244,8 @@ class JuniorProfileSerializer(serializers.ModelSerializer): model = Junior 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'] + 'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method', + 'is_invited'] class AddJuniorSerializer(serializers.ModelSerializer): """Add junior serializer""" @@ -259,7 +260,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['gender','dob', 'relationship', 'auth_token'] + fields = ['id', 'gender','dob', 'relationship', 'auth_token', 'is_invited'] def create(self, validated_data): @@ -296,3 +297,15 @@ class AddJuniorSerializer(serializers.ModelSerializer): +class RemoveJuniorSerializer(serializers.ModelSerializer): + """User Update Serializer""" + class Meta(object): + """Meta class""" + model = Junior + fields = ('id', 'is_invited') + def update(self, instance, validated_data): + if instance: + instance.is_invited = False + instance.guardian_code = '{}' + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index 3d2fc55..162f693 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -1,7 +1,8 @@ """ Urls files""" """Django import""" from django.urls import path, include -from .views import UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView +from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, + InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView) """Third party import""" from rest_framework import routers @@ -17,7 +18,12 @@ router.register('validate-guardian-code', ValidateGuardianCode, basename='valida router.register('junior-list', JuniorListAPIView, basename='junior-list') """Add junior list API""" router.register('add-junior', AddJuniorAPIView, basename='add-junior') +"""Invited junior list API""" +router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior') +"""Filter junior list API""" +router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior') """Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), + path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()) ] diff --git a/junior/views.py b/junior/views.py index d0e689f..bea0525 100644 --- a/junior/views.py +++ b/junior/views.py @@ -1,9 +1,12 @@ """Junior view file""" -from rest_framework import viewsets, status +from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated +from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response """Django app import""" from junior.models import Junior -from .serializers import CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer +from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ + RemoveJuniorSerializer) from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE from account.utils import custom_response, custom_error_response @@ -76,3 +79,70 @@ class AddJuniorAPIView(viewsets.ModelViewSet): serializer.save() return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + +class InvitedJuniorAPIView(viewsets.ModelViewSet): + """Junior list of assosicated guardian""" + + serializer_class = JuniorDetailListSerializer + queryset = Junior.objects.all() + permission_classes = [IsAuthenticated] + pagination_class = PageNumberPagination + + def get_queryset(self): + """Get the queryset for the view""" + guardian = Guardian.objects.filter(user__email=self.request.user).last() + junior_queryset = Junior.objects.filter(guardian_code__icontains=str(guardian.guardian_code), + is_invited=True) + return junior_queryset + def list(self, request, *args, **kwargs): + """ junior list""" + queryset = self.get_queryset() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = JuniorDetailListSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class FilterJuniorAPIView(viewsets.ModelViewSet): + """Update guardian profile""" + serializer_class = JuniorDetailListSerializer + permission_classes = [IsAuthenticated] + pagination_class = PageNumberPagination + queryset = Junior.objects.all() + + def get_queryset(self): + """Get the queryset for the view""" + title = self.request.GET.get('title') + guardian_data = Guardian.objects.filter(user__email=self.request.user).last() + queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code), + is_invited=True, auth__first_name=title) + return queryset + + def list(self, request, *args, **kwargs): + """Create guardian profile""" + queryset = self.get_queryset() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = JuniorDetailListSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class RemoveJuniorAPIView(views.APIView): + """Eater Update API""" + serializer_class = RemoveJuniorSerializer + model = Junior + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + junior_id = self.request.GET.get('id') + guardian = Guardian.objects.filter(user__email=self.request.user).last() + junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code), + is_invited=True).last() + if junior_queryset: + serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 9f4341c..d98d2db 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -154,10 +154,6 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] -JWT_AUTH = { - # Other JWT authentication settings - 'JWT_AUTHENTICATION': 'your_app.authentication.CustomJWTAuthentication', -} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 324a023d83f49f15b7be505b94e72f7c59b18351 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 13 Jul 2023 12:28:35 +0530 Subject: [PATCH 077/372] changes in complete profile field --- base/constants.py | 49 +++++++++++++++++++++++++++++++++++++++++ guardian/serializers.py | 13 ++++++----- junior/serializers.py | 13 +++++------ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/base/constants.py b/base/constants.py index c2458f8..7c83134 100644 --- a/base/constants.py +++ b/base/constants.py @@ -6,6 +6,55 @@ import os # GOOGLE_URL used for interact with google server to verify user existence. #GOOGLE_URL = "https://www.googleapis.com/plus/v1/" +NUMBER = { + 'point_zero': 0.0, + 'zero': 0, + 'one': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'twelve': 12, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty_four': 24, + 'twenty_one': 21, + 'twenty_two': 22, + 'twenty_five': 25, + 'thirty': 30, + 'thirty_five': 35, + 'thirty_six': 36, + 'forty': 40, + 'fifty': 50, + 'fifty_nine': 59, + 'sixty': 60, + 'seventy_five': 75, + 'eighty': 80, + 'ninty_five': 95, + 'ninty_six': 96, + 'ninety_nine': 99, + 'hundred': 100, + 'one_one_nine': 119, + 'one_twenty': 120, + 'four_zero_four': 404, + 'five_hundred': 500, + 'minus_one': -1, + 'point_three': 0.3, + 'point_seven': 0.7 +} + + # Super Admin string constant for 'role' SUPER_ADMIN = "Super Admin" diff --git a/guardian/serializers.py b/guardian/serializers.py index b52bc44..5d2d706 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -12,6 +12,7 @@ from account.models import UserProfile, UserEmailOtp, UserNotification from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE +from base.constants import NUMBER from .utils import upload_image_to_alibaba from junior.models import Junior, JuniorPoints class UserSerializer(serializers.ModelSerializer): @@ -117,9 +118,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) guardian.image = validated_data.get('image', guardian.image) """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([guardian.phone, guardian.gender, guardian.country_name, - guardian.dob, guardian.country_code, user.first_name, user.last_name, - user.email, guardian.image]) + complete_profile_field = all([guardian.gender, guardian.country_name, + guardian.dob, user.first_name, + guardian.image]) guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True @@ -224,12 +225,12 @@ class GuardianProfileSerializer(serializers.ModelSerializer): def get_total_count(self, obj): """total fields count""" - return 9 + return NUMBER['five'] def get_complete_field_count(self, obj): """total filled fields count""" - total_field_list = [obj.user.first_name, obj.user.last_name, obj.user.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image] + total_field_list = [obj.user.first_name, obj.country_name, + obj.gender, obj.dob, obj.image] total_complete_field = [data for data in total_field_list if data != '' and data is not None] return len(total_complete_field) diff --git a/junior/serializers.py b/junior/serializers.py index dbe7c95..f76553a 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,7 @@ from account.utils import send_otp_email from junior.models import Junior, JuniorPoints from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp from junior.utils import junior_notification_email, junior_approval_mail @@ -98,9 +98,8 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) junior.image = validated_data.get('image', junior.image) """Complete profile of the junior if below all data are filled""" - complete_profile_field = all([junior.phone, junior.gender, junior.country_name, junior.image, - junior.dob, junior.country_code, user.first_name, user.last_name, - user.email]) + complete_profile_field = all([junior.gender, junior.country_name, junior.image, + junior.dob, user.first_name]) junior.is_complete_profile = False if complete_profile_field: junior.is_complete_profile = True @@ -231,12 +230,12 @@ class JuniorProfileSerializer(serializers.ModelSerializer): def get_total_count(self, obj): """total fields count""" - return 9 + return NUMBER['five'] def get_complete_field_count(self, obj): """total filled fields count""" - field_list = [obj.auth.first_name, obj.auth.last_name, obj.auth.email, obj.country_name, obj.country_code, - obj.phone, obj.gender, obj.dob, obj.image] + field_list = [obj.auth.first_name, obj.country_name, + obj.gender, obj.dob, obj.image] complete_field = [data for data in field_list if data is not None and data != ''] return len(complete_field) class Meta(object): From 1ac6c7c01d0d8e84ad54a836a2c06d32dc786867 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 13 Jul 2023 13:50:40 +0530 Subject: [PATCH 078/372] check size of the image --- account/views.py | 41 +++++++++++++++++++++++++---------------- base/messages.py | 4 +++- guardian/serializers.py | 1 - guardian/views.py | 10 ++++++++++ junior/views.py | 6 ++++++ 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/account/views.py b/account/views.py index 6b93ac8..3a5f974 100644 --- a/account/views.py +++ b/account/views.py @@ -5,6 +5,7 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging +from PIL import Image from django.views.decorators.csrf import csrf_exempt from django.utils import timezone import jwt @@ -24,6 +25,7 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE +from base.constants import NUMBER from guardian.tasks import generate_otp from account.utils import send_otp_email, send_support_email, custom_response, custom_error_response from rest_framework.permissions import IsAuthenticated @@ -165,26 +167,29 @@ class UpdateProfileImage(views.APIView): """Update profile image""" permission_classes = [IsAuthenticated] def put(self, request, format=None): - if str(request.data['user_type']) == '1': - junior_query = Junior.objects.filter(auth=request.user).last() + try: image = request.data['image'] + img = Image.open(image) + width, height = img.size + if width == NUMBER['zero'] or height == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) image_data = image_url - serializer = UpdateJuniorProfileImageSerializer(junior_query, - data={'image':image_data}, partial=True) - if str(request.data['user_type']) == '2': - guardian_query = Guardian.objects.filter(user=request.user).last() - image = request.data['image'] - filename = f"images/{image.name}" - image_url = upload_image_to_alibaba(image, filename) - image_data = image_url - serializer = UpdateGuardianImageSerializer(guardian_query, - data={'image':image_data}, partial=True) - if serializer.is_valid(): - serializer.save() - return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + if str(request.data['user_type']) == '1': + junior_query = Junior.objects.filter(auth=request.user).last() + serializer = UpdateJuniorProfileImageSerializer(junior_query, + data={'image':image_data}, partial=True) + elif str(request.data['user_type']) == '2': + guardian_query = Guardian.objects.filter(user=request.user).last() + serializer = UpdateGuardianImageSerializer(guardian_query, + data={'image':image_data}, partial=True) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST) class ChangePasswordAPIView(views.APIView): """change password""" @@ -430,6 +435,10 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet): """profile view""" image_data = request.data['image_url'] filename = f"default_task_images/{image_data.name}" + img = Image.open(image_data) + width, height = img.size + if width == NUMBER['zero'] or height == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) image = upload_image_to_alibaba(image_data, filename) image_data = image request.data['image_url'] = image_data diff --git a/base/messages.py b/base/messages.py index f811439..2c0d9cd 100644 --- a/base/messages.py +++ b/base/messages.py @@ -57,7 +57,9 @@ ERROR_CODE = { "2031": "Invalid password", "2032": "Failed to send email", "2033": "Missing required fields", - "2034": "Junior is not associated" + "2034": "Junior is not associated", + "2035": "Image should not be 0 kb", + "2036": "Choose valid user" } SUCCESS_CODE = { # Success code for password diff --git a/guardian/serializers.py b/guardian/serializers.py index 109343e..592502e 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -13,7 +13,6 @@ from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER -from .utils import upload_image_to_alibaba from junior.models import Junior, JuniorPoints class UserSerializer(serializers.ModelSerializer): """User serializer""" diff --git a/guardian/views.py b/guardian/views.py index 5d28bfe..420e521 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -5,6 +5,7 @@ from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from django.utils import timezone +from PIL import Image from datetime import datetime, timedelta """Import Django app""" from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, @@ -17,6 +18,7 @@ from .tasks import generate_otp from account.utils import send_otp_email from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE +from base.constants import NUMBER from .utils import upload_image_to_alibaba from django.db.models import Sum # Create your views here. @@ -54,6 +56,10 @@ class UpdateGuardianProfile(viewsets.ViewSet): data = request.data image = request.data.get('image') image_url = '' + img = Image.open(image) + width, height = img.size + if width == NUMBER['zero'] or height == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) @@ -114,6 +120,10 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image_data = image else: filename = f"images/{image}" + img = Image.open(image) + width, height = img.size + if width == NUMBER['zero'] or height == 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') diff --git a/junior/views.py b/junior/views.py index bea0525..0dcdbbe 100644 --- a/junior/views.py +++ b/junior/views.py @@ -3,12 +3,14 @@ from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination from rest_framework.response import Response +from PIL import Image """Django app import""" from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer) from guardian.models import Guardian from base.messages import ERROR_CODE, SUCCESS_CODE +from base.constants import NUMBER from account.utils import custom_response, custom_error_response from guardian.utils import upload_image_to_alibaba # Create your views here. @@ -23,6 +25,10 @@ class UpdateJuniorProfile(viewsets.ViewSet): request_data = request.data image = request.data.get('image') image_url = '' + img = Image.open(image) + width, height = img.size + if width == NUMBER['zero'] or height == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) From a16fecb1c94ffe3d2d12312a0ed5fa71858ae721 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 13 Jul 2023 16:37:42 +0530 Subject: [PATCH 079/372] changes in image size --- account/views.py | 8 ++------ guardian/views.py | 8 ++------ junior/views.py | 4 +--- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/account/views.py b/account/views.py index 3a5f974..9d56de8 100644 --- a/account/views.py +++ b/account/views.py @@ -169,9 +169,7 @@ class UpdateProfileImage(views.APIView): def put(self, request, format=None): try: image = request.data['image'] - img = Image.open(image) - width, height = img.size - if width == NUMBER['zero'] or height == NUMBER['zero']: + if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) @@ -435,9 +433,7 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet): """profile view""" image_data = request.data['image_url'] filename = f"default_task_images/{image_data.name}" - img = Image.open(image_data) - width, height = img.size - if width == NUMBER['zero'] or height == NUMBER['zero']: + if image_data.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) image = upload_image_to_alibaba(image_data, filename) image_data = image diff --git a/guardian/views.py b/guardian/views.py index 420e521..409de89 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -56,9 +56,7 @@ class UpdateGuardianProfile(viewsets.ViewSet): data = request.data image = request.data.get('image') image_url = '' - img = Image.open(image) - width, height = img.size - if width == NUMBER['zero'] or height == NUMBER['zero']: + if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: filename = f"images/{image.name}" @@ -120,9 +118,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image_data = image else: filename = f"images/{image}" - img = Image.open(image) - width, height = img.size - if width == NUMBER['zero'] or height == NUMBER['zero']: + if 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 diff --git a/junior/views.py b/junior/views.py index 0dcdbbe..1dfe5d3 100644 --- a/junior/views.py +++ b/junior/views.py @@ -25,9 +25,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): request_data = request.data image = request.data.get('image') image_url = '' - img = Image.open(image) - width, height = img.size - if width == NUMBER['zero'] or height == NUMBER['zero']: + if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: filename = f"images/{image.name}" From 52a0f4b590d46d83da5556a442f4adf52d3eb2ea Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 13 Jul 2023 17:31:05 +0530 Subject: [PATCH 080/372] referral code --- account/views.py | 30 ++++++++++++------------------ guardian/serializers.py | 15 ++++++--------- guardian/views.py | 6 +++--- junior/serializers.py | 16 ++++++---------- junior/views.py | 4 ++-- 5 files changed, 29 insertions(+), 42 deletions(-) diff --git a/account/views.py b/account/views.py index 9d56de8..4e71ecd 100644 --- a/account/views.py +++ b/account/views.py @@ -27,7 +27,8 @@ from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from guardian.tasks import generate_otp -from account.utils import send_otp_email, send_support_email, custom_response, custom_error_response +from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response, + generate_alphanumeric_code) from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail import google.oauth2.credentials @@ -89,19 +90,15 @@ class GoogleLoginMixin: if str(user_type) == '1': junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, image=profile_picture, signup_method='2', - junior_code=''.join( - [str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join( - [str(random.randrange(9)) for _ in range(6)]) + junior_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6) ) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, image=profile_picture,signup_method='2', - guardian_code=''.join( - [str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join( - [str(random.randrange(9)) for _ in range(6)]) + guardian_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6) ) serializer = GuardianSerializer(guardian_query) # Return a JSON response with the user's email and name @@ -143,18 +140,15 @@ class SigninWithApple(views.APIView): user = User.objects.create(**user_data) if str(user_type) == '1': junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True, - signup_method='3',junior_code=''.join( - [str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join( - [str(random.randrange(9)) for _ in range(6)])) + signup_method='3', + junior_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6)) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, signup_method='3', - guardian_code=''.join( - [str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join( - [str(random.randrange(9)) for _ in range(6)])) + guardian_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6)) serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -169,7 +163,7 @@ class UpdateProfileImage(views.APIView): def put(self, request, format=None): try: image = request.data['image'] - if image.size == NUMBER['zero']: + if image and image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) diff --git a/guardian/serializers.py b/guardian/serializers.py index 592502e..65def21 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import User """Import Django app""" from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp, UserNotification +from account.utils import generate_alphanumeric_code from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -38,11 +39,11 @@ class UserSerializer(serializers.ModelSerializer): user = User.objects.create_user(username=email, email=email, password=password) UserNotification.objects.create(user=user) if user_type == '1': - Junior.objects.create(auth=user, junior_code=''.join([str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join([str(random.randrange(9)) for _ in range(6)])) + Junior.objects.create(auth=user, junior_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6)) if user_type == '2': - Guardian.objects.create(user=user, guardian_code=''.join([str(random.randrange(9)) for _ in range(6)]), - referral_code=''.join([str(random.randrange(9)) for _ in range(6)])) + Guardian.objects.create(user=user, guardian_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6)) return user except Exception as e: """Error handling""" @@ -99,11 +100,7 @@ class CreateGuardianSerializer(serializers.ModelSerializer): user.last_name = self.context.get('last_name') user.save() """Create guardian data""" - guardian, created = Guardian.objects.get_or_create(user=self.context['user']) - if created: - """Create referral code and guardian code""" - guardian.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) - guardian.guardian_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + guardian = Guardian.objects.filter(user=self.context['user']).last() if guardian: """update details according to the data get from request""" guardian.gender = validated_data.get('gender',guardian.gender) diff --git a/guardian/views.py b/guardian/views.py index 409de89..61bf9b9 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -56,9 +56,9 @@ class UpdateGuardianProfile(viewsets.ViewSet): data = request.data image = request.data.get('image') image_url = '' - if image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: + if image and image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) data = {"image":image_url} @@ -118,7 +118,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image_data = image else: filename = f"images/{image}" - if image.size == NUMBER['zero']: + 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 diff --git a/junior/serializers.py b/junior/serializers.py index 1f08674..2df13ca 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -8,7 +8,7 @@ from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken """Import django app""" -from account.utils import send_otp_email +from account.utils import send_otp_email, generate_alphanumeric_code from junior.models import Junior, JuniorPoints from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE @@ -76,11 +76,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): user.last_name = self.context.get('last_name') user.save() """Create junior data""" - junior, created = Junior.objects.get_or_create(auth=self.context['user']) - if created: - """Create referral code and junior code""" - junior.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)]) - junior.junior_code = ''.join([str(random.randrange(9)) for _ in range(4)]) + junior = Junior.objects.filter(auth=self.context['user']).last() if junior: """update details according to the data get from request""" junior.gender = validated_data.get('gender',junior.gender) @@ -134,8 +130,8 @@ class JuniorDetailSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', - 'updated_at'] + 'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at', + 'image', 'updated_at'] class JuniorDetailListSerializer(serializers.ModelSerializer): """junior serializer""" @@ -279,8 +275,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), dob=validated_data.get('dob'), is_invited=True, relationship=validated_data.get('relationship'), - junior_code=''.join([str(random.randrange(9)) for _ in range(4)]), - referral_code=''.join([str(random.randrange(9)) for _ in range(4)]), + junior_code=generate_alphanumeric_code(6), + referral_code=generate_alphanumeric_code(6), referral_code_used=guardian_data.referral_code) """Generate otp""" otp_value = generate_otp() diff --git a/junior/views.py b/junior/views.py index 1dfe5d3..a1053d4 100644 --- a/junior/views.py +++ b/junior/views.py @@ -25,9 +25,9 @@ class UpdateJuniorProfile(viewsets.ViewSet): request_data = request.data image = request.data.get('image') image_url = '' - if image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) if image: + if image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" image_url = upload_image_to_alibaba(image, filename) request_data = {"image": image_url} From 5d528b885abbb139237627dfe200431491fa050f Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 13 Jul 2023 19:24:33 +0530 Subject: [PATCH 081/372] jira-18 approval API --- account/utils.py | 6 ++++++ base/messages.py | 4 +++- guardian/serializers.py | 16 ++++++++++++++++ guardian/urls.py | 4 +++- guardian/views.py | 26 +++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/account/utils.py b/account/utils.py index 3b74d24..846add9 100644 --- a/account/utils.py +++ b/account/utils.py @@ -187,3 +187,9 @@ def generate_alphanumeric_code(length): code = ''.join(secrets.choice(alphabet) for _ in range(length)) return code + +def generate_code(value, user_id): + alphabet = value + user_id.zfill(3) + return alphabet + + diff --git a/base/messages.py b/base/messages.py index 2c0d9cd..ffbbbec 100644 --- a/base/messages.py +++ b/base/messages.py @@ -93,7 +93,9 @@ SUCCESS_CODE = { "3019": "Support Email sent successfully", "3020": "Logged out successfully.", "3021": "Add junior successfully", - "3022": "Remove junior successfully" + "3022": "Remove junior successfully", + "3023": "Approved junior successfully", + "3024": "Reject junior request successfully" } STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index 65def21..9d2a676 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -241,3 +241,19 @@ class GuardianProfileSerializer(serializers.ModelSerializer): 'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code', 'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method', 'updated_at', 'passcode'] + +class ApproveJuniorSerializer(serializers.ModelSerializer): + """approve junior serializer""" + class Meta(object): + """Meta info""" + model = Junior + fields = ['id', 'guardian_code'] + + def create(self, validated_data): + """update guardian code""" + instance = self.context['junior'] + instance.guardian_code = [self.context['guardian_code']] + instance.save() + return instance + + diff --git a/guardian/urls.py b/guardian/urls.py index b69b041..168610b 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -2,7 +2,7 @@ """Django import""" from django.urls import path, include from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, - SearchTaskListAPIView, TopJuniorListAPIView) + SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView) """Third party import""" from rest_framework import routers @@ -24,6 +24,8 @@ router.register('task-list', TaskListAPIView, basename='task-list') router.register('top-junior', TopJuniorListAPIView, basename='top-junior') """Search Task list on the bases of status, due date, and task title API""" router.register('filter-task', SearchTaskListAPIView, basename='filter-task') +"""Approve junior API""" +router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior') """Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/views.py b/guardian/views.py index 61bf9b9..f060732 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -9,7 +9,7 @@ from PIL import Image from datetime import datetime, timedelta """Import Django app""" from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, - TopJuniorSerializer) + TopJuniorSerializer, ApproveJuniorSerializer) from .models import Guardian, JuniorTask from junior.models import Junior, JuniorPoints from junior.serializers import JuniorDetailSerializer @@ -114,6 +114,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): image = request.data['default_image'] data = request.data + print("data===>",data,'==>',type(data)) if 'https' in str(image): image_data = image else: @@ -178,3 +179,26 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +class ApproveJuniorAPIView(viewsets.ViewSet): + """approve junior by guardian""" + serializer_class = ApproveJuniorSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + """Get the queryset for the view""" + guardian = Guardian.objects.filter(user__email=self.request.user).last() + junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last() + return guardian, junior_queryset + + def create(self, request, *args, **kwargs): + """ junior list""" + queryset = self.get_queryset() + if request.data['action'] == '1': + serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code, + "junior": queryset[1], "action": request.data['action']}, + data=request.data) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) + else: + return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) From a4d9997580b69973d271841eacdcaa55393042dc Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 13 Jul 2023 19:51:17 +0530 Subject: [PATCH 082/372] web_admin module added, api for article created --- web_admin/__init__.py | 0 web_admin/admin.py | 27 +++++++++++++ web_admin/apps.py | 6 +++ web_admin/migrations/0001_initial.py | 60 ++++++++++++++++++++++++++++ web_admin/migrations/__init__.py | 0 web_admin/models.py | 54 +++++++++++++++++++++++++ web_admin/serializers.py | 55 +++++++++++++++++++++++++ web_admin/tests.py | 3 ++ web_admin/urls.py | 16 ++++++++ web_admin/views.py | 23 +++++++++++ zod_bank/settings.py | 3 ++ zod_bank/urls.py | 1 + 12 files changed, 248 insertions(+) create mode 100644 web_admin/__init__.py create mode 100644 web_admin/admin.py create mode 100644 web_admin/apps.py create mode 100644 web_admin/migrations/0001_initial.py create mode 100644 web_admin/migrations/__init__.py create mode 100644 web_admin/models.py create mode 100644 web_admin/serializers.py create mode 100644 web_admin/tests.py create mode 100644 web_admin/urls.py create mode 100644 web_admin/views.py diff --git a/web_admin/__init__.py b/web_admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/admin.py b/web_admin/admin.py new file mode 100644 index 0000000..b5367ef --- /dev/null +++ b/web_admin/admin.py @@ -0,0 +1,27 @@ +from django.contrib import admin + +from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption + + +@admin.register(Article) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'title', 'description', 'is_published'] + + +@admin.register(ArticleCard) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'article', 'title', 'description', 'image'] + + +@admin.register(ArticleSurvey) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'article', 'question', 'points'] + + +@admin.register(SurveyOption) +class ArticleAdmin(admin.ModelAdmin): + """Junior Points Admin""" + list_display = ['id', 'survey', 'option', 'is_answer'] diff --git a/web_admin/apps.py b/web_admin/apps.py new file mode 100644 index 0000000..9701a1f --- /dev/null +++ b/web_admin/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WebAdminConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'web_admin' diff --git a/web_admin/migrations/0001_initial.py b/web_admin/migrations/0001_initial.py new file mode 100644 index 0000000..e652b7c --- /dev/null +++ b/web_admin/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 4.2.2 on 2023-07-13 13:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('is_published', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='ArticleSurvey', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('question', models.CharField(max_length=255)), + ('points', models.IntegerField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ], + ), + migrations.CreateModel( + name='SurveyOption', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('option', models.CharField(max_length=255)), + ('is_answer', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.articlesurvey')), + ], + ), + migrations.CreateModel( + name='ArticleCard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('description', models.TextField()), + ('image', models.ImageField(upload_to='card_images/')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ], + ), + ] diff --git a/web_admin/migrations/__init__.py b/web_admin/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/models.py b/web_admin/models.py new file mode 100644 index 0000000..411b0f9 --- /dev/null +++ b/web_admin/models.py @@ -0,0 +1,54 @@ +""" +web_admin model file +""" +# django imports +from django.db import models + + +class Article(models.Model): + title = models.CharField(max_length=255) + description = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + is_published = models.BooleanField(default=False) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.title}' + + +class ArticleCard(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + title = models.CharField(max_length=255) + description = models.TextField() + image = models.ImageField(upload_to='card_images/') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.title}' + + +class ArticleSurvey(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + question = models.CharField(max_length=255) + points = models.IntegerField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.article}' + + +class SurveyOption(models.Model): + survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE) + option = models.CharField(max_length=255) + is_answer = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.survey}' diff --git a/web_admin/serializers.py b/web_admin/serializers.py new file mode 100644 index 0000000..69fcb00 --- /dev/null +++ b/web_admin/serializers.py @@ -0,0 +1,55 @@ +""" +web_admin serializers file +""" +from rest_framework import serializers + +from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey + + +class ArticleCardSerializer(serializers.ModelSerializer): + class Meta: + model = ArticleCard + fields = ('title', 'description') + + +class SurveyOptionSerializer(serializers.ModelSerializer): + class Meta: + model = SurveyOption + fields = ('option', 'is_answer') + + +class ArticleSurveySerializer(serializers.ModelSerializer): + options = SurveyOptionSerializer(many=True) + + class Meta: + model = ArticleSurvey + fields = ('question', 'points', 'options') + + +class ArticleSerializer(serializers.ModelSerializer): + article_cards = ArticleCardSerializer(many=True) + article_survey = ArticleSurveySerializer(many=True) + + class Meta: + """ + meta class + """ + model = Article + fields = ('title', 'description', 'article_cards', 'article_survey') + + def create(self, validated_data): + article_cards = validated_data.pop('article_cards') + article_survey = validated_data.pop('article_survey') + + article = Article.objects.create(**validated_data) + + for card in article_cards: + ArticleCard.objects.create(article=article, **card) + + for survey in article_survey: + options = survey.pop('options') + survey_obj = ArticleSurvey.objects.create(article=article, **survey) + for option in options: + SurveyOption.objects.create(survey=survey_obj, **option) + + return article diff --git a/web_admin/tests.py b/web_admin/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/web_admin/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/web_admin/urls.py b/web_admin/urls.py new file mode 100644 index 0000000..d79fafa --- /dev/null +++ b/web_admin/urls.py @@ -0,0 +1,16 @@ +""" +web_admin urls file +""" +# django imports +from django.urls import path, include +from rest_framework import routers + +from web_admin.views import ArticleViewSet + +router = routers.SimpleRouter() + +router.register('article', ArticleViewSet, basename='article') + +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/web_admin/views.py b/web_admin/views.py new file mode 100644 index 0000000..7eecd8d --- /dev/null +++ b/web_admin/views.py @@ -0,0 +1,23 @@ +""" +web_admin views file +""" +# django imports +from rest_framework.viewsets import GenericViewSet, mixins +from rest_framework.response import Response +from rest_framework import status + +# local imports +from web_admin.models import Article +from web_admin.serializers import ArticleSerializer + + +class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin): + serializer_class = ArticleSerializer + permission_classes = [] + queryset = Article.objects.all() + + def create(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response({'message': "created"}, status=status.HTTP_201_CREATED) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index d98d2db..a4c37f5 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'account', 'junior', 'guardian', + 'web_admin', # 'social_django' ] @@ -221,3 +222,5 @@ ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') STATIC_URL = 'static/' STATIC_ROOT = 'static' +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media') diff --git a/zod_bank/urls.py b/zod_bank/urls.py index 274451e..009f1b3 100644 --- a/zod_bank/urls.py +++ b/zod_bank/urls.py @@ -30,4 +30,5 @@ urlpatterns = [ path('', include(('account.urls', 'account'), namespace='account')), path('', include('guardian.urls')), path('', include(('junior.urls', 'junior'), namespace='junior')), + path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')), ] From 7b9b5a2c6ff188965dbdfb28ffd3636c63f360bc Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 12:05:28 +0530 Subject: [PATCH 083/372] sonar issues --- account/admin.py | 10 +--------- account/models.py | 4 ++++ account/utils.py | 6 ++++-- account/views.py | 23 +++++++++++++---------- base/constants.py | 3 +++ base/messages.py | 3 ++- guardian/models.py | 24 +++++++++++++++++++++--- guardian/serializers.py | 12 ++++++------ junior/models.py | 16 ++++++++++++++++ junior/serializers.py | 8 ++++---- junior/utils.py | 16 +++------------- 11 files changed, 77 insertions(+), 48 deletions(-) diff --git a/account/admin.py b/account/admin.py index 7b59c84..e31076e 100644 --- a/account/admin.py +++ b/account/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin """Import django app""" -from .models import UserEmailOtp, UserPhoneOtp, DefaultTaskImages, UserNotification, UserDelete +from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete # Register your models here. @admin.register(UserDelete) @@ -37,11 +37,3 @@ class UserEmailOtpAdmin(admin.ModelAdmin): """Return object in email and otp format""" return self.email + '-' + self.otp -@admin.register(UserPhoneOtp) -class UserPhoneOtpAdmin(admin.ModelAdmin): - """User Phone otp admin""" - list_display = ['phone'] - - def __str__(self): - """Return object in phone number and otp format""" - return self.phone + '-' + self.otp diff --git a/account/models.py b/account/models.py index 60184c5..d89f499 100644 --- a/account/models.py +++ b/account/models.py @@ -90,6 +90,8 @@ class DefaultTaskImages(models.Model): class Meta(object): """ Meta information """ db_table = 'default_task_image' + verbose_name = 'Default Task images' + verbose_name_plural = 'Default Task images' def __str__(self): """return phone as an object""" @@ -112,6 +114,8 @@ class UserDelete(models.Model): class Meta(object): """ Meta information """ db_table = 'user_delete_information' + verbose_name = 'Deleted User' + verbose_name_plural = 'Deleted User' def __str__(self): return self.user.email diff --git a/account/utils.py b/account/utils.py index 846add9..b925b49 100644 --- a/account/utils.py +++ b/account/utils.py @@ -183,13 +183,15 @@ def get_token(data: dict = dict): def generate_alphanumeric_code(length): + """Generate alphanumeric code""" alphabet = string.ascii_letters + string.digits code = ''.join(secrets.choice(alphabet) for _ in range(length)) return code def generate_code(value, user_id): - alphabet = value + user_id.zfill(3) - return alphabet + """generate referral, junior and guardian code""" + code = value + str(user_id).zfill(3) + return code diff --git a/account/views.py b/account/views.py index 4e71ecd..0adc33c 100644 --- a/account/views.py +++ b/account/views.py @@ -25,10 +25,10 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import NUMBER +from base.constants import NUMBER, ZOD, JUN, GRD from guardian.tasks import generate_otp from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response, - generate_alphanumeric_code) + generate_code) from rest_framework.permissions import IsAuthenticated from templated_email import send_templated_mail import google.oauth2.credentials @@ -42,6 +42,7 @@ from guardian.serializers import GuardianProfileSerializer class GoogleLoginMixin: """google login mixin""" + @staticmethod def google_login(self, request): """google login function""" access_token = request.data.get('access_token') @@ -90,15 +91,15 @@ class GoogleLoginMixin: if str(user_type) == '1': junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, image=profile_picture, signup_method='2', - junior_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6) + junior_code=generate_code(JUN, user_obj.id), + referral_code=generate_code(ZOD, user_obj.id) ) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, image=profile_picture,signup_method='2', - guardian_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6) + guardian_code=generate_code(GRD, user_obj.id), + referral_code=generate_code(ZOD, user_obj.id) ) serializer = GuardianSerializer(guardian_query) # Return a JSON response with the user's email and name @@ -141,14 +142,14 @@ class SigninWithApple(views.APIView): if str(user_type) == '1': junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True, signup_method='3', - junior_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6)) + junior_code=generate_code(JUN, user.id), + referral_code=generate_code(ZOD, user.id)) serializer = JuniorSerializer(junior_query) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, signup_method='3', - guardian_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6)) + guardian_code=generate_code(GRD, user.id), + referral_code=generate_code(ZOD, user.id)) serializer = GuardianSerializer(guardian_query) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -181,6 +182,7 @@ class UpdateProfileImage(views.APIView): return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: + logging.error(e) return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST) class ChangePasswordAPIView(views.APIView): @@ -512,6 +514,7 @@ class SendSupportEmail(views.APIView): class LogoutAPIView(views.APIView): + """Log out API""" permission_classes = (IsAuthenticated,) def post(self, request): diff --git a/base/constants.py b/base/constants.py index 7c83134..6025e0b 100644 --- a/base/constants.py +++ b/base/constants.py @@ -6,6 +6,9 @@ import os # GOOGLE_URL used for interact with google server to verify user existence. #GOOGLE_URL = "https://www.googleapis.com/plus/v1/" +ZOD = 'ZOD' +JUN = 'JUN' +GRD = 'GRD' NUMBER = { 'point_zero': 0.0, 'zero': 0, diff --git a/base/messages.py b/base/messages.py index ffbbbec..49deaf9 100644 --- a/base/messages.py +++ b/base/messages.py @@ -61,6 +61,7 @@ ERROR_CODE = { "2035": "Image should not be 0 kb", "2036": "Choose valid user" } +"""Success message code""" SUCCESS_CODE = { # Success code for password "3001": "Sign up successfully", @@ -97,7 +98,7 @@ SUCCESS_CODE = { "3023": "Approved junior successfully", "3024": "Reject junior request successfully" } - +"""status code error""" STATUS_CODE_ERROR = { # Status code for Invalid Input "4001": ["Invalid input."], diff --git a/guardian/models.py b/guardian/models.py index 921138a..5636a80 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -4,6 +4,7 @@ from django.db import models from django.contrib.auth import get_user_model """Import Django app""" from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS +"""import Junior model""" from junior.models import Junior """Add user model""" User = get_user_model() @@ -15,23 +16,31 @@ class Guardian(models.Model): """Contact details""" country_code = models.IntegerField(blank=True, null=True) phone = models.CharField(max_length=31, null=True, blank=True, default=None) + """country name of the guardian""" country_name = models.CharField(max_length=100, null=True, blank=True, default=None) """Image info""" image = models.URLField(null=True, blank=True, default=None) """Personal info""" family_name = models.CharField(max_length=50, null=True, blank=True, default=None) + """gender of the guardian""" gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) + """date of birth of the guardian""" dob = models.DateField(max_length=15, null=True, blank=True, default=None) """Profile activity""" is_active = models.BooleanField(default=True) + """guardian is verified or not""" is_verified = models.BooleanField(default=False) + """guardian profile is complete or not""" is_complete_profile = models.BooleanField(default=False) + """passcode of the guardian profile""" passcode = models.IntegerField(null=True, blank=True, default=None) """Sign up method""" signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') - """Codes""" + """Guardian Codes""" guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None) + """Referral code""" referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + """Referral code that is used by guardian while signup""" referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) @@ -40,7 +49,10 @@ class Guardian(models.Model): class Meta(object): """ Meta class """ db_table = 'guardians' + """verbose name of the model""" verbose_name = 'Guardian' + """change another name""" + verbose_name_plural = 'Guardian' def __str__(self): """Return email id""" @@ -51,19 +63,23 @@ class JuniorTask(models.Model): guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian') """task details""" task_name = models.CharField(max_length=100) + """task description""" task_description = models.CharField(max_length=500) """points of the task""" points = models.IntegerField(default=TASK_POINTS) + """last date of the task""" due_date = models.DateField(auto_now_add=False, null=True, blank=True) - """Images of task""" + """Images of task that is upload by guardian""" default_image = models.URLField(null=True, blank=True, default=None) + """image that is uploaded by junior""" image = models.URLField(null=True, blank=True, default=None) """associated junior with the task""" junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') """task status""" task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) - """task stage""" + """task is active or not""" is_active = models.BooleanField(default=True) + """Task is approved or not""" is_approved = models.BooleanField(default=False) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) @@ -72,7 +88,9 @@ class JuniorTask(models.Model): class Meta(object): """ Meta class """ db_table = 'junior_task' + """verbose name of the model""" verbose_name = 'Junior Task' + verbose_name_plural = 'Junior Task' def __str__(self): """Return email id""" diff --git a/guardian/serializers.py b/guardian/serializers.py index 9d2a676..055d738 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -9,11 +9,11 @@ from django.contrib.auth.models import User """Import Django app""" from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp, UserNotification -from account.utils import generate_alphanumeric_code +from account.utils import generate_code from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import NUMBER +from base.constants import NUMBER, JUN, ZOD, GRD from junior.models import Junior, JuniorPoints class UserSerializer(serializers.ModelSerializer): """User serializer""" @@ -39,11 +39,11 @@ class UserSerializer(serializers.ModelSerializer): user = User.objects.create_user(username=email, email=email, password=password) UserNotification.objects.create(user=user) if user_type == '1': - Junior.objects.create(auth=user, junior_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6)) + Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), + referral_code=generate_code(ZOD, user.id)) if user_type == '2': - Guardian.objects.create(user=user, guardian_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6)) + Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id), + referral_code=generate_code(ZOD, user.id)) return user except Exception as e: """Error handling""" diff --git a/junior/models.py b/junior/models.py index 58b1ae3..381bbf4 100644 --- a/junior/models.py +++ b/junior/models.py @@ -1,10 +1,13 @@ """Junior model """ """Import django""" from django.db import models +"""Import get_user_model function""" from django.contrib.auth import get_user_model +"""Import ArrayField""" from django.contrib.postgres.fields import ArrayField """Import django app""" from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP +"""Define User model""" User = get_user_model() # Create your models here. @@ -14,10 +17,13 @@ class Junior(models.Model): """Contact details""" phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_code = models.IntegerField(blank=True, null=True) + """country name of the guardian""" country_name = models.CharField(max_length=100, null=True, blank=True, default=None) """Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) + """Date of birth""" dob = models.DateField(max_length=15, null=True, blank=True, default=None) + """Image of the junior""" image = models.URLField(null=True, blank=True, default=None) """relationship""" relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, @@ -26,15 +32,21 @@ class Junior(models.Model): signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') """Codes""" junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) + """Guardian Codes""" guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) + """Referral code""" referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) + """Referral code that is used by junior while signup""" referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) """invited junior""" is_invited = models.BooleanField(default=False) """Profile activity""" is_active = models.BooleanField(default=True) + """junior profile is complete or not""" is_complete_profile = models.BooleanField(default=False) + """passcode of the junior profile""" passcode = models.IntegerField(null=True, blank=True, default=None) + """junior is verified or not""" is_verified = models.BooleanField(default=False) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) @@ -44,6 +56,8 @@ class Junior(models.Model): """ Meta class """ db_table = 'junior' verbose_name = 'Junior' + """another name of the model""" + verbose_name_plural = 'Junior' def __str__(self): """Return email id""" @@ -64,6 +78,8 @@ class JuniorPoints(models.Model): """ Meta class """ db_table = 'junior_task_points' verbose_name = 'Junior Task Points' + """another name of the model""" + verbose_name_plural = 'Junior Task Points' def __str__(self): """Return email id""" diff --git a/junior/serializers.py b/junior/serializers.py index 2df13ca..013cd58 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -8,11 +8,11 @@ from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken """Import django app""" -from account.utils import send_otp_email, generate_alphanumeric_code +from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp from junior.utils import junior_notification_email, junior_approval_mail @@ -275,8 +275,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), dob=validated_data.get('dob'), is_invited=True, relationship=validated_data.get('relationship'), - junior_code=generate_alphanumeric_code(6), - referral_code=generate_alphanumeric_code(6), + junior_code=generate_code(JUN, user_data.id), + referral_code=generate_code(ZOD, user_data.id), referral_code_used=guardian_data.referral_code) """Generate otp""" otp_value = generate_otp() diff --git a/junior/utils.py b/junior/utils.py index 7d19a2d..41c393d 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -1,28 +1,17 @@ """Account utils""" """Import django""" from django.conf import settings -from rest_framework import viewsets, status -from rest_framework.response import Response """Third party Django app""" from templated_email import send_templated_mail -import jwt -import string -from datetime import datetime -from calendar import timegm -from uuid import uuid4 -import secrets -from rest_framework import serializers -from junior.models import Junior -from guardian.models import Guardian -from account.models import UserDelete -from base.messages import ERROR_CODE def junior_notification_email(recipient_email, full_name, email, password): """Notification email""" from_email = settings.EMAIL_FROM_ADDRESS + """recipient email""" recipient_list = [recipient_email] + """use send template mail for sending email""" send_templated_mail( template_name='junior_notification_email.email', from_email=from_email, @@ -40,6 +29,7 @@ def junior_approval_mail(guardian, full_name): """junior approval mail""" from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [guardian] + """use send tempolate mail for sending email""" send_templated_mail( template_name='junior_approval_mail.email', from_email=from_email, From b3f35d271b9ec789900bf94ece5cfc06036cd703 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 12:45:08 +0530 Subject: [PATCH 084/372] change access token duration --- account/serializers.py | 2 +- zod_bank/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index d75039a..6eaeea9 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -174,7 +174,7 @@ class JuniorSerializer(serializers.ModelSerializer): def get_user_type(self, obj): email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last() - if email_verified and email_verified.user_type != None: + if email_verified and email_verified.user_type is not None: return email_verified.user_type return '1' diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 108542c..d23e0d7 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -97,7 +97,7 @@ REST_FRAMEWORK = { 'PAGE_SIZE': 5, } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=23, minutes=59, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), } # Database From d163f43c852113bcb4f2885b00ca5b7d84afebf0 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 15:02:53 +0530 Subject: [PATCH 085/372] jira-25 access token API --- account/serializers.py | 25 ++++++++++++++++++------- account/urls.py | 5 +++-- account/views.py | 24 +++++++++++++++++++++--- zod_bank/settings.py | 4 ++-- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index d75039a..bbde6a0 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -125,12 +125,17 @@ class GuardianSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): refresh = RefreshToken.for_user(obj.user) access_token = str(refresh.access_token) return access_token + def get_refresh_token(self, obj): + refresh = RefreshToken.for_user(obj.user) + refresh_token = str(refresh) + return refresh_token def get_user_type(self, obj): """user type""" @@ -154,9 +159,9 @@ class GuardianSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Guardian - fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', - 'gender', 'dob', 'referral_code', 'is_active', 'is_complete_profile', 'passcode', 'image', - 'created_at', 'updated_at', 'user_type', 'country_name'] + fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', + 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', + 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name'] class JuniorSerializer(serializers.ModelSerializer): @@ -166,15 +171,21 @@ class JuniorSerializer(serializers.ModelSerializer): first_name = serializers.SerializerMethodField('get_first_name') last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): refresh = RefreshToken.for_user(obj.auth) access_token = str(refresh.access_token) return access_token + def get_refresh_token(self, obj): + refresh = RefreshToken.for_user(obj.user) + refresh_token = str(refresh) + return refresh_token + def get_user_type(self, obj): email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last() - if email_verified and email_verified.user_type != None: + if email_verified and email_verified.user_type is not None: return email_verified.user_type return '1' @@ -190,9 +201,9 @@ class JuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['id', 'auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', - 'updated_at', 'user_type', 'country_name','is_invited'] + fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', + 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', + 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited'] class EmailVerificationSerializer(serializers.ModelSerializer): """Email verification serializer""" diff --git a/account/urls.py b/account/urls.py index f2a1d62..9977658 100644 --- a/account/urls.py +++ b/account/urls.py @@ -8,7 +8,7 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView) + UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView) """Router""" router = routers.SimpleRouter() @@ -45,5 +45,6 @@ urlpatterns = [ path('api/v1/update-profile-image/', UpdateProfileImage.as_view()), path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'), path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email'), - path('api/v1/logout/', LogoutAPIView.as_view(), name='logout') + path('api/v1/logout/', LogoutAPIView.as_view(), name='logout'), + path('api/v1/generate-token/', AccessTokenAPIView.as_view(), name='generate-token') ] diff --git a/account/views.py b/account/views.py index 0adc33c..fcc5ecb 100644 --- a/account/views.py +++ b/account/views.py @@ -37,6 +37,7 @@ from rest_framework import status import requests from rest_framework.response import Response from django.conf import settings +from rest_framework_simplejwt.tokens import RefreshToken from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer @@ -302,7 +303,8 @@ class UserLogin(viewsets.ViewSet): email_verified = UserEmailOtp.objects.filter(email=username).last() refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) - data = {"auth_token":access_token, "is_profile_complete": False, + refresh_token = str(refresh) + data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False, "user_type": email_verified.user_type, } is_verified = False @@ -336,7 +338,8 @@ class UserLogin(viewsets.ViewSet): logging.error(e) refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) - data = {"auth_token": access_token, "user_role": '3'} + refresh_token = str(refresh) + data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'} return custom_response(None, data, response_status=status.HTTP_200_OK) class UserEmailVerification(viewsets.ModelViewSet): @@ -373,7 +376,8 @@ class UserEmailVerification(viewsets.ModelViewSet): guardian_data.save() refresh = RefreshToken.for_user(user_obj) access_token = str(refresh.access_token) - return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, + refresh_token = str(refresh) + return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token, "refresh_token":refresh_token}, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) @@ -521,3 +525,17 @@ class LogoutAPIView(views.APIView): logout(request) request.session.flush() return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK) + + +class AccessTokenAPIView(views.APIView): + """generate access token API""" + + def post(self, request): + # Assuming you have a refresh_token string + refresh_token = request.data['refresh_token'] + # Create a RefreshToken instance from the refresh token string + refresh = RefreshToken(refresh_token) + # Generate a new access token + access_token = str(refresh.access_token) + data = {"auth_token": access_token} + return custom_response(None, data, response_status=status.HTTP_200_OK) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index d98d2db..687b42b 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -97,8 +97,8 @@ REST_FRAMEWORK = { 'PAGE_SIZE': 5, } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=50), - 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), + 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases From c7983579527c9a63c651ab08a7a2b2664934e5af Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 15:05:18 +0530 Subject: [PATCH 086/372] migrations files --- ...lter_defaulttaskimages_options_and_more.py | 21 +++++++++++++++++++ ...ardian_options_alter_juniortask_options.py | 21 +++++++++++++++++++ ...nior_options_alter_juniorpoints_options.py | 21 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 account/migrations/0007_alter_defaulttaskimages_options_and_more.py create mode 100644 guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py create mode 100644 junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py diff --git a/account/migrations/0007_alter_defaulttaskimages_options_and_more.py b/account/migrations/0007_alter_defaulttaskimages_options_and_more.py new file mode 100644 index 0000000..4c134d7 --- /dev/null +++ b/account/migrations/0007_alter_defaulttaskimages_options_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-14 09:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0006_alter_useremailotp_options_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='defaulttaskimages', + options={'verbose_name': 'Default Task images', 'verbose_name_plural': 'Default Task images'}, + ), + migrations.AlterModelOptions( + name='userdelete', + options={'verbose_name': 'Deleted User', 'verbose_name_plural': 'Deleted User'}, + ), + ] diff --git a/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py b/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py new file mode 100644 index 0000000..45edd24 --- /dev/null +++ b/guardian/migrations/0015_alter_guardian_options_alter_juniortask_options.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-14 09:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0014_guardian_signup_method'), + ] + + operations = [ + migrations.AlterModelOptions( + name='guardian', + options={'verbose_name': 'Guardian', 'verbose_name_plural': 'Guardian'}, + ), + migrations.AlterModelOptions( + name='juniortask', + options={'verbose_name': 'Junior Task', 'verbose_name_plural': 'Junior Task'}, + ), + ] diff --git a/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py b/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py new file mode 100644 index 0000000..266d53c --- /dev/null +++ b/junior/migrations/0013_alter_junior_options_alter_juniorpoints_options.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-14 09:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0012_junior_is_invited'), + ] + + operations = [ + migrations.AlterModelOptions( + name='junior', + options={'verbose_name': 'Junior', 'verbose_name_plural': 'Junior'}, + ), + migrations.AlterModelOptions( + name='juniorpoints', + options={'verbose_name': 'Junior Task Points', 'verbose_name_plural': 'Junior Task Points'}, + ), + ] From a0e1dc11fae65f15d87590c711162efb2d4770e8 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 15:07:54 +0530 Subject: [PATCH 087/372] migrations files --- guardian/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index f060732..f35ca90 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -114,7 +114,6 @@ class CreateTaskAPIView(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): image = request.data['default_image'] data = request.data - print("data===>",data,'==>',type(data)) if 'https' in str(image): image_data = image else: From 485dc8391147696bd06048a5c34e770f4cb361a2 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 16:04:43 +0530 Subject: [PATCH 088/372] authenticate the api --- account/views.py | 11 +++++++++++ junior/views.py | 2 ++ 2 files changed, 13 insertions(+) diff --git a/account/views.py b/account/views.py index fcc5ecb..59260c2 100644 --- a/account/views.py +++ b/account/views.py @@ -208,6 +208,7 @@ class ResetPasswordAPIView(views.APIView): class ForgotPasswordAPIView(views.APIView): """Forgot password""" + def post(self, request): serializer = ForgotPasswordSerializer(data=request.data) if serializer.is_valid(): @@ -346,6 +347,7 @@ class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer queryset = UserEmailOtp.objects.all() + permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): try: @@ -389,6 +391,8 @@ class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer + permission_classes = [IsAuthenticated] + def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): @@ -410,6 +414,8 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" queryset = User.objects.all() serializer_class = JuniorProfileSerializer + permission_classes = [IsAuthenticated] + def list(self, request, *args, **kwargs): """profile view""" if str(self.request.GET.get('user_type')) == '1': @@ -448,6 +454,7 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesDetailsSerializer + permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): """profile view""" queryset = DefaultTaskImages.objects.all() @@ -478,6 +485,7 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): """notification viewset""" queryset = UserNotification.objects.all() serializer_class = UserNotificationSerializer + permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): """profile view""" queryset = self.queryset.filter(user=request.user) @@ -489,6 +497,7 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): """Update notification viewset""" queryset = UserNotification.objects.all() serializer_class = UpdateUserNotificationSerializer + permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): """profile view""" @@ -502,6 +511,8 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): class SendSupportEmail(views.APIView): """support email api""" + permission_classes = (IsAuthenticated,) + def post(self, request): name = request.data.get('name') sender = request.data.get('email') diff --git a/junior/views.py b/junior/views.py index a1053d4..5766c82 100644 --- a/junior/views.py +++ b/junior/views.py @@ -62,6 +62,8 @@ class JuniorListAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer queryset = Junior.objects.all() + permission_classes = [IsAuthenticated] + def list(self, request, *args, **kwargs): """ junior list""" guardian_data = Guardian.objects.filter(user__email=request.user).last() From 45410fa0ca066915cfc4c262430f35ca9c455293 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 14 Jul 2023 18:47:07 +0530 Subject: [PATCH 089/372] list, view, edit api for article --- web_admin/admin.py | 16 +++--- web_admin/migrations/0001_initial.py | 11 ++-- web_admin/models.py | 9 ++-- web_admin/serializers.py | 76 +++++++++++++++++++++++++--- web_admin/utils.py | 13 +++++ web_admin/views.py | 37 ++++++++++++-- 6 files changed, 136 insertions(+), 26 deletions(-) create mode 100644 web_admin/utils.py diff --git a/web_admin/admin.py b/web_admin/admin.py index b5367ef..1adf7e6 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -5,23 +5,23 @@ from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption @admin.register(Article) class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" - list_display = ['id', 'title', 'description', 'is_published'] + """Article Admin""" + list_display = ['id', 'title', 'description', 'is_published', 'is_deleted'] @admin.register(ArticleCard) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class ArticleCardAdmin(admin.ModelAdmin): + """Article Card Admin""" list_display = ['id', 'article', 'title', 'description', 'image'] @admin.register(ArticleSurvey) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class ArticleSurveyAdmin(admin.ModelAdmin): + """Article Survey Admin""" list_display = ['id', 'article', 'question', 'points'] @admin.register(SurveyOption) -class ArticleAdmin(admin.ModelAdmin): - """Junior Points Admin""" +class SurveyOptionAdmin(admin.ModelAdmin): + """Survey Option Admin""" list_display = ['id', 'survey', 'option', 'is_answer'] diff --git a/web_admin/migrations/0001_initial.py b/web_admin/migrations/0001_initial.py index e652b7c..ad25a50 100644 --- a/web_admin/migrations/0001_initial.py +++ b/web_admin/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-07-13 13:01 +# Generated by Django 4.2.2 on 2023-07-14 13:15 from django.db import migrations, models import django.db.models.deletion @@ -20,7 +20,8 @@ class Migration(migrations.Migration): ('description', models.TextField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('is_published', models.BooleanField(default=False)), + ('is_published', models.BooleanField(default=True)), + ('is_deleted', models.BooleanField(default=False)), ], ), migrations.CreateModel( @@ -31,7 +32,7 @@ class Migration(migrations.Migration): ('points', models.IntegerField()), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_survey', to='web_admin.article')), ], ), migrations.CreateModel( @@ -42,7 +43,7 @@ class Migration(migrations.Migration): ('is_answer', models.BooleanField(default=False)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.articlesurvey')), + ('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='survey_options', to='web_admin.articlesurvey')), ], ), migrations.CreateModel( @@ -54,7 +55,7 @@ class Migration(migrations.Migration): ('image', models.ImageField(upload_to='card_images/')), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web_admin.article')), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_cards', to='web_admin.article')), ], ), ] diff --git a/web_admin/models.py b/web_admin/models.py index 411b0f9..0437102 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -10,7 +10,8 @@ class Article(models.Model): description = models.TextField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - is_published = models.BooleanField(default=False) + is_published = models.BooleanField(default=True) + is_deleted = models.BooleanField(default=False) def __str__(self): """Return title""" @@ -18,7 +19,7 @@ class Article(models.Model): class ArticleCard(models.Model): - article = models.ForeignKey(Article, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() image = models.ImageField(upload_to='card_images/') @@ -31,7 +32,7 @@ class ArticleCard(models.Model): class ArticleSurvey(models.Model): - article = models.ForeignKey(Article, on_delete=models.CASCADE) + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey') question = models.CharField(max_length=255) points = models.IntegerField() created_at = models.DateTimeField(auto_now_add=True) @@ -43,7 +44,7 @@ class ArticleSurvey(models.Model): class SurveyOption(models.Model): - survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE) + survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options') option = models.CharField(max_length=255) is_answer = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 69fcb00..23e69e3 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -4,29 +4,38 @@ web_admin serializers file from rest_framework import serializers from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey +from web_admin.utils import pop_id class ArticleCardSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = ArticleCard - fields = ('title', 'description') + fields = ('id', 'title', 'description') class SurveyOptionSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) + class Meta: model = SurveyOption - fields = ('option', 'is_answer') + fields = ('id', 'option', 'is_answer') class ArticleSurveySerializer(serializers.ModelSerializer): - options = SurveyOptionSerializer(many=True) + id = serializers.IntegerField(required=False) + survey_options = SurveyOptionSerializer(many=True) class Meta: model = ArticleSurvey - fields = ('question', 'points', 'options') + fields = ('id', 'question', 'points', 'survey_options') class ArticleSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ article_cards = ArticleCardSerializer(many=True) article_survey = ArticleSurveySerializer(many=True) @@ -35,21 +44,76 @@ class ArticleSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('title', 'description', 'article_cards', 'article_survey') + fields = ('id', 'title', 'description', 'article_cards', 'article_survey') def create(self, validated_data): + """ + to create article. + ID in post data dict is for update api. + :param validated_data: + :return: success message + """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') article = Article.objects.create(**validated_data) for card in article_cards: + card = pop_id(card) ArticleCard.objects.create(article=article, **card) for survey in article_survey: - options = survey.pop('options') + survey = pop_id(survey) + options = survey.pop('survey_options') survey_obj = ArticleSurvey.objects.create(article=article, **survey) for option in options: + option = pop_id(option) SurveyOption.objects.create(survey=survey_obj, **option) return article + + def update(self, instance, validated_data): + article_cards = validated_data.pop('article_cards') + article_survey = validated_data.pop('article_survey') + instance.title = validated_data.get('title', instance.title) + instance.description = validated_data.get('description', instance.description) + instance.save() + + # Update or create cards + for card_data in article_cards: + card_id = card_data.get('id', None) + if card_id: + card = ArticleCard.objects.get(id=card_id, article=instance) + card.title = card_data.get('title', card.title) + card.description = card_data.get('description', card.description) + card.image = card_data.get('image', card.image) + card.save() + else: + card_data = pop_id(card_data) + ArticleCard.objects.create(article=instance, **card_data) + + # Update or create survey sections + for survey_data in article_survey: + survey_id = survey_data.get('id', None) + options_data = survey_data.pop('survey_options') + if survey_id: + survey = ArticleSurvey.objects.get(id=survey_id, article=instance) + survey.question = survey_data.get('question', survey.question) + survey.save() + else: + survey_data = pop_id(survey_data) + survey = ArticleSurvey.objects.create(article=instance, **survey_data) + + # Update or create survey options + for option_data in options_data: + option_id = option_data.get('id', None) + if option_id: + option = SurveyOption.objects.get(id=option_id, survey=survey) + option.option = option_data.get('option', option.option) + option.is_answer = option_data.get('is_answer', option.is_answer) + option.save() + else: + option_data = pop_id(option_data) + SurveyOption.objects.create(survey=survey, **option_data) + + return instance diff --git a/web_admin/utils.py b/web_admin/utils.py new file mode 100644 index 0000000..2e09e2f --- /dev/null +++ b/web_admin/utils.py @@ -0,0 +1,13 @@ +""" +web_utils file +""" + + +def pop_id(data): + """ + to pop id, not in use + :param data: + :return: data + """ + data.pop('id') if 'id' in data else data + return data diff --git a/web_admin/views.py b/web_admin/views.py index 7eecd8d..88147a1 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -7,17 +7,48 @@ from rest_framework.response import Response from rest_framework import status # local imports +from account.utils import custom_response, custom_error_response from web_admin.models import Article from web_admin.serializers import ArticleSerializer -class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin): +class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, + mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin): serializer_class = ArticleSerializer permission_classes = [] - queryset = Article.objects.all() + queryset = Article.objects.prefetch_related('article_cards', + 'article_survey', + 'article_survey__survey_options') def create(self, request, *args, **kwargs): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return Response({'message': "created"}, status=status.HTTP_201_CREATED) + return custom_response("created") + + def update(self, request, *args, **kwargs): + article = self.queryset.filter(id=kwargs['pk']).first() + serializer = self.serializer_class(article, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response("updated") + + def list(self, request, *args, **kwargs): + # queryset = Article.objects.prefetch_related('article_cards', + # 'article_survey', + # 'article_survey__survey_options') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(self.queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data) + + def retrieve(self, request, *args, **kwargs): + queryset = self.queryset.filter(id=kwargs['pk']) + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + + def destroy(self, request, *args, **kwargs): + article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True) + if article: + return custom_response("deleted") + return custom_error_response("article doesn't exist", status.HTTP_400_BAD_REQUEST) From 5bd865a685b979fcace94794c55068d0f94d7341 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 14 Jul 2023 19:38:58 +0530 Subject: [PATCH 090/372] login in only single device at a time --- account/admin.py | 9 ++++- account/custom_middleware.py | 36 ++++++++++++++++++ account/migrations/0008_userdevicedetails.py | 31 ++++++++++++++++ account/models.py | 20 ++++++++++ account/views.py | 39 ++++++++++---------- base/messages.py | 3 +- guardian/urls.py | 10 ++++- zod_bank/settings.py | 1 + 8 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 account/custom_middleware.py create mode 100644 account/migrations/0008_userdevicedetails.py diff --git a/account/admin.py b/account/admin.py index e31076e..b67b88a 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 +from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails # Register your models here. @admin.register(UserDelete) @@ -37,3 +37,10 @@ class UserEmailOtpAdmin(admin.ModelAdmin): """Return object in email and otp format""" return self.email + '-' + self.otp +@admin.register(UserDeviceDetails) +class UserDeviceDetailsAdmin(admin.ModelAdmin): + """User profile admin""" + list_display = ['user', 'device_id'] + + def __str__(self): + return self.user.email diff --git a/account/custom_middleware.py b/account/custom_middleware.py new file mode 100644 index 0000000..3bd5e60 --- /dev/null +++ b/account/custom_middleware.py @@ -0,0 +1,36 @@ +"""middleware file""" +"""Django import""" +from rest_framework import status +from rest_framework.response import Response +from rest_framework.renderers import JSONRenderer +"""App django""" +from account.utils import custom_error_response +from account.models import UserDeviceDetails +from base.messages import ERROR_CODE, SUCCESS_CODE + +"""Custom middleware +when user login with +multiple device simultaneously""" +class CustomMiddleware: + """Custom middleware""" + def __init__(self, get_response): + """response""" + self.get_response = get_response + + def __call__(self, request): + # Code to be executed before the view is called + response = self.get_response(request) + # Code to be executed after the view is called + device_id = request.META['HTTP_DEVICE_ID'] + if request.user.is_authenticated: + """device details""" + device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() + if not device_details: + custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND) + response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND) + # Set content type header to "application/json" + response['Content-Type'] = 'application/json' + # Render the response as JSON + renderer = JSONRenderer() + response.content = renderer.render(response.data) + return response diff --git a/account/migrations/0008_userdevicedetails.py b/account/migrations/0008_userdevicedetails.py new file mode 100644 index 0000000..0b655f6 --- /dev/null +++ b/account/migrations/0008_userdevicedetails.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.2 on 2023-07-14 11:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('account', '0007_alter_defaulttaskimages_options_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='UserDeviceDetails', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('device_id', models.CharField(max_length=500)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_device_details', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'User Device Details', + 'verbose_name_plural': 'User Device Details', + 'db_table': 'user_device_details', + }, + ), + ] diff --git a/account/models.py b/account/models.py index d89f499..d515e01 100644 --- a/account/models.py +++ b/account/models.py @@ -145,3 +145,23 @@ class UserNotification(models.Model): def __str__(self): return self.user.email + +class UserDeviceDetails(models.Model): + """ + User notification details + """ + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_device_details') + """Device ID""" + device_id = models.CharField(max_length=500) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta information """ + db_table = 'user_device_details' + verbose_name = 'User Device Details' + verbose_name_plural = 'User Device Details' + + def __str__(self): + return self.user.email diff --git a/account/views.py b/account/views.py index 59260c2..b6fc821 100644 --- a/account/views.py +++ b/account/views.py @@ -5,17 +5,23 @@ from rest_framework import viewsets, status, views from rest_framework.decorators import action import random import logging -from PIL import Image -from django.views.decorators.csrf import csrf_exempt from django.utils import timezone import jwt from django.contrib.auth import logout -"""App Import""" -from guardian.utils import upload_image_to_alibaba from django.contrib.auth import authenticate, login +from rest_framework.permissions import IsAuthenticated +from templated_email import send_templated_mail +import google.oauth2.credentials +import google.auth.transport.requests +from rest_framework import status +import requests +from rest_framework.response import Response +from django.conf import settings +"""App Import""" from guardian.models import Guardian from junior.models import Junior -from account.models import UserProfile, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification +from guardian.utils import upload_image_to_alibaba +from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User """Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, @@ -29,15 +35,6 @@ from base.constants import NUMBER, ZOD, JUN, GRD from guardian.tasks import generate_otp from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response, generate_code) -from rest_framework.permissions import IsAuthenticated -from templated_email import send_templated_mail -import google.oauth2.credentials -import google.auth.transport.requests -from rest_framework import status -import requests -from rest_framework.response import Response -from django.conf import settings -from rest_framework_simplejwt.tokens import RefreshToken from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer @@ -229,7 +226,6 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: @@ -285,6 +281,7 @@ class UserLogin(viewsets.ViewSet): def login(self, request): username = request.data.get('username') password = request.data.get('password') + device_id = request.META.get('HTTP_DEVICE_ID') user = authenticate(request, username=username, password=password) try: @@ -296,6 +293,10 @@ class UserLogin(viewsets.ViewSet): junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() if junior_data: serializer = JuniorSerializer(junior_data).data + device_details, created = UserDeviceDetails.objects.get_or_create(user=user) + if device_details: + device_details.device_id = device_id + device_details.save() return custom_response(SUCCESS_CODE['3003'], serializer, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) @@ -347,7 +348,6 @@ class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer queryset = UserEmailOtp.objects.all() - permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): try: @@ -379,7 +379,8 @@ class UserEmailVerification(viewsets.ModelViewSet): refresh = RefreshToken.for_user(user_obj) access_token = str(refresh.access_token) refresh_token = str(refresh) - return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token, "refresh_token":refresh_token}, + return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token, + "refresh_token":refresh_token}, response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) @@ -429,8 +430,6 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): serializer = GuardianProfileSerializer(guardian_data) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - - class UploadImageAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" queryset = DefaultTaskImages.objects.all() @@ -527,7 +526,6 @@ class SendSupportEmail(views.APIView): else: return custom_error_response(ERROR_CODE['2033'], response_status=status.HTTP_400_BAD_REQUEST) - class LogoutAPIView(views.APIView): """Log out API""" permission_classes = (IsAuthenticated,) @@ -550,3 +548,4 @@ class AccessTokenAPIView(views.APIView): access_token = str(refresh.access_token) data = {"auth_token": access_token} return custom_response(None, data, response_status=status.HTTP_200_OK) + diff --git a/base/messages.py b/base/messages.py index 49deaf9..02b0e34 100644 --- a/base/messages.py +++ b/base/messages.py @@ -59,7 +59,8 @@ ERROR_CODE = { "2033": "Missing required fields", "2034": "Junior is not associated", "2035": "Image should not be 0 kb", - "2036": "Choose valid user" + "2036": "Choose valid user", + "2037": "You are already log in another device" } """Success message code""" SUCCESS_CODE = { diff --git a/guardian/urls.py b/guardian/urls.py index 168610b..14398f2 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -9,7 +9,15 @@ from rest_framework import routers """Define Router""" router = routers.SimpleRouter() -"""API End points with router""" +"""API End points with router +in this file +we define various api end point +that is covered in this guardian +section API:- like +sign-up, create guardian profile, +create-task, +all task list, top junior, +filter-task""" """Sign up API""" router.register('sign-up', SignupViewset, basename='sign-up') """Create guardian profile API""" diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 687b42b..b052a4f 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -67,6 +67,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'account.custom_middleware.CustomMiddleware' ] ROOT_URLCONF = 'zod_bank.urls' From 32e52e17f4b5cb9daae5ed5af978e37af8abc278 Mon Sep 17 00:00:00 2001 From: jain Date: Sat, 15 Jul 2023 23:07:12 +0530 Subject: [PATCH 091/372] sonar issue --- account/admin.py | 3 +++ account/custom_middleware.py | 10 +++++----- base/messages.py | 2 ++ guardian/urls.py | 18 +++++++++--------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/account/admin.py b/account/admin.py index b67b88a..cf3ff23 100644 --- a/account/admin.py +++ b/account/admin.py @@ -19,6 +19,7 @@ class UserNotificationAdmin(admin.ModelAdmin): list_display = ['user', 'push_notification', 'email_notification', 'sms_notification'] def __str__(self): + """Return image url""" return self.image_url @admin.register(DefaultTaskImages) class DefaultTaskImagesAdmin(admin.ModelAdmin): @@ -26,6 +27,7 @@ class DefaultTaskImagesAdmin(admin.ModelAdmin): list_display = ['task_name', 'image_url'] def __str__(self): + """Return image url""" return self.image_url @admin.register(UserEmailOtp) @@ -43,4 +45,5 @@ class UserDeviceDetailsAdmin(admin.ModelAdmin): list_display = ['user', 'device_id'] def __str__(self): + """Return user email""" return self.user.email diff --git a/account/custom_middleware.py b/account/custom_middleware.py index 3bd5e60..7c4dc32 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -8,10 +8,10 @@ from account.utils import custom_error_response from account.models import UserDeviceDetails from base.messages import ERROR_CODE, SUCCESS_CODE -"""Custom middleware -when user login with +"""Custom middleware +when user login with multiple device simultaneously""" -class CustomMiddleware: +class CustomMiddleware(object): """Custom middleware""" def __init__(self, get_response): """response""" @@ -21,11 +21,11 @@ class CustomMiddleware: # Code to be executed before the view is called response = self.get_response(request) # Code to be executed after the view is called - device_id = request.META['HTTP_DEVICE_ID'] + device_id = request.META.get('HTTP_DEVICE_ID') if request.user.is_authenticated: """device details""" device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() - if not device_details: + 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" diff --git a/base/messages.py b/base/messages.py index 02b0e34..e3399ed 100644 --- a/base/messages.py +++ b/base/messages.py @@ -58,8 +58,10 @@ ERROR_CODE = { "2032": "Failed to send email", "2033": "Missing required fields", "2034": "Junior is not associated", + # image size "2035": "Image should not be 0 kb", "2036": "Choose valid user", + # log in multiple device msg "2037": "You are already log in another device" } """Success message code""" diff --git a/guardian/urls.py b/guardian/urls.py index 14398f2..4a1c644 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -9,15 +9,15 @@ from rest_framework import routers """Define Router""" router = routers.SimpleRouter() -"""API End points with router -in this file -we define various api end point -that is covered in this guardian -section API:- like -sign-up, create guardian profile, -create-task, -all task list, top junior, -filter-task""" +# API End points with router +# in this file +# we define various api end point +# that is covered in this guardian +# section API:- like +# sign-up, create guardian profile, +# create-task, +# all task list, top junior, +# filter-task""" """Sign up API""" router.register('sign-up', SignupViewset, basename='sign-up') """Create guardian profile API""" From cfc36735d61dbd5377a74eb885d5331be015bd46 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 11:58:04 +0530 Subject: [PATCH 092/372] sonar issues fixed --- account/custom_middleware.py | 10 ++++-- account/serializers.py | 32 +++++++++++++++++- account/urls.py | 22 +++++++++++- account/utils.py | 32 ++++++++++++++++-- base/constants.py | 36 +++++++++----------- guardian/urls.py | 16 ++++----- guardian/utils.py | 8 +++++ junior/models.py | 65 ++++++++++++++++++++++++------------ junior/urls.py | 24 ++++++++----- junior/utils.py | 16 ++++++--- 10 files changed, 190 insertions(+), 71 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index 7c4dc32..7a06e43 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -8,9 +8,13 @@ from account.utils import custom_error_response from account.models import UserDeviceDetails from base.messages import ERROR_CODE, SUCCESS_CODE -"""Custom middleware -when user login with -multiple device simultaneously""" +# Custom middleware +# when user login with +# multiple device simultaneously +# It restricted login in +# multiple devices only +# user can login in single +# device at a time""" class CustomMiddleware(object): """Custom middleware""" def __init__(self, get_response): diff --git a/account/serializers.py b/account/serializers.py index bbde6a0..4583b6a 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,10 +1,20 @@ """Account serializer""" -"""Django Imoprt""" +"""Django Import""" import random from rest_framework import serializers from django.contrib.auth.models import User from rest_framework_simplejwt.tokens import RefreshToken """App import""" +# Import guardian's model, +# Import junior's model, +# Import account's model, +# Import constant from +# base package, +# Import messages from +# base package, +# Import some functions +# from utils file""" + from guardian.models import Guardian from junior.models import Junior from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp @@ -12,6 +22,25 @@ from base.constants import GUARDIAN, JUNIOR, SUPERUSER from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR from .utils import delete_user_account_condition_social, delete_user_account_condition +# In this serializer file +# define google login serializer +# update junior profile, +# update guardian profile, +# super admin serializer, +# reset password, +# forgot password, +# change password, +# basic junior serializer, +# basic guardian serializer, +# user delete account serializer, +# user notification serializer, +# update user notification serializer, +# default task's images serializer, +# upload default task's images serializer, +# email verification serializer, +# phone otp serializer + + class GoogleLoginSerializer(serializers.Serializer): """google login serializer""" access_token = serializers.CharField(max_length=5000, required=True) @@ -89,6 +118,7 @@ class ChangePasswordSerializer(serializers.Serializer): def create(self, validated_data): new_password = validated_data.pop('new_password') current_password = validated_data.pop('current_password') + """Check new password is different from current password""" if new_password == current_password: raise serializers.ValidationError({"details": ERROR_CODE['2026']}) user_details = User.objects.filter(email=self.context).last() diff --git a/account/urls.py b/account/urls.py index 9977658..ef3d026 100644 --- a/account/urls.py +++ b/account/urls.py @@ -3,7 +3,27 @@ from django.urls import path, include """Third party import""" from rest_framework import routers -"""Import view functions""" +# Import view functions +# UserLogin views, +# SendPhoneOtp views, +# UserPhoneVerification views, +# UserEmailVerification views, +# ReSendEmailOtp views, +# ForgotPasswordAPIView views, +# ResetPasswordAPIView views, +# ChangePasswordAPIView views, +# UpdateProfileImage views, +# GoogleLoginViewSet views, +# SigninWithApple views, +# ProfileAPIViewSet views, +# UploadImageAPIViewSet views, +# DefaultImageAPIViewSet views, +# DeleteUserProfileAPIViewSet views, +# UserNotificationAPIViewSet views, +# UpdateUserNotificationAPIViewSet views, +# SendSupportEmail views, +# LogoutAPIView views, +# AccessTokenAPIView views""" from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp, ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, diff --git a/account/utils.py b/account/utils.py index b925b49..eeb6954 100644 --- a/account/utils.py +++ b/account/utils.py @@ -12,12 +12,37 @@ from calendar import timegm from uuid import uuid4 import secrets from rest_framework import serializers +# Django App Import +# Import models from junior App, +# Import models from guardian App, +# Import models from account App, +# Import messages from base package""" from junior.models import Junior from guardian.models import Guardian from account.models import UserDelete from base.messages import ERROR_CODE +# Define delete +# user account condition, +# Define delete +# user account +# condition for social +# login account, +# Update junior account, +# Update guardian account, +# Define custom email for otp verification, +# Define support email for user's query, +# Define custom success response, +# Define custom error response, +# Generate access token, +# refresh token by using jwt, +# Define function for generating +# guardian code, junior code, +# referral code, +# Define function for generating +# alphanumeric code + def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num): """delete user account""" if user_type == '1' and user_type_data == '1': @@ -28,10 +53,10 @@ def delete_user_account_condition(user, user_type_data, user_type, user_tb, data raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"}) user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.password = 'None' d_email = user_tb.email o_mail = user.email user_tb.save() + """create object in user delete model""" instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, is_active=True, reason=data) @@ -47,10 +72,10 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"}) user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() - user_tb.password = 'None' dummy_email = user_tb.email old_mail = user.email user_tb.save() + """create object in user delete model""" instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail, is_active=True, reason=data) @@ -72,6 +97,7 @@ def guardian_account_update(user_tb): guardian_data.is_verified = False guardian_data.save() jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + """Disassociate relation between guardian and junior""" for data in jun_data: data.guardian_code.remove(guardian_data.guardian_code) data.save() @@ -79,6 +105,7 @@ def send_otp_email(recipient_email, otp): """Send otp on email with template""" from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [recipient_email] + """Send otp on email""" send_templated_mail( template_name='email_otp_verification.email', from_email=from_email, @@ -93,6 +120,7 @@ def send_support_email(name, sender, subject, message): """Send otp on email with template""" to_email = [settings.EMAIL_FROM_ADDRESS] from_email = settings.DEFAULT_ADDRESS + """Send support email to zod bank support team""" send_templated_mail( template_name='support_mail.email', from_email=from_email, diff --git a/base/constants.py b/base/constants.py index 6025e0b..648bdd8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -6,31 +6,22 @@ import os # GOOGLE_URL used for interact with google server to verify user existence. #GOOGLE_URL = "https://www.googleapis.com/plus/v1/" +# Define Code prefix word +# for guardian code, +# junior code, +# referral code""" ZOD = 'ZOD' JUN = 'JUN' GRD = 'GRD' +# Define number variable +# from zero to +# twenty and +# some standard +# number""" NUMBER = { - 'point_zero': 0.0, - 'zero': 0, - 'one': 1, - 'two': 2, - 'three': 3, - 'four': 4, - 'five': 5, - 'six': 6, - 'seven': 7, - 'eight': 8, - 'nine': 9, - 'ten': 10, - 'eleven': 11, - 'twelve': 12, - 'thirteen': 13, - 'fourteen': 14, - 'fifteen': 15, - 'sixteen': 16, - 'seventeen': 17, - 'eighteen': 18, - 'nineteen': 19, + 'point_zero': 0.0, 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, + 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15, + 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty_four': 24, 'twenty_one': 21, 'twenty_two': 22, @@ -108,6 +99,9 @@ RELATIONSHIP = ( ('1', 'parent'), ('2', 'legal_guardian') ) +""" +Define task status +in a number""" PENDING = 1 IN_PROGRESS = 2 REJECTED = 3 diff --git a/guardian/urls.py b/guardian/urls.py index 4a1c644..71d3036 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -20,21 +20,21 @@ router = routers.SimpleRouter() # filter-task""" """Sign up API""" router.register('sign-up', SignupViewset, basename='sign-up') -"""Create guardian profile API""" +# Create guardian profile API""" router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') -"""Create Task API""" +# Create Task API""" router.register('create-task', CreateTaskAPIView, basename='create-task') -"""All Task list API""" +# All Task list API""" router.register('all-task-list', AllTaskListAPIView, basename='all-task-list') -"""Task list bases on the status API""" +# Task list bases on the status API""" router.register('task-list', TaskListAPIView, basename='task-list') -"""Leaderboard API""" +# Leaderboard API""" router.register('top-junior', TopJuniorListAPIView, basename='top-junior') -"""Search Task list on the bases of status, due date, and task title API""" +# Search Task list on the bases of status, due date, and task title API""" router.register('filter-task', SearchTaskListAPIView, basename='filter-task') -"""Approve junior API""" +# Approve junior API""" router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior') -"""Define Url pattern""" +# Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/guardian/utils.py b/guardian/utils.py index f565e4b..c3f9f39 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -6,6 +6,14 @@ from django.conf import settings """Import tempfile""" import tempfile +# Define upload image on +# ali baba cloud +# firstly save image +# in temporary file +# then check bucket name +# then upload on ali baba +# bucket and reform the image url""" + def upload_image_to_alibaba(image, filename): """upload image on oss alibaba bucket""" # Save the image object to a temporary file diff --git a/junior/models.py b/junior/models.py index 381bbf4..41dddc8 100644 --- a/junior/models.py +++ b/junior/models.py @@ -10,45 +10,66 @@ from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP """Define User model""" User = get_user_model() # Create your models here. +# Define junior model with +# various fields like +# phone, country code, +# country name, +# gender, +# date of birth, +# profile image, +# relationship type of the guardian +# signup method, +# guardian code, +# junior code, +# referral code, +# referral code that used by the junior +# is invited junior +# profile is active or not +# profile is complete or not +# passcode +# junior is verified or not +"""Define junior points model""" +# points of the junior +# position of the junior class Junior(models.Model): """Junior model""" auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') - """Contact details""" + # Contact details""" phone = models.CharField(max_length=31, null=True, blank=True, default=None) country_code = models.IntegerField(blank=True, null=True) - """country name of the guardian""" + # country name of the guardian""" country_name = models.CharField(max_length=100, null=True, blank=True, default=None) - """Personal info""" + # Personal info""" gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None) - """Date of birth""" + # Date of birth""" dob = models.DateField(max_length=15, null=True, blank=True, default=None) - """Image of the junior""" + # Image of the junior""" image = models.URLField(null=True, blank=True, default=None) - """relationship""" + # relationship""" relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, default='1') - """Sign up method""" + # Sign up method""" signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') - """Codes""" + # Codes""" junior_code = models.CharField(max_length=10, null=True, blank=True, default=None) - """Guardian Codes""" + # Guardian Codes""" guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True) - """Referral code""" + # Referral code""" referral_code = models.CharField(max_length=10, null=True, blank=True, default=None) - """Referral code that is used by junior while signup""" + # Referral code that is used by junior while signup""" referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None) - """invited junior""" + # invited junior""" is_invited = models.BooleanField(default=False) - """Profile activity""" + # Profile activity""" is_active = models.BooleanField(default=True) - """junior profile is complete or not""" + # junior profile is complete or not""" is_complete_profile = models.BooleanField(default=False) - """passcode of the junior profile""" + # passcode of the junior profile""" passcode = models.IntegerField(null=True, blank=True, default=None) - """junior is verified or not""" + # junior is verified or not""" is_verified = models.BooleanField(default=False) - """Profile created and updated time""" + # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -56,7 +77,7 @@ class Junior(models.Model): """ Meta class """ db_table = 'junior' verbose_name = 'Junior' - """another name of the model""" + # another name of the model""" verbose_name_plural = 'Junior' def __str__(self): @@ -66,11 +87,11 @@ class Junior(models.Model): class JuniorPoints(models.Model): """Junior model""" junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points') - """Contact details""" + # Contact details""" total_task_points = models.IntegerField(blank=True, null=True, default=0) - """position of the junior""" + # position of the junior""" position = models.IntegerField(blank=True, null=True, default=99999) - """Profile created and updated time""" + # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -78,7 +99,7 @@ class JuniorPoints(models.Model): """ Meta class """ db_table = 'junior_task_points' verbose_name = 'Junior Task Points' - """another name of the model""" + # another name of the model""" verbose_name_plural = 'Junior Task Points' def __str__(self): diff --git a/junior/urls.py b/junior/urls.py index 162f693..ecedb1a 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -8,21 +8,29 @@ from rest_framework import routers """Router""" router = routers.SimpleRouter() - +# API End points with router +# in this file +# we define various api end point +# that is covered in this guardian +# section API:- like +# Create junior profile API, validate junior profile, +# junior list, +# add junior list, invited junior, +# filter-junior, +# remove junior""" """API End points with router""" -"""Create junior profile API""" router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') -"""validate guardian code API""" +# validate guardian code API""" router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') -"""junior list API""" +# junior list API""" router.register('junior-list', JuniorListAPIView, basename='junior-list') -"""Add junior list API""" +# Add junior list API""" router.register('add-junior', AddJuniorAPIView, basename='add-junior') -"""Invited junior list API""" +# Invited junior list API""" router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior') -"""Filter junior list API""" +# Filter junior list API""" router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior') -"""Define url pattern""" +# Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()) diff --git a/junior/utils.py b/junior/utils.py index 41c393d..e1fb5df 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -4,14 +4,20 @@ from django.conf import settings """Third party Django app""" from templated_email import send_templated_mail - - +# junior notification +# email for sending email +# when guardian create junior profile +# guardian get email when junior send +# request for approving the profile and +# being part of the zod bank and access the platform +# define junior notification email +# junior approval email def junior_notification_email(recipient_email, full_name, email, password): """Notification email""" from_email = settings.EMAIL_FROM_ADDRESS - """recipient email""" + # recipient email""" recipient_list = [recipient_email] - """use send template mail for sending email""" + # use send template mail for sending email""" send_templated_mail( template_name='junior_notification_email.email', from_email=from_email, @@ -29,7 +35,7 @@ def junior_approval_mail(guardian, full_name): """junior approval mail""" from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [guardian] - """use send tempolate mail for sending email""" + # use send template mail for sending email""" send_templated_mail( template_name='junior_approval_mail.email', from_email=from_email, From 1399b585e860da6dea3f4688b160c0abae7014d8 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 16:07:06 +0530 Subject: [PATCH 093/372] sonar issues --- account/serializers.py | 22 +++++++----- account/utils.py | 5 +++ base/constants.py | 40 +++++----------------- base/messages.py | 9 +++-- guardian/models.py | 27 +++++++++++++++ guardian/serializers.py | 57 ++++++++++++++++++++++++++++--- guardian/urls.py | 4 ++- guardian/views.py | 75 +++++++++++++++++++++++++++++++++++++---- junior/views.py | 36 ++++++++++++++++++-- zod_bank/settings.py | 44 ++++++++++++++++-------- 10 files changed, 249 insertions(+), 70 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 4583b6a..3596c07 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -1,9 +1,10 @@ """Account serializer""" """Django Import""" -import random +# Import Refresh token of jwt from rest_framework import serializers from django.contrib.auth.models import User from rest_framework_simplejwt.tokens import RefreshToken +import secrets """App import""" # Import guardian's model, # Import junior's model, @@ -18,8 +19,8 @@ from rest_framework_simplejwt.tokens import RefreshToken from guardian.models import Guardian from junior.models import Junior from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp -from base.constants import GUARDIAN, JUNIOR, SUPERUSER -from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR +from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER +from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR from .utils import delete_user_account_condition_social, delete_user_account_condition # In this serializer file @@ -90,6 +91,7 @@ class ResetPasswordSerializer(serializers.Serializer): def create(self, validated_data): verification_code = validated_data.pop('verification_code') password = validated_data.pop('password') + # fetch email otp object of the user user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last() if user_opt_details: user_details = User.objects.filter(email=user_opt_details.email).last() @@ -112,6 +114,7 @@ class ChangePasswordSerializer(serializers.Serializer): def validate_current_password(self, value): user = self.context + # check old password if self.context.password not in ('', None) and user.check_password(value): return value raise serializers.ValidationError(ERROR_CODE['2015']) @@ -170,9 +173,9 @@ class GuardianSerializer(serializers.ModelSerializer): def get_user_type(self, obj): """user type""" email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last() - if email_verified and email_verified.user_type != None: + if email_verified and email_verified.user_type is not None: return email_verified.user_type - return '2' + return str(NUMBER['two']) def get_auth(self, obj): """user email address""" @@ -217,7 +220,7 @@ class JuniorSerializer(serializers.ModelSerializer): email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last() if email_verified and email_verified.user_type is not None: return email_verified.user_type - return '1' + return str(NUMBER['one']) def get_auth(self, obj): return obj.auth.username @@ -251,6 +254,7 @@ class DefaultTaskImagesSerializer(serializers.ModelSerializer): model = DefaultTaskImages fields = ['id', 'task_name', 'image_url'] def create(self, validated_data): + # create default task object data = DefaultTaskImages.objects.create(**validated_data) return data @@ -273,10 +277,11 @@ class UserDeleteSerializer(serializers.ModelSerializer): data = validated_data.get('reason') passwd = self.context['password'] signup_method = self.context['signup_method'] - random_num = random.randint(0, 10000) + random_num = secrets.randbelow(10001) user_tb = User.objects.filter(id=user.id).last() user_type_datas = UserEmailOtp.objects.filter(email=user.email).last() - if user_tb and user_tb.check_password(passwd) and signup_method == '1': + # check password and sign up method + if user_tb and user_tb.check_password(passwd) and signup_method == str(NUMBER['one']): user_type_data = user_type_datas.user_type instance = delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num) return instance @@ -305,6 +310,7 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): def create(self, validated_data): instance = UserNotification.objects.filter(user=self.context).last() if instance: + # change notification status instance.push_notification = validated_data.get('push_notification',instance.push_notification) instance.email_notification = validated_data.get('email_notification', instance.email_notification) instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) diff --git a/account/utils.py b/account/utils.py index eeb6954..9aca671 100644 --- a/account/utils.py +++ b/account/utils.py @@ -10,6 +10,7 @@ import string from datetime import datetime from calendar import timegm from uuid import uuid4 +# Import secrets module for generating random number import secrets from rest_framework import serializers # Django App Import @@ -55,6 +56,7 @@ def delete_user_account_condition(user, user_type_data, user_type, user_tb, data user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() d_email = user_tb.email o_mail = user.email + # update user email with dummy email user_tb.save() """create object in user delete model""" instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail, @@ -74,6 +76,7 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower() dummy_email = user_tb.email old_mail = user.email + # update user email with dummy email user_tb.save() """create object in user delete model""" instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail, @@ -84,6 +87,7 @@ def junior_account_update(user_tb): """junior account delete""" junior_data = Junior.objects.filter(auth__email=user_tb.email).first() if junior_data: + # Update junior account junior_data.is_active = False junior_data.is_verified = False junior_data.guardian_code = '{}' @@ -93,6 +97,7 @@ def guardian_account_update(user_tb): """update guardian account after delete the user account""" guardian_data = Guardian.objects.filter(user__email=user_tb.email).first() if guardian_data: + # Update guardian account guardian_data.is_active = False guardian_data.is_verified = False guardian_data.save() diff --git a/base/constants.py b/base/constants.py index 648bdd8..d8ca8a8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -21,31 +21,10 @@ GRD = 'GRD' NUMBER = { 'point_zero': 0.0, 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15, - 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, - 'twenty_four': 24, - 'twenty_one': 21, - 'twenty_two': 22, - 'twenty_five': 25, - 'thirty': 30, - 'thirty_five': 35, - 'thirty_six': 36, - 'forty': 40, - 'fifty': 50, - 'fifty_nine': 59, - 'sixty': 60, - 'seventy_five': 75, - 'eighty': 80, - 'ninty_five': 95, - 'ninty_six': 96, - 'ninety_nine': 99, - 'hundred': 100, - 'one_one_nine': 119, - 'one_twenty': 120, - 'four_zero_four': 404, - 'five_hundred': 500, - 'minus_one': -1, - 'point_three': 0.3, - 'point_seven': 0.7 + 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20, + 'twenty_one': 21, 'twenty_two': 22,'twenty_three': 23, 'twenty_four': 24, 'twenty_five': 25, + 'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninty': 90, + 'ninety_nine': 99, 'hundred': 100, } @@ -65,10 +44,6 @@ FILE_SIZE = 5 * 1024 * 1024 # String constant for configurable date for allocation lock period ALLOCATION_LOCK_DATE = 1 -sort_dict = { - '1': 'name', - '2': '-name' -} """user type""" USER_TYPE = ( ('1', 'junior'), @@ -80,7 +55,7 @@ GENDERS = ( ('1', 'Male'), ('2', 'Female') ) -"""Task status""" +# Task status""" TASK_STATUS = ( ('1', 'pending'), ('2', 'in-progress'), @@ -88,13 +63,13 @@ TASK_STATUS = ( ('4', 'requested'), ('5', 'completed') ) -"""sign up method""" +# sign up method SIGNUP_METHODS = ( ('1', 'manual'), ('2', 'google'), ('3', 'apple') ) -"""relationship""" +# relationship RELATIONSHIP = ( ('1', 'parent'), ('2', 'legal_guardian') @@ -110,6 +85,7 @@ COMPLETED = 5 TASK_POINTS = 5 # duplicate name used defined in constant PROJECT_NAME PROJECT_NAME = 'Zod Bank' +# define user type constant GUARDIAN = 'guardian' JUNIOR = 'junior' SUPERUSER = 'superuser' diff --git a/base/messages.py b/base/messages.py index e3399ed..8b65d2a 100644 --- a/base/messages.py +++ b/base/messages.py @@ -62,7 +62,8 @@ ERROR_CODE = { "2035": "Image should not be 0 kb", "2036": "Choose valid user", # log in multiple device msg - "2037": "You are already log in another device" + "2037": "You are already log in another device", + "2038": "Choose valid action for task" } """Success message code""" SUCCESS_CODE = { @@ -98,8 +99,10 @@ SUCCESS_CODE = { "3020": "Logged out successfully.", "3021": "Add junior successfully", "3022": "Remove junior successfully", - "3023": "Approved junior successfully", - "3024": "Reject junior request successfully" + "3023": "Junior is approved successfully", + "3024": "Junior request is rejected successfully", + "3025": "Task is approved successfully", + "3026": "Task is rejected successfully", } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/models.py b/guardian/models.py index 5636a80..1746c44 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -8,6 +8,33 @@ from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_ME from junior.models import Junior """Add user model""" User = get_user_model() + +# Create your models here. +# Define junior model with +# various fields like +# phone, country code, +# country name, +# gender, +# date of birth, +# profile image, +# signup method, +# guardian code, +# referral code, +# referral code that used by the guardian +# is invited guardian +# profile is active or not +# profile is complete or not +# passcode +# guardian is verified or not +"""Define junior task model""" +# define name of the Task +# task description +# points of the task +# default image of the task +# image uploaded by junior +# task status +# task approved or not + # Create your models here. class Guardian(models.Model): diff --git a/guardian/serializers.py b/guardian/serializers.py index 055d738..8bcd0cf 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -1,20 +1,41 @@ """Serializer of Guardian""" """Third party Django app""" import logging -import random from rest_framework import serializers +# Import Refresh token of jwt from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User """Import Django app""" +# Import guardian's model, +# Import junior's model, +# Import account's model, +# Import constant from +# base package, +# Import messages from +# base package, +# Import some functions +# from utils file""" from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp, UserNotification from account.utils import generate_code -from account.serializers import JuniorSerializer from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, JUN, ZOD, GRD from junior.models import Junior, JuniorPoints + + +# In this serializer file +# define user serializer, +# create guardian serializer, +# task serializer, +# guardian serializer, +# task details serializer, +# top junior serializer, +# guardian profile serializer, +# approve junior serializer, +# approve task serializer, + class UserSerializer(serializers.ModelSerializer): """User serializer""" auth_token = serializers.SerializerMethodField('get_auth_token') @@ -38,10 +59,12 @@ class UserSerializer(serializers.ModelSerializer): """Create user profile""" user = User.objects.create_user(username=email, email=email, password=password) UserNotification.objects.create(user=user) - if user_type == '1': + if user_type == str(NUMBER['one']): + # create junior profile Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), referral_code=generate_code(ZOD, user.id)) - if user_type == '2': + if user_type == str(NUMBER['two']): + # create guardian profile Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id), referral_code=generate_code(ZOD, user.id)) return user @@ -56,6 +79,7 @@ class UserSerializer(serializers.ModelSerializer): "code": 400, "status":"failed", }) +# update guardian profile class CreateGuardianSerializer(serializers.ModelSerializer): """Create guardian serializer""" """Basic info""" @@ -65,9 +89,12 @@ class CreateGuardianSerializer(serializers.ModelSerializer): """Contact details""" phone = serializers.CharField(max_length=20, required=False) country_code = serializers.IntegerField(required=False) + # basic info family_name = serializers.CharField(max_length=100, required=False) dob = serializers.DateField(required=False) + # code info referral_code = serializers.CharField(max_length=100, required=False) + # image info image = serializers.URLField(required=False) class Meta(object): @@ -140,6 +167,7 @@ class TaskSerializer(serializers.ModelSerializer): def create(self, validated_data): """create default task image data""" validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() + # update image of the task images = self.context['image'] validated_data['default_image'] = images instance = JuniorTask.objects.create(**validated_data) @@ -227,6 +255,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer): """total filled fields count""" total_field_list = [obj.user.first_name, obj.country_name, obj.gender, obj.dob, obj.image] + # count total complete field total_complete_field = [data for data in total_field_list if data != '' and data is not None] return len(total_complete_field) @@ -256,4 +285,24 @@ class ApproveJuniorSerializer(serializers.ModelSerializer): instance.save() return instance +class ApproveTaskSerializer(serializers.ModelSerializer): + """approve task serializer""" + class Meta(object): + """Meta info""" + model = JuniorTask + fields = ['id', 'task_status', 'is_approved'] + + def create(self, validated_data): + """update task status """ + instance = self.context['task_instance'] + if self.context['action'] == str(NUMBER['one']): + # approve the task + instance.task_status = str(NUMBER['five']) + instance.is_approved = True + else: + # reject the task + instance.task_status = str(NUMBER['three']) + instance.is_approved = False + instance.save() + return instance diff --git a/guardian/urls.py b/guardian/urls.py index 71d3036..b14aa77 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -2,7 +2,7 @@ """Django import""" from django.urls import path, include from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, - SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView) + SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView) """Third party import""" from rest_framework import routers @@ -34,6 +34,8 @@ router.register('top-junior', TopJuniorListAPIView, basename='top-junior') router.register('filter-task', SearchTaskListAPIView, basename='filter-task') # Approve junior API""" router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior') +# Approve junior API""" +router.register('approve-task', ApproveTaskAPIView, basename='approve-task') # Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/views.py b/guardian/views.py index f35ca90..f53dc7d 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -5,14 +5,24 @@ from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from django.utils import timezone -from PIL import Image -from datetime import datetime, timedelta """Import Django app""" + +# Import guardian's model, +# Import junior's model, +# Import account's model, +# Import constant from +# base package, +# Import messages from +# base package, +# Import some functions +# from utils file +# Import account's serializer +# Import account's task + from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, - TopJuniorSerializer, ApproveJuniorSerializer) + TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) from .models import Guardian, JuniorTask from junior.models import Junior, JuniorPoints -from junior.serializers import JuniorDetailSerializer from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp from account.utils import send_otp_email @@ -20,7 +30,17 @@ from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from .utils import upload_image_to_alibaba -from django.db.models import Sum + +""" Define APIs """ +# Define Signup API, +# update guardian profile, +# list of all task +# list of task according to the status of the task +# create task API +# search task by name of the task API +# top junior API, +# approve junior API +# approve task API # Create your views here. class SignupViewset(viewsets.ModelViewSet): """Signup view set""" @@ -35,6 +55,7 @@ class SignupViewset(viewsets.ModelViewSet): """Generate otp""" otp = generate_otp() expiry = timezone.now() + timezone.timedelta(days=1) + # create user email otp object UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" @@ -60,6 +81,7 @@ class UpdateGuardianProfile(viewsets.ViewSet): if image and image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" + # upload image on ali baba image_url = upload_image_to_alibaba(image, filename) data = {"image":image_url} serializer = CreateGuardianSerializer(context={"user":request.user, @@ -83,6 +105,7 @@ class AllTaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" queryset = JuniorTask.objects.filter(guardian__user=request.user) + # use TaskDetailsSerializer serializer serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -102,7 +125,9 @@ class TaskListAPIView(viewsets.ModelViewSet): queryset = JuniorTask.objects.filter(guardian__user=request.user, task_status=status_value).order_by('due_date','created_at') paginator = self.pagination_class() + # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -123,8 +148,10 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image_url = upload_image_to_alibaba(image, filename) image_data = image_url data.pop('default_image') + # use TaskSerializer serializer serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) @@ -139,6 +166,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): """Get the queryset for the view""" title = self.request.GET.get('title') + # fetch junior query junior_queryset = JuniorTask.objects.filter(guardian__user=self.request.user, task_name__icontains=title)\ .order_by('due_date', 'created_at') return junior_queryset @@ -149,7 +177,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) - + # use TaskSerializer serializer serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -186,18 +214,53 @@ class ApproveJuniorAPIView(viewsets.ViewSet): def get_queryset(self): """Get the queryset for the view""" guardian = Guardian.objects.filter(user__email=self.request.user).last() + # fetch junior query junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last() return guardian, junior_queryset def create(self, request, *args, **kwargs): """ junior list""" queryset = self.get_queryset() + # action 1 is use for approve and 2 for reject if request.data['action'] == '1': + # use ApproveJuniorSerializer serializer serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code, "junior": queryset[1], "action": request.data['action']}, data=request.data) if serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) + +class ApproveTaskAPIView(viewsets.ViewSet): + """approve junior by guardian""" + serializer_class = ApproveTaskSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + """Get the queryset for the view""" + guardian = Guardian.objects.filter(user__email=self.request.user).last() + task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'), + guardian=guardian, + junior=self.request.data.get('junior_id')).last() + return guardian, task_queryset + + def create(self, request, *args, **kwargs): + """ junior list""" + # action 1 is use for approve and 2 for reject + queryset = self.get_queryset() + # use ApproveJuniorSerializer serializer + serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code, + "task_instance": queryset[1], + "action": str(request.data['action'])}, + data=request.data) + if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): + 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(): + serializer.save() + return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) + else: + return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/views.py b/junior/views.py index 5766c82..d77727b 100644 --- a/junior/views.py +++ b/junior/views.py @@ -2,9 +2,19 @@ from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination -from rest_framework.response import Response -from PIL import Image """Django app import""" + +# Import guardian's model, +# Import junior's model, +# Import account's model, +# Import constant from +# base package, +# Import messages from +# base package, +# Import some functions +# from utils file +# Import account's serializer +# Import account's task from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer) @@ -13,6 +23,17 @@ from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from account.utils import custom_response, custom_error_response from guardian.utils import upload_image_to_alibaba + +""" Define APIs """ +# Define validate guardian code API, +# update junior profile, +# list of all assosicated junior +# Add junior API +# invite junior API +# search junior API +# remove junior API, +# approve junior API + # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" @@ -29,6 +50,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) filename = f"images/{image.name}" + # upload image on ali baba image_url = upload_image_to_alibaba(image, filename) request_data = {"image": image_url} serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url, @@ -51,6 +73,7 @@ class ValidateGuardianCode(viewsets.ViewSet): """check guardian code""" guardian_code = self.request.GET.get('guardian_code').split(',') for code in guardian_code: + # fetch guardian object guardian_data = Guardian.objects.filter(guardian_code=code).exists() if guardian_data: return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) @@ -68,6 +91,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): """ junior list""" guardian_data = Guardian.objects.filter(user__email=request.user).last() queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -80,8 +104,10 @@ class AddJuniorAPIView(viewsets.ModelViewSet): """ junior list""" info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name']} + # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info) if serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) @@ -105,6 +131,7 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): queryset = self.get_queryset() paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) + # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -128,7 +155,9 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): """Create guardian profile""" queryset = self.get_queryset() paginator = self.pagination_class() + # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) + # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -142,11 +171,14 @@ class RemoveJuniorAPIView(views.APIView): def put(self, request, format=None): junior_id = self.request.GET.get('id') guardian = Guardian.objects.filter(user__email=self.request.user).last() + # fetch junior query junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code), is_invited=True).last() if junior_queryset: + # use RemoveJuniorSerializer serializer serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) if serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index b052a4f..84191b0 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -9,13 +9,14 @@ https://docs.djangoproject.com/en/3.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.0/ref/settings/ """ - +# Django Import import os from dotenv import load_dotenv from datetime import timedelta load_dotenv() # OR, the same with increased verbosity: load_dotenv(verbose=True) +# env path env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env') load_dotenv(dotenv_path=env_path) @@ -34,32 +35,36 @@ SECRET_KEY = '-pb+8w#)6qsh+w&tr+q$tholf7=54v%05e^9!lneiqqgtddg6q' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True +# allow all host ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ + # Add your installed Django apps here 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + # Add Django rest frame work apps here 'django_extensions', 'storages', 'drf_yasg', 'corsheaders', 'django.contrib.postgres', 'rest_framework', + # Add your custom apps here. 'django_ses', 'account', 'junior', - 'guardian', - # 'social_django' + 'guardian' ] - +# define middle ware here MIDDLEWARE = [ + # Add your middleware classes here. 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -70,8 +75,10 @@ MIDDLEWARE = [ 'account.custom_middleware.CustomMiddleware' ] +# define root ROOT_URLCONF = 'zod_bank.urls' +# define templates TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -88,7 +95,9 @@ TEMPLATES = [ }, ] +# define wsgi WSGI_APPLICATION = 'zod_bank.wsgi.application' +# define rest frame work REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework.authentication.SessionAuthentication', @@ -97,6 +106,7 @@ REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 5, } +# define jwt token SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), @@ -113,7 +123,7 @@ DATABASES = { 'PORT':os.getenv('DB_PORT'), } } - +# define swagger setting SWAGGER_SETTINGS = { "exclude_namespaces": [], "api_version": '0.1', @@ -140,6 +150,7 @@ SWAGGER_SETTINGS = { # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators +# password validation AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -159,20 +170,23 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ +# language code LANGUAGE_CODE = 'en-us' - +# time zone TIME_ZONE = 'UTC' - +# define I18N USE_I18N = True - +# define L10N USE_L10N = True - +# define TZ USE_TZ = True # cors header settings SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +# cors allow setting CORS_ORIGIN_ALLOW_ALL = True +# cors allow method CORS_ALLOW_METHODS = ( 'DELETE', 'GET', @@ -181,7 +195,7 @@ CORS_ALLOW_METHODS = ( 'POST', 'PUT', ) - +# cors allow header CORS_ALLOW_HEADERS = ( 'accept', 'accept-encoding', @@ -197,11 +211,12 @@ CORS_ALLOW_HEADERS = ( """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" - +# google client id GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') +# google client secret key GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') - +# email settings EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" EMAIL_HOST="smtp.sendgrid.net" EMAIL_PORT="587" @@ -211,14 +226,15 @@ EMAIL_HOST_PASSWORD="SG.HAMnFRvaSMWeVLatqr4seg.Y9fQb-ckK9gyXLoMKdUE8eCh5lrel36Tm EMAIL_FROM_ADDRESS="support@zodbank.com" DEFAULT_ADDRESS="zodbank@yopmail.com" - +# ali baba cloud settings ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') - +# define static url STATIC_URL = 'static/' +# define static root STATIC_ROOT = 'static' From 6d6d21137fec028c6656e8fa1a3a37a3a49ef2b4 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 17:00:33 +0530 Subject: [PATCH 094/372] sonar issues --- account/serializers.py | 17 +++++++++++++++-- account/views.py | 2 +- base/messages.py | 2 ++ guardian/tasks.py | 5 +++-- guardian/views.py | 4 ++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 3596c07..11944e6 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -140,15 +140,28 @@ class ForgotPasswordSerializer(serializers.Serializer): class SuperUserSerializer(serializers.ModelSerializer): """Super admin serializer""" user_type = serializers.SerializerMethodField('get_user_type') + auth_token = serializers.SerializerMethodField('get_auth_token') + refresh_token = serializers.SerializerMethodField('get_refresh_token') + + def get_auth_token(self, obj): + refresh = RefreshToken.for_user(obj.auth) + access_token = str(refresh.access_token) + return access_token + + def get_refresh_token(self, obj): + refresh = RefreshToken.for_user(obj.user) + refresh_token = str(refresh) + return refresh_token def get_user_type(self, obj): """user type""" - return SUPERUSER + return str(NUMBER['three']) class Meta(object): """Meta info""" model = User - fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type'] + fields = ['id', 'auth_token', 'refresh_token', 'username', 'email', 'first_name', + 'last_name', 'is_active', 'user_type'] class GuardianSerializer(serializers.ModelSerializer): diff --git a/account/views.py b/account/views.py index b6fc821..c11e955 100644 --- a/account/views.py +++ b/account/views.py @@ -214,7 +214,7 @@ class ForgotPasswordAPIView(views.APIView): User.objects.get(email=email) except User.DoesNotExist: return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) - verification_code = ''.join([str(random.randrange(9)) for _ in range(6)]) + verification_code = generate_otp() # Send the verification code to the user's email from_email = settings.EMAIL_FROM_ADDRESS recipient_list = [email] diff --git a/base/messages.py b/base/messages.py index 8b65d2a..82d471a 100644 --- a/base/messages.py +++ b/base/messages.py @@ -47,6 +47,7 @@ ERROR_CODE = { "2021": "Already register", "2022": "Invalid Guardian code", "2023": "Invalid user", + # email not verified "2024": "Email not verified", "2025": "Invalid input. Expected a list of strings.", "2026": "New password should not same as old password", @@ -54,6 +55,7 @@ ERROR_CODE = { "2028": "You are not authorized person to sign up on this platform", "2029": "Validity of otp verification is expired", "2030": "Use correct user type and token", + # invalid password "2031": "Invalid password", "2032": "Failed to send email", "2033": "Missing required fields", diff --git a/guardian/tasks.py b/guardian/tasks.py index 7a5dd90..9cf39b3 100644 --- a/guardian/tasks.py +++ b/guardian/tasks.py @@ -1,6 +1,7 @@ """task files""" """Django import""" -import random +import secrets def generate_otp(): """generate random otp""" - return ''.join([str(random.randrange(9)) for _ in range(6)]) + digits = "0123456789" + return "".join(secrets.choice(digits) for _ in range(6)) diff --git a/guardian/views.py b/guardian/views.py index f53dc7d..1d1808e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -176,6 +176,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): queryset = self.get_queryset() paginator = self.pagination_class() + # use pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskSerializer serializer serializer = TaskDetailsSerializer(paginated_queryset, many=True) @@ -242,6 +243,7 @@ class ApproveTaskAPIView(viewsets.ViewSet): 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() @@ -257,9 +259,11 @@ class ApproveTaskAPIView(viewsets.ViewSet): "action": str(request.data['action'])}, data=request.data) if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK) elif str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid(): + # save serializer serializer.save() return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) else: From 4779749b0c85c111e54e44eff1aaba1e749554a3 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 17:05:45 +0530 Subject: [PATCH 095/372] success msg --- account/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index c11e955..015d505 100644 --- a/account/views.py +++ b/account/views.py @@ -321,7 +321,7 @@ class UserLogin(viewsets.ViewSet): return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, response_status=status.HTTP_200_OK) data.update({"is_email_verified": is_verified}) - return custom_response(None, data, response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3003'], data, response_status=status.HTTP_200_OK) @action(methods=['post'], detail=False) def admin_login(self, request): From 8f1f49de4551fe040a4263089f08e24ebc6dc7be Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 18:00:19 +0530 Subject: [PATCH 096/372] jira-21 approve task API --- account/views.py | 2 +- guardian/serializers.py | 7 +++++++ guardian/views.py | 3 ++- manage.py | 8 ++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/account/views.py b/account/views.py index 015d505..7bc9ab5 100644 --- a/account/views.py +++ b/account/views.py @@ -38,7 +38,7 @@ from account.utils import (send_otp_email, send_support_email, custom_response, from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer -class GoogleLoginMixin: +class GoogleLoginMixin(object): """google login mixin""" @staticmethod def google_login(self, request): diff --git a/guardian/serializers.py b/guardian/serializers.py index 8bcd0cf..ae96969 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -295,14 +295,21 @@ class ApproveTaskSerializer(serializers.ModelSerializer): def create(self, validated_data): """update task status """ instance = self.context['task_instance'] + junior = self.context['junior'] + junior_details = Junior.objects.filter(id=junior).last() + junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details) if self.context['action'] == str(NUMBER['one']): # approve the task instance.task_status = str(NUMBER['five']) instance.is_approved = True + junior_data.total_task_points = junior_data.total_task_points + instance.points + junior_data.save() else: # reject the task instance.task_status = str(NUMBER['three']) instance.is_approved = False + junior_data.total_task_points = junior_data.total_task_points - instance.points + junior_data.save() instance.save() return instance diff --git a/guardian/views.py b/guardian/views.py index 1d1808e..3450be7 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -256,7 +256,8 @@ class ApproveTaskAPIView(viewsets.ViewSet): # use ApproveJuniorSerializer serializer serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code, "task_instance": queryset[1], - "action": str(request.data['action'])}, + "action": str(request.data['action']), + "junior": self.request.data['junior_id']}, data=request.data) if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): # save serializer diff --git a/manage.py b/manage.py index a19d33b..81f9c88 100755 --- a/manage.py +++ b/manage.py @@ -1,11 +1,15 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" """Django import""" -"""Import OS module""" +# Import OS module import os -"""Import sys module""" +# Import sys module""" import sys +# define all function +# execute command line +# Import execute from command line +# fetch django settings def main(): """Main function""" From ae7250b34ac01104db439f71f2d6f4bba32624f1 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 17 Jul 2023 18:17:00 +0530 Subject: [PATCH 097/372] changes in env --- zod_bank/settings.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 84191b0..c8b85a6 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -30,11 +30,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '-pb+8w#)6qsh+w&tr+q$tholf7=54v%05e^9!lneiqqgtddg6q' - +SECRET_KEY = os.getenv('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - +DEBUG = os.getenv('DEBUG') # allow all host ALLOWED_HOSTS = ['*'] From 751af642b8d251eb7249099498476a611a650f4e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 17 Jul 2023 18:58:09 +0530 Subject: [PATCH 098/372] added admin permission --- base/messages.py | 12 ++++- web_admin/admin.py | 5 ++ web_admin/apps.py | 7 +++ web_admin/models.py | 12 +++++ web_admin/permission.py | 26 +++++++++ web_admin/serializers.py | 45 +++++++++++++++- web_admin/tests.py | 3 ++ web_admin/urls.py | 2 + web_admin/views.py | 112 ++++++++++++++++++++++++++++++++++----- zod_bank/settings.py | 4 ++ 10 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 web_admin/permission.py diff --git a/base/messages.py b/base/messages.py index 82d471a..a13c1d6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -65,7 +65,12 @@ ERROR_CODE = { "2036": "Choose valid user", # log in multiple device msg "2037": "You are already log in another device", - "2038": "Choose valid action for task" + "2038": "Choose valid action for task", + "2039": "Add at least one article card or maximum 6", + "2040": "Add at least 5 article survey or maximum 10", + "2041": "Article with given id doesn't exist.", + "2042": "Article Card with given id doesn't exist.", + "2043": "Article Survey with given id doesn't exist." } """Success message code""" SUCCESS_CODE = { @@ -105,6 +110,11 @@ SUCCESS_CODE = { "3024": "Junior request is rejected successfully", "3025": "Task is approved successfully", "3026": "Task is rejected successfully", + "3027": "Article has been created successfully.", + "3028": "Article has been updated successfully.", + "3029": "Article has been deleted successfully.", + "3030": "Article Card has been removed successfully.", + "3031": "Article Survey has been removed successfully.", } """status code error""" STATUS_CODE_ERROR = { diff --git a/web_admin/admin.py b/web_admin/admin.py index 1adf7e6..b1df483 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -1,5 +1,10 @@ +""" +web_admin admin file +""" +# django imports from django.contrib import admin +# local imports from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption diff --git a/web_admin/apps.py b/web_admin/apps.py index 9701a1f..2842e02 100644 --- a/web_admin/apps.py +++ b/web_admin/apps.py @@ -1,6 +1,13 @@ +""" +web_admin app file +""" +# django imports from django.apps import AppConfig class WebAdminConfig(AppConfig): + """ + web admin app config + """ default_auto_field = 'django.db.models.BigAutoField' name = 'web_admin' diff --git a/web_admin/models.py b/web_admin/models.py index 0437102..1e49c6b 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -6,6 +6,9 @@ from django.db import models class Article(models.Model): + """ + Article model + """ title = models.CharField(max_length=255) description = models.TextField() created_at = models.DateTimeField(auto_now_add=True) @@ -19,6 +22,9 @@ class Article(models.Model): class ArticleCard(models.Model): + """ + Article Card model + """ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() @@ -32,6 +38,9 @@ class ArticleCard(models.Model): class ArticleSurvey(models.Model): + """ + Article Survey model + """ article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey') question = models.CharField(max_length=255) points = models.IntegerField() @@ -44,6 +53,9 @@ class ArticleSurvey(models.Model): class SurveyOption(models.Model): + """ + Survey Options model + """ survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options') option = models.CharField(max_length=255) is_answer = models.BooleanField(default=False) diff --git a/web_admin/permission.py b/web_admin/permission.py new file mode 100644 index 0000000..5ecf33a --- /dev/null +++ b/web_admin/permission.py @@ -0,0 +1,26 @@ +""" +web_admin permission classes +""" +# django imports +from rest_framework import permissions + + +class AdminPermission(permissions.BasePermission): + """ + to check for usertype admin only + """ + def has_permission(self, request, view): + """ + Return True if user_type is admin + """ + if request.user.is_superuser: + return True + return False + + def has_object_permission(self, request, view, obj): + """ + check for object level permission + """ + if request.user.is_superuser: + return True + return False diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 23e69e3..57d4278 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -1,33 +1,55 @@ """ web_admin serializers file """ +# django imports from rest_framework import serializers +from django.conf import settings +# local imports +from base.messages import ERROR_CODE from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey from web_admin.utils import pop_id class ArticleCardSerializer(serializers.ModelSerializer): + """ + Article Card serializer + """ id = serializers.IntegerField(required=False) class Meta: + """ + meta class + """ model = ArticleCard fields = ('id', 'title', 'description') class SurveyOptionSerializer(serializers.ModelSerializer): + """ + survey option serializer + """ id = serializers.IntegerField(required=False) class Meta: + """ + meta class + """ model = SurveyOption fields = ('id', 'option', 'is_answer') class ArticleSurveySerializer(serializers.ModelSerializer): + """ + article survey serializer + """ id = serializers.IntegerField(required=False) survey_options = SurveyOptionSerializer(many=True) class Meta: + """ + meta class + """ model = ArticleSurvey fields = ('id', 'question', 'points', 'survey_options') @@ -46,12 +68,27 @@ class ArticleSerializer(serializers.ModelSerializer): model = Article fields = ('id', 'title', 'description', 'article_cards', 'article_survey') + def validate(self, attrs): + """ + to validate request data + :param attrs: + :return: validated attrs + """ + article_cards = attrs.get('article_cards', None) + article_survey = attrs.get('article_survey', None) + if article_cards is None or len(article_cards) > int(settings.MAX_ARTICLE_CARD): + raise serializers.ValidationError({'details': ERROR_CODE['2039']}) + if article_survey is None or len(article_survey) < int(settings.MIN_ARTICLE_SURVEY) or int( + settings.MAX_ARTICLE_SURVEY) < len(article_survey): + raise serializers.ValidationError({'details': ERROR_CODE['2040']}) + return attrs + def create(self, validated_data): """ to create article. ID in post data dict is for update api. :param validated_data: - :return: success message + :return: article object """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') @@ -73,6 +110,12 @@ class ArticleSerializer(serializers.ModelSerializer): return article def update(self, instance, validated_data): + """ + to update article and related table + :param instance: + :param validated_data: + :return: article object + """ article_cards = validated_data.pop('article_cards') article_survey = validated_data.pop('article_survey') instance.title = validated_data.get('title', instance.title) diff --git a/web_admin/tests.py b/web_admin/tests.py index 7ce503c..8800281 100644 --- a/web_admin/tests.py +++ b/web_admin/tests.py @@ -1,3 +1,6 @@ +""" +web_admin test file +""" from django.test import TestCase # Create your tests here. diff --git a/web_admin/urls.py b/web_admin/urls.py index d79fafa..95bc7d9 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -5,8 +5,10 @@ web_admin urls file from django.urls import path, include from rest_framework import routers +# local imports from web_admin.views import ArticleViewSet +# initiate router router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') diff --git a/web_admin/views.py b/web_admin/views.py index 88147a1..8003e2c 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -5,50 +5,138 @@ web_admin views file from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.response import Response from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated # local imports from account.utils import custom_response, custom_error_response -from web_admin.models import Article +from base.messages import SUCCESS_CODE, ERROR_CODE +from web_admin.models import Article, ArticleCard, ArticleSurvey +from web_admin.permission import AdminPermission from web_admin.serializers import ArticleSerializer class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin): + """ + article api + """ serializer_class = ArticleSerializer - permission_classes = [] + permission_classes = [IsAuthenticated, AdminPermission] queryset = Article.objects.prefetch_related('article_cards', 'article_survey', - 'article_survey__survey_options') + 'article_survey__survey_options').order_by('-created_at') + + http_method_names = ['get', 'post', 'put', 'delete'] def create(self, request, *args, **kwargs): + """ + article create api method + :param request: + :param args: + :param kwargs: + :return: success message + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response("created") + return custom_response(SUCCESS_CODE["3027"]) def update(self, request, *args, **kwargs): + """ + article update api method + :param request: + :param args: + :param kwargs: + :return: success message + """ article = self.queryset.filter(id=kwargs['pk']).first() serializer = self.serializer_class(article, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response("updated") + return custom_response(SUCCESS_CODE["3028"]) def list(self, request, *args, **kwargs): - # queryset = Article.objects.prefetch_related('article_cards', - # 'article_survey', - # 'article_survey__survey_options') + """ + article list api method + :param request: + :param args: + :param kwargs: + :return: list of article + """ + queryset = self.queryset.filter(is_deleted=False) paginator = self.pagination_class() - paginated_queryset = paginator.paginate_queryset(self.queryset, request) + paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, data=serializer.data) def retrieve(self, request, *args, **kwargs): - queryset = self.queryset.filter(id=kwargs['pk']) + """ + article detail api method + :param request: + :param args: + :param kwargs: + :return: article detail data + """ + queryset = self.queryset.filter(id=kwargs['pk'], is_deleted=False) serializer = self.serializer_class(queryset, many=True) return custom_response(None, data=serializer.data) def destroy(self, request, *args, **kwargs): + """ + article delete (soft delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True) if article: - return custom_response("deleted") - return custom_error_response("article doesn't exist", status.HTTP_400_BAD_REQUEST) + return custom_response(SUCCESS_CODE["3029"]) + return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) + + @action(methods=['get'], url_name='', url_path='', detail=False) + def search_article(self, request): + """ + article search api method + :param request: + :return: searched article + """ + search = request.GET.get('search') + queryset = self.queryset.filter(title__icontains=search) + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data) + + @action(methods=['get'], url_name='remove_card', url_path='remove_card', + detail=True, serializer_class=None) + def remove_article_card(self, request, *args, **kwargs): + """ + article card remove (delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ + try: + ArticleCard.objects.filter(id=kwargs['pk']).first().delete() + return custom_response(SUCCESS_CODE["3030"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST) + + @action(methods=['get'], url_name='remove_survey', url_path='remove_survey', + detail=True, serializer_class=None) + def remove_article_survey(self, request, *args, **kwargs): + """ + article survey remove (delete) api method + :param request: + :param args: + :param kwargs: + :return: success message + """ + try: + ArticleSurvey.objects.filter(id=kwargs['pk']).first().delete() + return custom_response(SUCCESS_CODE["3031"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index a319bbe..b107ffd 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -233,6 +233,10 @@ ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') +MAX_ARTICLE_CARD = os.getenv('MAX_ARTICLE_CARD', 6) +MIN_ARTICLE_SURVEY = os.getenv('MIN_ARTICLE_SURVEY', 5) +MAX_ARTICLE_SURVEY = os.getenv('MAX_ARTICLE_SURVEY', 10) + # define static url STATIC_URL = 'static/' # define static root From 032186bf43498cf7e6d50357e28afbd2c5a50631 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 11:01:16 +0530 Subject: [PATCH 099/372] task list --- account/serializers.py | 2 +- junior/urls.py | 7 +++++-- junior/views.py | 26 +++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 11944e6..4df4e89 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -225,7 +225,7 @@ class JuniorSerializer(serializers.ModelSerializer): return access_token def get_refresh_token(self, obj): - refresh = RefreshToken.for_user(obj.user) + refresh = RefreshToken.for_user(obj.auth) refresh_token = str(refresh) return refresh_token diff --git a/junior/urls.py b/junior/urls.py index ecedb1a..33d52e7 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -2,7 +2,7 @@ """Django import""" from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, - InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView) + InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView) """Third party import""" from rest_framework import routers @@ -17,7 +17,8 @@ router = routers.SimpleRouter() # junior list, # add junior list, invited junior, # filter-junior, -# remove junior""" +# remove junior, +# junior task list """API End points with router""" router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update') # validate guardian code API""" @@ -30,6 +31,8 @@ router.register('add-junior', AddJuniorAPIView, basename='add-junior') router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior') # Filter junior list API""" router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior') +# junior's task list API""" +router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index d77727b..0e1de75 100644 --- a/junior/views.py +++ b/junior/views.py @@ -18,7 +18,8 @@ from rest_framework.pagination import PageNumberPagination from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer) -from guardian.models import Guardian +from guardian.models import Guardian, JuniorTask +from guardian.serializers import TaskDetailsSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from account.utils import custom_response, custom_error_response @@ -184,3 +185,26 @@ class RemoveJuniorAPIView(views.APIView): return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) else: return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) + + +class JuniorTaskListAPIView(viewsets.ModelViewSet): + """Update guardian profile""" + serializer_class = TaskDetailsSerializer + permission_classes = [IsAuthenticated] + pagination_class = PageNumberPagination + queryset = JuniorTask.objects.all() + + def list(self, request, *args, **kwargs): + """Create guardian profile""" + status_value = self.request.GET.get('status') + if str(status_value) == '0': + queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at') + else: + queryset = JuniorTask.objects.filter(junior__auth=request.user, + task_status=status_value).order_by('due_date','created_at') + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer + serializer = TaskDetailsSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From ba4d5933de97e80afb2a9c69a9a9f3a54f2610c0 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 18 Jul 2023 14:06:04 +0530 Subject: [PATCH 100/372] notification app, api for device registration and fcm token --- base/messages.py | 1 + notifications/__init__.py | 0 notifications/admin.py | 6 ++++++ notifications/apps.py | 13 ++++++++++++ notifications/migrations/__init__.py | 0 notifications/models.py | 6 ++++++ notifications/serializers.py | 28 ++++++++++++++++++++++++++ notifications/tests.py | 6 ++++++ notifications/urls.py | 18 +++++++++++++++++ notifications/utils.py | 17 ++++++++++++++++ notifications/views.py | 30 ++++++++++++++++++++++++++++ requirements.txt | 2 ++ web_admin/urls.py | 2 +- zod_bank/settings.py | 13 ++++++++++++ zod_bank/urls.py | 1 + 15 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 notifications/__init__.py create mode 100644 notifications/admin.py create mode 100644 notifications/apps.py create mode 100644 notifications/migrations/__init__.py create mode 100644 notifications/models.py create mode 100644 notifications/serializers.py create mode 100644 notifications/tests.py create mode 100644 notifications/urls.py create mode 100644 notifications/utils.py create mode 100644 notifications/views.py diff --git a/base/messages.py b/base/messages.py index a13c1d6..950ffe1 100644 --- a/base/messages.py +++ b/base/messages.py @@ -74,6 +74,7 @@ ERROR_CODE = { } """Success message code""" SUCCESS_CODE = { + "3000": "ok", # Success code for password "3001": "Sign up successfully", # Success code for Thank you diff --git a/notifications/__init__.py b/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notifications/admin.py b/notifications/admin.py new file mode 100644 index 0000000..7bd27a7 --- /dev/null +++ b/notifications/admin.py @@ -0,0 +1,6 @@ +""" +notification admin file +""" +from django.contrib import admin + +# Register your models here. diff --git a/notifications/apps.py b/notifications/apps.py new file mode 100644 index 0000000..366a893 --- /dev/null +++ b/notifications/apps.py @@ -0,0 +1,13 @@ +""" +notification app file +""" +# django imports +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + """ + notification app config + """ + default_auto_field = 'django.db.models.BigAutoField' + name = 'notifications' diff --git a/notifications/migrations/__init__.py b/notifications/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notifications/models.py b/notifications/models.py new file mode 100644 index 0000000..90a6f02 --- /dev/null +++ b/notifications/models.py @@ -0,0 +1,6 @@ +""" +notification models file +""" +from django.db import models + +# Create your models here. diff --git a/notifications/serializers.py b/notifications/serializers.py new file mode 100644 index 0000000..053096f --- /dev/null +++ b/notifications/serializers.py @@ -0,0 +1,28 @@ +""" +notification serializer file +""" +# third party imports +from rest_framework import serializers + +# local imports +from notifications.utils import register_fcm_token + + +class RegisterDevice(serializers.Serializer): + """ + used to create and validate register device token + """ + registration_id = serializers.CharField(max_length=250) + device_id = serializers.CharField(max_length=250) + type = serializers.ChoiceField(choices=["ios", "web", "android"]) + + class Meta: + """ meta class """ + fields = ('registration_id', 'type', 'device_id') + + def create(self, validated_data): + """ override this method to create device token for users """ + registration_id = validated_data['registration_id'] + device_type = validated_data['type'] + return register_fcm_token(self.context['user_id'], registration_id, + validated_data['device_id'], device_type) diff --git a/notifications/tests.py b/notifications/tests.py new file mode 100644 index 0000000..fd087b9 --- /dev/null +++ b/notifications/tests.py @@ -0,0 +1,6 @@ +""" +notification test file +""" +from django.test import TestCase + +# Create your tests here. diff --git a/notifications/urls.py b/notifications/urls.py new file mode 100644 index 0000000..b184d02 --- /dev/null +++ b/notifications/urls.py @@ -0,0 +1,18 @@ +""" +notifications urls file +""" +# django imports +from django.urls import path, include +from rest_framework import routers + +# local imports +from notifications.views import NotificationViewSet + +# initiate router +router = routers.SimpleRouter() + +router.register('notifications', NotificationViewSet, basename='notifications') + +urlpatterns = [ + path('api/v1/', include(router.urls)), +] diff --git a/notifications/utils.py b/notifications/utils.py new file mode 100644 index 0000000..b3a0ede --- /dev/null +++ b/notifications/utils.py @@ -0,0 +1,17 @@ +""" +notifications utils file +""" +# third party imports +from fcm_django.models import FCMDevice + +# local imports + + +def register_fcm_token(user_id, registration_id, device_id, device_type): + """ used to register the fcm device token""" + device, _ = FCMDevice.objects.update_or_create(device_id=device_id, + defaults={'user_id': user_id, 'type': device_type, + 'active': True, + 'registration_id': registration_id}) + return device + diff --git a/notifications/views.py b/notifications/views.py new file mode 100644 index 0000000..8ac3786 --- /dev/null +++ b/notifications/views.py @@ -0,0 +1,30 @@ +""" +notifications views file +""" +# django imports +from django.db.models import Q +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +from account.utils import custom_response +from base.messages import SUCCESS_CODE +from notifications.serializers import RegisterDevice + + +class NotificationViewSet(viewsets.GenericViewSet): + """ used to do the notification actions """ + serializer_class = None + permission_classes = [IsAuthenticated, ] + + @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) + def fcm_registration(self, request): + """ + used to save the fcm token + """ + serializer = self.get_serializer_class()(data=request.data, + context={'user_id': request.auth.payload['user_id']}) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3000"]) diff --git a/requirements.txt b/requirements.txt index 5dd7f76..6a141cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -79,3 +79,5 @@ uritemplate==4.1.1 urllib3==1.26.16 vine==5.0.0 wcwidth==0.2.6 + +fcm-django==2.0.0 \ No newline at end of file diff --git a/web_admin/urls.py b/web_admin/urls.py index 95bc7d9..fa272f4 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -11,7 +11,7 @@ from web_admin.views import ArticleViewSet # initiate router router = routers.SimpleRouter() -router.register('article', ArticleViewSet, basename='article') +router.register('articles', ArticleViewSet, basename='articles') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/zod_bank/settings.py b/zod_bank/settings.py index b107ffd..aea5896 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -54,6 +54,7 @@ INSTALLED_APPS = [ 'corsheaders', 'django.contrib.postgres', 'rest_framework', + 'fcm_django', # Add your custom apps here. 'django_ses', 'account', @@ -208,6 +209,18 @@ CORS_ALLOW_HEADERS = ( 'x-requested-with', ) +CORS_EXPOSE_HEADERS = ( + 'Access-Control-Allow-Origin: *', +) + +# fcm django settings +FCM_DJANGO_SETTINGS = { + "APP_VERBOSE_NAME": "ZOD_Bank", + "ONE_DEVICE_PER_USER": False, + "DELETE_INACTIVE_DEVICES": True, + "UPDATE_ON_DUPLICATE_REG_ID": True, +} + """Static files (CSS, JavaScript, Images) https://docs.djangoproject.com/en/3.0/howto/static-files/""" diff --git a/zod_bank/urls.py b/zod_bank/urls.py index 009f1b3..a34ef93 100644 --- a/zod_bank/urls.py +++ b/zod_bank/urls.py @@ -30,5 +30,6 @@ urlpatterns = [ path('', include(('account.urls', 'account'), namespace='account')), path('', include('guardian.urls')), path('', include(('junior.urls', 'junior'), namespace='junior')), + path('', include(('notifications.urls', 'notifications'), namespace='notifications')), path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')), ] From 4c0cac7cb08b8d025c29dd5bacb0975dc5680c15 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 15:19:50 +0530 Subject: [PATCH 101/372] jira-32 list of all task of junior API, changes in Search task API --- account/serializers.py | 2 +- .../junior_notification_email.email | 2 +- base/constants.py | 2 +- ...eted_on_juniortask_rejected_on_and_more.py | 28 +++++++++++++++++++ guardian/models.py | 6 ++++ guardian/serializers.py | 23 ++++++++++++++- guardian/utils.py | 27 ++++++++++++++++++ guardian/views.py | 11 ++++++-- .../migrations/0014_junior_is_password_set.py | 18 ++++++++++++ junior/models.py | 2 ++ junior/serializers.py | 5 ++-- junior/views.py | 11 ++++++-- zod_bank/settings.py | 5 ++-- 13 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py create mode 100644 junior/migrations/0014_junior_is_password_set.py diff --git a/account/serializers.py b/account/serializers.py index 4df4e89..5810f1d 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -248,7 +248,7 @@ class JuniorSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', - 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', + 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited'] class EmailVerificationSerializer(serializers.ModelSerializer): diff --git a/account/templates/templated_email/junior_notification_email.email b/account/templates/templated_email/junior_notification_email.email index ea9c214..9f489ce 100644 --- a/account/templates/templated_email/junior_notification_email.email +++ b/account/templates/templated_email/junior_notification_email.email @@ -15,7 +15,7 @@

- You are receiving this email for join the ZOD bank platform. Please use {{ url }} link to join the platform. + You are receiving this email for joining the ZOD bank platform. Please use {{ url }} link to join the platform.
Your credentials are:- username = {{email}} and password {{password}}

Below are the steps to complete the account and how to use this platform.

diff --git a/base/constants.py b/base/constants.py index d8ca8a8..29da8db 100644 --- a/base/constants.py +++ b/base/constants.py @@ -24,7 +24,7 @@ NUMBER = { 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20, 'twenty_one': 21, 'twenty_two': 22,'twenty_three': 23, 'twenty_four': 24, 'twenty_five': 25, 'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninty': 90, - 'ninety_nine': 99, 'hundred': 100, + 'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600 } diff --git a/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py b/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py new file mode 100644 index 0000000..2223e0e --- /dev/null +++ b/guardian/migrations/0016_juniortask_completed_on_juniortask_rejected_on_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.2 on 2023-07-18 07:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0015_alter_guardian_options_alter_juniortask_options'), + ] + + operations = [ + migrations.AddField( + model_name='juniortask', + name='completed_on', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='juniortask', + name='rejected_on', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='juniortask', + name='requested_on', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 1746c44..b4a31dd 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -108,6 +108,12 @@ class JuniorTask(models.Model): is_active = models.BooleanField(default=True) """Task is approved or not""" is_approved = models.BooleanField(default=False) + """request task on particular date""" + requested_on = models.DateTimeField(auto_now_add=False, null=True, blank=True) + """reject task on particular date""" + rejected_on = models.DateTimeField(auto_now_add=False, null=True, blank=True) + """complete task on particular date""" + completed_on = models.DateTimeField(auto_now_add=False, null=True, blank=True) """Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/guardian/serializers.py b/guardian/serializers.py index ae96969..9459334 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -6,6 +6,7 @@ from rest_framework import serializers from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User +from datetime import datetime, time """Import Django app""" # Import guardian's model, # Import junior's model, @@ -23,6 +24,8 @@ from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, JUN, ZOD, GRD from junior.models import Junior, JuniorPoints +from .utils import real_time, convert_timedelta_into_datetime + # In this serializer file @@ -201,11 +204,27 @@ class TaskDetailsSerializer(serializers.ModelSerializer): """Task detail serializer""" junior = JuniorDetailSerializer() + remaining_time = serializers.SerializerMethodField('get_remaining_time') + + def get_remaining_time(self, obj): + """ remaining time to complete task""" + due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) + # fetch real time + current_datetime = real_time() + # Perform the subtraction + if due_date_datetime > current_datetime: + time_difference = due_date_datetime - current_datetime + time_only = convert_timedelta_into_datetime(time_difference) + return str(time_difference.days) + ' days ' + str(time_only) + return str(NUMBER['zero']) + ' days ' + '00:00:00:00000' + + class Meta(object): """Meta info""" model = JuniorTask fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image', - 'junior', 'task_status', 'is_active', 'created_at','updated_at'] + 'requested_on', 'rejected_on', 'completed_on', + 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at'] class TopJuniorSerializer(serializers.ModelSerializer): @@ -303,12 +322,14 @@ class ApproveTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['five']) instance.is_approved = True junior_data.total_task_points = junior_data.total_task_points + instance.points + instance.completed_on = datetime.today() junior_data.save() else: # reject the task instance.task_status = str(NUMBER['three']) instance.is_approved = False junior_data.total_task_points = junior_data.total_task_points - instance.points + instance.rejected_on = datetime.today() junior_data.save() instance.save() return instance diff --git a/guardian/utils.py b/guardian/utils.py index c3f9f39..8619b66 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -5,6 +5,12 @@ import oss2 from django.conf import settings """Import tempfile""" import tempfile +# Import date time module's function +from datetime import datetime, time +# Import real time client module +import ntplib +# import Number constant +from base.constants import NUMBER # Define upload image on # ali baba cloud @@ -13,6 +19,8 @@ import tempfile # then check bucket name # then upload on ali baba # bucket and reform the image url""" +# fetch real time data without depend on system time +# convert time delta into date time object def upload_image_to_alibaba(image, filename): """upload image on oss alibaba bucket""" @@ -30,3 +38,22 @@ def upload_image_to_alibaba(image, filename): new_filename = filename.replace(' ', '%20') return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" +def real_time(): + # Fetch real time. + # time is not depend on system time + # Get the current datetime + ntp_client = ntplib.NTPClient() + ntp_server = 'pool.ntp.org' + response = ntp_client.request(ntp_server) + current_datetime = datetime.fromtimestamp(response.tx_time) + return current_datetime + +def convert_timedelta_into_datetime(time_difference): + # convert timedelta into datetime format + hours = time_difference.seconds // NUMBER['thirty_six_hundred'] + minutes = (time_difference.seconds // NUMBER['sixty']) % NUMBER['sixty'] + seconds = time_difference.seconds % NUMBER['sixty'] + microseconds = time_difference.microseconds + # Create a new time object with the extracted time components + time_only = time(hours, minutes, seconds, microseconds) + return time_only diff --git a/guardian/views.py b/guardian/views.py index 3450be7..ec14ef7 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -119,9 +119,16 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') - if str(status_value) == '0': + search = self.request.GET.get('search') + if search and str(status_value) == '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user, + task_name__icontains=search).order_by('due_date', 'created_at') + elif search and str(status_value) != '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, + task_status=status_value).order_by('due_date', 'created_at') + if search is None and str(status_value) == '0': queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') - else: + elif search is None and str(status_value) != '0': queryset = JuniorTask.objects.filter(guardian__user=request.user, task_status=status_value).order_by('due_date','created_at') paginator = self.pagination_class() diff --git a/junior/migrations/0014_junior_is_password_set.py b/junior/migrations/0014_junior_is_password_set.py new file mode 100644 index 0000000..a71e6c0 --- /dev/null +++ b/junior/migrations/0014_junior_is_password_set.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-18 09:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0013_alter_junior_options_alter_juniorpoints_options'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='is_password_set', + field=models.BooleanField(default=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 41dddc8..f4ef488 100644 --- a/junior/models.py +++ b/junior/models.py @@ -63,6 +63,8 @@ class Junior(models.Model): is_invited = models.BooleanField(default=False) # Profile activity""" is_active = models.BooleanField(default=True) + # check password is set or not + is_password_set = models.BooleanField(default=True) # junior profile is complete or not""" is_complete_profile = models.BooleanField(default=False) # passcode of the junior profile""" diff --git a/junior/serializers.py b/junior/serializers.py index 013cd58..ef1b5de 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -277,12 +277,13 @@ class AddJuniorSerializer(serializers.ModelSerializer): relationship=validated_data.get('relationship'), junior_code=generate_code(JUN, user_data.id), referral_code=generate_code(ZOD, user_data.id), - referral_code_used=guardian_data.referral_code) + referral_code_used=guardian_data.referral_code, + is_password_set=False, is_verified=True) """Generate otp""" otp_value = generate_otp() expiry_time = timezone.now() + timezone.timedelta(days=1) UserEmailOtp.objects.create(email=email, otp=otp_value, - user_type='1', expired_at=expiry_time) + user_type='1', expired_at=expiry_time, is_verified=True) """Send email to the register user""" send_otp_email(email, otp_value) """Notification email""" diff --git a/junior/views.py b/junior/views.py index 0e1de75..50e42be 100644 --- a/junior/views.py +++ b/junior/views.py @@ -197,9 +197,16 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') - if str(status_value) == '0': + search = self.request.GET.get('search') + if search and str(status_value) == '0': + queryset = JuniorTask.objects.filter(junior__auth=request.user, + task_name__icontains=search).order_by('due_date', 'created_at') + elif search and str(status_value) != '0': + queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search, + task_status=status_value).order_by('due_date', 'created_at') + if search is None and str(status_value) == '0': queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at') - else: + elif search is None and str(status_value) != '0': queryset = JuniorTask.objects.filter(junior__auth=request.user, task_status=status_value).order_by('due_date','created_at') paginator = self.pagination_class() diff --git a/zod_bank/settings.py b/zod_bank/settings.py index b107ffd..9961b80 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -104,11 +104,12 @@ REST_FRAMEWORK = { 'rest_framework.authentication.BasicAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication',], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 5, + 'PAGE_SIZE': 10, } # define jwt token SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), + # 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } # Database From 5cc43aa2b8e2a748d07692c8ea265c341f2e0a4e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 15:59:26 +0530 Subject: [PATCH 102/372] jira-33 mark as complete the task --- junior/serializers.py | 16 +++++++++++++++- junior/urls.py | 6 ++++-- junior/views.py | 31 +++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index ef1b5de..011cf97 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers from django.contrib.auth.models import User from django.db import transaction -import random +from datetime import datetime from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken @@ -306,3 +306,17 @@ class RemoveJuniorSerializer(serializers.ModelSerializer): instance.save() return instance + +class CompleteTaskSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = JuniorTask + fields = ('id', 'image') + def update(self, instance, validated_data): + instance.image = validated_data.get('image', instance.image) + instance.requested_on = datetime.today() + instance.task_status = str(NUMBER['four']) + instance.is_approved = False + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index 33d52e7..013b497 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -2,7 +2,8 @@ """Django import""" from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, - InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView) + InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, + CompleteJuniorTaskAPIView) """Third party import""" from rest_framework import routers @@ -36,5 +37,6 @@ router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), - path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()) + path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()), + path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()) ] diff --git a/junior/views.py b/junior/views.py index 50e42be..6e4c494 100644 --- a/junior/views.py +++ b/junior/views.py @@ -17,7 +17,7 @@ from rest_framework.pagination import PageNumberPagination # Import account's task from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ - RemoveJuniorSerializer) + RemoveJuniorSerializer, CompleteTaskSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -164,7 +164,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): class RemoveJuniorAPIView(views.APIView): - """Eater Update API""" + """Remove junior API""" serializer_class = RemoveJuniorSerializer model = Junior permission_classes = [IsAuthenticated] @@ -215,3 +215,30 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): # use TaskDetailsSerializer serializer serializer = TaskDetailsSerializer(paginated_queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + + +class CompleteJuniorTaskAPIView(views.APIView): + """Update junior task API""" + serializer_class = CompleteTaskSerializer + model = JuniorTask + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + task_id = self.request.data.get('task_id') + image = request.data['image'] + if image and image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + # fetch junior query + task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() + if task_queryset: + # use RemoveJuniorSerializer serializer + serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) From 3d84c0316318dff300b4a1641308b52c622c2cfc Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 18 Jul 2023 16:03:39 +0530 Subject: [PATCH 103/372] remove fcm token --- account/views.py | 6 ++++++ notifications/utils.py | 10 ++++++++++ web_admin/urls.py | 2 +- zod_bank/settings.py | 6 ++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index 7bc9ab5..95b4694 100644 --- a/account/views.py +++ b/account/views.py @@ -1,4 +1,6 @@ """Account view """ +from notifications.utils import remove_fcm_token + """Django import""" from datetime import datetime, timedelta from rest_framework import viewsets, status, views @@ -531,6 +533,10 @@ class LogoutAPIView(views.APIView): permission_classes = (IsAuthenticated,) def post(self, request): + remove_fcm_token( + request.auth.payload['user_id'], + request.META['HTTP_AUTHORIZATION'].split(" ")[1], + request.data.get('registration_id', "")) logout(request) request.session.flush() return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK) diff --git a/notifications/utils.py b/notifications/utils.py index b3a0ede..d68756c 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -15,3 +15,13 @@ def register_fcm_token(user_id, registration_id, device_id, device_type): 'registration_id': registration_id}) return device + +def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None: + """ + remove access_token and registration_token + """ + try: + # remove fcm token for this device + FCMDevice.objects.filter(user_id=user_id).delete() + except Exception as e: + print(e) diff --git a/web_admin/urls.py b/web_admin/urls.py index fa272f4..95bc7d9 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -11,7 +11,7 @@ from web_admin.views import ArticleViewSet # initiate router router = routers.SimpleRouter() -router.register('articles', ArticleViewSet, basename='articles') +router.register('article', ArticleViewSet, basename='article') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/zod_bank/settings.py b/zod_bank/settings.py index aea5896..091b908 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -33,6 +33,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = os.getenv('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv('DEBUG') + +# cors allow setting +CORS_ORIGIN_ALLOW_ALL = True + # allow all host ALLOWED_HOSTS = ['*'] @@ -184,8 +188,6 @@ USE_TZ = True # cors header settings SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -# cors allow setting -CORS_ORIGIN_ALLOW_ALL = True # cors allow method CORS_ALLOW_METHODS = ( From 130dcd83e7da56885386929193545bf8ba08514e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 16:56:24 +0530 Subject: [PATCH 104/372] sonar fixes --- account/serializers.py | 2 +- base/messages.py | 3 +++ guardian/serializers.py | 4 ++++ guardian/utils.py | 2 ++ guardian/views.py | 3 +++ requirements.txt | 2 ++ 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/account/serializers.py b/account/serializers.py index 5810f1d..d4b6179 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -41,7 +41,7 @@ from .utils import delete_user_account_condition_social, delete_user_account_con # email verification serializer, # phone otp serializer - +# create all serializer here class GoogleLoginSerializer(serializers.Serializer): """google login serializer""" access_token = serializers.CharField(max_length=5000, required=True) diff --git a/base/messages.py b/base/messages.py index a13c1d6..66585c6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -50,6 +50,7 @@ ERROR_CODE = { # email not verified "2024": "Email not verified", "2025": "Invalid input. Expected a list of strings.", + # check old and new password "2026": "New password should not same as old password", "2027": "data should contain `identityToken`", "2028": "You are not authorized person to sign up on this platform", @@ -66,8 +67,10 @@ ERROR_CODE = { # log in multiple device msg "2037": "You are already log in another device", "2038": "Choose valid action for task", + # card length limit "2039": "Add at least one article card or maximum 6", "2040": "Add at least 5 article survey or maximum 10", + # add article msg "2041": "Article with given id doesn't exist.", "2042": "Article Card with given id doesn't exist.", "2043": "Article Survey with given id doesn't exist." diff --git a/guardian/serializers.py b/guardian/serializers.py index 9459334..9343f8f 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -321,14 +321,18 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # approve the task instance.task_status = str(NUMBER['five']) instance.is_approved = True + # update total task point junior_data.total_task_points = junior_data.total_task_points + instance.points + # update complete time of task instance.completed_on = datetime.today() junior_data.save() else: # reject the task instance.task_status = str(NUMBER['three']) instance.is_approved = False + # update total task point junior_data.total_task_points = junior_data.total_task_points - instance.points + # update reject time of task instance.rejected_on = datetime.today() junior_data.save() instance.save() diff --git a/guardian/utils.py b/guardian/utils.py index 8619b66..f45caa8 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -39,6 +39,7 @@ def upload_image_to_alibaba(image, filename): return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" def real_time(): + """ real time """ # Fetch real time. # time is not depend on system time # Get the current datetime @@ -49,6 +50,7 @@ def real_time(): return current_datetime def convert_timedelta_into_datetime(time_difference): + """convert date time""" # convert timedelta into datetime format hours = time_difference.seconds // NUMBER['thirty_six_hundred'] minutes = (time_difference.seconds // NUMBER['sixty']) % NUMBER['sixty'] diff --git a/guardian/views.py b/guardian/views.py index ec14ef7..7ef470e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -19,6 +19,7 @@ from django.utils import timezone # Import account's serializer # Import account's task + from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) from .models import Guardian, JuniorTask @@ -42,6 +43,7 @@ from .utils import upload_image_to_alibaba # approve junior API # approve task API # Create your views here. +# create approve task API class SignupViewset(viewsets.ModelViewSet): """Signup view set""" queryset = User.objects.all() @@ -197,6 +199,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): queryset = JuniorPoints.objects.all() def get_serializer_context(self): + # context list context = super().get_serializer_context() context.update({'view': self}) return context diff --git a/requirements.txt b/requirements.txt index 5dd7f76..6e1a2ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -51,6 +51,8 @@ jmespath==0.10.0 kombu==5.3.1 MarkupSafe==2.1.3 msgpack==1.0.5 +ntplib==0.4.0 +numpy==1.25.1 oss2==2.18.0 packaging==23.1 phonenumbers==8.13.15 From 06d841f444e17964d1e3951bd97738246bf480d7 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 17:32:34 +0530 Subject: [PATCH 105/372] email setting changes --- zod_bank/settings.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 8bbd82a..9ef0f6b 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -233,14 +233,14 @@ GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') # email settings -EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" -EMAIL_HOST="smtp.sendgrid.net" -EMAIL_PORT="587" -EMAIL_USE_TLS="True" -EMAIL_HOST_USER="apikey" -EMAIL_HOST_PASSWORD="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" -EMAIL_FROM_ADDRESS="support@zodbank.com" -DEFAULT_ADDRESS="zodbank@yopmail.com" +EMAIL_BACKEND = os.getenv("EMAIL_BACKEND") +EMAIL_HOST = os.getenv("EMAIL_HOST") +EMAIL_PORT = os.getenv("EMAIL_PORT") +EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS") +EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") +EMAIL_FROM_ADDRESS = os.getenv("EMAIL_FROM_ADDRESS") +DEFAULT_ADDRESS = os.getenv("DEFAULT_ADDRESS") # ali baba cloud settings ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') From 0df748ccbfc6ed3651bb95333d4976c11be5ec82 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 18 Jul 2023 18:27:27 +0530 Subject: [PATCH 106/372] checking email settings --- zod_bank/settings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 9ef0f6b..b4be9b4 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -242,6 +242,15 @@ EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") EMAIL_FROM_ADDRESS = os.getenv("EMAIL_FROM_ADDRESS") DEFAULT_ADDRESS = os.getenv("DEFAULT_ADDRESS") +print(EMAIL_BACKEND, "EMAIL_BACKEND") +print(EMAIL_HOST, "EMAIL_HOST") +print(EMAIL_PORT, "EMAIL_PORT") +print(EMAIL_USE_TLS, "EMAIL_USE_TLS") +print(EMAIL_HOST_USER, "EMAIL_HOST_USER") +print(EMAIL_HOST_PASSWORD, "EMAIL_HOST_PASSWORD") +print(EMAIL_FROM_ADDRESS, "EMAIL_FROM_ADDRESS") +print(DEFAULT_ADDRESS, "DEFAULT_ADDRESS") + # ali baba cloud settings ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') ALIYUN_OSS_ACCESS_KEY_SECRET = os.getenv('ALIYUN_OSS_ACCESS_KEY_SECRET') From 9b84d176af95058660925456c4d12472e81b664d Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 18 Jul 2023 19:18:38 +0530 Subject: [PATCH 107/372] jira-34 points earned --- base/messages.py | 4 +++- guardian/views.py | 2 +- junior/serializers.py | 46 +++++++++++++++++++++++++++++++++++++++++++ junior/urls.py | 4 +++- junior/views.py | 34 +++++++++++++++++++++++++++++--- 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/base/messages.py b/base/messages.py index c5516e9..f04c2b8 100644 --- a/base/messages.py +++ b/base/messages.py @@ -73,7 +73,8 @@ ERROR_CODE = { # add article msg "2041": "Article with given id doesn't exist.", "2042": "Article Card with given id doesn't exist.", - "2043": "Article Survey with given id doesn't exist." + "2043": "Article Survey with given id doesn't exist.", + "2044": "Task does not exist" } """Success message code""" SUCCESS_CODE = { @@ -119,6 +120,7 @@ SUCCESS_CODE = { "3029": "Article has been deleted successfully.", "3030": "Article Card has been removed successfully.", "3031": "Article Survey has been removed successfully.", + "3032": "Task request sent successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/views.py b/guardian/views.py index 7ef470e..b0e2c34 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -62,7 +62,7 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) - return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp}, + 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: diff --git a/junior/serializers.py b/junior/serializers.py index 011cf97..cd99999 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -320,3 +320,49 @@ class CompleteTaskSerializer(serializers.ModelSerializer): instance.is_approved = False instance.save() return instance + +class JuniorPointsSerializer(serializers.ModelSerializer): + """Junior points serializer""" + + total_points = serializers.SerializerMethodField('get_points') + in_progress_task = serializers.SerializerMethodField('get_in_progress_task') + completed_task = serializers.SerializerMethodField('get_completed_task') + requested_task = serializers.SerializerMethodField('get_requested_task') + rejected_task = serializers.SerializerMethodField('get_rejected_task') + pending_task = serializers.SerializerMethodField('get_pending_task') + position = serializers.SerializerMethodField('get_position') + + def get_position(self, obj): + data = JuniorPoints.objects.filter(junior=obj.junior).last() + if data: + return data.position + return 99999 + def get_points(self, obj): + """total points""" + points = JuniorPoints.objects.filter(junior=obj.junior).last() + if points: + return points.total_task_points + + def get_in_progress_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=IN_PROGRESS).count() + + def get_completed_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=COMPLETED).count() + + + def get_requested_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=REQUESTED).count() + + + def get_rejected_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=REJECTED).count() + + + def get_pending_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=PENDING).count() + + class Meta(object): + """Meta info""" + model = Junior + fields = ['id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', + 'requested_task', 'rejected_task'] diff --git a/junior/urls.py b/junior/urls.py index 013b497..6057c05 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -3,7 +3,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, - CompleteJuniorTaskAPIView) + CompleteJuniorTaskAPIView, JuniorPointsListAPIView) """Third party import""" from rest_framework import routers @@ -34,6 +34,8 @@ router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior') # junior's task list API""" router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list') +# junior's task list API""" +router.register('junior-points', JuniorPointsListAPIView, basename='junior-points') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 6e4c494..8160107 100644 --- a/junior/views.py +++ b/junior/views.py @@ -1,7 +1,10 @@ """Junior view file""" +import os + from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination +import requests """Django app import""" # Import guardian's model, @@ -17,7 +20,7 @@ from rest_framework.pagination import PageNumberPagination # Import account's task from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ - RemoveJuniorSerializer, CompleteTaskSerializer) + RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -90,6 +93,11 @@ class JuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """ junior list""" + auth_token = self.request.META['HTTP_AUTHORIZATION'] + headers_token = { + 'Authorization': auth_token + } + requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) # use JuniorDetailListSerializer serializer @@ -238,7 +246,27 @@ class CompleteJuniorTaskAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() - return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) else: - return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST) + + +class JuniorPointsListAPIView(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = JuniorPointsSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + """get queryset""" + return JuniorTask.objects.filter(junior__auth__email=self.request.user).last() + def list(self, request, *args, **kwargs): + """profile view""" + queryset = self.get_queryset() + token = self.request.META['HTTP_AUTHORIZATION'] + headers = { + 'Authorization': token + } + requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) + serializer = JuniorPointsSerializer(queryset) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 49fc8d9dcab4bf76ef0267937141891c788d493b Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 18 Jul 2023 19:37:30 +0530 Subject: [PATCH 108/372] checking email settings --- zod_bank/settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index b4be9b4..5917e92 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -233,13 +233,13 @@ GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') # email settings -EMAIL_BACKEND = os.getenv("EMAIL_BACKEND") -EMAIL_HOST = os.getenv("EMAIL_HOST") -EMAIL_PORT = os.getenv("EMAIL_PORT") -EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS") -EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER") -EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") -EMAIL_FROM_ADDRESS = os.getenv("EMAIL_FROM_ADDRESS") +EMAIL_BACKEND = os.getenv("MAIL_BACKEND") +EMAIL_HOST = os.getenv("MAIL_HOST") +EMAIL_PORT = os.getenv("MAIL_PORT") +EMAIL_USE_TLS = os.getenv("MAIL_USE_TLS") +EMAIL_HOST_USER = os.getenv("MAIL_HOST_USER") +EMAIL_HOST_PASSWORD = os.getenv("MAIL_HOST_PASSWORD") +EMAIL_FROM_ADDRESS = os.getenv("MAIL_FROM_ADDRESS") DEFAULT_ADDRESS = os.getenv("DEFAULT_ADDRESS") print(EMAIL_BACKEND, "EMAIL_BACKEND") From 74fc656bd16adf4588e0680c757a6ccce27ce616 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 19 Jul 2023 13:11:36 +0530 Subject: [PATCH 109/372] checking email settings --- zod_bank/settings.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 5917e92..bc12176 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -64,6 +64,7 @@ INSTALLED_APPS = [ 'account', 'junior', 'guardian', + 'notifications', 'web_admin', # 'social_django' ] @@ -242,14 +243,6 @@ EMAIL_HOST_PASSWORD = os.getenv("MAIL_HOST_PASSWORD") EMAIL_FROM_ADDRESS = os.getenv("MAIL_FROM_ADDRESS") DEFAULT_ADDRESS = os.getenv("DEFAULT_ADDRESS") -print(EMAIL_BACKEND, "EMAIL_BACKEND") -print(EMAIL_HOST, "EMAIL_HOST") -print(EMAIL_PORT, "EMAIL_PORT") -print(EMAIL_USE_TLS, "EMAIL_USE_TLS") -print(EMAIL_HOST_USER, "EMAIL_HOST_USER") -print(EMAIL_HOST_PASSWORD, "EMAIL_HOST_PASSWORD") -print(EMAIL_FROM_ADDRESS, "EMAIL_FROM_ADDRESS") -print(DEFAULT_ADDRESS, "DEFAULT_ADDRESS") # ali baba cloud settings ALIYUN_OSS_ACCESS_KEY_ID = os.getenv('ALIYUN_OSS_ACCESS_KEY_ID') From 5429140a5c34314876938ab58f37d036e5d7748f Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 19 Jul 2023 14:58:14 +0530 Subject: [PATCH 110/372] cors error settings --- zod_bank/settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index bc12176..1f2fdc6 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ import os from dotenv import load_dotenv from datetime import timedelta +from firebase_admin import initialize_app + load_dotenv() # OR, the same with increased verbosity: load_dotenv(verbose=True) @@ -71,6 +73,7 @@ INSTALLED_APPS = [ # define middle ware here MIDDLEWARE = [ # Add your middleware classes here. + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -217,6 +220,9 @@ CORS_EXPOSE_HEADERS = ( 'Access-Control-Allow-Origin: *', ) +# Firebase settings +FIREBASE_APP = initialize_app() + # fcm django settings FCM_DJANGO_SETTINGS = { "APP_VERBOSE_NAME": "ZOD_Bank", From 86e2c75afcf4a3cdc38190a1c7e84ea4eab27317 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 19 Jul 2023 15:01:18 +0530 Subject: [PATCH 111/372] jira-34 allocated points --- account/views.py | 4 ++-- guardian/serializers.py | 7 +++---- junior/serializers.py | 11 ++++++++--- junior/views.py | 40 +++++++++++++++++++++------------------- zod_bank/settings.py | 3 +-- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/account/views.py b/account/views.py index 95b4694..10a538f 100644 --- a/account/views.py +++ b/account/views.py @@ -433,11 +433,11 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) class UploadImageAPIViewSet(viewsets.ModelViewSet): - """Profile viewset""" + """upload task image""" queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesSerializer def create(self, request, *args, **kwargs): - """profile view""" + """upload images""" image_data = request.data['image_url'] filename = f"default_task_images/{image_data.name}" if image_data.size == NUMBER['zero']: diff --git a/guardian/serializers.py b/guardian/serializers.py index 9343f8f..ff9edaf 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -324,8 +324,7 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_task_points = junior_data.total_task_points + instance.points # update complete time of task - instance.completed_on = datetime.today() - junior_data.save() + instance.completed_on = real_time() else: # reject the task instance.task_status = str(NUMBER['three']) @@ -333,8 +332,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_task_points = junior_data.total_task_points - instance.points # update reject time of task - instance.rejected_on = datetime.today() - junior_data.save() + instance.rejected_on = real_time() instance.save() + junior_data.save() return instance diff --git a/junior/serializers.py b/junior/serializers.py index cd99999..cd12a2e 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -16,6 +16,7 @@ from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp from junior.utils import junior_notification_email, junior_approval_mail +from guardian.utils import real_time class ListCharField(serializers.ListField): @@ -315,7 +316,7 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - instance.requested_on = datetime.today() + instance.requested_on = real_time() instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() @@ -323,7 +324,7 @@ class CompleteTaskSerializer(serializers.ModelSerializer): class JuniorPointsSerializer(serializers.ModelSerializer): """Junior points serializer""" - + junior_id = serializers.SerializerMethodField('get_junior_id') total_points = serializers.SerializerMethodField('get_points') in_progress_task = serializers.SerializerMethodField('get_in_progress_task') completed_task = serializers.SerializerMethodField('get_completed_task') @@ -332,6 +333,10 @@ class JuniorPointsSerializer(serializers.ModelSerializer): pending_task = serializers.SerializerMethodField('get_pending_task') position = serializers.SerializerMethodField('get_position') + def get_junior_id(self, obj): + """junior id""" + return obj.junior.id + def get_position(self, obj): data = JuniorPoints.objects.filter(junior=obj.junior).last() if data: @@ -364,5 +369,5 @@ class JuniorPointsSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', + fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', 'requested_task', 'rejected_task'] diff --git a/junior/views.py b/junior/views.py index 8160107..e1064af 100644 --- a/junior/views.py +++ b/junior/views.py @@ -232,25 +232,27 @@ class CompleteJuniorTaskAPIView(views.APIView): permission_classes = [IsAuthenticated] def put(self, request, format=None): - task_id = self.request.data.get('task_id') - image = request.data['image'] - if image and image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) - filename = f"images/{image.name}" - image_url = upload_image_to_alibaba(image, filename) - # fetch junior query - task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() - if task_queryset: - # use RemoveJuniorSerializer serializer - serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) - else: - return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST) - + try: + task_id = self.request.data.get('task_id') + image = request.data['image'] + if image and image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + filename = f"images/{image.name}" + image_url = upload_image_to_alibaba(image, filename) + # fetch junior query + task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() + if task_queryset: + # use RemoveJuniorSerializer serializer + serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class JuniorPointsListAPIView(viewsets.ModelViewSet): """Junior Points viewset""" diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 5917e92..1de920f 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -113,8 +113,7 @@ REST_FRAMEWORK = { } # define jwt token SIMPLE_JWT = { - # 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } # Database From 3223a2b05069e2465c82f1f1a301eb20c7160fc9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 19 Jul 2023 16:24:53 +0530 Subject: [PATCH 112/372] notification set up and celery configuration --- docker-compose.yml | 19 ++++++++++ guardian/views.py | 8 +++-- notifications/admin.py | 8 ++++- notifications/constants.py | 16 +++++++++ notifications/migrations/0001_initial.py | 30 ++++++++++++++++ notifications/models.py | 20 ++++++++++- notifications/utils.py | 45 ++++++++++++++++++++++++ notifications/views.py | 12 +++++++ zod-bank-fcm-credentials.json | 13 +++++++ zod_bank/celery.py | 34 ++++++++++++++++++ 10 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 notifications/constants.py create mode 100644 notifications/migrations/0001_initial.py create mode 100644 zod-bank-fcm-credentials.json create mode 100644 zod_bank/celery.py diff --git a/docker-compose.yml b/docker-compose.yml index 700bf1b..b9bc35b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,3 +16,22 @@ services: command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" volumes: - .:/usr/src/app + + broker: + image: rabbitmq:3.7 + container_name: rabbitmq + volumes: + - .:/usr/src/app + ports: + - 5673:5673 + + worker: + build: . + image: celery + container_name: dev_celery + restart: "always" + command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E" + volumes: + - .:/usr/src/app + depends_on: + - broker diff --git a/guardian/views.py b/guardian/views.py index b0e2c34..2776d38 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -1,11 +1,11 @@ """Views of Guardian""" -"""Third party Django app""" + +# django imports from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from django.utils import timezone -"""Import Django app""" # Import guardian's model, # Import junior's model, @@ -19,7 +19,6 @@ from django.utils import timezone # Import account's serializer # Import account's task - from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) from .models import Guardian, JuniorTask @@ -31,6 +30,8 @@ from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from .utils import upload_image_to_alibaba +from notifications.constants import REGISTRATION +from notifications.utils import send_notification """ Define APIs """ # Define Signup API, @@ -62,6 +63,7 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) + send_notification(REGISTRATION, None, request.auth.payload['user_id'], {}) return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/notifications/admin.py b/notifications/admin.py index 7bd27a7..382e97b 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -3,4 +3,10 @@ notification admin file """ from django.contrib import admin -# Register your models here. +from notifications.models import Notification + + +@admin.register(Notification) +class NotificationAdmin(admin.ModelAdmin): + """Notification Admin""" + list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read'] diff --git a/notifications/constants.py b/notifications/constants.py new file mode 100644 index 0000000..b72ed61 --- /dev/null +++ b/notifications/constants.py @@ -0,0 +1,16 @@ +""" +notification constants file +""" +REGISTRATION = 1 +TEST_NOTIFICATION = 99 + +NOTIFICATION_DICT = { + REGISTRATION: { + "title": "Successfully registered!", + "body": "You have registered successfully. Now login and complete your profile." + }, + TEST_NOTIFICATION: { + "title": "Test Notification", + "body": "This notification is for testing purpose" + } +} diff --git a/notifications/migrations/0001_initial.py b/notifications/migrations/0001_initial.py new file mode 100644 index 0000000..ea73b4a --- /dev/null +++ b/notifications/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.2 on 2023-07-19 07:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('notification_type', models.CharField(blank=True, max_length=50, null=True)), + ('data', models.JSONField(blank=True, default=dict, null=True)), + ('is_read', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('notification_from', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='from_notification', to=settings.AUTH_USER_MODEL)), + ('notification_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_notification', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/notifications/models.py b/notifications/models.py index 90a6f02..5d6d02c 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -1,6 +1,24 @@ """ notification models file """ +# django imports from django.db import models +from django.contrib.auth import get_user_model +from django.utils import timezone -# Create your models here. +USER = get_user_model() + + +class Notification(models.Model): + """ used to save the notifications """ + notification_type = models.CharField(max_length=50, blank=True, null=True) + notification_to = models.ForeignKey(USER, related_name='to_notification', on_delete=models.CASCADE) + notification_from = models.ForeignKey(USER, related_name='from_notification', on_delete=models.SET_NULL, + blank=True, null=True) + data = models.JSONField(default=dict, blank=True, null=True) + is_read = models.BooleanField(default=False) + created_at = models.DateTimeField(default=timezone.now) + + def __str__(self): + """ string representation """ + return f"{self.notification_to.id} | {self.notification_to.email}" diff --git a/notifications/utils.py b/notifications/utils.py index d68756c..893947a 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -1,12 +1,25 @@ """ notifications utils file """ +import constant as constant # third party imports from fcm_django.models import FCMDevice +from celery import shared_task +from firebase_admin.messaging import Message, Notification as FirebaseNotification + +# django imports +from django.contrib.auth import get_user_model + +from account.models import UserNotification +from notifications.constants import NOTIFICATION_DICT +from notifications.models import Notification # local imports +User = get_user_model() + + def register_fcm_token(user_id, registration_id, device_id, device_type): """ used to register the fcm device token""" device, _ = FCMDevice.objects.update_or_create(device_id=device_id, @@ -25,3 +38,35 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None: FCMDevice.objects.filter(user_id=user_id).delete() except Exception as e: print(e) + + +def get_basic_detail(notification_type, from_user_id, to_user_id): + """ used to get the basic details """ + notification_data = NOTIFICATION_DICT[notification_type] + from_user = User.objects.get(id=from_user_id) if from_user_id else None + to_user = User.objects.get(id=to_user_id) + return notification_data, from_user, to_user + + +@shared_task() +def send_notification(notification_type, from_user_id, to_user_id, extra_data): + """ used to send the push for the given notification type """ + (notification_data, from_user, to_user) = get_basic_detail(notification_type, from_user_id, to_user_id) + print(notification_data, to_user) + user_notification_type = UserNotification.objects.filter(user=to_user).first() + # data = notification_data.data + data = notification_data + Notification.objects.create(notification_type=notification_type, notification_from=from_user, + notification_to=to_user, data=data) + if user_notification_type.push_notification: + data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()}) + send_push(to_user, data) + + +def send_push(user, data): + """ used to send push notification to specific user """ + # if user.push_notification: + notification_data = data.pop('data', None) + user.fcmdevice_set.filter(active=True).send_message( + Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data) + ) diff --git a/notifications/views.py b/notifications/views.py index 8ac3786..d9e5fd7 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -8,9 +8,12 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +# local imports from account.utils import custom_response from base.messages import SUCCESS_CODE +from notifications.constants import TEST_NOTIFICATION from notifications.serializers import RegisterDevice +from notifications.utils import send_notification class NotificationViewSet(viewsets.GenericViewSet): @@ -28,3 +31,12 @@ class NotificationViewSet(viewsets.GenericViewSet): serializer.is_valid(raise_exception=True) serializer.save() return custom_response(SUCCESS_CODE["3000"]) + + @action(methods=['get'], detail=False, url_path='test', url_name='test', serializer_class=None) + def send_test_notification(self, request): + """ + to send test notification + :return: + """ + send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {}) + return custom_response(SUCCESS_CODE["3000"]) diff --git a/zod-bank-fcm-credentials.json b/zod-bank-fcm-credentials.json new file mode 100644 index 0000000..75b13ab --- /dev/null +++ b/zod-bank-fcm-credentials.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "zod-bank-ebb2a", + "private_key_id": "f1115f1b1fece77ba7a119b70e2502ce0777a7d4", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCvLWEobyVN7D4+\nSZ4NwcugwuVk9MV7CjhQGB8lqzf/1Z0plBytjpjM75+orFk5n2tnOgTxsWCqR1R7\nLry4x2QH3HgJebd/TZTIyfepcAeuzUVhq9prgWVRsvxjpihMPZufA/Q1GEX5TBwX\nEasBW91Qwas2NBhUrzotnUBxOshVB4zCo3Ls9dbAN9O2O6paUMvcofSsRZ9XkB6P\nFFKy6nbQ3Bo+Lw3ntUfG1JQgkkxto2Vudiiq6J2dE6Eih2acEhEezQoJVpkMK+si\nlGp88T3j8nTx3o6ol99ke+3ZejPVE5sUbuhokSV/tS1Goy3whP+ys9lQtIyt3/mJ\nlmkoB9ShAgMBAAECggEAAk3H0GFF08C3EBWyDqH5dbLXbtH92e/UeNAMNcV4mGbY\n5GKGFywmEVmg2N22EPqa/s+Y6QxxrD9u9G1+EhbnIIx7olOBkScJ9C0c9CWztCon\ntPaofd1E1cI7I7UfVTg2ZLAHrBN4H70eadYc4va8bBtHj0EHYONz7S8sEBQ1Qna2\nIQuCEWY6MzhwCNEFIJd8ikd0GnkAJCuInK3F+2c37kugdgjRKxkTIfWmhNIfyWn3\ngoui5wltuuDKETVj9VUMyN6P7kffIJzS4mMRm/9F92scpPRbK+X1TrHpFsO826oP\nUuSBi5oOZYNyAvMBXGdnemoUNtAE+xg4kqwYJu5T0QKBgQDlbO08yH5MSLUxXZfL\nkCYg1mCUC4ySpL/qVFNuyvY+ex9gIg6gj4vEpK8s1lt0yvxrTng/MXxMMg7o9dO8\nmSlv/665twk/7NfkexJPWs1ph+54tud3Sa0TiVw6U5ObPMr1EYc7RnoELDR9Exc1\nUDJB+T3f3SGJicZn4pALyx132wKBgQDDd9lhqWLXJeG7JkInhLywuSVmnk077hY8\nxAfHqlO+/EEkM/YBtUvtC4KNiSkVftQwSM80NRiHlPvlWZ0G8l9RAfM/xxL/XUq6\nOu1fW1uJuukJgXFePV9SQLzfgd1fk1f/dKuiPSNhCzgTD7dFks0Ip6FD6/PCdN7x\neqRUqDccMwKBgQCTk1mW+7CiCTLkKjv2KScdgEhncnZd7bO1W8C/R7bVwgUQpVeb\nWDqjpvs3cDssCVYNAFDA9Wfq61hD6bzlV/AbpvARbfd5MzQ8OB4zBUmUVGfFJoIF\nbVLzeivlKNWNybETqs6+Bjt+a6Dnw1vuY0OwxE5Urb1g50rEkCvwKhsueQKBgQDB\nUt3rG46oX80cPkCbuUquNs/o6JRWu6m+u9s9/RYLBI6g8ctT8S2A6ytaNNgvbFsM\nzlYwunriTdW9Bp6p6jmfcyBUad4+NtTbz8BJ2Z91Xylwv1eS73xBa8nh/R0nlCEq\nhQfj1DgTmPcC0z5eT0z+TFzRQqK6JsEBcFzrZdvrxQKBgQDGEtWElG5XoDwnwO5e\nmfFLRKHfG+Xa6FChl2eeDIGOxv/Y6SMwT68t0gtnDFQGtcIMvC+kC/1Rv1v8yrOD\nCzEToh91Fw1c1eayNnsLq07qYnVWoMa7xFFSDhtBAnepqlU+81qaDvp+yILTIYSP\nwUuk3NpdJs2LkMetm5zJC9PJ2w==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-umcph@zod-bank-ebb2a.iam.gserviceaccount.com", + "client_id": "102629742363778142120", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-umcph%40zod-bank-ebb2a.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/zod_bank/celery.py b/zod_bank/celery.py new file mode 100644 index 0000000..177c318 --- /dev/null +++ b/zod_bank/celery.py @@ -0,0 +1,34 @@ +""" +Celery Basic configuration +""" + +# python imports +import os + +# third party imports +from celery import Celery +from dotenv import load_dotenv +from celery.schedules import crontab + +# OR, the same with increased verbosity: +load_dotenv(verbose=True) + +env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env') + +load_dotenv(dotenv_path=env_path) + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', os.getenv('SETTINGS')) + +# celery app +app = Celery() + +app.config_from_object('django.conf:settings') + +# Load task modules from all registered Django apps. +app.autodiscover_tasks() + + +@app.task(bind=True) +def debug_task(self): + """ celery debug task """ + print(f'Request: {self.request!r}') From 9f9926da14b9230077c9df0216563f123e09cf77 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 19 Jul 2023 17:45:20 +0530 Subject: [PATCH 113/372] jira-34 referral code validation API --- base/messages.py | 5 ++-- guardian/serializers.py | 12 +++++---- guardian/utils.py | 18 +++++++++++++ guardian/views.py | 4 +-- junior/admin.py | 2 +- ...l_task_points_juniorpoints_total_points.py | 18 +++++++++++++ .../0016_juniorpoints_referral_points.py | 18 +++++++++++++ junior/models.py | 6 +++-- junior/serializers.py | 8 +++--- junior/urls.py | 4 ++- junior/views.py | 27 +++++++++++++++++++ 11 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py create mode 100644 junior/migrations/0016_juniorpoints_referral_points.py diff --git a/base/messages.py b/base/messages.py index f04c2b8..998a5b2 100644 --- a/base/messages.py +++ b/base/messages.py @@ -42,7 +42,7 @@ ERROR_CODE = { "2016": "Invalid search.", "2017": "{model} object with {pk} does not exist", "2018": "Attached File not found", - "2019": "Either File extension or File size doesn't meet the requirements", + "2019": "Invalid Referral code", "2020": "Enter valid mobile number", "2021": "Already register", "2022": "Invalid Guardian code", @@ -120,7 +120,8 @@ SUCCESS_CODE = { "3029": "Article has been deleted successfully.", "3030": "Article Card has been removed successfully.", "3031": "Article Survey has been removed successfully.", - "3032": "Task request sent successfully" + "3032": "Task request sent successfully", + "3033": "Valid Referral code", } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index ff9edaf..a5ad978 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -24,7 +24,7 @@ from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, JUN, ZOD, GRD from junior.models import Junior, JuniorPoints -from .utils import real_time, convert_timedelta_into_datetime +from .utils import real_time, convert_timedelta_into_datetime, update_referral_points @@ -141,7 +141,6 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.country_code = validated_data.get('country_code', guardian.country_code) guardian.passcode = validated_data.get('passcode', guardian.passcode) guardian.country_name = validated_data.get('country_name', guardian.country_name) - guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) guardian.image = validated_data.get('image', guardian.image) """Complete profile of the junior if below all data are filled""" complete_profile_field = all([guardian.gender, guardian.country_name, @@ -150,6 +149,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer): guardian.is_complete_profile = False if complete_profile_field: guardian.is_complete_profile = True + referral_code_used = validated_data.get('referral_code_used') + update_referral_points(guardian.referral_code, referral_code_used) + guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used) guardian.save() return guardian @@ -235,7 +237,7 @@ class TopJuniorSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = JuniorPoints - fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at'] + fields = ['id', 'junior', 'total_points', 'position', 'created_at', 'updated_at'] def to_representation(self, instance): """Convert instance to representation""" @@ -322,7 +324,7 @@ class ApproveTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['five']) instance.is_approved = True # update total task point - junior_data.total_task_points = junior_data.total_task_points + instance.points + junior_data.total_points = junior_data.total_points + instance.points # update complete time of task instance.completed_on = real_time() else: @@ -330,7 +332,7 @@ class ApproveTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['three']) instance.is_approved = False # update total task point - junior_data.total_task_points = junior_data.total_task_points - instance.points + junior_data.total_points = junior_data.total_points - instance.points # update reject time of task instance.rejected_on = real_time() instance.save() diff --git a/guardian/utils.py b/guardian/utils.py index f45caa8..45bf69b 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -11,6 +11,8 @@ from datetime import datetime, time import ntplib # import Number constant from base.constants import NUMBER +# Import Junior's model +from junior.models import Junior, JuniorPoints # Define upload image on # ali baba cloud @@ -21,6 +23,10 @@ from base.constants import NUMBER # bucket and reform the image url""" # fetch real time data without depend on system time # convert time delta into date time object +# update junior total points +# update referral points +# if any one use referral code of the junior +# junior earn 5 points def upload_image_to_alibaba(image, filename): """upload image on oss alibaba bucket""" @@ -59,3 +65,15 @@ def convert_timedelta_into_datetime(time_difference): # Create a new time object with the extracted time components time_only = time(hours, minutes, seconds, microseconds) return time_only + +def update_referral_points(referral_code, referral_code_used): + """Update benefit of referral points""" + junior_queryset = Junior.objects.filter(referral_code=referral_code_used).last() + if junior_queryset and junior_queryset.referral_code != referral_code: + # create data if junior points is not exist + junior_query, created = JuniorPoints.objects.get_or_create(junior=junior_queryset) + if junior_query: + # update junior total points and referral points + junior_query.total_points = junior_query.total_points + NUMBER['five'] + junior_query.referral_points = junior_query.referral_points + NUMBER['five'] + junior_query.save() diff --git a/guardian/views.py b/guardian/views.py index b0e2c34..725eab2 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -94,7 +94,7 @@ class UpdateGuardianProfile(viewsets.ViewSet): if serializer.is_valid(): """save serializer""" serializer.save() - return custom_response(None, serializer.data,response_status=status.HTTP_200_OK) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) @@ -206,7 +206,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Fetch junior list of those who complete their tasks""" - junior_total_points = self.get_queryset().order_by('-total_task_points') + junior_total_points = self.get_queryset().order_by('-total_points') # Update the position field for each JuniorPoints object for index, junior in enumerate(junior_total_points): diff --git a/junior/admin.py b/junior/admin.py index 3764f48..99f1cd0 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -16,7 +16,7 @@ class JuniorAdmin(admin.ModelAdmin): @admin.register(JuniorPoints) class JuniorPointsAdmin(admin.ModelAdmin): """Junior Points Admin""" - list_display = ['junior', 'total_task_points', 'position'] + list_display = ['junior', 'total_points', 'position'] def __str__(self): """Return email id""" diff --git a/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py b/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py new file mode 100644 index 0000000..038adba --- /dev/null +++ b/junior/migrations/0015_rename_total_task_points_juniorpoints_total_points.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-19 09:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0014_junior_is_password_set'), + ] + + operations = [ + migrations.RenameField( + model_name='juniorpoints', + old_name='total_task_points', + new_name='total_points', + ), + ] diff --git a/junior/migrations/0016_juniorpoints_referral_points.py b/junior/migrations/0016_juniorpoints_referral_points.py new file mode 100644 index 0000000..a70f45d --- /dev/null +++ b/junior/migrations/0016_juniorpoints_referral_points.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-19 11:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0015_rename_total_task_points_juniorpoints_total_points'), + ] + + operations = [ + migrations.AddField( + model_name='juniorpoints', + name='referral_points', + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index f4ef488..28754b1 100644 --- a/junior/models.py +++ b/junior/models.py @@ -89,8 +89,10 @@ class Junior(models.Model): class JuniorPoints(models.Model): """Junior model""" junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points') - # Contact details""" - total_task_points = models.IntegerField(blank=True, null=True, default=0) + # Total earned points""" + total_points = models.IntegerField(blank=True, null=True, default=0) + # referral points""" + referral_points = models.IntegerField(blank=True, null=True, default=0) # position of the junior""" position = models.IntegerField(blank=True, null=True, default=99999) # Profile created and updated time""" diff --git a/junior/serializers.py b/junior/serializers.py index cd12a2e..b0ddfa1 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -16,7 +16,7 @@ from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp from junior.utils import junior_notification_email, junior_approval_mail -from guardian.utils import real_time +from guardian.utils import real_time, update_referral_points class ListCharField(serializers.ListField): @@ -92,7 +92,6 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """Update country code and phone number""" junior.phone = validated_data.get('phone', junior.phone) junior.country_code = validated_data.get('country_code', junior.country_code) - junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) junior.image = validated_data.get('image', junior.image) """Complete profile of the junior if below all data are filled""" complete_profile_field = all([junior.gender, junior.country_name, junior.image, @@ -100,6 +99,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): junior.is_complete_profile = False if complete_profile_field: junior.is_complete_profile = True + referral_code_used = validated_data.get('referral_code_used') + update_referral_points(junior.referral_code, referral_code_used) + junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used) junior.save() return junior @@ -346,7 +348,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer): """total points""" points = JuniorPoints.objects.filter(junior=obj.junior).last() if points: - return points.total_task_points + return points.total_points def get_in_progress_task(self, obj): return JuniorTask.objects.filter(junior=obj.junior, task_status=IN_PROGRESS).count() diff --git a/junior/urls.py b/junior/urls.py index 6057c05..e4a160d 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -3,7 +3,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, - CompleteJuniorTaskAPIView, JuniorPointsListAPIView) + CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode) """Third party import""" from rest_framework import routers @@ -36,6 +36,8 @@ router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior') router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list') # junior's task list API""" router.register('junior-points', JuniorPointsListAPIView, basename='junior-points') +# validate referral code API""" +router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index e1064af..29f8331 100644 --- a/junior/views.py +++ b/junior/views.py @@ -37,6 +37,8 @@ from guardian.utils import upload_image_to_alibaba # search junior API # remove junior API, # approve junior API +# create referral code +# validation API # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): @@ -272,3 +274,28 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + +class ValidateReferralCode(viewsets.ViewSet): + """Check guardian code exist or not""" + permission_classes = [IsAuthenticated] + + def get_queryset(self): + """Get queryset based on referral_code.""" + referral_code = self.request.GET.get('referral_code') + if referral_code: + # search referral code in guardian model + guardian_queryset = Guardian.objects.filter(referral_code=referral_code).last() + if guardian_queryset: + return guardian_queryset + else: + # search referral code in junior model + junior_queryset = Junior.objects.filter(referral_code=referral_code).last() + if junior_queryset: + return junior_queryset + return None + + def list(self, request, *args, **kwargs): + """check guardian code""" + if self.get_queryset(): + return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST) From 4002d1e56103ca6060bf9b529e76e1cfadac868f Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 19 Jul 2023 17:48:27 +0530 Subject: [PATCH 114/372] celery settings --- notifications/utils.py | 1 - zod_bank/settings.py | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/notifications/utils.py b/notifications/utils.py index 893947a..26d6778 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -1,7 +1,6 @@ """ notifications utils file """ -import constant as constant # third party imports from fcm_django.models import FCMDevice from celery import shared_task diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 9b01f69..bc7c038 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -238,6 +238,13 @@ GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID') # google client secret key GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET') +# CELERY SETUP +CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL') +CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND') +CELERY_ACCEPT_CONTENT = ['application/json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' + # email settings EMAIL_BACKEND = os.getenv("MAIL_BACKEND") EMAIL_HOST = os.getenv("MAIL_HOST") From 49abf12ce86253d6070f261c74b7fc6839db3d0f Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 19 Jul 2023 17:50:10 +0530 Subject: [PATCH 115/372] jira-34 referral code validation API --- junior/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/junior/views.py b/junior/views.py index 29f8331..e920c97 100644 --- a/junior/views.py +++ b/junior/views.py @@ -18,6 +18,7 @@ import requests # from utils file # Import account's serializer # Import account's task +# import junior serializer from junior.models import Junior from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer) @@ -55,6 +56,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): if image: if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + # convert into file filename = f"images/{image.name}" # upload image on ali baba image_url = upload_image_to_alibaba(image, filename) @@ -101,6 +103,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): } requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() + # fetch junior object queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(queryset, many=True) @@ -141,6 +144,7 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): """ junior list""" queryset = self.get_queryset() paginator = self.pagination_class() + # pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(paginated_queryset, many=True) @@ -158,6 +162,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): """Get the queryset for the view""" title = self.request.GET.get('title') guardian_data = Guardian.objects.filter(user__email=self.request.user).last() + # fetch junior query queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code), is_invited=True, auth__first_name=title) return queryset @@ -209,14 +214,18 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): status_value = self.request.GET.get('status') search = self.request.GET.get('search') if search and str(status_value) == '0': + # search with title and for all task list queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search).order_by('due_date', 'created_at') elif search and str(status_value) != '0': + # search with title and fetch task list with status wise queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search, task_status=status_value).order_by('due_date', 'created_at') if search is None and str(status_value) == '0': + # fetch all task list queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at') elif search is None and str(status_value) != '0': + # fetch task list with status wise queryset = JuniorTask.objects.filter(junior__auth=request.user, task_status=status_value).order_by('due_date','created_at') paginator = self.pagination_class() @@ -239,7 +248,9 @@ class CompleteJuniorTaskAPIView(views.APIView): image = request.data['image'] if image and image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + # create file filename = f"images/{image.name}" + # upload image 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() @@ -267,6 +278,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """profile view""" queryset = self.get_queryset() + # update position of junior token = self.request.META['HTTP_AUTHORIZATION'] headers = { 'Authorization': token From b488b08f3f84c8af3e037a7604807a91948745db Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 20 Jul 2023 12:25:41 +0530 Subject: [PATCH 116/372] google login chnages --- account/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/views.py b/account/views.py index 10a538f..9d985fb 100644 --- a/account/views.py +++ b/account/views.py @@ -40,10 +40,10 @@ from account.utils import (send_otp_email, send_support_email, custom_response, from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer -class GoogleLoginMixin(object): +class GoogleLoginMixin: """google login mixin""" @staticmethod - def google_login(self, request): + def google_login(request): """google login function""" access_token = request.data.get('access_token') user_type = request.data.get('user_type') From 0f5dfc2e9ac0a12c2a9514f1456d9f69b0074fb3 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 20 Jul 2023 12:27:22 +0530 Subject: [PATCH 117/372] google login chnages --- account/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index 9d985fb..c19cd4f 100644 --- a/account/views.py +++ b/account/views.py @@ -40,7 +40,7 @@ from account.utils import (send_otp_email, send_support_email, custom_response, from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer -class GoogleLoginMixin: +class GoogleLoginMixin(object): """google login mixin""" @staticmethod def google_login(request): From 4410712b11f06ecbbcd5ffc0d7907d456da83399 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 20 Jul 2023 12:26:19 +0530 Subject: [PATCH 118/372] sign up issue resolved --- guardian/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index 2776d38..e311833 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -54,7 +54,7 @@ class SignupViewset(viewsets.ModelViewSet): if request.data['user_type'] in ['1', '2']: serializer = UserSerializer(context=request.data['user_type'], data=request.data) if serializer.is_valid(): - serializer.save() + user = serializer.save() """Generate otp""" otp = generate_otp() expiry = timezone.now() + timezone.timedelta(days=1) @@ -63,7 +63,7 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) - send_notification(REGISTRATION, None, request.auth.payload['user_id'], {}) + send_notification(REGISTRATION, None, user.id, {}) return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) From 4b09ff41cbd8ea4d0ecc030e653c01f33b06dd0a Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 20 Jul 2023 12:44:48 +0530 Subject: [PATCH 119/372] admin login --- account/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index d4b6179..d4fd335 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -144,12 +144,12 @@ class SuperUserSerializer(serializers.ModelSerializer): refresh_token = serializers.SerializerMethodField('get_refresh_token') def get_auth_token(self, obj): - refresh = RefreshToken.for_user(obj.auth) + refresh = RefreshToken.for_user(obj) access_token = str(refresh.access_token) return access_token def get_refresh_token(self, obj): - refresh = RefreshToken.for_user(obj.user) + refresh = RefreshToken.for_user(obj) refresh_token = str(refresh) return refresh_token From 96d71d951eab96827ff492b2ccf2ec224833abcc Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 20 Jul 2023 17:09:49 +0530 Subject: [PATCH 120/372] real time changes --- .../0009_alter_userdevicedetails_device_id.py | 18 +++++++++++++ account/models.py | 2 +- guardian/utils.py | 27 ++++++++++--------- 3 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 account/migrations/0009_alter_userdevicedetails_device_id.py diff --git a/account/migrations/0009_alter_userdevicedetails_device_id.py b/account/migrations/0009_alter_userdevicedetails_device_id.py new file mode 100644 index 0000000..2db22a3 --- /dev/null +++ b/account/migrations/0009_alter_userdevicedetails_device_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-20 11:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0008_userdevicedetails'), + ] + + operations = [ + migrations.AlterField( + model_name='userdevicedetails', + name='device_id', + field=models.CharField(blank=True, max_length=500, null=True), + ), + ] diff --git a/account/models.py b/account/models.py index d515e01..784a60e 100644 --- a/account/models.py +++ b/account/models.py @@ -152,7 +152,7 @@ class UserDeviceDetails(models.Model): """ user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_device_details') """Device ID""" - device_id = models.CharField(max_length=500) + device_id = models.CharField(max_length=500, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/guardian/utils.py b/guardian/utils.py index 45bf69b..3254b82 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -3,12 +3,12 @@ import oss2 """Import setting""" from django.conf import settings +import logging +import requests """Import tempfile""" import tempfile # Import date time module's function from datetime import datetime, time -# Import real time client module -import ntplib # import Number constant from base.constants import NUMBER # Import Junior's model @@ -44,16 +44,19 @@ def upload_image_to_alibaba(image, filename): new_filename = filename.replace(' ', '%20') return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" -def real_time(): - """ real time """ - # Fetch real time. - # time is not depend on system time - # Get the current datetime - ntp_client = ntplib.NTPClient() - ntp_server = 'pool.ntp.org' - response = ntp_client.request(ntp_server) - current_datetime = datetime.fromtimestamp(response.tx_time) - return current_datetime + +def real_time(timezone='Asia/Riyadh'): + url = f'http://worldtimeapi.org/api/timezone/{timezone}' + response = requests.get(url) + if response.status_code == 200: + data = response.json() + time_str = data['datetime'] + realtime = datetime.fromisoformat(time_str.replace('Z', '+00:00')).replace(tzinfo=None) + return realtime + else: + logging.error("Could not fetch error") + return None + def convert_timedelta_into_datetime(time_difference): """convert date time""" From 532b56c687582a5d5d9c6dd966b895004bf43364 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 20 Jul 2023 18:01:53 +0530 Subject: [PATCH 121/372] added article point --- base/constants.py | 7 ++++++ .../0002_alter_articlecard_image.py | 18 ++++++++++++++ web_admin/models.py | 2 +- web_admin/serializers.py | 24 +++++++++++++------ web_admin/views.py | 13 ++++++++-- zod_bank/settings.py | 3 --- 6 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 web_admin/migrations/0002_alter_articlecard_image.py diff --git a/base/constants.py b/base/constants.py index 29da8db..2ad8c83 100644 --- a/base/constants.py +++ b/base/constants.py @@ -96,3 +96,10 @@ BYTE_IMAGE_SIZE = 1024 # validate file size MAX_FILE_SIZE = 1024 * 1024 * 5 + +ARTICLE_SURVEY_POINTS = 5 +MAX_ARTICLE_CARD = 6 + +# min and max survey +MIN_ARTICLE_SURVEY = 5 +MAX_ARTICLE_SURVEY = 10 diff --git a/web_admin/migrations/0002_alter_articlecard_image.py b/web_admin/migrations/0002_alter_articlecard_image.py new file mode 100644 index 0000000..4f438f5 --- /dev/null +++ b/web_admin/migrations/0002_alter_articlecard_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-20 11:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='articlecard', + name='image', + field=models.URLField(blank=True, default=None, null=True), + ), + ] diff --git a/web_admin/models.py b/web_admin/models.py index 1e49c6b..30f0550 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -28,7 +28,7 @@ class ArticleCard(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() - image = models.ImageField(upload_to='card_images/') + image = models.URLField(null=True, blank=True, default=None) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 57d4278..b9f6fa9 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -3,10 +3,11 @@ web_admin serializers file """ # django imports from rest_framework import serializers -from django.conf import settings +from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY # local imports from base.messages import ERROR_CODE +from guardian.utils import upload_image_to_alibaba from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey from web_admin.utils import pop_id @@ -16,14 +17,23 @@ class ArticleCardSerializer(serializers.ModelSerializer): Article Card serializer """ id = serializers.IntegerField(required=False) + image = serializers.FileField(required=False) class Meta: """ meta class """ model = ArticleCard - fields = ('id', 'title', 'description') + fields = ('id', 'title', 'description', 'image') + def create(self, validated_data): + image = validated_data.get('image', '') + filename = f"article/{image.name}" + # upload image on ali baba + validated_data['image'] = upload_image_to_alibaba(image, filename) + + article_card = ArticleCard.objects.create(article_id='1', **validated_data) + return article_card class SurveyOptionSerializer(serializers.ModelSerializer): """ @@ -51,7 +61,7 @@ class ArticleSurveySerializer(serializers.ModelSerializer): meta class """ model = ArticleSurvey - fields = ('id', 'question', 'points', 'survey_options') + fields = ('id', 'question', 'survey_options') class ArticleSerializer(serializers.ModelSerializer): @@ -76,10 +86,10 @@ class ArticleSerializer(serializers.ModelSerializer): """ article_cards = attrs.get('article_cards', None) article_survey = attrs.get('article_survey', None) - if article_cards is None or len(article_cards) > int(settings.MAX_ARTICLE_CARD): + if article_cards is None or len(article_cards) > int(MAX_ARTICLE_CARD): raise serializers.ValidationError({'details': ERROR_CODE['2039']}) - if article_survey is None or len(article_survey) < int(settings.MIN_ARTICLE_SURVEY) or int( - settings.MAX_ARTICLE_SURVEY) < len(article_survey): + if article_survey is None or len(article_survey) < int(MIN_ARTICLE_SURVEY) or int( + MAX_ARTICLE_SURVEY) < len(article_survey): raise serializers.ValidationError({'details': ERROR_CODE['2040']}) return attrs @@ -102,7 +112,7 @@ class ArticleSerializer(serializers.ModelSerializer): for survey in article_survey: survey = pop_id(survey) options = survey.pop('survey_options') - survey_obj = ArticleSurvey.objects.create(article=article, **survey) + survey_obj = ArticleSurvey.objects.create(article=article, points=ARTICLE_SURVEY_POINTS, **survey) for option in options: option = pop_id(option) SurveyOption.objects.create(survey=survey_obj, **option) diff --git a/web_admin/views.py b/web_admin/views.py index 8003e2c..3918f1e 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -13,7 +13,7 @@ from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey from web_admin.permission import AdminPermission -from web_admin.serializers import ArticleSerializer +from web_admin.serializers import ArticleSerializer, ArticleCardSerializer class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, @@ -22,7 +22,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel article api """ serializer_class = ArticleSerializer - permission_classes = [IsAuthenticated, AdminPermission] + # permission_classes = [IsAuthenticated, AdminPermission] queryset = Article.objects.prefetch_related('article_cards', 'article_survey', 'article_survey__survey_options').order_by('-created_at') @@ -140,3 +140,12 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3031"]) except AttributeError: return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST) + + @action(methods=['post'], url_name='test-add-card', url_path='test-add-card', + detail=False, serializer_class=ArticleCardSerializer) + def add_card(self, request): + print(request.data) + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3000"]) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index bc7c038..7424069 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -263,9 +263,6 @@ ALIYUN_OSS_BUCKET_NAME = os.getenv('ALIYUN_OSS_BUCKET_NAME') ALIYUN_OSS_ENDPOINT = os.getenv('ALIYUN_OSS_ENDPOINT') ALIYUN_OSS_REGION = os.getenv('ALIYUN_OSS_REGION') -MAX_ARTICLE_CARD = os.getenv('MAX_ARTICLE_CARD', 6) -MIN_ARTICLE_SURVEY = os.getenv('MIN_ARTICLE_SURVEY', 5) -MAX_ARTICLE_SURVEY = os.getenv('MAX_ARTICLE_SURVEY', 10) # define static url STATIC_URL = 'static/' From 5ee01af82552f825bcb953467f7f7f2cae66aa78 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 21 Jul 2023 11:35:58 +0530 Subject: [PATCH 122/372] article api changes --- notifications/utils.py | 2 -- web_admin/views.py | 48 ++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/notifications/utils.py b/notifications/utils.py index 26d6778..b5114c2 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -51,7 +51,6 @@ def get_basic_detail(notification_type, from_user_id, to_user_id): def send_notification(notification_type, from_user_id, to_user_id, extra_data): """ used to send the push for the given notification type """ (notification_data, from_user, to_user) = get_basic_detail(notification_type, from_user_id, to_user_id) - print(notification_data, to_user) user_notification_type = UserNotification.objects.filter(user=to_user).first() # data = notification_data.data data = notification_data @@ -64,7 +63,6 @@ def send_notification(notification_type, from_user_id, to_user_id, extra_data): def send_push(user, data): """ used to send push notification to specific user """ - # if user.push_notification: notification_data = data.pop('data', None) user.fcmdevice_set.filter(active=True).send_message( Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data) diff --git a/web_admin/views.py b/web_admin/views.py index 3918f1e..b387a97 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -4,6 +4,7 @@ web_admin views file # django imports from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.response import Response +from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -22,13 +23,19 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel article api """ serializer_class = ArticleSerializer - # permission_classes = [IsAuthenticated, AdminPermission] - queryset = Article.objects.prefetch_related('article_cards', - 'article_survey', - 'article_survey__survey_options').order_by('-created_at') - + permission_classes = [IsAuthenticated, AdminPermission] + queryset = Article + filter_backends = (OrderingFilter, SearchFilter,) + search_fields = ['title'] http_method_names = ['get', 'post', 'put', 'delete'] + def get_queryset(self): + article = self.queryset.objects.filter(is_deleted=False).prefetch_related( + 'article_cards', 'article_survey', 'article_survey__survey_options' + ).order_by('-created_at') + queryset = self.filter_queryset(article) + return queryset + def create(self, request, *args, **kwargs): """ article create api method @@ -50,7 +57,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel :param kwargs: :return: success message """ - article = self.queryset.filter(id=kwargs['pk']).first() + article = self.get_object() serializer = self.serializer_class(article, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() @@ -64,7 +71,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel :param kwargs: :return: list of article """ - queryset = self.queryset.filter(is_deleted=False) + queryset = self.get_queryset() paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) @@ -78,8 +85,8 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel :param kwargs: :return: article detail data """ - queryset = self.queryset.filter(id=kwargs['pk'], is_deleted=False) - serializer = self.serializer_class(queryset, many=True) + queryset = self.get_object() + serializer = self.serializer_class(queryset, many=False) return custom_response(None, data=serializer.data) def destroy(self, request, *args, **kwargs): @@ -90,25 +97,13 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel :param kwargs: :return: success message """ - article = self.queryset.filter(id=kwargs['pk']).update(is_deleted=True) + article = self.get_object() + article.is_deleted = True + article.save() if article: return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='', url_path='', detail=False) - def search_article(self, request): - """ - article search api method - :param request: - :return: searched article - """ - search = request.GET.get('search') - queryset = self.queryset.filter(title__icontains=search) - paginator = self.pagination_class() - paginated_queryset = paginator.paginate_queryset(queryset, request) - serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, data=serializer.data) - @action(methods=['get'], url_name='remove_card', url_path='remove_card', detail=True, serializer_class=None) def remove_article_card(self, request, *args, **kwargs): @@ -144,7 +139,10 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel @action(methods=['post'], url_name='test-add-card', url_path='test-add-card', detail=False, serializer_class=ArticleCardSerializer) def add_card(self, request): - print(request.data) + """ + :param request: + :return: + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() From fdd0b88b11dfb9b76861c4e0b94faf1c27420797 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 14:48:18 +0530 Subject: [PATCH 123/372] add print in junior list --- junior/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/junior/views.py b/junior/views.py index 592326a..c951cb5 100644 --- a/junior/views.py +++ b/junior/views.py @@ -97,10 +97,18 @@ class JuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """ junior list""" + print("self.request.META====>",self.request.META) + print() auth_token = self.request.META['HTTP_AUTHORIZATION'] headers_token = { 'Authorization': auth_token } + print("auth_token====>", auth_token) + print("headers_token====>", headers_token) + print("os.getenv('BASE_URL')===>", os.getenv('BASE_URL')) + url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) + print("url data====>",url) + requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object From 8013767d71b7d83b956f9e4fb951c14034c62ee3 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 14:49:38 +0530 Subject: [PATCH 124/372] add print in junior list --- junior/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/junior/views.py b/junior/views.py index c951cb5..5de49dc 100644 --- a/junior/views.py +++ b/junior/views.py @@ -106,6 +106,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): print("auth_token====>", auth_token) print("headers_token====>", headers_token) print("os.getenv('BASE_URL')===>", os.getenv('BASE_URL')) + print("url====>", os.getenv('BASE_URL') + '/api/v1/top-junior/') url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) print("url data====>",url) From 2a225ea79df7619d4501455c6fde1165bd45ae53 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 16:19:30 +0530 Subject: [PATCH 125/372] add dev url for top junior api --- junior/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/junior/views.py b/junior/views.py index 5de49dc..42b1690 100644 --- a/junior/views.py +++ b/junior/views.py @@ -107,10 +107,10 @@ class JuniorListAPIView(viewsets.ModelViewSet): print("headers_token====>", headers_token) print("os.getenv('BASE_URL')===>", os.getenv('BASE_URL')) print("url====>", os.getenv('BASE_URL') + '/api/v1/top-junior/') - url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) - print("url data====>",url) + # url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) + # print("url data====>",url) - requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) + requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) @@ -294,7 +294,8 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): headers = { 'Authorization': token } - requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) + # requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) + requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers) serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From b32d4def2d0c79ea77138c7e59bd7b5ae26c6e3e Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 16:25:21 +0530 Subject: [PATCH 126/372] add qa top -junior url --- junior/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junior/views.py b/junior/views.py index 592326a..32258e3 100644 --- a/junior/views.py +++ b/junior/views.py @@ -101,7 +101,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): headers_token = { 'Authorization': auth_token } - requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) + requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) @@ -285,7 +285,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): headers = { 'Authorization': token } - requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) + requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers) serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 0a5eaa233fc31975fa046a7a7ad53b11080d25af Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 17:33:21 +0530 Subject: [PATCH 127/372] add qa top -junior url --- junior/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junior/views.py b/junior/views.py index 42b1690..9217a0d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -110,7 +110,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): # url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) # print("url data====>",url) - requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) + # requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) @@ -295,7 +295,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): 'Authorization': token } # requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) - requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers) + # requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers) serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From a5a309cf5bc35ee0af06b30ba76c881272d59bfa Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 18:33:18 +0530 Subject: [PATCH 128/372] comment top junior call api --- junior/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junior/views.py b/junior/views.py index 32258e3..b6a779f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -101,7 +101,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): headers_token = { 'Authorization': auth_token } - requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) + # requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) @@ -285,7 +285,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): headers = { 'Authorization': token } - requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers) + # requests.get('https://qa-api.zodqaapp.com/api/v1/top-junior/', headers=headers) serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From d86f7d3436aeaf74bab3ea2c43afb3a77f7caf57 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 21 Jul 2023 18:38:16 +0530 Subject: [PATCH 129/372] guardian code --- base/messages.py | 3 ++- junior/views.py | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/base/messages.py b/base/messages.py index 998a5b2..38c345d 100644 --- a/base/messages.py +++ b/base/messages.py @@ -74,7 +74,8 @@ ERROR_CODE = { "2041": "Article with given id doesn't exist.", "2042": "Article Card with given id doesn't exist.", "2043": "Article Survey with given id doesn't exist.", - "2044": "Task does not exist" + "2044": "Task does not exist", + "2045": "Invalid guardian" } """Success message code""" SUCCESS_CODE = { diff --git a/junior/views.py b/junior/views.py index 9217a0d..d67dfde 100644 --- a/junior/views.py +++ b/junior/views.py @@ -113,10 +113,12 @@ class JuniorListAPIView(viewsets.ModelViewSet): # requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object - queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - # use JuniorDetailListSerializer serializer - serializer = JuniorDetailListSerializer(queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + if guardian_data: + queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + # use JuniorDetailListSerializer serializer + serializer = JuniorDetailListSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" From a2b4f3b75864103d69a0860a267a39e02985192a Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 24 Jul 2023 11:00:22 +0530 Subject: [PATCH 130/372] update position of the junior --- junior/utils.py | 14 +++++++++++++- junior/views.py | 27 +++++---------------------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/junior/utils.py b/junior/utils.py index e1fb5df..418f3c2 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -3,7 +3,8 @@ from django.conf import settings """Third party Django app""" from templated_email import send_templated_mail - +from .models import JuniorPoints +from django.db.models import F # junior notification # email for sending email # when guardian create junior profile @@ -45,3 +46,14 @@ def junior_approval_mail(guardian, full_name): } ) return full_name + +def update_positions_based_on_points(): + # First, retrieve all the JuniorPoints instances ordered by total_points in descending order. + juniors_points = JuniorPoints.objects.order_by('-total_points') + + # Now, iterate through the queryset and update the position field based on the order. + position = 1 + for junior_point in juniors_points: + junior_point.position = position + junior_point.save() + position += 1 diff --git a/junior/views.py b/junior/views.py index d67dfde..a764adf 100644 --- a/junior/views.py +++ b/junior/views.py @@ -19,7 +19,7 @@ import requests # Import account's serializer # Import account's task # import junior serializer -from junior.models import Junior +from junior.models import Junior, JuniorPoints from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer) from guardian.models import Guardian, JuniorTask @@ -28,6 +28,7 @@ from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from account.utils import custom_response, custom_error_response from guardian.utils import upload_image_to_alibaba +from .utils import update_positions_based_on_points """ Define APIs """ # Define validate guardian code API, @@ -97,20 +98,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """ junior list""" - print("self.request.META====>",self.request.META) - print() - auth_token = self.request.META['HTTP_AUTHORIZATION'] - headers_token = { - 'Authorization': auth_token - } - print("auth_token====>", auth_token) - print("headers_token====>", headers_token) - print("os.getenv('BASE_URL')===>", os.getenv('BASE_URL')) - print("url====>", os.getenv('BASE_URL') + '/api/v1/top-junior/') - # url = requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token) - # print("url data====>",url) - - # requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers_token) + update_positions_based_on_points() guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object if guardian_data: @@ -149,7 +137,7 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): """Get the queryset for the view""" guardian = Guardian.objects.filter(user__email=self.request.user).last() junior_queryset = Junior.objects.filter(guardian_code__icontains=str(guardian.guardian_code), - is_invited=True) + is_invited=True) return junior_queryset def list(self, request, *args, **kwargs): """ junior list""" @@ -292,12 +280,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): """profile view""" queryset = self.get_queryset() # update position of junior - token = self.request.META['HTTP_AUTHORIZATION'] - headers = { - 'Authorization': token - } - # requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers) - # requests.get('https://dev-api.zodqaapp.com/api/v1/top-junior/', headers=headers) + update_positions_based_on_points() serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From ed28117a7f30f0f1d444e8bf6a44b2bab53e849b Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 24 Jul 2023 15:10:59 +0530 Subject: [PATCH 131/372] bugs and push notification for create task --- base/messages.py | 6 +++++- celerybeat-schedule | Bin 0 -> 16384 bytes guardian/serializers.py | 8 ++++++++ guardian/views.py | 14 ++++++++++++-- junior/views.py | 5 ++++- notifications/constants.py | 5 +++++ 6 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 celerybeat-schedule diff --git a/base/messages.py b/base/messages.py index 38c345d..865a6d6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -75,7 +75,11 @@ ERROR_CODE = { "2042": "Article Card with given id doesn't exist.", "2043": "Article Survey with given id doesn't exist.", "2044": "Task does not exist", - "2045": "Invalid guardian" + "2045": "Invalid guardian", + "2046": "Due date must be future date", + "2047": "Invalid Junior ID ", + "2048": "Choose right file for image", + "2049": "This task is already requested " } """Success message code""" SUCCESS_CODE = { diff --git a/celerybeat-schedule b/celerybeat-schedule new file mode 100644 index 0000000000000000000000000000000000000000..2e805bda3e0039d93c7d85c2524de869889c8544 GIT binary patch literal 16384 zcmeI(J#W)M7zgl6-8RjY^o2kPAR#1>S{STWyo?B`>VPU1Q-sBUWnT_r)m-w}XG9}K z$`rxI%m+Y>h%W#u6CZ$yiH%Qy1b4AhGO!dxB+%cH{anu0aJWqwu7Fc&Mh{~2f4QPP_69i3;*JV}f#c7a>0WcMxLf9o?wJn9bllzBHg9bumMV-A*%~;{ z+{r=wHFK4aiA)dNC=~lrwE~gIP~}~!i17&-YJNwwWh+nQj+w#%r%9ty4~OqSsL}Ru4P;zG>XD4Vw4u9~pvN zbz`xgNU9EL!K2#Y*WkOAAuW1zY)EyFmUh{Ac3C;GjEl6RbQp13-tbK_uuWPS#jCVB zikInl+q^V2JUY>*lhLIrB&WkIrPS>Bj~E2tWV=5P$## PAOHafKmY;|n7Y6ZTU<&y literal 0 HcmV?d00001 diff --git a/guardian/serializers.py b/guardian/serializers.py index a5ad978..a8a2e46 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -169,6 +169,14 @@ class TaskSerializer(serializers.ModelSerializer): """Meta info""" model = JuniorTask fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image'] + + def validate_due_date(self, value): + """validation on due date""" + if value < datetime.today().date(): + raise serializers.ValidationError({"details": ERROR_CODE['2046'], + "code": 400, "status": "failed", + }) + return value def create(self, validated_data): """create default task image data""" validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() diff --git a/guardian/views.py b/guardian/views.py index a320385..973ac19 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -30,7 +30,7 @@ from account.utils import custom_response, custom_error_response from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from .utils import upload_image_to_alibaba -from notifications.constants import REGISTRATION +from notifications.constants import REGISTRATION, TASK_CREATED from notifications.utils import send_notification """ Define APIs """ @@ -119,6 +119,7 @@ class TaskListAPIView(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination queryset = JuniorTask.objects.all() + http_method_names = ('get',) def list(self, request, *args, **kwargs): """Create guardian profile""" @@ -146,9 +147,17 @@ class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" serializer_class = TaskSerializer queryset = JuniorTask.objects.all() + http_method_names = ('post', ) def create(self, request, *args, **kwargs): 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 @@ -164,6 +173,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): 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) @@ -172,7 +183,6 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() def get_queryset(self): """Get the queryset for the view""" diff --git a/junior/views.py b/junior/views.py index a764adf..67f24e9 100644 --- a/junior/views.py +++ b/junior/views.py @@ -256,7 +256,10 @@ class CompleteJuniorTaskAPIView(views.APIView): # fetch junior query task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() if task_queryset: - # use RemoveJuniorSerializer serializer + # use CompleteTaskSerializer serializer + if task_queryset.task_status in ['4', '5']: + """Already request send """ + return custom_error_response(ERROR_CODE['2049'], response_status=status.HTTP_400_BAD_REQUEST) serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) if serializer.is_valid(): # save serializer diff --git a/notifications/constants.py b/notifications/constants.py index b72ed61..25602a8 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -2,6 +2,7 @@ notification constants file """ REGISTRATION = 1 +TASK_CREATED = 2 TEST_NOTIFICATION = 99 NOTIFICATION_DICT = { @@ -9,6 +10,10 @@ 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." + }, TEST_NOTIFICATION: { "title": "Test Notification", "body": "This notification is for testing purpose" From 096961976d6b3461b0ff1293195b92aa4078de0d Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 24 Jul 2023 15:28:54 +0530 Subject: [PATCH 132/372] sonar issues --- base/constants.py | 3 +++ base/messages.py | 1 + guardian/utils.py | 7 ++++--- junior/utils.py | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/base/constants.py b/base/constants.py index 2ad8c83..93bcfde 100644 --- a/base/constants.py +++ b/base/constants.py @@ -103,3 +103,6 @@ MAX_ARTICLE_CARD = 6 # min and max survey MIN_ARTICLE_SURVEY = 5 MAX_ARTICLE_SURVEY = 10 + +# real time url +time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" diff --git a/base/messages.py b/base/messages.py index 865a6d6..059d9e8 100644 --- a/base/messages.py +++ b/base/messages.py @@ -77,6 +77,7 @@ ERROR_CODE = { "2044": "Task does not exist", "2045": "Invalid guardian", "2046": "Due date must be future date", + # invalid junior id msg "2047": "Invalid Junior ID ", "2048": "Choose right file for image", "2049": "This task is already requested " diff --git a/guardian/utils.py b/guardian/utils.py index 3254b82..adb7f87 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -10,7 +10,7 @@ import tempfile # Import date time module's function from datetime import datetime, time # import Number constant -from base.constants import NUMBER +from base.constants import NUMBER, time_url # Import Junior's model from junior.models import Junior, JuniorPoints @@ -45,8 +45,9 @@ def upload_image_to_alibaba(image, filename): return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" -def real_time(timezone='Asia/Riyadh'): - url = f'http://worldtimeapi.org/api/timezone/{timezone}' +def real_time(): + """fetch real time from world time api""" + url = time_url response = requests.get(url) if response.status_code == 200: data = response.json() diff --git a/junior/utils.py b/junior/utils.py index 418f3c2..fefe950 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -48,6 +48,7 @@ def junior_approval_mail(guardian, full_name): return full_name def update_positions_based_on_points(): + """Update position of the junior""" # First, retrieve all the JuniorPoints instances ordered by total_points in descending order. juniors_points = JuniorPoints.objects.order_by('-total_points') From 79e85fa968e1da9210feb396630bda5fb814768f Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 11:17:57 +0530 Subject: [PATCH 133/372] jira-27 invite guardian api --- base/messages.py | 4 +- celerybeat-schedule | Bin 16384 -> 16384 bytes ...k_is_invited_juniortask_is_password_set.py | 23 +++++++ ...8_remove_juniortask_is_invited_and_more.py | 21 ++++++ ...ian_is_invited_guardian_is_password_set.py | 23 +++++++ guardian/models.py | 4 ++ junior/serializers.py | 62 ++++++++++++++++-- junior/urls.py | 5 +- junior/views.py | 26 +++++++- notifications/constants.py | 10 +++ 10 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 guardian/migrations/0017_juniortask_is_invited_juniortask_is_password_set.py create mode 100644 guardian/migrations/0018_remove_juniortask_is_invited_and_more.py create mode 100644 guardian/migrations/0019_guardian_is_invited_guardian_is_password_set.py diff --git a/base/messages.py b/base/messages.py index 059d9e8..7bec3cc 100644 --- a/base/messages.py +++ b/base/messages.py @@ -80,7 +80,8 @@ ERROR_CODE = { # invalid junior id msg "2047": "Invalid Junior ID ", "2048": "Choose right file for image", - "2049": "This task is already requested " + "2049": "This task is already requested ", + "2059": "Already exist junior" } """Success message code""" SUCCESS_CODE = { @@ -128,6 +129,7 @@ SUCCESS_CODE = { "3031": "Article Survey has been removed successfully.", "3032": "Task request sent successfully", "3033": "Valid Referral code", + "3034": "Invite guardian successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/celerybeat-schedule b/celerybeat-schedule index 2e805bda3e0039d93c7d85c2524de869889c8544..cd2e03e65079afc52f74f782d60c0b3e9a98a19e 100644 GIT binary patch delta 282 zcmZo@U~Fh$T%f?nJeg5JTuPgf0SsIXAhZ`Rgx+Q}(a>shj=~aN-U3625Ic~v-z=-w z$Hc~?$;|40VDd(T{gYh{+$RSb$S^Z!Fi)OfAd2+OwP_pE#zn`-SpAJ(1 diff --git a/guardian/migrations/0017_juniortask_is_invited_juniortask_is_password_set.py b/guardian/migrations/0017_juniortask_is_invited_juniortask_is_password_set.py new file mode 100644 index 0000000..e340016 --- /dev/null +++ b/guardian/migrations/0017_juniortask_is_invited_juniortask_is_password_set.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.2 on 2023-07-24 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0016_juniortask_completed_on_juniortask_rejected_on_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='juniortask', + name='is_invited', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='juniortask', + name='is_password_set', + field=models.BooleanField(default=True), + ), + ] diff --git a/guardian/migrations/0018_remove_juniortask_is_invited_and_more.py b/guardian/migrations/0018_remove_juniortask_is_invited_and_more.py new file mode 100644 index 0000000..a9870e1 --- /dev/null +++ b/guardian/migrations/0018_remove_juniortask_is_invited_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-24 13:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0017_juniortask_is_invited_juniortask_is_password_set'), + ] + + operations = [ + migrations.RemoveField( + model_name='juniortask', + name='is_invited', + ), + migrations.RemoveField( + model_name='juniortask', + name='is_password_set', + ), + ] diff --git a/guardian/migrations/0019_guardian_is_invited_guardian_is_password_set.py b/guardian/migrations/0019_guardian_is_invited_guardian_is_password_set.py new file mode 100644 index 0000000..6c188e4 --- /dev/null +++ b/guardian/migrations/0019_guardian_is_invited_guardian_is_password_set.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.2 on 2023-07-24 13:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0018_remove_juniortask_is_invited_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='guardian', + name='is_invited', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='guardian', + name='is_password_set', + field=models.BooleanField(default=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index b4a31dd..6ecd27b 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -53,6 +53,10 @@ class Guardian(models.Model): gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None) """date of birth of the guardian""" dob = models.DateField(max_length=15, null=True, blank=True, default=None) + # invited junior""" + is_invited = models.BooleanField(default=False) + # Profile activity""" + is_password_set = models.BooleanField(default=True) """Profile activity""" is_active = models.BooleanField(default=True) """guardian is verified or not""" diff --git a/junior/serializers.py b/junior/serializers.py index b1d1615..9b8a8fe 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -17,7 +17,8 @@ from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp from junior.utils import junior_notification_email, junior_approval_mail from guardian.utils import real_time, update_referral_points - +from notifications.utils import send_notification +from notifications.constants import INVITED_GUARDIAN, APPROVED_JUNIOR class ListCharField(serializers.ListField): @@ -272,7 +273,6 @@ class AddJuniorSerializer(serializers.ModelSerializer): user_data = User.objects.create(username=email, email=email, first_name=self.context['first_name'], last_name=self.context['last_name']) - password = User.objects.make_random_password() user_data.set_password(password) user_data.save() @@ -288,8 +288,6 @@ class AddJuniorSerializer(serializers.ModelSerializer): expiry_time = timezone.now() + timezone.timedelta(days=1) UserEmailOtp.objects.create(email=email, otp=otp_value, user_type='1', expired_at=expiry_time, is_verified=True) - """Send email to the register user""" - send_otp_email(email, otp_value) """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(guardian, full_name) @@ -374,3 +372,59 @@ class JuniorPointsSerializer(serializers.ModelSerializer): model = Junior fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', 'requested_task', 'rejected_task'] + +class AddGuardianSerializer(serializers.ModelSerializer): + """Add guardian serializer""" + auth_token = serializers.SerializerMethodField('get_auth_token') + + def get_auth_token(self, obj): + """auth token""" + refresh = RefreshToken.for_user(obj) + access_token = str(refresh.access_token) + return access_token + + class Meta(object): + """Meta info""" + model = Guardian + fields = ['id', 'auth_token'] + + + def create(self, validated_data): + """ invite and create guardian""" + with transaction.atomic(): + email = self.context['email'] + junior = self.context['user'] + full_name = self.context['first_name'] + ' ' + self.context['last_name'] + junior_data = Junior.objects.filter(auth__username=junior).last() + instance = User.objects.filter(username=email).last() + if instance: + guardian_data = Guardian.objects.filter(user=user).update(is_invited=True, + referral_code=generate_code(ZOD, + instance.id), + referral_code_used=junior_data.referral_code, + is_verified=True) + return guardian_data + else: + user = User.objects.create(username=email, email=email, + first_name=self.context['first_name'], + last_name=self.context['last_name']) + + password = User.objects.make_random_password() + user.set_password(password) + user.save() + Guardian.objects.create(user=user, is_invited=True, + referral_code=generate_code(ZOD, user.id), + referral_code_used=junior_data.referral_code, + is_password_set=False, is_verified=True) + """Generate otp""" + otp_value = generate_otp() + expiry_time = timezone.now() + timezone.timedelta(days=1) + UserEmailOtp.objects.create(email=email, otp=otp_value, + user_type=str(NUMBER['two']), expired_at=expiry_time, + is_verified=True) + """Notification email""" + junior_notification_email(email, full_name, email, password) + junior_approval_mail(email, full_name) + send_notification(INVITED_GUARDIAN, None, junior_data.auth.id, {}) + send_notification(APPROVED_JUNIOR, None, email, {}) + return instance diff --git a/junior/urls.py b/junior/urls.py index e4a160d..bd3ed35 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -3,7 +3,8 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, - CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode) + CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, + InviteGuardianAPIView) """Third party import""" from rest_framework import routers @@ -38,6 +39,8 @@ router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task router.register('junior-points', JuniorPointsListAPIView, basename='junior-points') # validate referral code API""" router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') +# invite guardian API""" +router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 67f24e9..aecbf80 100644 --- a/junior/views.py +++ b/junior/views.py @@ -4,6 +4,7 @@ import os from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination +from django.contrib.auth.models import User import requests """Django app import""" @@ -21,7 +22,8 @@ import requests # import junior serializer from junior.models import Junior, JuniorPoints from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ - RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer) + RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, + AddGuardianSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -117,6 +119,9 @@ class AddJuniorAPIView(viewsets.ModelViewSet): """ junior list""" info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name']} + if User.objects.filter(username=request.data['email']): + return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) + # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info) if serializer.is_valid(): @@ -257,7 +262,7 @@ class CompleteJuniorTaskAPIView(views.APIView): task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() if task_queryset: # use CompleteTaskSerializer serializer - if task_queryset.task_status in ['4', '5']: + if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]: """Already request send """ return custom_error_response(ERROR_CODE['2049'], response_status=status.HTTP_400_BAD_REQUEST) serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) @@ -311,3 +316,20 @@ class ValidateReferralCode(viewsets.ViewSet): if self.get_queryset(): return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK) return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST) + +class InviteGuardianAPIView(viewsets.ModelViewSet): + """Invite guardian by junior""" + serializer_class = AddGuardianSerializer + permission_classes = [IsAuthenticated] + + def create(self, request, *args, **kwargs): + """ junior list""" + info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name']} + # use AddJuniorSerializer serializer + serializer = AddGuardianSerializer(data=request.data, context=info) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/notifications/constants.py b/notifications/constants.py index 25602a8..c6f0a9f 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -3,6 +3,8 @@ notification constants file """ REGISTRATION = 1 TASK_CREATED = 2 +INVITED_GUARDIAN = 3 +APPROVED_JUNIOR = 4 TEST_NOTIFICATION = 99 NOTIFICATION_DICT = { @@ -14,6 +16,14 @@ NOTIFICATION_DICT = { "title": "Task created!", "body": "Task created successfully." }, + INVITED_GUARDIAN: { + "title": "Invite guardian", + "body": "Invite guardian successfully" + }, + APPROVED_JUNIOR: { + "title": "Approve junior", + "body": "You have request for associate the junior" + }, TEST_NOTIFICATION: { "title": "Test Notification", "body": "This notification is for testing purpose" From e6d45f3bf2f7059d6de0087621ce0ff4878bdc7a Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 12:02:59 +0530 Subject: [PATCH 134/372] start task api --- base/messages.py | 6 ++++-- junior/serializers.py | 11 +++++++++++ junior/urls.py | 5 +++-- junior/views.py | 35 +++++++++++++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/base/messages.py b/base/messages.py index 7bec3cc..61ee573 100644 --- a/base/messages.py +++ b/base/messages.py @@ -81,7 +81,8 @@ ERROR_CODE = { "2047": "Invalid Junior ID ", "2048": "Choose right file for image", "2049": "This task is already requested ", - "2059": "Already exist junior" + "2059": "Already exist junior", + "2060": "Task does not exist or not in pending state" } """Success message code""" SUCCESS_CODE = { @@ -129,7 +130,8 @@ SUCCESS_CODE = { "3031": "Article Survey has been removed successfully.", "3032": "Task request sent successfully", "3033": "Valid Referral code", - "3034": "Invite guardian successfully" + "3034": "Invite guardian successfully", + "3035": "Task started successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/junior/serializers.py b/junior/serializers.py index 9b8a8fe..d2d6b6d 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -428,3 +428,14 @@ class AddGuardianSerializer(serializers.ModelSerializer): send_notification(INVITED_GUARDIAN, None, junior_data.auth.id, {}) send_notification(APPROVED_JUNIOR, None, email, {}) return instance + +class StartTaskSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = JuniorTask + fields = ('id', 'task_status') + def update(self, instance, validated_data): + instance.task_status = str(NUMBER['two']) + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index bd3ed35..e4b2489 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView) + InviteGuardianAPIView, StartTaskAPIView) """Third party import""" from rest_framework import routers @@ -45,5 +45,6 @@ router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guard urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()), - path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()) + path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()), + path('api/v1/start-task/', StartTaskAPIView.as_view()) ] diff --git a/junior/views.py b/junior/views.py index aecbf80..acbb5b2 100644 --- a/junior/views.py +++ b/junior/views.py @@ -23,7 +23,7 @@ import requests from junior.models import Junior, JuniorPoints from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, - AddGuardianSerializer) + AddGuardianSerializer, StartTaskSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -97,6 +97,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer queryset = Junior.objects.all() permission_classes = [IsAuthenticated] + http_method_names = ('get',) def list(self, request, *args, **kwargs): """ junior list""" @@ -115,6 +116,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): queryset = Junior.objects.all() serializer_class = AddJuniorSerializer permission_classes = [IsAuthenticated] + http_method_names = ('post',) def create(self, request, *args, **kwargs): """ junior list""" info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], @@ -137,6 +139,7 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): queryset = Junior.objects.all() permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination + http_method_names = ('get',) def get_queryset(self): """Get the queryset for the view""" @@ -161,6 +164,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination queryset = Junior.objects.all() + http_method_names = ('get',) def get_queryset(self): """Get the queryset for the view""" @@ -212,6 +216,7 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination queryset = JuniorTask.objects.all() + http_method_names = ('get',) def list(self, request, *args, **kwargs): """Create guardian profile""" @@ -280,6 +285,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): """Junior Points viewset""" serializer_class = JuniorPointsSerializer permission_classes = [IsAuthenticated] + http_method_names = ('get',) def get_queryset(self): """get queryset""" @@ -295,6 +301,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): class ValidateReferralCode(viewsets.ViewSet): """Check guardian code exist or not""" permission_classes = [IsAuthenticated] + http_method_names = ('get',) def get_queryset(self): """Get queryset based on referral_code.""" @@ -321,7 +328,7 @@ class InviteGuardianAPIView(viewsets.ModelViewSet): """Invite guardian by junior""" serializer_class = AddGuardianSerializer permission_classes = [IsAuthenticated] - + http_method_names = ('post',) def create(self, request, *args, **kwargs): """ junior list""" info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], @@ -333,3 +340,27 @@ class InviteGuardianAPIView(viewsets.ModelViewSet): serializer.save() return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + + +class StartTaskAPIView(views.APIView): + """Update junior task API""" + serializer_class = StartTaskSerializer + model = JuniorTask + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + try: + task_id = self.request.data.get('task_id') + task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() + if task_queryset and task_queryset.task_status == str(NUMBER['one']): + # use StartTaskSerializer serializer + serializer = StartTaskSerializer(task_queryset, data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3035'], None, 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['2060'], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From bb56fc7a33de092d15d67bf97307553dc15ff426 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 12:53:56 +0530 Subject: [PATCH 135/372] try and except in api --- base/constants.py | 3 +- guardian/views.py | 282 +++++++++++++++++++++++++--------------------- junior/models.py | 28 ++++- junior/views.py | 269 ++++++++++++++++++++++++------------------- 4 files changed, 333 insertions(+), 249 deletions(-) diff --git a/base/constants.py b/base/constants.py index 93bcfde..b351cde 100644 --- a/base/constants.py +++ b/base/constants.py @@ -61,7 +61,8 @@ TASK_STATUS = ( ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), - ('5', 'completed') + ('5', 'completed'), + ('6', 'expired') ) # sign up method SIGNUP_METHODS = ( diff --git a/guardian/views.py b/guardian/views.py index 973ac19..24cc345 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -51,24 +51,27 @@ class SignupViewset(viewsets.ModelViewSet): serializer_class = UserSerializer def create(self, request, *args, **kwargs): """Create user profile""" - if request.data['user_type'] in ['1', '2']: - serializer = UserSerializer(context=request.data['user_type'], data=request.data) - if serializer.is_valid(): - user = serializer.save() - """Generate otp""" - otp = generate_otp() - expiry = timezone.now() + timezone.timedelta(days=1) - # create user email otp object - UserEmailOtp.objects.create(email=request.data['email'], otp=otp, - user_type=str(request.data['user_type']), expired_at=expiry) - """Send email to the register user""" - send_otp_email(request.data['email'], otp) - send_notification(REGISTRATION, None, user.id, {}) - return custom_response(SUCCESS_CODE['3001'], - response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) - else: - return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST) + try: + 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(): + user = serializer.save() + """Generate otp""" + otp = generate_otp() + expiry = timezone.now() + timezone.timedelta(days=1) + # create user email otp object + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, + user_type=str(request.data['user_type']), expired_at=expiry) + """Send email to the register user""" + send_otp_email(request.data['email'], otp) + send_notification(REGISTRATION, None, user.id, {}) + return custom_response(SUCCESS_CODE['3001'], + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2028'], 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 UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" @@ -78,26 +81,29 @@ class UpdateGuardianProfile(viewsets.ViewSet): def create(self, request, *args, **kwargs): """Create guardian profile""" - data = request.data - image = request.data.get('image') - image_url = '' - if image: - if image and image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) - filename = f"images/{image.name}" - # upload image on ali baba - image_url = upload_image_to_alibaba(image, filename) - data = {"image":image_url} - serializer = CreateGuardianSerializer(context={"user":request.user, - "first_name":request.data.get('first_name'), - "last_name": request.data.get('last_name'), - "image":image_url}, - data=data) - if serializer.is_valid(): - """save serializer""" - serializer.save() - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + try: + data = request.data + image = request.data.get('image') + image_url = '' + if image: + if image and image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + filename = f"images/{image.name}" + # upload image on ali baba + image_url = upload_image_to_alibaba(image, filename) + data = {"image":image_url} + serializer = CreateGuardianSerializer(context={"user":request.user, + "first_name":request.data.get('first_name'), + "last_name": request.data.get('last_name'), + "image":image_url}, + data=data) + if serializer.is_valid(): + """save serializer""" + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class AllTaskListAPIView(viewsets.ModelViewSet): @@ -123,25 +129,28 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" - status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_name__icontains=search).order_by('due_date', 'created_at') - elif search and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, - task_status=status_value).order_by('due_date', 'created_at') - if search is None and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') - elif search is None and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=status_value).order_by('due_date','created_at') - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + try: + status_value = self.request.GET.get('status') + search = self.request.GET.get('search') + if search and str(status_value) == '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user, + task_name__icontains=search).order_by('due_date', 'created_at') + elif search and str(status_value) != '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, + task_status=status_value).order_by('due_date', 'created_at') + if search is None and str(status_value) == '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') + elif search is None and str(status_value) != '0': + queryset = JuniorTask.objects.filter(guardian__user=request.user, + task_status=status_value).order_by('due_date','created_at') + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer + serializer = TaskDetailsSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" @@ -150,33 +159,36 @@ class CreateTaskAPIView(viewsets.ModelViewSet): http_method_names = ('post', ) def create(self, request, *args, **kwargs): - 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 - 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) + 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 + 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) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class SearchTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" @@ -194,14 +206,16 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" - queryset = self.get_queryset() - - paginator = self.pagination_class() - # use pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + try: + queryset = self.get_queryset() + paginator = self.pagination_class() + # use pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskSerializer serializer + serializer = TaskDetailsSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class TopJuniorListAPIView(viewsets.ModelViewSet): @@ -218,15 +232,18 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Fetch junior list of those who complete their tasks""" - junior_total_points = self.get_queryset().order_by('-total_points') + 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 - junior.save() + # Update the position field for each JuniorPoints object + for index, junior in enumerate(junior_total_points): + junior.position = index + 1 + junior.save() - serializer = self.get_serializer(junior_total_points, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + 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): @@ -243,19 +260,22 @@ class ApproveJuniorAPIView(viewsets.ViewSet): def create(self, request, *args, **kwargs): """ junior list""" - queryset = self.get_queryset() - # action 1 is use for approve and 2 for reject - if request.data['action'] == '1': - # use ApproveJuniorSerializer serializer - serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code, - "junior": queryset[1], "action": request.data['action']}, - data=request.data) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) - else: - return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) + try: + queryset = self.get_queryset() + # action 1 is use for approve and 2 for reject + if request.data['action'] == '1': + # use ApproveJuniorSerializer serializer + serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code, + "junior": queryset[1], "action": request.data['action']}, + data=request.data) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) + else: + return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class ApproveTaskAPIView(viewsets.ViewSet): """approve junior by guardian""" @@ -274,20 +294,24 @@ class ApproveTaskAPIView(viewsets.ViewSet): def create(self, request, *args, **kwargs): """ junior list""" # action 1 is use for approve and 2 for reject - queryset = self.get_queryset() - # use ApproveJuniorSerializer serializer - serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code, - "task_instance": queryset[1], - "action": str(request.data['action']), - "junior": self.request.data['junior_id']}, - data=request.data) - if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK) - elif str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) - else: - return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST) + try: + queryset = self.get_queryset() + # use ApproveJuniorSerializer serializer + serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code, + "task_instance": queryset[1], + "action": str(request.data['action']), + "junior": self.request.data['junior_id']}, + data=request.data) + if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK) + elif str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) + else: + return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + diff --git a/junior/models.py b/junior/models.py index 28754b1..8098109 100644 --- a/junior/models.py +++ b/junior/models.py @@ -7,6 +7,7 @@ from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField """Import django app""" from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP + """Define User model""" User = get_user_model() # Create your models here. @@ -31,7 +32,7 @@ User = get_user_model() """Define junior points model""" # points of the junior # position of the junior - +# define junior guardian relation model class Junior(models.Model): """Junior model""" auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email') @@ -109,3 +110,28 @@ class JuniorPoints(models.Model): def __str__(self): """Return email id""" return f'{self.junior.auth}' + +class JuniorGuardianRelationship(models.Model): + """Junior Guardian relationship model""" + guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian') + # associated junior with the task + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') + # relation between guardian and junior""" + relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, + default='1') + """Profile created and updated time""" + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta(object): + """ Meta class """ + db_table = 'junior_guardian_relation' + """verbose name of the model""" + verbose_name = 'Junior Guardian Relation' + verbose_name_plural = 'Junior Guardian Relation' + + def __str__(self): + """Return email id""" + return f'{self.guardian.user}' + + diff --git a/junior/views.py b/junior/views.py index acbb5b2..00dcaa4 100644 --- a/junior/views.py +++ b/junior/views.py @@ -53,27 +53,30 @@ class UpdateJuniorProfile(viewsets.ViewSet): def create(self, request, *args, **kwargs): """Use CreateJuniorSerializer""" - request_data = request.data - image = request.data.get('image') - image_url = '' - if image: - if image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) - # convert into file - filename = f"images/{image.name}" - # upload image on ali baba - image_url = upload_image_to_alibaba(image, filename) - request_data = {"image": image_url} - serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url, - "first_name": request.data.get('first_name'), - "last_name": request.data.get('last_name') - }, - data=request_data) - if serializer.is_valid(): - """save serializer""" - serializer.save() - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + try: + request_data = request.data + image = request.data.get('image') + image_url = '' + if image: + if image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + # convert into file + filename = f"images/{image.name}" + # upload image on ali baba + image_url = upload_image_to_alibaba(image, filename) + request_data = {"image": image_url} + serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url, + "first_name": request.data.get('first_name'), + "last_name": request.data.get('last_name') + }, + data=request_data) + if serializer.is_valid(): + """save serializer""" + serializer.save() + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class ValidateGuardianCode(viewsets.ViewSet): """Check guardian code exist or not""" @@ -82,14 +85,17 @@ class ValidateGuardianCode(viewsets.ViewSet): def list(self, request, *args, **kwargs): """check guardian code""" - guardian_code = self.request.GET.get('guardian_code').split(',') - for code in guardian_code: - # fetch guardian object - guardian_data = Guardian.objects.filter(guardian_code=code).exists() - if guardian_data: - return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) - else: - return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) + try: + guardian_code = self.request.GET.get('guardian_code').split(',') + for code in guardian_code: + # fetch guardian object + guardian_data = Guardian.objects.filter(guardian_code=code).exists() + if guardian_data: + return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) + else: + return custom_error_response(ERROR_CODE["2022"], 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 JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" @@ -101,15 +107,18 @@ class JuniorListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """ junior list""" - update_positions_based_on_points() - guardian_data = Guardian.objects.filter(user__email=request.user).last() - # fetch junior object - if guardian_data: - queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - # use JuniorDetailListSerializer serializer - serializer = JuniorDetailListSerializer(queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) + try: + update_positions_based_on_points() + guardian_data = Guardian.objects.filter(user__email=request.user).last() + # fetch junior object + if guardian_data: + queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + # use JuniorDetailListSerializer serializer + serializer = JuniorDetailListSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" @@ -119,18 +128,21 @@ class AddJuniorAPIView(viewsets.ModelViewSet): http_method_names = ('post',) def create(self, request, *args, **kwargs): """ junior list""" - info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name']} - if User.objects.filter(username=request.data['email']): - return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) + try: + info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name']} + if User.objects.filter(username=request.data['email']): + return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) - # use AddJuniorSerializer serializer - serializer = AddJuniorSerializer(data=request.data, context=info) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + # use AddJuniorSerializer serializer + serializer = AddJuniorSerializer(data=request.data, context=info) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class InvitedJuniorAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" @@ -149,13 +161,16 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): return junior_queryset def list(self, request, *args, **kwargs): """ junior list""" - queryset = self.get_queryset() - paginator = self.pagination_class() - # pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use JuniorDetailListSerializer serializer - serializer = JuniorDetailListSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + try: + queryset = self.get_queryset() + paginator = self.pagination_class() + # pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use JuniorDetailListSerializer serializer + serializer = JuniorDetailListSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class FilterJuniorAPIView(viewsets.ModelViewSet): @@ -177,13 +192,16 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" - queryset = self.get_queryset() - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use JuniorDetailListSerializer serializer - serializer = JuniorDetailListSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + try: + queryset = self.get_queryset() + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use JuniorDetailListSerializer serializer + serializer = JuniorDetailListSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class RemoveJuniorAPIView(views.APIView): @@ -193,21 +211,24 @@ class RemoveJuniorAPIView(views.APIView): permission_classes = [IsAuthenticated] def put(self, request, format=None): - junior_id = self.request.GET.get('id') - guardian = Guardian.objects.filter(user__email=self.request.user).last() - # fetch junior query - junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code), - is_invited=True).last() - if junior_queryset: - # use RemoveJuniorSerializer serializer - serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) - else: - return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) + try: + junior_id = self.request.GET.get('id') + guardian = Guardian.objects.filter(user__email=self.request.user).last() + # fetch junior query + junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code), + is_invited=True).last() + if junior_queryset: + # use RemoveJuniorSerializer serializer + serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class JuniorTaskListAPIView(viewsets.ModelViewSet): @@ -220,29 +241,32 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" - status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search and str(status_value) == '0': - # search with title and for all task list - queryset = JuniorTask.objects.filter(junior__auth=request.user, - task_name__icontains=search).order_by('due_date', 'created_at') - elif search and str(status_value) != '0': - # search with title and fetch task list with status wise - queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search, - task_status=status_value).order_by('due_date', 'created_at') - if search is None and str(status_value) == '0': - # fetch all task list - queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at') - elif search is None and str(status_value) != '0': - # fetch task list with status wise - queryset = JuniorTask.objects.filter(junior__auth=request.user, - task_status=status_value).order_by('due_date','created_at') - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + 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') + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer + serializer = TaskDetailsSerializer(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class CompleteJuniorTaskAPIView(views.APIView): @@ -292,11 +316,14 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): return JuniorTask.objects.filter(junior__auth__email=self.request.user).last() def list(self, request, *args, **kwargs): """profile view""" - queryset = self.get_queryset() - # update position of junior - update_positions_based_on_points() - serializer = JuniorPointsSerializer(queryset) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + try: + queryset = self.get_queryset() + # update position of junior + update_positions_based_on_points() + serializer = JuniorPointsSerializer(queryset) + 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 ValidateReferralCode(viewsets.ViewSet): """Check guardian code exist or not""" @@ -320,9 +347,12 @@ class ValidateReferralCode(viewsets.ViewSet): def list(self, request, *args, **kwargs): """check guardian code""" - if self.get_queryset(): - return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST) + try: + if self.get_queryset(): + return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class InviteGuardianAPIView(viewsets.ModelViewSet): """Invite guardian by junior""" @@ -331,15 +361,18 @@ class InviteGuardianAPIView(viewsets.ModelViewSet): http_method_names = ('post',) def create(self, request, *args, **kwargs): """ junior list""" - info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name']} - # use AddJuniorSerializer serializer - serializer = AddGuardianSerializer(data=request.data, context=info) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + try: + info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name']} + # use AddJuniorSerializer serializer + serializer = AddGuardianSerializer(data=request.data, context=info) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(serializer.error, 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 StartTaskAPIView(views.APIView): From 6be06829135a2020b3debb1ef43457c754dce190 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 13:17:27 +0530 Subject: [PATCH 136/372] junior guardian relation table --- .../0020_alter_juniortask_task_status.py | 18 +++++++++++ guardian/models.py | 4 +-- .../0017_juniorguardianrelationship.py | 31 +++++++++++++++++++ junior/models.py | 6 ++-- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 guardian/migrations/0020_alter_juniortask_task_status.py create mode 100644 junior/migrations/0017_juniorguardianrelationship.py diff --git a/guardian/migrations/0020_alter_juniortask_task_status.py b/guardian/migrations/0020_alter_juniortask_task_status.py new file mode 100644 index 0000000..241ae10 --- /dev/null +++ b/guardian/migrations/0020_alter_juniortask_task_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-07-25 07:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0019_guardian_is_invited_guardian_is_password_set'), + ] + + operations = [ + migrations.AlterField( + model_name='juniortask', + name='task_status', + field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed'), ('6', 'expired')], default=1, max_length=15), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 6ecd27b..45afee7 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model """Import Django app""" from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS """import Junior model""" -from junior.models import Junior +import junior.models """Add user model""" User = get_user_model() @@ -105,7 +105,7 @@ class JuniorTask(models.Model): """image that is uploaded by junior""" image = models.URLField(null=True, blank=True, default=None) """associated junior with the task""" - junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') + junior = models.ForeignKey('junior.Junior', on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') """task status""" task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING) """task is active or not""" diff --git a/junior/migrations/0017_juniorguardianrelationship.py b/junior/migrations/0017_juniorguardianrelationship.py new file mode 100644 index 0000000..99d1707 --- /dev/null +++ b/junior/migrations/0017_juniorguardianrelationship.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.2 on 2023-07-25 07:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0020_alter_juniortask_task_status'), + ('junior', '0016_juniorpoints_referral_points'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorGuardianRelationship', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('relationship', models.CharField(blank=True, choices=[('1', 'parent'), ('2', 'legal_guardian')], default='1', max_length=31, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('guardian', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_relation', to='guardian.guardian', verbose_name='Guardian')), + ('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_relation', to='junior.junior', verbose_name='Junior')), + ], + options={ + 'verbose_name': 'Junior Guardian Relation', + 'verbose_name_plural': 'Junior Guardian Relation', + 'db_table': 'junior_guardian_relation', + }, + ), + ] diff --git a/junior/models.py b/junior/models.py index 8098109..2bba176 100644 --- a/junior/models.py +++ b/junior/models.py @@ -7,6 +7,8 @@ from django.contrib.auth import get_user_model from django.contrib.postgres.fields import ArrayField """Import django app""" from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP +# Import guardian's model +from guardian.models import Guardian """Define User model""" User = get_user_model() @@ -113,9 +115,9 @@ class JuniorPoints(models.Model): class JuniorGuardianRelationship(models.Model): """Junior Guardian relationship model""" - guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian') + guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian_relation', verbose_name='Guardian') # associated junior with the task - junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior') + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior_relation', verbose_name='Junior') # relation between guardian and junior""" relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, default='1') From 6e8481411725e6b6585a8567091d9b611ada3067 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 25 Jul 2023 14:09:51 +0530 Subject: [PATCH 137/372] modified yml file, default article card image upload api, --- docker-compose.yml | 2 + web_admin/admin.py | 10 +- .../0003_defaultarticlecardimage_and_more.py | 28 ++++ web_admin/models.py | 16 ++- web_admin/serializers.py | 133 ++++++++++++++++-- web_admin/urls.py | 4 +- web_admin/views.py | 84 ++++++++++- 7 files changed, 255 insertions(+), 22 deletions(-) create mode 100644 web_admin/migrations/0003_defaultarticlecardimage_and_more.py diff --git a/docker-compose.yml b/docker-compose.yml index b9bc35b..edd309d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: nginx: image: nginx:latest container_name: nginx + restart: always ports: - "8000:8000" volumes: @@ -13,6 +14,7 @@ services: web: build: . container_name: django + restart: always command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" volumes: - .:/usr/src/app diff --git a/web_admin/admin.py b/web_admin/admin.py index b1df483..29114de 100644 --- a/web_admin/admin.py +++ b/web_admin/admin.py @@ -5,7 +5,7 @@ web_admin admin file from django.contrib import admin # local imports -from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption +from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption, DefaultArticleCardImage @admin.register(Article) @@ -17,7 +17,7 @@ class ArticleAdmin(admin.ModelAdmin): @admin.register(ArticleCard) class ArticleCardAdmin(admin.ModelAdmin): """Article Card Admin""" - list_display = ['id', 'article', 'title', 'description', 'image'] + list_display = ['id', 'article', 'title', 'description', 'image_url'] @admin.register(ArticleSurvey) @@ -30,3 +30,9 @@ class ArticleSurveyAdmin(admin.ModelAdmin): class SurveyOptionAdmin(admin.ModelAdmin): """Survey Option Admin""" list_display = ['id', 'survey', 'option', 'is_answer'] + + +@admin.register(DefaultArticleCardImage) +class DefaultArticleCardImagesAdmin(admin.ModelAdmin): + """Default Article Card Images Option Admin""" + list_display = ['image_name', 'image_url'] diff --git a/web_admin/migrations/0003_defaultarticlecardimage_and_more.py b/web_admin/migrations/0003_defaultarticlecardimage_and_more.py new file mode 100644 index 0000000..bf2f66f --- /dev/null +++ b/web_admin/migrations/0003_defaultarticlecardimage_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.2 on 2023-07-24 14:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0002_alter_articlecard_image'), + ] + + operations = [ + migrations.CreateModel( + name='DefaultArticleCardImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image_name', models.CharField(max_length=20)), + ('image_url', models.URLField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + migrations.RenameField( + model_name='articlecard', + old_name='image', + new_name='image_url', + ), + ] diff --git a/web_admin/models.py b/web_admin/models.py index 30f0550..950eec6 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -28,7 +28,7 @@ class ArticleCard(models.Model): article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards') title = models.CharField(max_length=255) description = models.TextField() - image = models.URLField(null=True, blank=True, default=None) + image_url = models.URLField(null=True, blank=True, default=None) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -65,3 +65,17 @@ class SurveyOption(models.Model): def __str__(self): """Return title""" return f'{self.id} | {self.survey}' + + +class DefaultArticleCardImage(models.Model): + """ + Default images upload in oss bucket + """ + image_name = models.CharField(max_length=20) + image_url = models.URLField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """return image_name as an object""" + return self.image_name diff --git a/web_admin/serializers.py b/web_admin/serializers.py index b9f6fa9..66bac06 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -3,14 +3,18 @@ web_admin serializers file """ # django imports from rest_framework import serializers +from django.contrib.auth import get_user_model -from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY +from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, \ + USER_TYPE # local imports from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba -from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey +from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage from web_admin.utils import pop_id +USER = get_user_model() + class ArticleCardSerializer(serializers.ModelSerializer): """ @@ -18,23 +22,25 @@ class ArticleCardSerializer(serializers.ModelSerializer): """ id = serializers.IntegerField(required=False) image = serializers.FileField(required=False) + image_url = serializers.URLField(required=False) class Meta: """ meta class """ model = ArticleCard - fields = ('id', 'title', 'description', 'image') + fields = ('id', 'title', 'description', 'image', 'image_url') def create(self, validated_data): - image = validated_data.get('image', '') - filename = f"article/{image.name}" - # upload image on ali baba - validated_data['image'] = upload_image_to_alibaba(image, filename) - + if 'image' in validated_data and validated_data['image'] is not None: + image = validated_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + validated_data['image_url'] = upload_image_to_alibaba(image, filename) article_card = ArticleCard.objects.create(article_id='1', **validated_data) return article_card + class SurveyOptionSerializer(serializers.ModelSerializer): """ survey option serializer @@ -81,7 +87,6 @@ class ArticleSerializer(serializers.ModelSerializer): def validate(self, attrs): """ to validate request data - :param attrs: :return: validated attrs """ article_cards = attrs.get('article_cards', None) @@ -97,7 +102,6 @@ class ArticleSerializer(serializers.ModelSerializer): """ to create article. ID in post data dict is for update api. - :param validated_data: :return: article object """ article_cards = validated_data.pop('article_cards') @@ -107,6 +111,11 @@ class ArticleSerializer(serializers.ModelSerializer): for card in article_cards: card = pop_id(card) + if 'image' in card and card['image'] is not None: + image = card.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=article, **card) for survey in article_survey: @@ -122,8 +131,7 @@ class ArticleSerializer(serializers.ModelSerializer): def update(self, instance, validated_data): """ to update article and related table - :param instance: - :param validated_data: + :param instance: article object, :return: article object """ article_cards = validated_data.pop('article_cards') @@ -139,10 +147,19 @@ class ArticleSerializer(serializers.ModelSerializer): card = ArticleCard.objects.get(id=card_id, article=instance) card.title = card_data.get('title', card.title) card.description = card_data.get('description', card.description) - card.image = card_data.get('image', card.image) + if 'image' in card_data and card_data['image'] is not None: + image = card_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card.image_url = upload_image_to_alibaba(image, filename) card.save() else: card_data = pop_id(card_data) + if 'image' in card_data and card_data['image'] is not None: + image = card_data.pop('image') + filename = f"article/{image.name}" + # upload image on ali baba + card_data['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=instance, **card_data) # Update or create survey sections @@ -170,3 +187,93 @@ class ArticleSerializer(serializers.ModelSerializer): SurveyOption.objects.create(survey=survey, **option_data) return instance + + +class DefaultArticleCardImageSerializer(serializers.ModelSerializer): + """ + Article Card serializer + """ + image = serializers.FileField(required=False) + image_url = serializers.URLField(required=False) + + class Meta: + """ + meta class + """ + model = DefaultArticleCardImage + fields = ('image_name', 'image', 'image_url') + + def validate(self, attrs): + """ + to validate data + :return: validated data + """ + if 'image' not in attrs and attrs.get('image') is None: + raise serializers.ValidationError({'details': 'insert image'}) + return attrs + + def create(self, validated_data): + """ + to create and upload image + :return: card_image object + """ + image = validated_data.pop('image') + filename = f"article/{image.name}" + if image and image.size == NUMBER['zero']: + raise serializers.ValidationError(ERROR_CODE['2035']) + # upload image on ali baba + validated_data['image_url'] = upload_image_to_alibaba(image, filename) + + card_image = DefaultArticleCardImage.objects.create(**validated_data) + return card_image + + +class UserManagementListSerializer(serializers.ModelSerializer): + """ + user management serializer + """ + name = serializers.SerializerMethodField() + phone_number = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('name', 'email', 'phone_number', 'user_type', 'is_active') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return (obj.first_name + obj.last_name) if obj.last_name else obj.first_name + + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return profile.phone + elif profile := obj.junior_profile.all().first(): + return 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') + elif obj.junior_profile.all().first(): + return dict(USER_TYPE).get('1') + else: + return None + diff --git a/web_admin/urls.py b/web_admin/urls.py index 95bc7d9..3fe7eb2 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,12 +6,14 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views import ArticleViewSet +from web_admin.views import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet # initiate router router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') +router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') +router.register('user', UserManagementViewSet, basename='user') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/web_admin/views.py b/web_admin/views.py index b387a97..c40cd54 100644 --- a/web_admin/views.py +++ b/web_admin/views.py @@ -3,18 +3,22 @@ web_admin views file """ # django imports from rest_framework.viewsets import GenericViewSet, mixins -from rest_framework.response import Response from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework import status from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny +from django.contrib.auth import get_user_model # local imports from account.utils import custom_response, custom_error_response +from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.models import Article, ArticleCard, ArticleSurvey +from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission -from web_admin.serializers import ArticleSerializer, ArticleCardSerializer +from web_admin.serializers import (ArticleSerializer, ArticleCardSerializer, DefaultArticleCardImageSerializer, + UserManagementListSerializer) + +USER = get_user_model() class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModelMixin, @@ -137,7 +141,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_error_response(ERROR_CODE["2043"], response_status=status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='test-add-card', url_path='test-add-card', - detail=False, serializer_class=ArticleCardSerializer) + detail=False, serializer_class=ArticleCardSerializer, permission_classes=[AllowAny]) def add_card(self, request): """ :param request: @@ -147,3 +151,73 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel serializer.is_valid(raise_exception=True) serializer.save() return custom_response(SUCCESS_CODE["3000"]) + + @action(methods=['get'], url_name='test-list-card', url_path='test-list-card', + detail=False, serializer_class=ArticleCardSerializer, permission_classes=[AllowAny]) + def list_card(self, request): + """ + :param request: + :return: + """ + queryset = ArticleCard.objects.all() + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, serializer.data) + + +class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin): + """ + api to upload and list default article card images + """ + serializer_class = DefaultArticleCardImageSerializer + permission_classes = [IsAuthenticated, AdminPermission] + queryset = DefaultArticleCardImage.objects.all() + + def create(self, request, *args, **kwargs): + """ + api method to upload default article card images + :param request: + :return: success message + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3000"]) + + def list(self, request, *args, **kwargs): + """ + api method to list default article card images + :param request: + :return: default article card images + """ + queryset = self.queryset + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + + +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin): + """ + api to manage (list, view, edit) user + """ + serializer_class = UserManagementListSerializer + permission_classes = [] + queryset = USER.objects.prefetch_related( + 'guardian_profile', 'junior_profile') + + def get_queryset(self): + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + return self.queryset.filter(junior_profile__isnull=True) + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + return self.queryset.filter(guardian_profile__isnull=True) + else: + return self.queryset + + def list(self, request, *args, **kwargs): + """ + api method to list all the user + :param request: + :return: + """ + queryset = self.get_queryset() + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + From c8a8e1149bcfdfc46ad9b4af556ff49dcc9b2491 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 15:27:29 +0530 Subject: [PATCH 138/372] jira-28 invite guardian --- .../junior_approval_mail.email | 2 +- junior/admin.py | 8 +++- junior/models.py | 3 -- junior/serializers.py | 40 ++++++++----------- junior/views.py | 4 +- 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/account/templates/templated_email/junior_approval_mail.email b/account/templates/templated_email/junior_approval_mail.email index fd3c19f..e9884d6 100644 --- a/account/templates/templated_email/junior_approval_mail.email +++ b/account/templates/templated_email/junior_approval_mail.email @@ -8,7 +8,7 @@

- Hi {{full_name}}, + Hello,

diff --git a/junior/admin.py b/junior/admin.py index 99f1cd0..acd8734 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior, JuniorPoints +from .models import Junior, JuniorPoints, JuniorGuardianRelationship # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): @@ -21,3 +21,9 @@ class JuniorPointsAdmin(admin.ModelAdmin): def __str__(self): """Return email id""" return self.junior.auth.email + +@admin.register(JuniorGuardianRelationship) +class JuniorGuardianRelationshipAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['guardian', 'junior', 'relationship'] + diff --git a/junior/models.py b/junior/models.py index 2bba176..529e1e0 100644 --- a/junior/models.py +++ b/junior/models.py @@ -49,9 +49,6 @@ class Junior(models.Model): dob = models.DateField(max_length=15, null=True, blank=True, default=None) # Image of the junior""" image = models.URLField(null=True, blank=True, default=None) - # relationship""" - relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True, - default='1') # Sign up method""" signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1') # Codes""" diff --git a/junior/serializers.py b/junior/serializers.py index d2d6b6d..3bc2540 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -9,7 +9,7 @@ from rest_framework_simplejwt.tokens import RefreshToken """Import django app""" from account.utils import send_otp_email, generate_code -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship 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 @@ -249,18 +249,11 @@ class JuniorProfileSerializer(serializers.ModelSerializer): class AddJuniorSerializer(serializers.ModelSerializer): """Add junior serializer""" - auth_token = serializers.SerializerMethodField('get_auth_token') - - def get_auth_token(self, obj): - """auth token""" - refresh = RefreshToken.for_user(obj) - access_token = str(refresh.access_token) - return access_token class Meta(object): """Meta info""" model = Junior - fields = ['id', 'gender','dob', 'relationship', 'auth_token', 'is_invited'] + fields = ['id', 'gender','dob', 'is_invited'] def create(self, validated_data): @@ -268,6 +261,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): with transaction.atomic(): email = self.context['email'] guardian = self.context['user'] + relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] guardian_data = Guardian.objects.filter(user__username=guardian).last() user_data = User.objects.create(username=email, email=email, @@ -278,11 +272,13 @@ class AddJuniorSerializer(serializers.ModelSerializer): user_data.save() junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), dob=validated_data.get('dob'), is_invited=True, - relationship=validated_data.get('relationship'), + guardian_code=[guardian_data.guardian_code], junior_code=generate_code(JUN, user_data.id), referral_code=generate_code(ZOD, user_data.id), referral_code_used=guardian_data.referral_code, is_password_set=False, is_verified=True) + JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, + relationship=relationship) """Generate otp""" otp_value = generate_otp() expiry_time = timezone.now() + timezone.timedelta(days=1) @@ -375,18 +371,11 @@ class JuniorPointsSerializer(serializers.ModelSerializer): class AddGuardianSerializer(serializers.ModelSerializer): """Add guardian serializer""" - auth_token = serializers.SerializerMethodField('get_auth_token') - - def get_auth_token(self, obj): - """auth token""" - refresh = RefreshToken.for_user(obj) - access_token = str(refresh.access_token) - return access_token class Meta(object): """Meta info""" model = Guardian - fields = ['id', 'auth_token'] + fields = ['id'] def create(self, validated_data): @@ -394,11 +383,12 @@ class AddGuardianSerializer(serializers.ModelSerializer): with transaction.atomic(): email = self.context['email'] junior = self.context['user'] + relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] junior_data = Junior.objects.filter(auth__username=junior).last() instance = User.objects.filter(username=email).last() if instance: - guardian_data = Guardian.objects.filter(user=user).update(is_invited=True, + guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True, referral_code=generate_code(ZOD, instance.id), referral_code_used=junior_data.referral_code, @@ -412,22 +402,24 @@ class AddGuardianSerializer(serializers.ModelSerializer): password = User.objects.make_random_password() user.set_password(password) user.save() - Guardian.objects.create(user=user, is_invited=True, - referral_code=generate_code(ZOD, user.id), - referral_code_used=junior_data.referral_code, - is_password_set=False, is_verified=True) + guardian_data = Guardian.objects.create(user=user, is_invited=True, + referral_code=generate_code(ZOD, user.id), + referral_code_used=junior_data.referral_code, + is_password_set=False, is_verified=True) """Generate otp""" otp_value = generate_otp() expiry_time = timezone.now() + timezone.timedelta(days=1) UserEmailOtp.objects.create(email=email, otp=otp_value, user_type=str(NUMBER['two']), expired_at=expiry_time, is_verified=True) + JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, + relationship=relationship) """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(email, full_name) send_notification(INVITED_GUARDIAN, None, junior_data.auth.id, {}) send_notification(APPROVED_JUNIOR, None, email, {}) - return instance + return guardian_data class StartTaskSerializer(serializers.ModelSerializer): """User task Serializer""" diff --git a/junior/views.py b/junior/views.py index 00fd188..1021bc8 100644 --- a/junior/views.py +++ b/junior/views.py @@ -130,7 +130,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): """ junior list""" try: info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name']} + 'last_name': request.data['last_name'], 'relationship': str(request.data['relationship'])} if User.objects.filter(username=request.data['email']): return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) @@ -365,7 +365,7 @@ class InviteGuardianAPIView(viewsets.ModelViewSet): """ junior list""" try: info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name']} + 'last_name': request.data['last_name'], 'relationship': str(request.data['relationship'])} # use AddJuniorSerializer serializer serializer = AddGuardianSerializer(data=request.data, context=info) if serializer.is_valid(): From 32476149bce05657473f7a922d7517183fefef1a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 25 Jul 2023 15:33:22 +0530 Subject: [PATCH 139/372] article card folder name change --- base/constants.py | 2 ++ web_admin/serializers.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/base/constants.py b/base/constants.py index b351cde..3c1501c 100644 --- a/base/constants.py +++ b/base/constants.py @@ -107,3 +107,5 @@ MAX_ARTICLE_SURVEY = 10 # real time url time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" + +ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' diff --git a/web_admin/serializers.py b/web_admin/serializers.py index 7027c47..185482f 100644 --- a/web_admin/serializers.py +++ b/web_admin/serializers.py @@ -5,8 +5,8 @@ web_admin serializers file from rest_framework import serializers from django.contrib.auth import get_user_model -from base.constants import ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, \ - USER_TYPE +from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, + USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) # local imports from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba @@ -34,7 +34,7 @@ class ArticleCardSerializer(serializers.ModelSerializer): def create(self, validated_data): if 'image' in validated_data and validated_data['image'] is not None: image = validated_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba validated_data['image_url'] = upload_image_to_alibaba(image, filename) article_card = ArticleCard.objects.create(article_id='1', **validated_data) @@ -113,7 +113,7 @@ class ArticleSerializer(serializers.ModelSerializer): card = pop_id(card) if 'image' in card and card['image'] is not None: image = card.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=article, **card) @@ -149,7 +149,7 @@ class ArticleSerializer(serializers.ModelSerializer): card.description = card_data.get('description', card.description) if 'image' in card_data and card_data['image'] is not None: image = card_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card.image_url = upload_image_to_alibaba(image, filename) card.save() @@ -157,7 +157,7 @@ class ArticleSerializer(serializers.ModelSerializer): card_data = pop_id(card_data) if 'image' in card_data and card_data['image'] is not None: image = card_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba card_data['image_url'] = upload_image_to_alibaba(image, filename) ArticleCard.objects.create(article=instance, **card_data) @@ -218,7 +218,7 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): :return: card_image object """ image = validated_data.pop('image') - filename = f"article/{image.name}" + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" if image and image.size == NUMBER['zero']: raise serializers.ValidationError(ERROR_CODE['2035']) # upload image on ali baba @@ -276,4 +276,3 @@ class UserManagementListSerializer(serializers.ModelSerializer): return dict(USER_TYPE).get('1') else: return None - From 7c4f9b250600578712ccab0c190ddd049a72ac37 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 16:54:48 +0530 Subject: [PATCH 140/372] jira-291 start task api --- guardian/serializers.py | 9 +++++++-- junior/serializers.py | 24 ++++++++++++++++++++---- junior/views.py | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index a8a2e46..f7016f5 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -215,6 +215,7 @@ class TaskDetailsSerializer(serializers.ModelSerializer): junior = JuniorDetailSerializer() remaining_time = serializers.SerializerMethodField('get_remaining_time') + is_expired = serializers.SerializerMethodField('get_is_expired') def get_remaining_time(self, obj): """ remaining time to complete task""" @@ -228,12 +229,16 @@ class TaskDetailsSerializer(serializers.ModelSerializer): return str(time_difference.days) + ' days ' + str(time_only) return str(NUMBER['zero']) + ' days ' + '00:00:00:00000' - + def get_is_expired(self, obj): + """ task expired or not""" + if obj.due_date < datetime.today().date(): + return True + return False class Meta(object): """Meta info""" model = JuniorTask fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image', - 'requested_on', 'rejected_on', 'completed_on', + 'requested_on', 'rejected_on', 'completed_on', 'is_expired', 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at'] diff --git a/junior/serializers.py b/junior/serializers.py index 3bc2540..195efa5 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -14,9 +14,9 @@ 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 from guardian.models import Guardian, JuniorTask -from account.models import UserEmailOtp +from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail -from guardian.utils import real_time, update_referral_points +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 @@ -393,6 +393,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): instance.id), referral_code_used=junior_data.referral_code, is_verified=True) + UserNotification.objects.create(user=instance) return guardian_data else: user = User.objects.create(username=email, email=email, @@ -412,21 +413,36 @@ class AddGuardianSerializer(serializers.ModelSerializer): UserEmailOtp.objects.create(email=email, otp=otp_value, user_type=str(NUMBER['two']), expired_at=expiry_time, is_verified=True) + UserNotification.objects.create(user=user) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) + """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(email, full_name) send_notification(INVITED_GUARDIAN, None, junior_data.auth.id, {}) - send_notification(APPROVED_JUNIOR, None, email, {}) + send_notification(APPROVED_JUNIOR, None, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): """User task Serializer""" + task_duration = serializers.SerializerMethodField('get_task_duration') + + def get_task_duration(self, obj): + """ remaining time to complete task""" + due_date = datetime.combine(obj.due_date, datetime.max.time()) + # fetch real time + real_datetime = real_time() + # Perform the subtraction + if due_date > real_datetime: + time_difference = due_date - real_datetime + time_only = convert_timedelta_into_datetime(time_difference) + return str(time_difference.days) + ' days ' + str(time_only) + return str(NUMBER['zero']) + ' days ' + '00:00:00:00000' class Meta(object): """Meta class""" model = JuniorTask - fields = ('id', 'task_status') + fields = ('id', 'task_duration') def update(self, instance, validated_data): instance.task_status = str(NUMBER['two']) instance.save() diff --git a/junior/views.py b/junior/views.py index 1021bc8..bb79df2 100644 --- a/junior/views.py +++ b/junior/views.py @@ -393,7 +393,7 @@ class StartTaskAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() - return custom_response(SUCCESS_CODE['3035'], None, response_status=status.HTTP_200_OK) + return custom_response(SUCCESS_CODE['3035'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) else: return custom_error_response(ERROR_CODE['2060'], response_status=status.HTTP_400_BAD_REQUEST) From a8558b5e6e0a5ddbb6546b14cc2a7597ddf0be07 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 17:06:26 +0530 Subject: [PATCH 141/372] sonar issues --- base/messages.py | 3 +++ guardian/views.py | 10 +++++++++- junior/models.py | 3 ++- junior/views.py | 18 ++++++++++++++---- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/base/messages.py b/base/messages.py index 61ee573..25bfab4 100644 --- a/base/messages.py +++ b/base/messages.py @@ -76,12 +76,15 @@ ERROR_CODE = { "2043": "Article Survey with given id doesn't exist.", "2044": "Task does not exist", "2045": "Invalid guardian", + # past due date "2046": "Due date must be future date", # invalid junior id msg "2047": "Invalid Junior ID ", "2048": "Choose right file for image", + # task request "2049": "This task is already requested ", "2059": "Already exist junior", + # task status "2060": "Task does not exist or not in pending state" } """Success message code""" diff --git a/guardian/views.py b/guardian/views.py index 24cc345..d996f29 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -1,6 +1,11 @@ """Views of Guardian""" # django imports +# Import IsAuthenticated +# Import viewsets and status +# Import PageNumberPagination +# Import User +# Import timezone from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination @@ -18,7 +23,8 @@ from django.utils import timezone # from utils file # Import account's serializer # Import account's task - +# Import notification constant +# Import send_notification function from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) from .models import Guardian, JuniorTask @@ -58,12 +64,14 @@ class SignupViewset(viewsets.ModelViewSet): user = serializer.save() """Generate otp""" otp = generate_otp() + # expire otp after 1 day 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(REGISTRATION, None, user.id, {}) return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK) diff --git a/junior/models.py b/junior/models.py index 529e1e0..dc71c97 100644 --- a/junior/models.py +++ b/junior/models.py @@ -112,7 +112,8 @@ class JuniorPoints(models.Model): class JuniorGuardianRelationship(models.Model): """Junior Guardian relationship model""" - guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian_relation', verbose_name='Guardian') + guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian_relation', + verbose_name='Guardian') # associated junior with the task junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior_relation', verbose_name='Junior') # relation between guardian and junior""" diff --git a/junior/views.py b/junior/views.py index bb79df2..7f3452a 100644 --- a/junior/views.py +++ b/junior/views.py @@ -20,6 +20,10 @@ import requests # Import account's serializer # Import account's task # import junior serializer +# Import update_positions_based_on_points +# Import upload_image_to_alibaba +# Import custom_response, custom_error_response +# Import constants from junior.models import Junior, JuniorPoints from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, @@ -43,7 +47,10 @@ from .utils import update_positions_based_on_points # approve junior API # create referral code # validation API - +# invite guardian API +# by junior +# Start task +# by junior API # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" @@ -58,6 +65,7 @@ class UpdateJuniorProfile(viewsets.ViewSet): image = request.data.get('image') image_url = '' if image: + # check image size if image.size == NUMBER['zero']: return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) # convert into file @@ -91,6 +99,7 @@ class ValidateGuardianCode(viewsets.ViewSet): # fetch guardian object guardian_data = Guardian.objects.filter(guardian_code=code).exists() if guardian_data: + # successfully check guardian code return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK) else: return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST) @@ -129,13 +138,13 @@ class AddJuniorAPIView(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): """ junior list""" try: - info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name'], 'relationship': str(request.data['relationship'])} + info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name']} if User.objects.filter(username=request.data['email']): return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) # use AddJuniorSerializer serializer - serializer = AddJuniorSerializer(data=request.data, context=info) + serializer = AddJuniorSerializer(data=request.data, context=info_data) if serializer.is_valid(): # save serializer serializer.save() @@ -396,6 +405,7 @@ class StartTaskAPIView(views.APIView): return custom_response(SUCCESS_CODE['3035'], serializer.data, 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['2060'], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From 8a454c3f97ac9f4ec766da8af53f27724b534031 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 17:15:16 +0530 Subject: [PATCH 142/372] sonar issues --- account/utils.py | 3 ++- account/views.py | 6 +++--- guardian/views.py | 5 ++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/account/utils.py b/account/utils.py index 9aca671..d1e6e54 100644 --- a/account/utils.py +++ b/account/utils.py @@ -22,7 +22,7 @@ from junior.models import Junior from guardian.models import Guardian from account.models import UserDelete from base.messages import ERROR_CODE - +from django.utils import timezone # Define delete # user account condition, @@ -228,3 +228,4 @@ def generate_code(value, user_id): return code +OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1) diff --git a/account/views.py b/account/views.py index c19cd4f..91ff650 100644 --- a/account/views.py +++ b/account/views.py @@ -22,7 +22,7 @@ from django.conf import settings """App Import""" from guardian.models import Guardian from junior.models import Junior -from guardian.utils import upload_image_to_alibaba +from guardian.utils import upload_image_to_alibaba, OTP_EXPIRY from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User """Account serializer""" @@ -228,7 +228,7 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - expiry = timezone.now() + timezone.timedelta(days=1) + expiry = OTP_EXPIRY user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry @@ -399,7 +399,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): otp = generate_otp() if User.objects.filter(email=request.data['email']): - expiry = timezone.now() + timezone.timedelta(days=1) + expiry = OTP_EXPIRY email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) if created: email_data.expired_at = expiry diff --git a/guardian/views.py b/guardian/views.py index d996f29..41ede37 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -31,8 +31,7 @@ from .models import Guardian, JuniorTask from junior.models import Junior, JuniorPoints from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp -from account.utils import send_otp_email -from account.utils import custom_response, custom_error_response +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 from .utils import upload_image_to_alibaba @@ -65,7 +64,7 @@ class SignupViewset(viewsets.ModelViewSet): """Generate otp""" otp = generate_otp() # expire otp after 1 day - expiry = timezone.now() + timezone.timedelta(days=1) + expiry = OTP_EXPIRY # create user email otp object UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type']), expired_at=expiry) From 3cdd685ea85d0d32d0f9bfec2d07bbbc662f4346 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 17:21:18 +0530 Subject: [PATCH 143/372] sonar issues --- guardian/views.py | 2 +- junior/serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index 41ede37..5fe331e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -71,7 +71,7 @@ class SignupViewset(viewsets.ModelViewSet): """Send email to the register user""" send_otp_email(request.data['email'], otp) # send push notification for registration - send_notification(REGISTRATION, None, user.id, {}) + send_notification.delay(REGISTRATION, None, user.id, {}) return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/serializers.py b/junior/serializers.py index 195efa5..13b6815 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -420,8 +420,8 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(email, full_name) - send_notification(INVITED_GUARDIAN, None, junior_data.auth.id, {}) - send_notification(APPROVED_JUNIOR, None, guardian_data.user.id, {}) + send_notification.delay(INVITED_GUARDIAN, None, junior_data.auth.id, {}) + send_notification.delay(APPROVED_JUNIOR, None, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): From 80843502a694aafad0d0e2f6e610778922bd8f48 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 25 Jul 2023 19:13:25 +0530 Subject: [PATCH 144/372] jira-290 cron job for expired task --- account/views.py | 4 ++-- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/utils.py | 19 ++++++++++++++++++- junior/views.py | 1 + zod_bank/celery.py | 11 +++++++++++ zod_bank/settings.py | 1 + 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index 91ff650..c95aa7e 100644 --- a/account/views.py +++ b/account/views.py @@ -22,7 +22,7 @@ from django.conf import settings """App Import""" from guardian.models import Guardian from junior.models import Junior -from guardian.utils import upload_image_to_alibaba, OTP_EXPIRY +from guardian.utils import upload_image_to_alibaba from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User """Account serializer""" @@ -36,7 +36,7 @@ from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, ZOD, JUN, GRD from guardian.tasks import generate_otp from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response, - generate_code) + generate_code, OTP_EXPIRY) from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer diff --git a/celerybeat-schedule b/celerybeat-schedule index cd2e03e65079afc52f74f782d60c0b3e9a98a19e..816eeebd0c744921639a15bc8fa7970b63417ff1 100644 GIT binary patch delta 303 zcmZo@U~Fh$T%f?nG?`IBoQso>0SvwyPBiq|oTIRWm)F$*BE$})R&SQoGhq_ah~NQo zQY#8Fi&9hKOA?E-C+8R_8ANCTMbk?Yi&8QZ^YltfGINUcN()jFOH$*J)y5Z>B$kvG z7cx(tU?9TF;>^YVoL!PvgM~NKVDfjvsL9-hGMWM!-kRRL5sW|swF(6@fUu-cD5y}l zwooKVv$jyQwot63P&}wmVsfB?Oqyf{lSYOl5F#0tZCWUm!P{0S9aJde&5$9W0XEE_ zP!>#^6v}}N1sN+JRH#r}s0cDvskTr#0_0DKbBhaAd=IQBE-6$EDpV`g+i1wi2LKlH BP}%?h delta 528 zcmaKoO-lk%6o$QxHhFXU@cRQn+GGXdh^sa&L?oj{Q^6pCjMrN;I87es3KJOo08A>%ARgZB_l*IyA$>Y~u^D6($l4`pv4KHtS8yP(L>v(LX@qJFpPdt(c84ad^Yamg zmEy8nK^OjOEVyVa6T-W~>y`IWZXx8!a=!6x<{MQZ`fV1|`7nqpkl<2tvy=nrKV})< NEUQ4yF1^W3=?m4LlhObH diff --git a/guardian/utils.py b/guardian/utils.py index adb7f87..b1d5eda 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -5,6 +5,7 @@ import oss2 from django.conf import settings import logging import requests +from django.core.exceptions import ObjectDoesNotExist """Import tempfile""" import tempfile # Import date time module's function @@ -13,7 +14,10 @@ from datetime import datetime, time from base.constants import NUMBER, time_url # Import Junior's model from junior.models import Junior, JuniorPoints - +# Import guardian's model +from .models import JuniorTask +# Import app from celery +from zod_bank.celery import app # Define upload image on # ali baba cloud # firstly save image @@ -81,3 +85,16 @@ 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() + + +@app.task +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'])] + JuniorTask.objects.filter(due_date__lt=datetime.today().date(), + task_status__in=task_status).update(task_status=str(NUMBER['six'])) + except ObjectDoesNotExist as e: + logging.error(str(e)) diff --git a/junior/views.py b/junior/views.py index 7f3452a..4ccca91 100644 --- a/junior/views.py +++ b/junior/views.py @@ -5,6 +5,7 @@ from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User +import datetime import requests """Django app import""" diff --git a/zod_bank/celery.py b/zod_bank/celery.py index 177c318..5cc2829 100644 --- a/zod_bank/celery.py +++ b/zod_bank/celery.py @@ -32,3 +32,14 @@ app.autodiscover_tasks() 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 5415f14..3781f89 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -61,6 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.postgres', 'rest_framework', 'fcm_django', + 'django_celery_beat', # Add your custom apps here. 'django_ses', 'account', From 7bf0e626041eaa6aa03300841f7bee0e49c13b2c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 25 Jul 2023 19:39:01 +0530 Subject: [PATCH 145/372] added api for forgot password, verify otp and resend otp for admin --- base/messages.py | 2 +- web_admin/messages.py | 0 web_admin/serializers/__init__.py | 0 .../article_serializer.py} | 0 web_admin/serializers/auth_serializer.py | 88 +++++++++++++++++++ web_admin/urls.py | 6 +- web_admin/views/__init__.py | 0 web_admin/{views.py => views/article.py} | 7 +- web_admin/views/auth.py | 43 +++++++++ 9 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 web_admin/messages.py create mode 100644 web_admin/serializers/__init__.py rename web_admin/{serializers.py => serializers/article_serializer.py} (100%) create mode 100644 web_admin/serializers/auth_serializer.py create mode 100644 web_admin/views/__init__.py rename web_admin/{views.py => views/article.py} (95%) create mode 100644 web_admin/views/auth.py diff --git a/base/messages.py b/base/messages.py index 74e4740..274a6aa 100644 --- a/base/messages.py +++ b/base/messages.py @@ -83,7 +83,7 @@ ERROR_CODE = { "2049": "This task is already requested ", "2059": "Already exist junior", "2060": "Task does not exist or not in pending state", - "2061": "Please insert image or check the image is valid or not." + "2061": "Please insert image or check the image is valid or not.", } """Success message code""" SUCCESS_CODE = { diff --git a/web_admin/messages.py b/web_admin/messages.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/serializers/__init__.py b/web_admin/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/serializers.py b/web_admin/serializers/article_serializer.py similarity index 100% rename from web_admin/serializers.py rename to web_admin/serializers/article_serializer.py diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py new file mode 100644 index 0000000..386571b --- /dev/null +++ b/web_admin/serializers/auth_serializer.py @@ -0,0 +1,88 @@ +""" +web_admin auth serializers file +""" +from datetime import datetime + +# django imports +from rest_framework import serializers +from django.contrib.auth import get_user_model +from django.conf import settings +from django.utils import timezone +from rest_framework import status +from templated_email import send_templated_mail + +# local imports +from account.models import UserEmailOtp +from account.utils import custom_error_response +from base.messages import ERROR_CODE +from guardian.tasks import generate_otp + +USER = get_user_model() + + +class AdminForgotPasswordSerializer(serializers.ModelSerializer): + email = serializers.EmailField(required=True) + + class Meta: + model = USER + fields = ('email',) + + def validate(self, attrs): + """ used to validate the incoming data """ + user = USER.objects.filter(email=attrs['email']).first() + if not user: + raise serializers.ValidationError(ERROR_CODE['2004']) + elif not user.is_superuser: + raise serializers.ValidationError(ERROR_CODE['2036']) + attrs.update({'user': user}) + return attrs + + def create(self, validated_data): + email = 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 = timezone.now() + timezone.timedelta(days=1) + user_data, created = UserEmailOtp.objects.get_or_create(email=email) + if created: + user_data.expired_at = expiry + if user_data: + user_data.otp = verification_code + user_data.expired_at = expiry + user_data.save() + return user_data + + +class AdminVerifyOTPSerializer(serializers.Serializer): + otp = serializers.CharField(max_length=6, min_length=6) + + class Meta: + """ meta class """ + fields = ('otp',) + + def validate(self, attrs): + otp = attrs.pop('otp') + # fetch email otp object of the user + user_opt_details = UserEmailOtp.objects.filter(otp=otp).last() + if not user_opt_details: + raise serializers.ValidationError(ERROR_CODE['2008']) + if user_opt_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): + raise serializers.ValidationError(ERROR_CODE['2029']) + user_details = USER.objects.filter(email=user_opt_details.email).last() + user_opt_details.delete() + if user_details: + return attrs diff --git a/web_admin/urls.py b/web_admin/urls.py index 3fe7eb2..95228ad 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,8 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet +from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet +from web_admin.views.auth import ForgetAndResetPasswordViewSet # initiate router router = routers.SimpleRouter() @@ -15,6 +16,9 @@ router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user', UserManagementViewSet, basename='user') +# forgot and reset password api for admin +router.register('admin', ForgetAndResetPasswordViewSet, basename='admin') + urlpatterns = [ path('api/v1/', include(router.urls)), ] diff --git a/web_admin/views/__init__.py b/web_admin/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/views.py b/web_admin/views/article.py similarity index 95% rename from web_admin/views.py rename to web_admin/views/article.py index c40cd54..6bbf573 100644 --- a/web_admin/views.py +++ b/web_admin/views/article.py @@ -15,8 +15,9 @@ from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission -from web_admin.serializers import (ArticleSerializer, ArticleCardSerializer, DefaultArticleCardImageSerializer, - UserManagementListSerializer) +from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, + DefaultArticleCardImageSerializer, + UserManagementListSerializer) USER = get_user_model() @@ -169,7 +170,7 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m api to upload and list default article card images """ serializer_class = DefaultArticleCardImageSerializer - permission_classes = [IsAuthenticated, AdminPermission] + # permission_classes = [IsAuthenticated, AdminPermission] queryset = DefaultArticleCardImage.objects.all() def create(self, request, *args, **kwargs): diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py new file mode 100644 index 0000000..84835cb --- /dev/null +++ b/web_admin/views/auth.py @@ -0,0 +1,43 @@ +""" +web_admin auth views file +""" +# django imports +from rest_framework.viewsets import GenericViewSet, mixins +from rest_framework.decorators import action +from django.contrib.auth import get_user_model + +# local imports +from account.utils import custom_response +from base.constants import USER_TYPE +from base.messages import SUCCESS_CODE, ERROR_CODE +from web_admin.permission import AdminPermission +from web_admin.serializers.auth_serializer import AdminForgotPasswordSerializer, AdminVerifyOTPSerializer + +USER = get_user_model() + + +class ForgetAndResetPasswordViewSet(GenericViewSet): + queryset = None + + @action(methods=['post'], url_name='forgot-password', url_path='forgot-password', + detail=False, serializer_class=AdminForgotPasswordSerializer) + def admin_forgot_password(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE['3015']) + + @action(methods=['post'], url_name='verify-otp', url_path='verify-otp', + detail=False, serializer_class=AdminVerifyOTPSerializer) + def admin_verify_otp(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + return custom_response(SUCCESS_CODE['3011']) + + @action(methods=['post'], url_name='resend-otp', url_path='resend-otp', + detail=False, serializer_class=AdminForgotPasswordSerializer) + def admin_resend_otp(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE['3015']) From b860d937f545a8f4d4903229fa32eabeaffb9716 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 26 Jul 2023 11:06:58 +0530 Subject: [PATCH 146/372] requirement .txt file add --- celerybeat-schedule | Bin 16384 -> 16384 bytes requirements.txt | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 816eeebd0c744921639a15bc8fa7970b63417ff1..2fd0ce139fd0ddb41e3d63d94701251cc695247b 100644 GIT binary patch delta 391 zcmZo@U~Fh$T%f?nJeg5JTxt^|0~oj(Kxj7}2))aAqM_C19EBykyx$EWLhL|l&t_S@ zTg;PP4eTZd83+N*Xf@EUXGu;;EJ-cN%uVHjGYVOqx!9kxOY&+mFkIv7j6#kGPM|gMrFjLJ$=Nxng`91LTtS80 z5zIi*&=BWB9&g6BLf)W4zP3XCph5v}#^mJ5#)gvhf*RhM-nR;4 zNNu5Ll4fn8SZ$$rNufkgp=5*@P)TxXPHIu5UQ%Lmc4}Tqd~!}|VqR%Mp;QKwMurp+ z!W^%`!kcYcD4hYcO(v*N)|(+iPy=j-L7^O&HYt<`*_ELKv`!(YP_ed931ppeZJ|m8 gD7;cD3NnjQiwjkK53DFIDO3w8R4>)rXz0cR0Ana0SvwyPBiq|oTIRWm)F$*BE$})R&SQoyT!cGkdqGp DSRo7E diff --git a/requirements.txt b/requirements.txt index 85e0fbb..f1540e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ async-timeout==4.0.2 billiard==4.1.0 boto3==1.26.157 botocore==1.29.157 +CacheControl==0.13.1 cachetools==5.3.1 celery==5.3.1 certifi==2023.5.7 @@ -41,8 +42,22 @@ django-timezone-field==5.1 djangorestframework==3.14.0 djangorestframework-simplejwt==5.2.2 drf-yasg==1.21.6 +fcm-django==2.0.0 +firebase-admin==6.2.0 +google-api-core==2.11.1 +google-api-python-client==2.93.0 google-auth==2.21.0 +google-auth-httplib2==0.1.0 +google-cloud-core==2.3.3 +google-cloud-firestore==2.11.1 +google-cloud-storage==2.10.0 +google-crc32c==1.5.0 +google-resumable-media==2.5.0 +googleapis-common-protos==1.59.1 +grpcio==1.56.0 +grpcio-status==1.56.0 gunicorn==20.1.0 +httplib2==0.22.0 idna==3.4 inflection==0.5.1 itypes==1.2.0 @@ -58,12 +73,15 @@ packaging==23.1 phonenumbers==8.13.15 Pillow==9.5.0 prompt-toolkit==3.0.38 +proto-plus==1.22.3 +protobuf==4.23.4 psycopg==3.1.9 pyasn1==0.5.0 pyasn1-modules==0.3.0 pycparser==2.21 pycryptodome==3.18.0 PyJWT==2.7.0 +pyparsing==3.1.0 python-crontab==2.7.1 python-dateutil==2.8.2 python-dotenv==1.0.0 @@ -81,5 +99,3 @@ uritemplate==4.1.1 urllib3==1.26.16 vine==5.0.0 wcwidth==0.2.6 - -fcm-django==2.0.0 \ No newline at end of file From 2b6e943d8c15e23642861e200415ae3295f55d7e Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 26 Jul 2023 16:38:36 +0530 Subject: [PATCH 147/372] jira-28 notification --- base/messages.py | 2 ++ celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 56 ++++++++++++++++++++----------------- guardian/utils.py | 6 ++++ guardian/views.py | 10 +++++-- junior/serializers.py | 13 +++++++-- junior/views.py | 5 ++++ notifications/constants.py | 43 +++++++++++++++++++++++++++- 8 files changed, 103 insertions(+), 32 deletions(-) diff --git a/base/messages.py b/base/messages.py index a6a24c9..f99f9cb 100644 --- a/base/messages.py +++ b/base/messages.py @@ -87,6 +87,8 @@ ERROR_CODE = { # task status "2060": "Task does not exist or not in pending state", "2061": "Please insert image or check the image is valid or not.", + # email not null + "2062": "Please enter email address" } """Success message code""" SUCCESS_CODE = { diff --git a/celerybeat-schedule b/celerybeat-schedule index 2fd0ce139fd0ddb41e3d63d94701251cc695247b..573b0c9bd5140b34794915695cfc78142e742fee 100644 GIT binary patch delta 88 zcmZo@U~Fh$T%f?nG?`IBoQso>0SvwyPBiq|oTIRWm)F$*BE$})R&SQo+r-2!#bwFI fU12@>nn5KqgEzzEAVWWhSY_Sh<%R(p4LSJ$N2e3m delta 423 zcmX|-%}&BV6osjvNTn*MASjCeU<|fFHZI%{NKB(K6HOX71cuQzt%VEIL`)jGk-g~) zxG>SJurn@v1vkEekKooRMCUHf$;nK2 zI55&rg771aq9Bv^K`PfD3nqk2K+r_@OYHcwvfv`=bd>1@_gOk~OX87>#3*Cji~HRG zu2(6c+703V4U8B0;5U1e_4z&pn7}nlGpJ^^`D;R}l(u*X;=6`PH1n##Uovy-8JJW; ztVYE$n;O;)0-+x82&NQ2OE1og5SFR3iiviU2|wxAbaNoBNU z1S#34MlwT&2rEN9fK`vB?lD{%!I};09&W>iO~u*kC!%uZ3H`3?o}#> Fe*yUta83XK diff --git a/guardian/serializers.py b/guardian/serializers.py index f7016f5..8ec66bf 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -25,7 +25,10 @@ from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, JUN, ZOD, GRD from junior.models import Junior, JuniorPoints from .utils import real_time, convert_timedelta_into_datetime, update_referral_points - +# notification's constant +from notifications.constants import TASK_POINTS, TASK_REJECTED +# send notification function +from notifications.utils import send_notification # In this serializer file @@ -61,7 +64,7 @@ class UserSerializer(serializers.ModelSerializer): try: """Create user profile""" user = User.objects.create_user(username=email, email=email, password=password) - UserNotification.objects.create(user=user) + UserNotification.objects.get_or_create(user=user) if user_type == str(NUMBER['one']): # create junior profile Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), @@ -328,27 +331,30 @@ class ApproveTaskSerializer(serializers.ModelSerializer): def create(self, validated_data): """update task status """ - instance = self.context['task_instance'] - junior = self.context['junior'] - junior_details = Junior.objects.filter(id=junior).last() - junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details) - if self.context['action'] == str(NUMBER['one']): - # approve the task - instance.task_status = str(NUMBER['five']) - instance.is_approved = True - # update total task point - junior_data.total_points = junior_data.total_points + instance.points - # update complete time of task - instance.completed_on = real_time() - else: - # reject the task - instance.task_status = str(NUMBER['three']) - instance.is_approved = False - # update total task point - junior_data.total_points = junior_data.total_points - instance.points - # update reject time of task - instance.rejected_on = real_time() - instance.save() - junior_data.save() - return instance + with transaction.atomic(): + instance = self.context['task_instance'] + junior = self.context['junior'] + junior_details = Junior.objects.filter(id=junior).last() + junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details) + if self.context['action'] == str(NUMBER['one']): + # approve the task + instance.task_status = str(NUMBER['five']) + instance.is_approved = True + # update total task point + junior_data.total_points = junior_data.total_points + instance.points + # update complete time of task + instance.completed_on = real_time() + send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {}) + else: + # reject the task + instance.task_status = str(NUMBER['three']) + instance.is_approved = False + # update total task point + junior_data.total_points = junior_data.total_points - instance.points + # update reject time of task + instance.rejected_on = real_time() + send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {}) + instance.save() + junior_data.save() + return junior_details diff --git a/guardian/utils.py b/guardian/utils.py index b1d5eda..4a25d9e 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -18,6 +18,10 @@ from junior.models import Junior, JuniorPoints from .models import JuniorTask # Import app from celery from zod_bank.celery import app +# notification's constant +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 @@ -85,6 +89,8 @@ 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, {}) + @app.task diff --git a/guardian/views.py b/guardian/views.py index 5fe331e..7a56656 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -35,7 +35,7 @@ from account.utils import custom_response, custom_error_response, OTP_EXPIRY, se from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from .utils import upload_image_to_alibaba -from notifications.constants import REGISTRATION, TASK_CREATED +from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING from notifications.utils import send_notification """ Define APIs """ @@ -245,6 +245,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): # 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, many=True) @@ -309,11 +310,14 @@ class ApproveTaskAPIView(viewsets.ViewSet): "action": str(request.data['action']), "junior": self.request.data['junior_id']}, data=request.data) - if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid(): + 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): # 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(): + 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): # save serializer serializer.save() return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) diff --git a/junior/serializers.py b/junior/serializers.py index 13b6815..27a6808 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -18,7 +18,8 @@ from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail 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 +from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION, + TASK_SUBMITTED) class ListCharField(serializers.ListField): @@ -284,9 +285,13 @@ class AddJuniorSerializer(serializers.ModelSerializer): expiry_time = timezone.now() + timezone.timedelta(days=1) UserEmailOtp.objects.create(email=email, otp=otp_value, user_type='1', expired_at=expiry_time, is_verified=True) + # add push notification + UserNotification.objects.get_or_create(user=user_data) """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(guardian, full_name) + # push notification + send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) return junior_data @@ -317,6 +322,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, {}) return instance class JuniorPointsSerializer(serializers.ModelSerializer): @@ -393,7 +400,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): instance.id), referral_code_used=junior_data.referral_code, is_verified=True) - UserNotification.objects.create(user=instance) + UserNotification.objects.get_or_create(user=instance) return guardian_data else: user = User.objects.create(username=email, email=email, @@ -413,7 +420,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): UserEmailOtp.objects.create(email=email, otp=otp_value, user_type=str(NUMBER['two']), expired_at=expiry_time, is_verified=True) - UserNotification.objects.create(user=user) + UserNotification.objects.get_or_create(user=user) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) diff --git a/junior/views.py b/junior/views.py index 4ccca91..429b2a1 100644 --- a/junior/views.py +++ b/junior/views.py @@ -36,6 +36,8 @@ from base.constants import NUMBER 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 """ Define APIs """ # Define validate guardian code API, @@ -233,6 +235,7 @@ class RemoveJuniorAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() + send_notification.delay(REMOVE_JUNIOR, 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: @@ -374,6 +377,8 @@ class InviteGuardianAPIView(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): """ junior list""" try: + if request.data['email'] == '': + return custom_error_response(ERROR_CODE['2062'], response_status=status.HTTP_400_BAD_REQUEST) info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name'], 'relationship': str(request.data['relationship'])} # use AddJuniorSerializer serializer diff --git a/notifications/constants.py b/notifications/constants.py index c6f0a9f..b754ef0 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -1,10 +1,19 @@ """ notification constants file """ + REGISTRATION = 1 TASK_CREATED = 2 INVITED_GUARDIAN = 3 APPROVED_JUNIOR = 4 +REFERRAL_POINTS = 5 +TASK_POINTS = 6 +TASK_REJECTED = 7 +SKIPPED_PROFILE_SETUP = 8 +TASK_SUBMITTED = 9 +TASK_ACTION = 10 +LEADERBOARD_RANKING = 11 +REMOVE_JUNIOR = 12 TEST_NOTIFICATION = 99 NOTIFICATION_DICT = { @@ -22,7 +31,39 @@ NOTIFICATION_DICT = { }, APPROVED_JUNIOR: { "title": "Approve junior", - "body": "You have request for associate the junior" + "body": "You have request from junior to associate with you" + }, + 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." + }, + TASK_REJECTED: { + "title": "Task rejected!", + "body": "Your task has been rejected." + }, + 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." + }, + TASK_ACTION: { + "title": "Task approval!", + "body": "You have request for task approval." + }, + LEADERBOARD_RANKING: { + "title": "Leader board rank!", + "body": "Your rank is ." + }, + REMOVE_JUNIOR: { + "title": "Disassociate by guardian!", + "body": "Your guardian disassociate you ." }, TEST_NOTIFICATION: { "title": "Test Notification", From a323ad7df772e4d645c4cae7e071a3a6d15bc685 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 26 Jul 2023 16:42:26 +0530 Subject: [PATCH 148/372] jira-28 notification --- notifications/constants.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/notifications/constants.py b/notifications/constants.py index b754ef0..b861142 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -1,19 +1,19 @@ """ notification constants file """ - -REGISTRATION = 1 -TASK_CREATED = 2 -INVITED_GUARDIAN = 3 -APPROVED_JUNIOR = 4 -REFERRAL_POINTS = 5 -TASK_POINTS = 6 -TASK_REJECTED = 7 -SKIPPED_PROFILE_SETUP = 8 -TASK_SUBMITTED = 9 -TASK_ACTION = 10 -LEADERBOARD_RANKING = 11 -REMOVE_JUNIOR = 12 +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'] TEST_NOTIFICATION = 99 NOTIFICATION_DICT = { From 87aa1af02b7d1aa6a60ba130df4c1e0ddf471c0e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 26 Jul 2023 17:04:35 +0530 Subject: [PATCH 149/372] change password api --- web_admin/serializers/auth_serializer.py | 102 +++++++++++++++++++---- web_admin/urls.py | 4 +- web_admin/views/auth.py | 38 +++++++-- 3 files changed, 122 insertions(+), 22 deletions(-) diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 386571b..06cdf3a 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -1,6 +1,7 @@ """ web_admin auth serializers file """ +# python imports from datetime import datetime # django imports @@ -13,7 +14,6 @@ from templated_email import send_templated_mail # local imports from account.models import UserEmailOtp -from account.utils import custom_error_response from base.messages import ERROR_CODE from guardian.tasks import generate_otp @@ -21,9 +21,15 @@ USER = get_user_model() class AdminForgotPasswordSerializer(serializers.ModelSerializer): - email = serializers.EmailField(required=True) + """ + admin forgot password serializer + """ + email = serializers.EmailField() class Meta: + """ + meta class + """ model = USER fields = ('email',) @@ -38,11 +44,12 @@ class AdminForgotPasswordSerializer(serializers.ModelSerializer): return attrs def create(self, validated_data): + """ + to send otp + :return: user_data + """ email = 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 @@ -68,21 +75,88 @@ class AdminForgotPasswordSerializer(serializers.ModelSerializer): class AdminVerifyOTPSerializer(serializers.Serializer): + """ + admin verify otp serializer + """ + email = serializers.EmailField() otp = serializers.CharField(max_length=6, min_length=6) class Meta: """ meta class """ - fields = ('otp',) + fields = ('email', 'otp',) def validate(self, attrs): - otp = attrs.pop('otp') + """ + to validate data + :return: validated data + """ + email = attrs.get('email') + otp = attrs.get('otp') + + user = USER.objects.filter(email=attrs['email']).first() + if not user: + raise serializers.ValidationError(ERROR_CODE['2004']) + elif not user.is_superuser: + raise serializers.ValidationError(ERROR_CODE['2036']) # fetch email otp object of the user - user_opt_details = UserEmailOtp.objects.filter(otp=otp).last() - if not user_opt_details: + user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last() + if not user_otp_details: raise serializers.ValidationError(ERROR_CODE['2008']) - if user_opt_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): + if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): raise serializers.ValidationError(ERROR_CODE['2029']) - user_details = USER.objects.filter(email=user_opt_details.email).last() - user_opt_details.delete() - if user_details: + user_otp_details.is_verified = True + user_otp_details.save() + return attrs + + +class AdminCreatePasswordSerializer(serializers.ModelSerializer): + """ + admin create new password serializer + """ + email = serializers.EmailField() + new_password = serializers.CharField() + confirm_password = serializers.CharField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('email', 'new_password', 'confirm_password') + + def validate(self, attrs): + """ + to validate data + :return: validated data + """ + email = attrs.get('email') + new_password = attrs.get('new_password') + confirm_password = attrs.get('confirm_password') + + # matching password + if new_password != confirm_password: + raise serializers.ValidationError('password do not match') + + user = USER.objects.filter(email=attrs['email']).first() + if not user: + raise serializers.ValidationError(ERROR_CODE['2004']) + elif not user.is_superuser: + raise serializers.ValidationError(ERROR_CODE['2036']) + + user_otp_details = UserEmailOtp.objects.filter(email=email).last() + + if user_otp_details and user_otp_details.is_verified: + user_otp_details.delete() + attrs.update({'user': user}) return attrs + raise serializers.ValidationError(ERROR_CODE['2036']) + + def create(self, validated_data): + """ + to create password + :return: user + """ + user = validated_data.get('user') + user.set_password(validated_data.get('password')) + user.save() + return user diff --git a/web_admin/urls.py b/web_admin/urls.py index 95228ad..5fbe21e 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -7,7 +7,7 @@ from rest_framework import routers # local imports from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet -from web_admin.views.auth import ForgetAndResetPasswordViewSet +from web_admin.views.auth import ForgotAndResetPasswordViewSet # initiate router router = routers.SimpleRouter() @@ -17,7 +17,7 @@ router.register('default-card-images', DefaultArticleCardImagesViewSet, basename router.register('user', UserManagementViewSet, basename='user') # forgot and reset password api for admin -router.register('admin', ForgetAndResetPasswordViewSet, basename='admin') +router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index 84835cb..2129600 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -2,26 +2,32 @@ web_admin auth views file """ # django imports -from rest_framework.viewsets import GenericViewSet, mixins +from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from django.contrib.auth import get_user_model # local imports from account.utils import custom_response -from base.constants import USER_TYPE -from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.permission import AdminPermission -from web_admin.serializers.auth_serializer import AdminForgotPasswordSerializer, AdminVerifyOTPSerializer +from base.messages import SUCCESS_CODE +from web_admin.serializers.auth_serializer import (AdminForgotPasswordSerializer, AdminVerifyOTPSerializer, + AdminCreatePasswordSerializer) USER = get_user_model() -class ForgetAndResetPasswordViewSet(GenericViewSet): +class ForgotAndResetPasswordViewSet(GenericViewSet): + """ + to reset admin password + """ queryset = None @action(methods=['post'], url_name='forgot-password', url_path='forgot-password', detail=False, serializer_class=AdminForgotPasswordSerializer) def admin_forgot_password(self, request): + """ + api method to send otp + :return: success message + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() @@ -30,6 +36,10 @@ class ForgetAndResetPasswordViewSet(GenericViewSet): @action(methods=['post'], url_name='verify-otp', url_path='verify-otp', detail=False, serializer_class=AdminVerifyOTPSerializer) def admin_verify_otp(self, request): + """ + api method to verify otp + :return: success message + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) return custom_response(SUCCESS_CODE['3011']) @@ -37,7 +47,23 @@ class ForgetAndResetPasswordViewSet(GenericViewSet): @action(methods=['post'], url_name='resend-otp', url_path='resend-otp', detail=False, serializer_class=AdminForgotPasswordSerializer) def admin_resend_otp(self, request): + """ + api method to resend otp + :return: success message + """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return custom_response(SUCCESS_CODE['3015']) + + @action(methods=['post'], url_name='create-password', url_path='create-password', + detail=False, serializer_class=AdminCreatePasswordSerializer) + def admin_create_password(self, request): + """ + api method to create new password + :return: success message + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE['3007']) From c079f3ceca942fe2f461b644beefed35bc0c13de Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 27 Jul 2023 10:59:10 +0530 Subject: [PATCH 150/372] some changes in forgot password api --- .gitignore | 2 +- base/messages.py | 4 +- celerybeat-schedule | Bin 16384 -> 16384 bytes web_admin/serializers/auth_serializer.py | 47 ++++++++--------------- web_admin/views/auth.py | 4 +- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 063a8af..f1d4456 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ static/* __pycache__/ *.env ve/* - +celerybeat-schedule \ No newline at end of file diff --git a/base/messages.py b/base/messages.py index f99f9cb..a58dd79 100644 --- a/base/messages.py +++ b/base/messages.py @@ -88,7 +88,9 @@ ERROR_CODE = { "2060": "Task does not exist or not in pending state", "2061": "Please insert image or check the image is valid or not.", # email not null - "2062": "Please enter email address" + "2062": "Please enter email address", + "2063": "Unauthorized access.", + "2064": "To change your password first request an OTP and get it verify then change your password." } """Success message code""" SUCCESS_CODE = { diff --git a/celerybeat-schedule b/celerybeat-schedule index 573b0c9bd5140b34794915695cfc78142e742fee..71062ba3971bec6320e397a68913989f4b8aa458 100644 GIT binary patch literal 16384 zcmeI3O-~a+7{|A?Sl+5Ah(H0s7ha6nfT)S_VnR5O#!H3RaB#ETj_js&+v(04AgSR* zG}#-=#SdUS8ZRF8>d6n_$&)v)UQF~EW|}s7F$5Ac|J}?y&%Er+?(doTXSYc|y?i)e z^c%)OA|q|Lwy&hUCo{wNp$u{m009sH0T2KI5C8!X009sH0T2Lzf04kXzG+wm0vy0W z62LmZI>0)>K7bBD2cQGc0q6j906G92_|J6Ul-kus(@xE+ij1x|boq=f&**Zb%jE^- zOed6G*6r{5RC#4lcO;Xd^plQOP0K^QVgU}|(ApK)FHIK5y)Qn0{4!%%PuX{^XT3D> zAf@A;7vrl4g9A7y0^k4+?F5vcee`xB6pnRUzh6py#`|Q86ZBKD0EfeIuwQ%|V7{mi z-B8!3$y}#zI`BLk&-ZUuhsCyGWVBv&&@ksHoiv^@<{k@nk_~Q{VtXgGFC#CHD7m(} z6q}3Yf@(uw|NmE8dG|-9$9E5!&^VXZtk1R|(9pRbxc69k&H^>d*oGeE}R*bhDiUsjl($Bad$4ucJHWj{O;3!iNI{KmY_l S00ck)1V8`;KmY_zGJ#(a8VJ$= delta 402 zcmX|*F;Bu!6op#~mOgkz#mR((kQl)M>EJ*d35nXRYSOqg?c*sgl$P5*444o*5eFSi z{{VxVli^4C0~}m^gvNV^d-9!}lba-RlE|xiBPVSKXIJWMg{E)sukmhNt^N8(9^=d^ z38%8I9sLw>KSQcOpX*w?C*$J`ZHjR^K3!EE=aEJ+3j@bV6uTh!Sz7NjE|ixx4S2+8 zoG1&Bns!-ObT(lT9XQ;LM<5eIeHzW%9(6goCPO-y`m`@Xa|F5OP_|nllD>6X&!CWj z%PD7Liv9)Udr19O4^dYy&Ca1fyeacJ3u5#@xyyNkV23<~0cF8`i0*{A+z3!@P4qg` zV8Y1Ar%+siW&yGlL2qw*P%`Bulr6wZ&@HH#66t1-#@UZCB2gG{*F&(|G$m8AWsyCB Yy`fkR`xaCeaNxBTkdBPkFGJJ+0CH?-P5=M^ diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 9ce0491..bed2891 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -9,11 +9,11 @@ from rest_framework import serializers from django.contrib.auth import get_user_model from django.conf import settings from django.utils import timezone -from rest_framework import status from templated_email import send_templated_mail # local imports from account.models import UserEmailOtp +from base.constants import USER_TYPE from base.messages import ERROR_CODE from guardian.tasks import generate_otp @@ -35,11 +35,11 @@ class AdminOTPSerializer(serializers.ModelSerializer): def validate(self, attrs): """ used to validate the incoming data """ - user = USER.objects.filter(email=attrs['email']).first() + user = USER.objects.filter(email=attrs.get('email')).first() if not user: raise serializers.ValidationError(ERROR_CODE['2004']) elif not user.is_superuser: - raise serializers.ValidationError(ERROR_CODE['2036']) + raise serializers.ValidationError(ERROR_CODE['2063']) attrs.update({'user': user}) return attrs @@ -67,9 +67,11 @@ class AdminOTPSerializer(serializers.ModelSerializer): user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry + user_data.user_type = dict(USER_TYPE).get('3') if user_data: user_data.otp = verification_code user_data.expired_at = expiry + user_data.user_type = dict(USER_TYPE).get('3') user_data.save() return user_data @@ -93,15 +95,12 @@ class AdminVerifyOTPSerializer(serializers.Serializer): email = attrs.get('email') otp = attrs.get('otp') - user = USER.objects.filter(email=attrs['email']).first() - if not user: - raise serializers.ValidationError(ERROR_CODE['2004']) - elif not user.is_superuser: - raise serializers.ValidationError(ERROR_CODE['2036']) # fetch email otp object of the user user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last() if not user_otp_details: - raise serializers.ValidationError(ERROR_CODE['2008']) + raise serializers.ValidationError(ERROR_CODE['2064']) + if user_otp_details.user_type != dict(USER_TYPE).get('3'): + raise serializers.ValidationError(ERROR_CODE['2063']) if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): raise serializers.ValidationError(ERROR_CODE['2029']) user_otp_details.is_verified = True @@ -137,26 +136,12 @@ class AdminCreatePasswordSerializer(serializers.ModelSerializer): if new_password != confirm_password: raise serializers.ValidationError('password do not match') - user = USER.objects.filter(email=attrs['email']).first() - if not user: - raise serializers.ValidationError(ERROR_CODE['2004']) - elif not user.is_superuser: - raise serializers.ValidationError(ERROR_CODE['2036']) - user_otp_details = UserEmailOtp.objects.filter(email=email).last() - - if user_otp_details and user_otp_details.is_verified: - user_otp_details.delete() - attrs.update({'user': user}) - return attrs - raise serializers.ValidationError(ERROR_CODE['2036']) - - def create(self, validated_data): - """ - to create password - :return: user - """ - user = validated_data.get('user') - user.set_password(validated_data.get('password')) - user.save() - return user + if not user_otp_details: + raise serializers.ValidationError(ERROR_CODE['2064']) + if user_otp_details.user_type != dict(USER_TYPE).get('3'): + raise serializers.ValidationError(ERROR_CODE['2063']) + if not user_otp_details.is_verified: + raise serializers.ValidationError(ERROR_CODE['2064']) + user_otp_details.delete() + return attrs diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index d55cff2..009d7db 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -53,5 +53,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() + user = USER.objects.filter(email=serializer.validated_data.get('email')).first() + user.set_password(serializer.validated_data.get('new_password')) + user.save() return custom_response(SUCCESS_CODE['3007']) From 68b77ef766244bbf304d7bf3c0cdce2345dfcac9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 27 Jul 2023 12:56:50 +0530 Subject: [PATCH 151/372] added task to send otp on email --- .gitignore | 2 +- base/messages.py | 3 ++- base/tasks.py | 29 +++++++++++++++++++++ web_admin/serializers/auth_serializer.py | 33 ++++++++---------------- 4 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 base/tasks.py diff --git a/.gitignore b/.gitignore index f1d4456..63de945 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ static/* __pycache__/ *.env ve/* -celerybeat-schedule \ No newline at end of file +celerybeat-schedule diff --git a/base/messages.py b/base/messages.py index a58dd79..0407719 100644 --- a/base/messages.py +++ b/base/messages.py @@ -90,7 +90,8 @@ ERROR_CODE = { # email not null "2062": "Please enter email address", "2063": "Unauthorized access.", - "2064": "To change your password first request an OTP and get it verify then change your password." + "2064": "To change your password first request an OTP and get it verify then change your password.", + "2065": "Passwords do not match. Please try again." } """Success message code""" SUCCESS_CODE = { diff --git a/base/tasks.py b/base/tasks.py new file mode 100644 index 0000000..791be81 --- /dev/null +++ b/base/tasks.py @@ -0,0 +1,29 @@ +""" +web_admin tasks file +""" +# third party imports +from celery import shared_task +from templated_email import send_templated_mail + +# django imports +from django.conf import settings + + +@shared_task +def send_email_otp(email, verification_code): + """ + used to send otp on email + :param email: e-mail + :param verification_code: otp + """ + 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 + } + ) + return True diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index bed2891..249340b 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -7,15 +7,14 @@ from datetime import datetime # django imports from rest_framework import serializers from django.contrib.auth import get_user_model -from django.conf import settings from django.utils import timezone -from templated_email import send_templated_mail # local imports 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 USER = get_user_model() @@ -53,26 +52,16 @@ class AdminOTPSerializer(serializers.ModelSerializer): 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 - } - ) + send_email_otp.delay(email, verification_code) + expiry = timezone.now() + timezone.timedelta(days=1) - user_data, created = UserEmailOtp.objects.get_or_create(email=email) - if created: - user_data.expired_at = expiry - user_data.user_type = dict(USER_TYPE).get('3') - if user_data: - user_data.otp = verification_code - user_data.expired_at = expiry - user_data.user_type = dict(USER_TYPE).get('3') - user_data.save() + user_data, created = UserEmailOtp.objects.update_or_create(email=email, + defaults={ + "otp": verification_code, + "expired_at": expiry, + "user_type": dict(USER_TYPE).get('3'), + }) + return user_data @@ -134,7 +123,7 @@ class AdminCreatePasswordSerializer(serializers.ModelSerializer): # matching password if new_password != confirm_password: - raise serializers.ValidationError('password do not match') + raise serializers.ValidationError(ERROR_CODE['2065']) user_otp_details = UserEmailOtp.objects.filter(email=email).last() if not user_otp_details: From 83c66ab3b6089ff017bca274da21ff82c0ed1b2a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 27 Jul 2023 15:26:59 +0530 Subject: [PATCH 152/372] added request status in expired task cronjob --- guardian/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/utils.py b/guardian/utils.py index 4a25d9e..920a119 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -99,7 +99,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'])] + task_status = [str(NUMBER['one']), str(NUMBER['two']), str(NUMBER['four'])] JuniorTask.objects.filter(due_date__lt=datetime.today().date(), task_status__in=task_status).update(task_status=str(NUMBER['six'])) except ObjectDoesNotExist as e: From 151a177e76c082d413ab817d6cdcdcc6ac2e64ea Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 27 Jul 2023 19:29:12 +0530 Subject: [PATCH 153/372] some changes in admin password reset related api --- web_admin/serializers/auth_serializer.py | 18 +++++++-------- web_admin/views/auth.py | 28 ++++++++++++++---------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 249340b..511d344 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -36,9 +36,9 @@ class AdminOTPSerializer(serializers.ModelSerializer): """ used to validate the incoming data """ user = USER.objects.filter(email=attrs.get('email')).first() if not user: - raise serializers.ValidationError(ERROR_CODE['2004']) + raise serializers.ValidationError({'details': ERROR_CODE['2004']}) elif not user.is_superuser: - raise serializers.ValidationError(ERROR_CODE['2063']) + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) attrs.update({'user': user}) return attrs @@ -87,11 +87,11 @@ class AdminVerifyOTPSerializer(serializers.Serializer): # fetch email otp object of the user user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last() if not user_otp_details: - raise serializers.ValidationError(ERROR_CODE['2064']) + raise serializers.ValidationError({'details': ERROR_CODE['2064']}) if user_otp_details.user_type != dict(USER_TYPE).get('3'): - raise serializers.ValidationError(ERROR_CODE['2063']) + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): - raise serializers.ValidationError(ERROR_CODE['2029']) + raise serializers.ValidationError({'details': ERROR_CODE['2029']}) user_otp_details.is_verified = True user_otp_details.save() return attrs @@ -123,14 +123,14 @@ class AdminCreatePasswordSerializer(serializers.ModelSerializer): # matching password if new_password != confirm_password: - raise serializers.ValidationError(ERROR_CODE['2065']) + raise serializers.ValidationError({'details': ERROR_CODE['2065']}) user_otp_details = UserEmailOtp.objects.filter(email=email).last() if not user_otp_details: - raise serializers.ValidationError(ERROR_CODE['2064']) + raise serializers.ValidationError({'details': ERROR_CODE['2064']}) if user_otp_details.user_type != dict(USER_TYPE).get('3'): - raise serializers.ValidationError(ERROR_CODE['2063']) + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) if not user_otp_details.is_verified: - raise serializers.ValidationError(ERROR_CODE['2064']) + raise serializers.ValidationError({'details': ERROR_CODE['2064']}) user_otp_details.delete() return attrs diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index 009d7db..0273a08 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -4,11 +4,12 @@ web_admin auth views file # django imports from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action +from rest_framework import status from django.contrib.auth import get_user_model # local imports -from account.utils import custom_response -from base.messages import SUCCESS_CODE +from account.utils import custom_response, custom_error_response +from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.serializers.auth_serializer import (AdminOTPSerializer, AdminVerifyOTPSerializer, AdminCreatePasswordSerializer) @@ -29,9 +30,10 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): :return: success message """ serializer = self.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return custom_response(SUCCESS_CODE['3015']) + if serializer.is_valid(): + serializer.save() + return custom_response(SUCCESS_CODE['3015']) + return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='verify-otp', url_path='verify-otp', detail=False, serializer_class=AdminVerifyOTPSerializer) @@ -41,8 +43,9 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): :return: success message """ serializer = self.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - return custom_response(SUCCESS_CODE['3011']) + if serializer.is_valid(): + return custom_response(SUCCESS_CODE['3011']) + return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='create-password', url_path='create-password', detail=False, serializer_class=AdminCreatePasswordSerializer) @@ -52,8 +55,9 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): :return: success message """ serializer = self.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - user = USER.objects.filter(email=serializer.validated_data.get('email')).first() - user.set_password(serializer.validated_data.get('new_password')) - user.save() - return custom_response(SUCCESS_CODE['3007']) + if serializer.is_valid(): + user = USER.objects.filter(email=serializer.validated_data.get('email')).first() + user.set_password(serializer.validated_data.get('new_password')) + user.save() + return custom_response(SUCCESS_CODE['3007']) + return custom_error_response(ERROR_CODE['2064'], status.HTTP_400_BAD_REQUEST) From de774111c068231dfab6065c7db0905ab0a7db89 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 28 Jul 2023 16:08:40 +0530 Subject: [PATCH 154/372] some changes in artcle api --- web_admin/serializers/article_serializer.py | 8 ++++---- web_admin/serializers/auth_serializer.py | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 185482f..e374679 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -60,14 +60,14 @@ class ArticleSurveySerializer(serializers.ModelSerializer): article survey serializer """ id = serializers.IntegerField(required=False) - survey_options = SurveyOptionSerializer(many=True) + options = SurveyOptionSerializer(many=True) class Meta: """ meta class """ model = ArticleSurvey - fields = ('id', 'question', 'survey_options') + fields = ('id', 'question', 'options') class ArticleSerializer(serializers.ModelSerializer): @@ -120,7 +120,7 @@ class ArticleSerializer(serializers.ModelSerializer): for survey in article_survey: survey = pop_id(survey) - options = survey.pop('survey_options') + options = survey.pop('options') survey_obj = ArticleSurvey.objects.create(article=article, points=ARTICLE_SURVEY_POINTS, **survey) for option in options: option = pop_id(option) @@ -165,7 +165,7 @@ class ArticleSerializer(serializers.ModelSerializer): # Update or create survey sections for survey_data in article_survey: survey_id = survey_data.get('id', None) - options_data = survey_data.pop('survey_options') + options_data = survey_data.pop('options') if survey_id: survey = ArticleSurvey.objects.get(id=survey_id, article=instance) survey.question = survey_data.get('question', survey.question) diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 511d344..9f603ce 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -125,12 +125,10 @@ class AdminCreatePasswordSerializer(serializers.ModelSerializer): if new_password != confirm_password: raise serializers.ValidationError({'details': ERROR_CODE['2065']}) - user_otp_details = UserEmailOtp.objects.filter(email=email).last() + user_otp_details = UserEmailOtp.objects.filter(email=email, is_verified=True).last() if not user_otp_details: raise serializers.ValidationError({'details': ERROR_CODE['2064']}) if user_otp_details.user_type != dict(USER_TYPE).get('3'): raise serializers.ValidationError({'details': ERROR_CODE['2063']}) - if not user_otp_details.is_verified: - raise serializers.ValidationError({'details': ERROR_CODE['2064']}) user_otp_details.delete() return attrs From d68f24543d72798dbac70c98fb2f9e771b221828 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 28 Jul 2023 17:29:15 +0530 Subject: [PATCH 155/372] changes in approve junior and add juinior api, added search in junior list api --- guardian/serializers.py | 3 +++ guardian/views.py | 3 +++ junior/views.py | 26 ++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 8ec66bf..df2178a 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -308,6 +308,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer): 'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method', 'updated_at', 'passcode'] + class ApproveJuniorSerializer(serializers.ModelSerializer): """approve junior serializer""" class Meta(object): @@ -319,9 +320,11 @@ class ApproveJuniorSerializer(serializers.ModelSerializer): """update guardian code""" instance = self.context['junior'] instance.guardian_code = [self.context['guardian_code']] + instance.is_invited = True instance.save() return instance + class ApproveTaskSerializer(serializers.ModelSerializer): """approve task serializer""" class Meta(object): diff --git a/guardian/views.py b/guardian/views.py index 7a56656..9706e87 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -281,10 +281,13 @@ class ApproveJuniorAPIView(viewsets.ViewSet): serializer.save() return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: + queryset[1].guardian_code = None + queryset[1].save() 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): """approve junior by guardian""" serializer_class = ApproveTaskSerializer diff --git a/junior/views.py b/junior/views.py index 429b2a1..b9c3f88 100644 --- a/junior/views.py +++ b/junior/views.py @@ -5,6 +5,8 @@ from rest_framework import viewsets, status, generics,views from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User +from rest_framework.filters import SearchFilter + import datetime import requests """Django app import""" @@ -115,8 +117,14 @@ class JuniorListAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer queryset = Junior.objects.all() permission_classes = [IsAuthenticated] + filter_backends = (SearchFilter,) + search_fields = ['auth__first_name', 'auth__last_name'] http_method_names = ('get',) + def get_queryset(self): + queryset = self.filter_queryset(self.queryset) + return queryset + def list(self, request, *args, **kwargs): """ junior list""" try: @@ -124,7 +132,8 @@ class JuniorListAPIView(viewsets.ModelViewSet): guardian_data = Guardian.objects.filter(user__email=request.user).last() # fetch junior object if guardian_data: - queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + queryset = self.get_queryset() + queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code)) # use JuniorDetailListSerializer serializer serializer = JuniorDetailListSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -132,20 +141,21 @@ class JuniorListAPIView(viewsets.ModelViewSet): except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" queryset = Junior.objects.all() serializer_class = AddJuniorSerializer permission_classes = [IsAuthenticated] http_method_names = ('post',) + def create(self, request, *args, **kwargs): """ junior list""" try: info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name']} - if User.objects.filter(username=request.data['email']): - return custom_error_response(ERROR_CODE['2059'], response_status=status.HTTP_400_BAD_REQUEST) - + if user := User.objects.filter(username=request.data['email']): + self.associate_guardian(user) # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info_data) if serializer.is_valid(): @@ -156,6 +166,14 @@ class AddJuniorAPIView(viewsets.ModelViewSet): except Exception as e: 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() + guardian = Guardian.objects.filter(user=self.request.user).first() + junior.guardian_code = [guardian.guardian_code] + junior.save() + return True + + class InvitedJuniorAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" From 6ffaa66a4dcf6a1ce6c2daf200f54643ee0dc624 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 28 Jul 2023 18:57:11 +0530 Subject: [PATCH 156/372] fixed add junior api --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index b9c3f88..06eeef0 100644 --- a/junior/views.py +++ b/junior/views.py @@ -154,7 +154,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): try: info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name']} - if user := User.objects.filter(username=request.data['email']): + if user := User.objects.filter(username=request.data['email']).first(): self.associate_guardian(user) # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info_data) From afaed69eecbe86783b434fe49ff0c5fd219bde5f Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 28 Jul 2023 19:19:59 +0530 Subject: [PATCH 157/372] fixed add junior api --- junior/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/junior/views.py b/junior/views.py index 06eeef0..c9c8739 100644 --- a/junior/views.py +++ b/junior/views.py @@ -156,6 +156,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 'last_name': request.data['last_name']} if user := User.objects.filter(username=request.data['email']).first(): self.associate_guardian(user) + return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK) # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info_data) if serializer.is_valid(): From ee92c98f34d5cedc996ed6b956396915e4e6f77e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 1 Aug 2023 13:12:37 +0530 Subject: [PATCH 158/372] article list api changes, changed related name for survey_options to options --- .../0004_alter_surveyoption_survey.py | 19 +++++++++++++++++++ web_admin/models.py | 2 +- web_admin/views/article.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 web_admin/migrations/0004_alter_surveyoption_survey.py diff --git a/web_admin/migrations/0004_alter_surveyoption_survey.py b/web_admin/migrations/0004_alter_surveyoption_survey.py new file mode 100644 index 0000000..8d28957 --- /dev/null +++ b/web_admin/migrations/0004_alter_surveyoption_survey.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.2 on 2023-08-01 07:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0003_defaultarticlecardimage_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='surveyoption', + name='survey', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='web_admin.articlesurvey'), + ), + ] diff --git a/web_admin/models.py b/web_admin/models.py index 950eec6..8a3bb16 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -56,7 +56,7 @@ class SurveyOption(models.Model): """ Survey Options model """ - survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='survey_options') + survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='options') option = models.CharField(max_length=255) is_answer = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 5aa88b3..5b0ca92 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -36,7 +36,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel def get_queryset(self): article = self.queryset.objects.filter(is_deleted=False).prefetch_related( - 'article_cards', 'article_survey', 'article_survey__survey_options' + 'article_cards', 'article_survey', 'article_survey__options' ).order_by('-created_at') queryset = self.filter_queryset(article) return queryset From d3564efbb97a49829442f8c16a698bfcee186ffc Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 1 Aug 2023 20:13:35 +0530 Subject: [PATCH 159/372] change in update api, added method to upload and get image url --- web_admin/serializers/article_serializer.py | 42 +++++++-------------- web_admin/utils.py | 11 ++++++ 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index e374679..6945880 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -11,7 +11,7 @@ from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage -from web_admin.utils import pop_id +from web_admin.utils import pop_id, get_image_url USER = get_user_model() @@ -32,11 +32,7 @@ class ArticleCardSerializer(serializers.ModelSerializer): fields = ('id', 'title', 'description', 'image', 'image_url') def create(self, validated_data): - if 'image' in validated_data and validated_data['image'] is not None: - image = validated_data.pop('image') - filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" - # upload image on ali baba - validated_data['image_url'] = upload_image_to_alibaba(image, filename) + validated_data['image_url'] = get_image_url(validated_data) article_card = ArticleCard.objects.create(article_id='1', **validated_data) return article_card @@ -111,11 +107,7 @@ class ArticleSerializer(serializers.ModelSerializer): for card in article_cards: card = pop_id(card) - if 'image' in card and card['image'] is not None: - image = card.pop('image') - filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" - # upload image on ali baba - card['image_url'] = upload_image_to_alibaba(image, filename) + card['image_url'] = get_image_url(card) ArticleCard.objects.create(article=article, **card) for survey in article_survey: @@ -139,28 +131,22 @@ class ArticleSerializer(serializers.ModelSerializer): instance.title = validated_data.get('title', instance.title) instance.description = validated_data.get('description', instance.description) instance.save() - + prev_card = list(ArticleCard.objects.filter(article=instance).values_list('id', flat=True)) # Update or create cards for card_data in article_cards: card_id = card_data.get('id', None) if card_id: + prev_card.remove(card_id) card = ArticleCard.objects.get(id=card_id, article=instance) card.title = card_data.get('title', card.title) card.description = card_data.get('description', card.description) - if 'image' in card_data and card_data['image'] is not None: - image = card_data.pop('image') - filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" - # upload image on ali baba - card.image_url = upload_image_to_alibaba(image, filename) + card.image_url = get_image_url(card_data) card.save() else: card_data = pop_id(card_data) - if 'image' in card_data and card_data['image'] is not None: - image = card_data.pop('image') - filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" - # upload image on ali baba - card_data['image_url'] = upload_image_to_alibaba(image, filename) + card_data['image_url'] = get_image_url(card_data) ArticleCard.objects.create(article=instance, **card_data) + ArticleCard.objects.filter(id__in=prev_card, article=instance).delete() # Update or create survey sections for survey_data in article_survey: @@ -172,7 +158,7 @@ class ArticleSerializer(serializers.ModelSerializer): survey.save() else: survey_data = pop_id(survey_data) - survey = ArticleSurvey.objects.create(article=instance, **survey_data) + survey = ArticleSurvey.objects.create(article=instance, points=ARTICLE_SURVEY_POINTS, **survey_data) # Update or create survey options for option_data in options_data: @@ -210,6 +196,9 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): """ if 'image' not in attrs and attrs.get('image') is None: raise serializers.ValidationError({'details': ERROR_CODE['2061']}) + image = attrs.get('image') + if image and image.size == NUMBER['zero']: + raise serializers.ValidationError(ERROR_CODE['2035']) return attrs def create(self, validated_data): @@ -217,12 +206,7 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): to create and upload image :return: card_image object """ - image = validated_data.pop('image') - filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" - if image and image.size == NUMBER['zero']: - raise serializers.ValidationError(ERROR_CODE['2035']) - # upload image on ali baba - validated_data['image_url'] = upload_image_to_alibaba(image, filename) + validated_data['image_url'] = get_image_url(validated_data) card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image diff --git a/web_admin/utils.py b/web_admin/utils.py index 2e09e2f..aea72e8 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -1,6 +1,8 @@ """ web_utils file """ +from base.constants import ARTICLE_CARD_IMAGE_FOLDER +from guardian.utils import upload_image_to_alibaba def pop_id(data): @@ -11,3 +13,12 @@ def pop_id(data): """ data.pop('id') if 'id' in data else data return data + + +def get_image_url(data): + if 'image' in data and data['image'] is not None: + image = data.pop('image') + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" + # upload image on ali baba + image_url = upload_image_to_alibaba(image, filename) + return image_url From f3a8b5261708bed0b49c62c73255b7ff9bf6e793 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 2 Aug 2023 11:00:02 +0530 Subject: [PATCH 160/372] api response time --- guardian/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 9706e87..15b29c5 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -131,7 +131,6 @@ class TaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() http_method_names = ('get',) def list(self, request, *args, **kwargs): From af121f5a539c628ae59a70962a2764c4d10bf84e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 2 Aug 2023 11:16:12 +0530 Subject: [PATCH 161/372] changes in article update api and get image url method --- web_admin/serializers/article_serializer.py | 6 +++++- web_admin/utils.py | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 6945880..f3b5d06 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -33,7 +33,8 @@ class ArticleCardSerializer(serializers.ModelSerializer): def create(self, validated_data): validated_data['image_url'] = get_image_url(validated_data) - article_card = ArticleCard.objects.create(article_id='1', **validated_data) + article = Article.objects.all().first() + article_card = ArticleCard.objects.create(article=article, **validated_data) return article_card @@ -148,11 +149,13 @@ class ArticleSerializer(serializers.ModelSerializer): ArticleCard.objects.create(article=instance, **card_data) ArticleCard.objects.filter(id__in=prev_card, article=instance).delete() + prev_survey = list(ArticleSurvey.objects.filter(article=instance).values_list('id', flat=True)) # Update or create survey sections for survey_data in article_survey: survey_id = survey_data.get('id', None) options_data = survey_data.pop('options') if survey_id: + prev_survey.remove(survey_id) survey = ArticleSurvey.objects.get(id=survey_id, article=instance) survey.question = survey_data.get('question', survey.question) survey.save() @@ -171,6 +174,7 @@ class ArticleSerializer(serializers.ModelSerializer): else: option_data = pop_id(option_data) SurveyOption.objects.create(survey=survey, **option_data) + ArticleSurvey.objects.filter(id__in=prev_survey, article=instance).delete() return instance diff --git a/web_admin/utils.py b/web_admin/utils.py index aea72e8..6ecd5e3 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -16,7 +16,9 @@ def pop_id(data): def get_image_url(data): - if 'image' in data and data['image'] is not None: + if 'image_url' in data: + return data['image_url'] + elif 'image' in data and data['image'] is not None: image = data.pop('image') filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" # upload image on ali baba From ed8fc156ac3946d8c15f3c79ae68dbd99784081b Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 2 Aug 2023 14:23:36 +0530 Subject: [PATCH 162/372] change junior task list api response --- base/constants.py | 1 + guardian/serializers.py | 27 ++++++++++++++++++++++++++- junior/serializers.py | 8 ++++++-- junior/views.py | 6 +++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/base/constants.py b/base/constants.py index 3c1501c..6022f62 100644 --- a/base/constants.py +++ b/base/constants.py @@ -83,6 +83,7 @@ IN_PROGRESS = 2 REJECTED = 3 REQUESTED = 4 COMPLETED = 5 +EXPIRED = 6 TASK_POINTS = 5 # duplicate name used defined in constant PROJECT_NAME PROJECT_NAME = 'Zod Bank' diff --git a/guardian/serializers.py b/guardian/serializers.py index df2178a..1509fdf 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -211,7 +211,7 @@ class GuardianDetailSerializer(serializers.ModelSerializer): """Meta info""" model = Guardian fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', + 'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at'] class TaskDetailsSerializer(serializers.ModelSerializer): """Task detail serializer""" @@ -244,6 +244,31 @@ class TaskDetailsSerializer(serializers.ModelSerializer): 'requested_on', 'rejected_on', 'completed_on', 'is_expired', 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at'] +class TaskDetailsjuniorSerializer(serializers.ModelSerializer): + """Task detail serializer""" + + guardian = GuardianDetailSerializer() + remaining_time = serializers.SerializerMethodField('get_remaining_time') + + def get_remaining_time(self, obj): + """ remaining time to complete task""" + due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) + # fetch real time + current_datetime = real_time() + # Perform the subtraction + if due_date_datetime > current_datetime: + time_difference = due_date_datetime - current_datetime + time_only = convert_timedelta_into_datetime(time_difference) + return str(time_difference.days) + ' days ' + str(time_only) + return str(NUMBER['zero']) + ' days ' + '00:00:00:00000' + + + class Meta(object): + """Meta info""" + model = JuniorTask + fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image', + 'requested_on', 'rejected_on', 'completed_on', + 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at'] class TopJuniorSerializer(serializers.ModelSerializer): """Top junior serializer""" diff --git a/junior/serializers.py b/junior/serializers.py index 27a6808..0c5911a 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,7 @@ from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship 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 +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail @@ -335,6 +335,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer): requested_task = serializers.SerializerMethodField('get_requested_task') rejected_task = serializers.SerializerMethodField('get_rejected_task') pending_task = serializers.SerializerMethodField('get_pending_task') + expired_task = serializers.SerializerMethodField('get_expired_task') position = serializers.SerializerMethodField('get_position') def get_junior_id(self, obj): @@ -370,11 +371,14 @@ class JuniorPointsSerializer(serializers.ModelSerializer): def get_pending_task(self, obj): return JuniorTask.objects.filter(junior=obj.junior, task_status=PENDING).count() + def get_expired_task(self, obj): + return JuniorTask.objects.filter(junior=obj.junior, task_status=EXPIRED).count() + class Meta(object): """Meta info""" model = Junior fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', - 'requested_task', 'rejected_task'] + 'requested_task', 'rejected_task', 'expired_task'] class AddGuardianSerializer(serializers.ModelSerializer): """Add guardian serializer""" diff --git a/junior/views.py b/junior/views.py index c9c8739..8cb95a3 100644 --- a/junior/views.py +++ b/junior/views.py @@ -32,7 +32,7 @@ from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, Ad RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer) from guardian.models import Guardian, JuniorTask -from guardian.serializers import TaskDetailsSerializer +from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER from account.utils import custom_response, custom_error_response @@ -265,7 +265,7 @@ class RemoveJuniorAPIView(views.APIView): class JuniorTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" - serializer_class = TaskDetailsSerializer + serializer_class = TaskDetailsjuniorSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination queryset = JuniorTask.objects.all() @@ -295,7 +295,7 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) + serializer = TaskDetailsjuniorSerializer(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) From ebb468166edc39c6dc0761e7d42bf1969dcd5f7e Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 2 Aug 2023 15:20:43 +0530 Subject: [PATCH 163/372] sprint4 reassign task after expired --- base/messages.py | 6 ++++-- junior/serializers.py | 14 ++++++++++++++ junior/urls.py | 5 +++-- junior/views.py | 26 +++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/base/messages.py b/base/messages.py index 0407719..bff508d 100644 --- a/base/messages.py +++ b/base/messages.py @@ -91,7 +91,8 @@ ERROR_CODE = { "2062": "Please enter email address", "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", - "2065": "Passwords do not match. Please try again." + "2065": "Passwords do not match. Please try again.", + "2066": "Task does not exist or not in expired state" } """Success message code""" SUCCESS_CODE = { @@ -140,7 +141,8 @@ SUCCESS_CODE = { "3032": "Task request sent successfully", "3033": "Valid Referral code", "3034": "Invite guardian successfully", - "3035": "Task started successfully" + "3035": "Task started successfully", + "3036": "Task reassign successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/junior/serializers.py b/junior/serializers.py index 0c5911a..54332f0 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -458,3 +458,17 @@ class StartTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['two']) instance.save() return instance + +class ReAssignTaskSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = JuniorTask + fields = ('id', 'due_date') + def update(self, instance, validated_data): + instance.task_status = str(NUMBER['one']) + instance.due_date = validated_data.get('due_date') + instance.is_approved = False + instance.requested_on = None + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index e4b2489..f66fa30 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView) """Third party import""" from rest_framework import routers @@ -46,5 +46,6 @@ urlpatterns = [ path('api/v1/', include(router.urls)), path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()), path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()), - path('api/v1/start-task/', StartTaskAPIView.as_view()) + path('api/v1/start-task/', StartTaskAPIView.as_view()), + path('api/v1/reassign-task/', ReAssignJuniorTaskAPIView.as_view()), ] diff --git a/junior/views.py b/junior/views.py index 8cb95a3..14920ec 100644 --- a/junior/views.py +++ b/junior/views.py @@ -30,7 +30,7 @@ import requests from junior.models import Junior, JuniorPoints from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, - AddGuardianSerializer, StartTaskSerializer) + AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -434,3 +434,27 @@ class StartTaskAPIView(views.APIView): return custom_error_response(ERROR_CODE['2060'], 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 ReAssignJuniorTaskAPIView(views.APIView): + """Update junior task API""" + serializer_class = ReAssignTaskSerializer + model = JuniorTask + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + try: + task_id = self.request.data.get('task_id') + task_queryset = JuniorTask.objects.filter(id=task_id, guardian__user__email=self.request.user).last() + if task_queryset and task_queryset.task_status == str(NUMBER['six']): + # use StartTaskSerializer serializer + serializer = ReAssignTaskSerializer(task_queryset, data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3036'], 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['2066'], response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From 75d0b12008ff4ca616d2ea11d6ca254b47f15d45 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 2 Aug 2023 16:26:20 +0530 Subject: [PATCH 164/372] changes in remove guardian code api --- guardian/serializers.py | 2 +- junior/models.py | 2 ++ junior/serializers.py | 2 +- junior/views.py | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 1509fdf..db465ad 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -345,7 +345,7 @@ class ApproveJuniorSerializer(serializers.ModelSerializer): """update guardian code""" instance = self.context['junior'] instance.guardian_code = [self.context['guardian_code']] - instance.is_invited = True + instance.guardian_code_approved = True instance.save() return instance diff --git a/junior/models.py b/junior/models.py index dc71c97..b18cbf5 100644 --- a/junior/models.py +++ b/junior/models.py @@ -71,6 +71,8 @@ class Junior(models.Model): 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) # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/junior/serializers.py b/junior/serializers.py index 54332f0..316c1bf 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -246,7 +246,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'] + 'is_invited', 'passcode', 'guardian_code_approved'] class AddJuniorSerializer(serializers.ModelSerializer): """Add junior serializer""" diff --git a/junior/views.py b/junior/views.py index 14920ec..3f3cd5a 100644 --- a/junior/views.py +++ b/junior/views.py @@ -246,8 +246,7 @@ class RemoveJuniorAPIView(views.APIView): junior_id = self.request.GET.get('id') guardian = Guardian.objects.filter(user__email=self.request.user).last() # fetch junior query - junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code), - is_invited=True).last() + junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code)).last() if junior_queryset: # use RemoveJuniorSerializer serializer serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) From 404825dc858d6e346c50efe68af3f8ac397b03cc Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 2 Aug 2023 16:57:40 +0530 Subject: [PATCH 165/372] migration file --- ...018_remove_junior_relationship_and_more.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 junior/migrations/0018_remove_junior_relationship_and_more.py diff --git a/junior/migrations/0018_remove_junior_relationship_and_more.py b/junior/migrations/0018_remove_junior_relationship_and_more.py new file mode 100644 index 0000000..d24b43f --- /dev/null +++ b/junior/migrations/0018_remove_junior_relationship_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.2 on 2023-08-02 11:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0017_juniorguardianrelationship'), + ] + + operations = [ + migrations.RemoveField( + model_name='junior', + name='relationship', + ), + migrations.AddField( + model_name='junior', + name='guardian_code_approved', + field=models.BooleanField(default=False), + ), + ] From 685f62770752ac8a136cf95ea6412a097a6207f3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 2 Aug 2023 19:16:29 +0530 Subject: [PATCH 166/372] change method to get real time --- guardian/serializers.py | 21 ++++++++++++--------- junior/serializers.py | 15 +++++++++++---- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index c81744a..4d0ea65 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -1,5 +1,5 @@ """Serializer of Guardian""" -"""Third party Django app""" +# third party imports import logging from rest_framework import serializers # Import Refresh token of jwt @@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User from datetime import datetime, time -"""Import Django app""" +import pytz +from django.utils import timezone # Import guardian's model, # Import junior's model, # Import account's model, @@ -16,7 +17,7 @@ from datetime import datetime, time # Import messages from # base package, # Import some functions -# from utils file""" +# local imports from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp, UserNotification from account.utils import generate_code @@ -222,12 +223,9 @@ class TaskDetailsSerializer(serializers.ModelSerializer): def get_remaining_time(self, obj): """ remaining time to complete task""" - import pytz - from django.utils import timezone due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time # current_datetime = real_time() - print(due_date_datetime.astimezone(pytz.utc), datetime.now(pytz.utc), timezone.now().astimezone(pytz.utc)) # new code due_date_datetime = due_date_datetime.astimezone(pytz.utc) current_datetime = timezone.now().astimezone(pytz.utc) @@ -260,7 +258,10 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer): """ remaining time to complete task""" due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - current_datetime = real_time() + # current_datetime = real_time() + # new code + due_date_datetime = due_date_datetime.astimezone(pytz.utc) + current_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date_datetime > current_datetime: time_difference = due_date_datetime - current_datetime @@ -377,7 +378,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points + instance.points # update complete time of task - instance.completed_on = real_time() + # instance.completed_on = real_time() + instance.completed_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {}) else: # reject the task @@ -386,7 +388,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points - instance.points # update reject time of task - instance.rejected_on = real_time() + # instance.rejected_on = real_time() + instance.rejected_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {}) instance.save() junior_data.save() diff --git a/junior/serializers.py b/junior/serializers.py index 1dc04da..bdedbb5 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -1,5 +1,8 @@ """Serializer file for junior""" -"""Import Django 3rd party app""" +# third party imports +import pytz + +# django imports from rest_framework import serializers from django.contrib.auth.models import User from django.db import transaction @@ -7,7 +10,7 @@ from datetime import datetime from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken -"""Import django app""" +# local imports from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from guardian.tasks import generate_otp @@ -320,7 +323,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - instance.requested_on = real_time() + # instance.requested_on = real_time() + instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() @@ -445,7 +449,10 @@ class StartTaskSerializer(serializers.ModelSerializer): """ remaining time to complete task""" due_date = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - real_datetime = real_time() + # real_datetime = real_time() + # new code + due_date = due_date.astimezone(pytz.utc) + real_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date > real_datetime: time_difference = due_date - real_datetime From f3e2ab9a346ca28e3acc7b9b37db6fc32167906b Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 2 Aug 2023 20:45:46 +0530 Subject: [PATCH 167/372] notification list modified and mark as read method changed --- notifications/serializers.py | 4 +++- notifications/views.py | 25 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index ff05dcd..a061369 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -8,6 +8,7 @@ from rest_framework import serializers from notifications.utils import register_fcm_token from notifications.models import Notification + class RegisterDevice(serializers.Serializer): """ used to create and validate register device token @@ -27,7 +28,8 @@ class RegisterDevice(serializers.Serializer): return register_fcm_token(self.context['user_id'], registration_id, validated_data['device_id'], device_type) -class NotificationListSerailizer(serializers.ModelSerializer): + +class NotificationListSerializer(serializers.ModelSerializer): """List of notification""" class Meta(object): diff --git a/notifications/views.py b/notifications/views.py index 3d2ff4a..4644e62 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -11,15 +11,32 @@ from rest_framework import viewsets, status, views from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE from notifications.constants import TEST_NOTIFICATION -from notifications.serializers import RegisterDevice, NotificationListSerailizer, ReadNotificationSerializer +from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer from notifications.utils import send_notification from notifications.models import Notification + class NotificationViewSet(viewsets.GenericViewSet): """ used to do the notification actions """ - serializer_class = RegisterDevice + serializer_class = NotificationListSerializer permission_classes = [IsAuthenticated, ] + def list(self, request, *args, **kwargs) -> Response: + """ list the notifications """ + 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) + @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) def fcm_registration(self, request): """ @@ -41,14 +58,14 @@ class NotificationViewSet(viewsets.GenericViewSet): return custom_response(SUCCESS_CODE["3000"]) @action(methods=['get'], detail=False, url_path='list', url_name='list', - serializer_class=NotificationListSerailizer) + serializer_class=NotificationListSerializer) def notification_list(self, request): """ notification list """ try: queryset = Notification.objects.filter(notification_to=request.user) - serializer = NotificationListSerailizer(queryset, many=True) + 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) From 3806d1f3a6980eed807bd110cc0499aad8e0d06a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 3 Aug 2023 18:12:44 +0530 Subject: [PATCH 168/372] user detail api, changed image upload method --- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/utils.py | 5 +- web_admin/serializers/article_serializer.py | 54 +------ .../serializers/user_management_serializer.py | 135 ++++++++++++++++++ web_admin/urls.py | 5 +- web_admin/utils.py | 18 ++- web_admin/views/article.py | 30 +--- web_admin/views/user_management.py | 58 ++++++++ 8 files changed, 220 insertions(+), 85 deletions(-) create mode 100644 web_admin/serializers/user_management_serializer.py create mode 100644 web_admin/views/user_management.py diff --git a/celerybeat-schedule b/celerybeat-schedule index 71062ba3971bec6320e397a68913989f4b8aa458..4a51b891af30ec5d0a7f9b91777d2f6b8b1a45d1 100644 GIT binary patch delta 32 ocmZo@U~Fh$+_1@rU53R}nCpA=;j7*bt4K+8L7@y$*0IpsNU;qFB delta 32 ocmZo@U~Fh$+_1@rU7A&xg+n%K@@<1aM#jmyhMJpAjL+}@0Gr?mQ2+n{ diff --git a/guardian/utils.py b/guardian/utils.py index 920a119..9a6ae05 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -41,7 +41,10 @@ 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""" - temp_file.write(image.read()) + 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""" diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index f3b5d06..b7dbf69 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -21,8 +21,8 @@ class ArticleCardSerializer(serializers.ModelSerializer): Article Card serializer """ id = serializers.IntegerField(required=False) - image = serializers.FileField(required=False) - image_url = serializers.URLField(required=False) + image_name = serializers.CharField(required=False) + image_url = serializers.CharField(required=False) class Meta: """ @@ -214,53 +214,3 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image - - -class UserManagementListSerializer(serializers.ModelSerializer): - """ - user management serializer - """ - name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() - user_type = serializers.SerializerMethodField() - - class Meta: - """ - meta class - """ - model = USER - fields = ('name', 'email', 'phone_number', 'user_type', 'is_active') - - @staticmethod - def get_name(obj): - """ - :param obj: user object - :return: full name - """ - return (obj.first_name + obj.last_name) if obj.last_name else obj.first_name - - @staticmethod - def get_phone_number(obj): - """ - :param obj: user object - :return: user phone number - """ - if profile := obj.guardian_profile.all().first(): - return profile.phone - elif profile := obj.junior_profile.all().first(): - return 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') - elif obj.junior_profile.all().first(): - return dict(USER_TYPE).get('1') - else: - return None diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py new file mode 100644 index 0000000..9e1920b --- /dev/null +++ b/web_admin/serializers/user_management_serializer.py @@ -0,0 +1,135 @@ +""" +web_admin user_management serializers file +""" +# django imports +from rest_framework import serializers +from django.contrib.auth import get_user_model + +from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, + USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) +# local imports +from base.messages import ERROR_CODE +from guardian.models import Guardian +from guardian.utils import upload_image_to_alibaba +from junior.models import Junior +from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage +from web_admin.utils import pop_id, get_image_url + +USER = get_user_model() + + +class UserManagementListSerializer(serializers.ModelSerializer): + """ + user management serializer + """ + name = serializers.SerializerMethodField() + phone_number = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('id', 'name', 'email', 'phone_number', 'user_type', 'is_active') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name + + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + elif profile := 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') + elif obj.junior_profile.all().first(): + return dict(USER_TYPE).get('1') + else: + return None + + +class GuardianSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + + class Meta: + model = Guardian + fields = ('name', 'dob', 'gender', 'phone', 'is_active', 'country_name', 'image') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name + + +class JuniorSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + + class Meta: + model = Junior + fields = ('name', 'dob', 'gender', 'phone', 'is_active', 'country_name', 'image') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name + + +class UserManagementDetailSerializer(serializers.ModelSerializer): + user_type = serializers.SerializerMethodField() + guardian_profile = GuardianSerializer(many=True) + junior_profile = JuniorSerializer(many=True) + associated_users = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users') + + @staticmethod + def get_user_type(obj): + """ + :param obj: user object + :return: user type + """ + profile = obj.guardian_profile.all().first() + 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 + + @staticmethod + def get_associated_users(obj): + if profile := obj.guardian_profile.all().first(): + junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) + serializer = JuniorSerializer(junior, many=True) + return serializer.data diff --git a/web_admin/urls.py b/web_admin/urls.py index 5fbe21e..30645a2 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,15 +6,16 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, UserManagementViewSet +from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet +from web_admin.views.user_management import UserManagementViewSet # initiate router router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') -router.register('user', UserManagementViewSet, basename='user') +router.register('user-management', UserManagementViewSet, basename='user') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/utils.py b/web_admin/utils.py index 6ecd5e3..6cdc240 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -1,6 +1,8 @@ """ web_utils file """ +import base64 + from base.constants import ARTICLE_CARD_IMAGE_FOLDER from guardian.utils import upload_image_to_alibaba @@ -16,8 +18,22 @@ def pop_id(data): def get_image_url(data): - if 'image_url' in data: + """ + to get image url + :param data: + :return: image url + """ + if 'image_url' in data and 'http' in data['image_url']: + if 'image_name' in data: + data.pop('image_name') 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'] + filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image_name}" + # upload image on ali baba + image_url = upload_image_to_alibaba(base64_image, filename) + return image_url elif 'image' in data and data['image'] is not None: image = data.pop('image') filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image.name}" diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 5b0ca92..b8f8a1f 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -16,8 +16,7 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer, - UserManagementListSerializer) + DefaultArticleCardImageSerializer) USER = get_user_model() @@ -195,30 +194,3 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m return custom_response(None, data=serializer.data) -class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin): - """ - api to manage (list, view, edit) user - """ - serializer_class = UserManagementListSerializer - permission_classes = [] - queryset = USER.objects.prefetch_related( - 'guardian_profile', 'junior_profile') - - def get_queryset(self): - if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): - return self.queryset.filter(junior_profile__isnull=True) - elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): - return self.queryset.filter(guardian_profile__isnull=True) - else: - return self.queryset - - def list(self, request, *args, **kwargs): - """ - api method to list all the user - :param request: - :return: - """ - queryset = self.get_queryset() - serializer = self.serializer_class(queryset, many=True) - return custom_response(None, data=serializer.data) - diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py new file mode 100644 index 0000000..ffca49d --- /dev/null +++ b/web_admin/views/user_management.py @@ -0,0 +1,58 @@ +""" +web_admin user_management views file +""" +# django imports +from rest_framework.viewsets import GenericViewSet, mixins +from rest_framework.filters import OrderingFilter, SearchFilter +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated, AllowAny +from django.contrib.auth import get_user_model + +# local imports +from account.utils import custom_response, custom_error_response +from base.constants import USER_TYPE +from base.messages import SUCCESS_CODE, ERROR_CODE +from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage +from web_admin.permission import AdminPermission +from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, + UserManagementDetailSerializer) + +USER = get_user_model() + + +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin): + """ + api to manage (list, view, edit) user + """ + serializer_class = UserManagementListSerializer + permission_classes = [] + queryset = USER.objects.filter(is_superuser=False).prefetch_related( + 'guardian_profile', 'junior_profile') + + def get_queryset(self): + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + return self.queryset.filter(junior_profile__isnull=True) + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + return self.queryset.filter(guardian_profile__isnull=True) + else: + return self.queryset + + def list(self, request, *args, **kwargs): + """ + api method to list all the user + :param request: + :return: + """ + queryset = self.get_queryset() + serializer = self.serializer_class(queryset, many=True) + return custom_response(None, data=serializer.data) + + def retrieve(self, request, *args, **kwargs): + 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) + return custom_response(None, data=serializer.data) From dd0f2b027a84d9e510c6c1fd3b94da3dfef65dab Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 3 Aug 2023 18:17:01 +0530 Subject: [PATCH 169/372] task list api modified --- guardian/serializers.py | 8 ++++- guardian/views.py | 78 +++++++++++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index db465ad..d92b637 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -222,9 +222,15 @@ class TaskDetailsSerializer(serializers.ModelSerializer): def get_remaining_time(self, obj): """ remaining time to complete task""" + import pytz + from django.utils import timezone due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - current_datetime = real_time() + # current_datetime = real_time() + print(due_date_datetime.astimezone(pytz.utc), datetime.now(pytz.utc), timezone.now().astimezone(pytz.utc)) + # new code + due_date_datetime = due_date_datetime.astimezone(pytz.utc) + current_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date_datetime > current_datetime: time_difference = due_date_datetime - current_datetime diff --git a/guardian/views.py b/guardian/views.py index 15b29c5..612f837 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,6 +10,8 @@ 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 rest_framework.filters import SearchFilter + from django.utils import timezone # Import guardian's model, @@ -126,37 +128,69 @@ class AllTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# class TaskListAPIView(viewsets.ModelViewSet): +# """Update guardian profile""" +# serializer_class = TaskDetailsSerializer +# permission_classes = [IsAuthenticated] +# pagination_class = PageNumberPagination +# http_method_names = ('get',) +# +# def list(self, request, *args, **kwargs): +# """Create guardian profile""" +# try: +# status_value = self.request.GET.get('status') +# search = self.request.GET.get('search') +# if search and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_name__icontains=search).order_by('due_date', 'created_at') +# elif search and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, +# task_status=status_value).order_by('due_date', 'created_at') +# if search is None and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') +# elif search is None and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_status=status_value).order_by('due_date','created_at') +# paginator = self.pagination_class() +# # use Pagination +# paginated_queryset = paginator.paginate_queryset(queryset, request) +# # use TaskDetailsSerializer serializer +# serializer = TaskDetailsSerializer(paginated_queryset, many=True) +# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# except Exception as e: +# return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer 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(guardian__user=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""" - try: - status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_name__icontains=search).order_by('due_date', 'created_at') - elif search and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, - task_status=status_value).order_by('due_date', 'created_at') - if search is None and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') - elif search is None and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=status_value).order_by('due_date','created_at') - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - except Exception as e: - return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + status_value = self.request.GET.get('status') + 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 = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" From b9e2d9bc8a643cee6fe68accd1a7ef9b9e534b3c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 3 Aug 2023 18:35:19 +0530 Subject: [PATCH 170/372] user detail api, changed image upload method --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index b7dbf69..427f20d 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -29,7 +29,7 @@ class ArticleCardSerializer(serializers.ModelSerializer): meta class """ model = ArticleCard - fields = ('id', 'title', 'description', 'image', 'image_url') + fields = ('id', 'title', 'description', 'image_name', 'image_url') def create(self, validated_data): validated_data['image_url'] = get_image_url(validated_data) From 756bea047129cdeec2462028fa5c30e0e104ff54 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 4 Aug 2023 13:15:32 +0530 Subject: [PATCH 171/372] user detail view api, searching user list, count --- account/utils.py | 6 ++- .../serializers/user_management_serializer.py | 42 +++++++++++++++++-- web_admin/views/article.py | 3 +- web_admin/views/user_management.py | 25 +++++++---- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/account/utils.py b/account/utils.py index d1e6e54..4dc6acc 100644 --- a/account/utils.py +++ b/account/utils.py @@ -138,12 +138,14 @@ def send_support_email(name, sender, subject, message): } ) return name -def custom_response(detail, data=None, response_status=status.HTTP_200_OK): + + +def custom_response(detail, data=None, response_status=status.HTTP_200_OK, count=None): """Custom response code""" if not data: """when data is none""" data = None - return Response({"data": data, "message": detail, "status": "success", "code": response_status}) + return Response({"data": data, "message": detail, "status": "success", "code": response_status, "count": count}) def custom_error_response(detail, response_status): diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 9e1920b..5293cc4 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -25,6 +25,7 @@ class UserManagementListSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() phone_number = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() + is_active = serializers.SerializerMethodField() class Meta: """ @@ -67,13 +68,27 @@ class UserManagementListSerializer(serializers.ModelSerializer): else: return None + @staticmethod + def get_is_active(obj): + """ + :param obj: user object + :return: user type + """ + if profile := obj.guardian_profile.all().first(): + return profile.is_active + elif profile := obj.junior_profile.all().first(): + return profile.is_active + else: + return obj.is_active + class GuardianSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() + phone_number = serializers.SerializerMethodField() class Meta: model = Guardian - fields = ('name', 'dob', 'gender', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') @staticmethod def get_name(obj): @@ -83,13 +98,22 @@ class GuardianSerializer(serializers.ModelSerializer): """ return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone + class JuniorSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() + phone_number = serializers.SerializerMethodField() class Meta: model = Junior - fields = ('name', 'dob', 'gender', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') @staticmethod def get_name(obj): @@ -99,6 +123,14 @@ class JuniorSerializer(serializers.ModelSerializer): """ return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone + class UserManagementDetailSerializer(serializers.ModelSerializer): user_type = serializers.SerializerMethodField() @@ -111,7 +143,7 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users') + fields = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users') @staticmethod def get_user_type(obj): @@ -133,3 +165,7 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) serializer = JuniorSerializer(junior, many=True) return serializer.data + elif profile := obj.junior_profile.all().first(): + guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) + serializer = GuardianSerializer(guardian, many=True) + return serializer.data diff --git a/web_admin/views/article.py b/web_admin/views/article.py index b8f8a1f..314f36e 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -76,10 +76,11 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel :return: list of article """ queryset = self.get_queryset() + count = queryset.count() 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) + return custom_response(None, data=serializer.data, count=count) def retrieve(self, request, *args, **kwargs): """ diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index ffca49d..54dc874 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -8,6 +8,7 @@ from rest_framework import status from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated, AllowAny from django.contrib.auth import get_user_model +from django.db.models import Q # local imports from account.utils import custom_response, custom_error_response @@ -27,16 +28,23 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie """ serializer_class = UserManagementListSerializer permission_classes = [] - queryset = USER.objects.filter(is_superuser=False).prefetch_related( - 'guardian_profile', 'junior_profile') + queryset = USER.objects.filter(is_superuser=False + ).prefetch_related('guardian_profile', + 'junior_profile' + ).exclude(junior_profile__isnull=True, + guardian_profile__isnull=True).order_by('date_joined') + filter_backends = (SearchFilter,) + search_fields = ['first_name', 'last_name'] def get_queryset(self): if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): - return self.queryset.filter(junior_profile__isnull=True) + queryset = self.queryset.filter(junior_profile__isnull=True) elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): - return self.queryset.filter(guardian_profile__isnull=True) + queryset = self.queryset.filter(guardian_profile__isnull=True) else: - return self.queryset + queryset = self.queryset + queryset = self.filter_queryset(queryset) + return queryset def list(self, request, *args, **kwargs): """ @@ -45,8 +53,11 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie :return: """ queryset = self.get_queryset() - serializer = self.serializer_class(queryset, many=True) - return custom_response(None, data=serializer.data) + count = queryset.count() + 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, count=count) def retrieve(self, request, *args, **kwargs): queryset = self.queryset From 5f1c645e3a26627a7d683dd4d2f626289e022213 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 4 Aug 2023 16:27:46 +0530 Subject: [PATCH 172/372] user list api --- .../serializers/user_management_serializer.py | 38 +++++++++---------- web_admin/views/article.py | 2 +- web_admin/views/user_management.py | 35 +++++++++++++++-- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 5293cc4..78a1136 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -84,11 +84,18 @@ class UserManagementListSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() class Meta: model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + + def update(self, instance, validated_data): + instance.user.email = self.context.get('email', instance.user.email) + instance.user.save() + instance.country_code = validated_data.get('country_code', instance.country_code) + instance.phone = validated_data.get('phone', instance.phone) + instance.save() + return instance @staticmethod def get_name(obj): @@ -98,22 +105,21 @@ class GuardianSerializer(serializers.ModelSerializer): """ return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name - @staticmethod - def get_phone_number(obj): - """ - :param obj: user object - :return: user phone number - """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone - class JuniorSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() class Meta: model = Junior - fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + + def update(self, instance, validated_data): + instance.auth.email = self.context.get('email', instance.auth.email) + instance.auth.save() + instance.country_code = validated_data.get('country_code', instance.country_code) + instance.phone = validated_data.get('phone', instance.phone) + instance.save() + return instance @staticmethod def get_name(obj): @@ -123,14 +129,6 @@ class JuniorSerializer(serializers.ModelSerializer): """ return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name - @staticmethod - def get_phone_number(obj): - """ - :param obj: user object - :return: user phone number - """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone - class UserManagementDetailSerializer(serializers.ModelSerializer): user_type = serializers.SerializerMethodField() diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 314f36e..be93b4c 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -29,7 +29,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel serializer_class = ArticleSerializer permission_classes = [IsAuthenticated, AdminPermission] queryset = Article - filter_backends = (OrderingFilter, SearchFilter,) + filter_backends = (SearchFilter,) search_fields = ['title'] http_method_names = ['get', 'post', 'put', 'delete'] diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 54dc874..db41855 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -14,15 +14,16 @@ from django.db.models import Q from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, - UserManagementDetailSerializer) + UserManagementDetailSerializer, GuardianSerializer, + JuniorSerializer) USER = get_user_model() -class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin): +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, + mixins.RetrieveModelMixin, mixins.UpdateModelMixin): """ api to manage (list, view, edit) user """ @@ -49,7 +50,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie def list(self, request, *args, **kwargs): """ api method to list all the user - :param request: + :param request: user_type {'guardian' for Guardian list, 'junior' for Junior list} :return: """ queryset = self.get_queryset() @@ -60,6 +61,14 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie return custom_response(None, data=serializer.data, count=count) def retrieve(self, request, *args, **kwargs): + """ + to get details of single user + :param request: user_id along with + user_type {'guardian' for Guardian, 'junior' for Junior} mandatory + :return: user details + """ + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', 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']) @@ -67,3 +76,21 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']) serializer = UserManagementDetailSerializer(queryset, many=True) return custom_response(None, data=serializer.data) + + def partial_update(self, request, *args, **kwargs): + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', 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']).first() + serializer = GuardianSerializer(queryset.guardian_profile.all().first(), + request.data, context={'email': request.data.get('email', queryset.email)}) + + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + serializer = JuniorSerializer(queryset.junior_profile.all().first(), + request.data, context={'email': request.data.get('email', queryset.email)}) + + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response('Profile updated successfully.') From baacb1a18fac585be1e67b6e3743792c53720eb9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 4 Aug 2023 16:28:58 +0530 Subject: [PATCH 173/372] user edit api --- .../serializers/user_management_serializer.py | 24 +++++++++++++++---- web_admin/views/user_management.py | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 78a1136..5221e12 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -23,7 +23,8 @@ class UserManagementListSerializer(serializers.ModelSerializer): user management serializer """ name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() + country_code = serializers.SerializerMethodField() + phone = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() @@ -32,7 +33,7 @@ class UserManagementListSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('id', 'name', 'email', 'phone_number', 'user_type', 'is_active') + fields = ('id', 'name', 'email', 'country_code', 'phone', 'user_type', 'is_active') @staticmethod def get_name(obj): @@ -43,15 +44,28 @@ class UserManagementListSerializer(serializers.ModelSerializer): return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name @staticmethod - def get_phone_number(obj): + def get_country_code(obj): """ :param obj: user object :return: user phone number """ if profile := obj.guardian_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None elif profile := obj.junior_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None + else: + return None + + @staticmethod + def get_phone(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return profile.phone if profile.phone else None + elif profile := obj.junior_profile.all().first(): + return profile.phone if profile.phone else None else: return None diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index db41855..e4018ac 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -28,7 +28,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, api to manage (list, view, edit) user """ serializer_class = UserManagementListSerializer - permission_classes = [] + permission_classes = [IsAuthenticated, AdminPermission] queryset = USER.objects.filter(is_superuser=False ).prefetch_related('guardian_profile', 'junior_profile' From 4a2f36cde801a3fba3dd6154bca884c4de195368 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 10:55:34 +0530 Subject: [PATCH 174/372] edit user validations, active-inactive api --- base/messages.py | 4 +- .../serializers/user_management_serializer.py | 77 ++++++++++++++++--- web_admin/views/article.py | 4 +- web_admin/views/user_management.py | 45 +++++++++-- 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/base/messages.py b/base/messages.py index bff508d..e891ae1 100644 --- a/base/messages.py +++ b/base/messages.py @@ -142,7 +142,9 @@ SUCCESS_CODE = { "3033": "Valid Referral code", "3034": "Invite guardian successfully", "3035": "Task started successfully", - "3036": "Task reassign successfully" + "3036": "Task reassign successfully", + "3037": "Profile has been updated successfully.", + "3038": "Status has been changed successfully." } """status code error""" STATUS_CODE_ERROR = { diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 5221e12..9333155 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,15 +5,11 @@ web_admin user_management serializers file from rest_framework import serializers from django.contrib.auth import get_user_model -from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, - USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) +from base.constants import USER_TYPE # local imports -from base.messages import ERROR_CODE +from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.models import Guardian -from guardian.utils import upload_image_to_alibaba from junior.models import Junior -from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage -from web_admin.utils import pop_id, get_image_url USER = get_user_model() @@ -97,14 +93,41 @@ class UserManagementListSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer): + """ + guardian serializer + """ name = serializers.SerializerMethodField() + email = serializers.CharField(required=False) class Meta: + """ + meta class + """ model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + + def validate(self, attrs): + """ + to validate request data + :return: validated attrs + """ + email = attrs.get('email') + phone = attrs.get('phone') + if USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2003']}) + if Guardian.objects.filter(phone=phone).exclude(user__id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2012']}) + return attrs def update(self, instance, validated_data): - instance.user.email = self.context.get('email', instance.user.email) + """ + to update user and its related profile + :param instance: user's guardian object + :param validated_data: + :return: guardian object + """ + instance.user.email = self.validated_data.get('email', instance.user.email) + instance.user.username = self.validated_data.get('email', instance.user.username) instance.user.save() instance.country_code = validated_data.get('country_code', instance.country_code) instance.phone = validated_data.get('phone', instance.phone) @@ -121,14 +144,40 @@ class GuardianSerializer(serializers.ModelSerializer): class JuniorSerializer(serializers.ModelSerializer): + """ + junior serializer + """ name = serializers.SerializerMethodField() + email = serializers.CharField(required=False) class Meta: + """ + meta class + """ model = Junior - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + + def validate(self, attrs): + """ + to validate request data + :return: validated attrs + """ + email = attrs.get('email') + phone = attrs.get('phone') + if email and USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2003']}) + if phone and Junior.objects.filter(phone=phone).exclude(auth__id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2012']}) + return attrs def update(self, instance, validated_data): - instance.auth.email = self.context.get('email', instance.auth.email) + """ + :param instance: user's junior object + :param validated_data: validated data + :return: instance + """ + 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.country_code = validated_data.get('country_code', instance.country_code) instance.phone = validated_data.get('phone', instance.phone) @@ -145,6 +194,9 @@ class JuniorSerializer(serializers.ModelSerializer): class UserManagementDetailSerializer(serializers.ModelSerializer): + """ + user management details serializer + """ user_type = serializers.SerializerMethodField() guardian_profile = GuardianSerializer(many=True) junior_profile = JuniorSerializer(many=True) @@ -163,7 +215,6 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): :param obj: user object :return: user type """ - profile = obj.guardian_profile.all().first() if obj.guardian_profile.all().first(): return dict(USER_TYPE).get('2') elif obj.junior_profile.all().first(): @@ -173,6 +224,10 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): @staticmethod def get_associated_users(obj): + """ + :param obj: user object + :return: associated user + """ if profile := obj.guardian_profile.all().first(): junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) serializer = JuniorSerializer(junior, many=True) diff --git a/web_admin/views/article.py b/web_admin/views/article.py index be93b4c..53da6e1 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -109,7 +109,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_card', url_path='remove_card', + @action(methods=['get'], url_name='remove-card', url_path='remove-card', detail=True) def remove_article_card(self, request, *args, **kwargs): """ @@ -125,7 +125,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel except AttributeError: return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_survey', url_path='remove_survey', + @action(methods=['get'], url_name='remove-survey', url_path='remove-survey', detail=True) def remove_article_survey(self, request, *args, **kwargs): """ diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index e4018ac..256248b 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -13,7 +13,7 @@ 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.messages import SUCCESS_CODE, ERROR_CODE +from base.messages import SUCCESS_CODE from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, UserManagementDetailSerializer, GuardianSerializer, @@ -78,19 +78,48 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, return custom_response(None, data=serializer.data) def partial_update(self, request, *args, **kwargs): + """ + api method to update user detail (email, phone number) + :param request: user_id along with + 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')]: return custom_error_response('Action not allowed', 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']).first() - serializer = GuardianSerializer(queryset.guardian_profile.all().first(), - request.data, context={'email': request.data.get('email', queryset.email)}) + user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), + request.data, context={'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']).first() - serializer = JuniorSerializer(queryset.junior_profile.all().first(), - request.data, context={'email': request.data.get('email', queryset.email)}) + user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + serializer = JuniorSerializer(user_obj.junior_profile.all().first(), + request.data, context={'user_id': kwargs['pk']}) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response('Profile updated successfully.') + return custom_response(SUCCESS_CODE['3037']) + + @action(methods=['get'], url_name='change-status', url_path='change-status', detail=True) + def change_status(self, request, *args, **kwargs): + """ + api to change user status (mark as active or inactive) + :param request: user_id along with + 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')]: + return custom_error_response('Action not allowed', 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() + return custom_response(SUCCESS_CODE['3038']) From e85334691064be94beb4f9b5af72b6e436b7d47a Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 11:26:23 +0530 Subject: [PATCH 175/372] notifications API --- base/messages.py | 4 +++- guardian/urls.py | 5 +++- guardian/views.py | 30 +++++++++++++++++++++++ notifications/serializers.py | 21 +++++++++++++++- notifications/urls.py | 3 ++- notifications/views.py | 46 +++++++++++++++++++++++++++++++----- 6 files changed, 99 insertions(+), 10 deletions(-) diff --git a/base/messages.py b/base/messages.py index bff508d..9f36be6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -142,7 +142,9 @@ SUCCESS_CODE = { "3033": "Valid Referral code", "3034": "Invite guardian successfully", "3035": "Task started successfully", - "3036": "Task reassign successfully" + "3036": "Task reassign successfully", + # notification read + "3037": "Notification read successfully", } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/urls.py b/guardian/urls.py index b14aa77..e95ea8e 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -2,7 +2,8 @@ """Django import""" from django.urls import path, include from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, - SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView) + SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView, + GuardianListAPIView) """Third party import""" from rest_framework import routers @@ -36,6 +37,8 @@ router.register('filter-task', SearchTaskListAPIView, basename='filter-task') router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior') # Approve junior API""" router.register('approve-task', ApproveTaskAPIView, basename='approve-task') +# guardian list API""" +router.register('guardian-list', GuardianListAPIView, basename='guardian-list') # Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/views.py b/guardian/views.py index 15b29c5..47fbdda 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -328,3 +328,33 @@ class ApproveTaskAPIView(viewsets.ViewSet): except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) +class GuardianListAPIView(viewsets.ModelViewSet): + """Junior list of assosicated guardian""" + pass + + # serializer_class = JuniorDetailListSerializer + # queryset = Junior.objects.all() + # permission_classes = [IsAuthenticated] + # filter_backends = (SearchFilter,) + # search_fields = ['auth__first_name', 'auth__last_name'] + # http_method_names = ('get',) + # + # def get_queryset(self): + # queryset = self.filter_queryset(self.queryset) + # return queryset + # + # def list(self, request, *args, **kwargs): + # """ junior list""" + # try: + # update_positions_based_on_points() + # 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) + # 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: + # return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/notifications/serializers.py b/notifications/serializers.py index 053096f..a085cf6 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers # local imports from notifications.utils import register_fcm_token - +from notifications.models import Notification class RegisterDevice(serializers.Serializer): """ @@ -26,3 +26,22 @@ class RegisterDevice(serializers.Serializer): device_type = validated_data['type'] return register_fcm_token(self.context['user_id'], registration_id, validated_data['device_id'], device_type) + +class NotificationListSerailizer(serializers.ModelSerializer): + """List of notification""" + + class Meta(object): + """meta info""" + model = Notification + fields = ['id', 'data', 'is_read'] + +class ReadNotificationSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = Notification + fields = ('id',) + def update(self, instance, validated_data): + instance.is_read = True + instance.save() + return instance diff --git a/notifications/urls.py b/notifications/urls.py index b184d02..713aae3 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 +from notifications.views import NotificationViewSet, ReadNotification # initiate router router = routers.SimpleRouter() @@ -15,4 +15,5 @@ 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/views.py b/notifications/views.py index a8659e3..d4dec80 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -3,18 +3,17 @@ notifications views file """ # django imports from django.db.models import Q -from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response - +from rest_framework import viewsets, status, views # local imports -from account.utils import custom_response -from base.messages import SUCCESS_CODE +from account.utils import custom_response, custom_error_response +from base.messages import SUCCESS_CODE, ERROR_CODE from notifications.constants import TEST_NOTIFICATION -from notifications.serializers import RegisterDevice +from notifications.serializers import RegisterDevice, NotificationListSerailizer, ReadNotificationSerializer from notifications.utils import send_notification - +from notifications.models import Notification class NotificationViewSet(viewsets.GenericViewSet): """ used to do the notification actions """ @@ -40,3 +39,38 @@ class NotificationViewSet(viewsets.GenericViewSet): """ send_notification.delay(TEST_NOTIFICATION, 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=NotificationListSerailizer) + def notification_list(self, request): + """ + notification list + """ + try: + queryset = Notification.objects.filter(notification_to=request.user) + serializer = NotificationListSerailizer(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 = str(self.request.data.get('notification_id')) + notification_queryset = Notification.objects.filter(id=notification_id, notification_to=self.request.user).last() + if notification_queryset: + # use ReadNotificationSerializer serializer + serializer = ReadNotificationSerializer(notification_queryset, data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3037'], 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) From 83ec922584df163b05f523983be2e5027f1a3310 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 12:00:58 +0530 Subject: [PATCH 176/372] admin login api modified --- account/serializers.py | 31 ++++++++++++++++++++++++++ account/urls.py | 5 +++-- account/views.py | 35 ++++++++++++++++++++++++++---- base/messages.py | 3 ++- web_admin/views/user_management.py | 7 +++--- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index d4fd335..284ae09 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -137,6 +137,37 @@ class ForgotPasswordSerializer(serializers.Serializer): """Forget password serializer""" email = serializers.EmailField() + +class AdminLoginSerializer(serializers.ModelSerializer): + """admin login serializer""" + email = serializers.EmailField(required=True) + password = serializers.CharField(required=True) + + class Meta: + """ + meta class + """ + model = User + fields = ('email', 'password') + + def validate(self, attrs): + user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True + ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + + if not user: + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + elif not user.check_password(attrs['password']): + raise serializers.ValidationError({'details': ERROR_CODE['2031']}) + self.context.update({'user': user}) + return attrs + + def create(self, validated_data): + """ + used to return the user object after validation + """ + return self.context['user'] + + class SuperUserSerializer(serializers.ModelSerializer): """Super admin serializer""" user_type = serializers.SerializerMethodField('get_user_type') diff --git a/account/urls.py b/account/urls.py index ef3d026..02ac124 100644 --- a/account/urls.py +++ b/account/urls.py @@ -28,14 +28,15 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView) + UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView, + AdminLoginViewSet) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') """super admin login""" -router.register('admin', UserLogin, basename='admin') +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') diff --git a/account/views.py b/account/views.py index c95aa7e..61c4e0e 100644 --- a/account/views.py +++ b/account/views.py @@ -1,7 +1,7 @@ """Account view """ from notifications.utils import remove_fcm_token -"""Django import""" +# django imports from datetime import datetime, timedelta from rest_framework import viewsets, status, views from rest_framework.decorators import action @@ -18,19 +18,21 @@ import google.auth.transport.requests from rest_framework import status import requests from rest_framework.response import Response +from rest_framework import mixins from django.conf import settings -"""App Import""" + +# local imports from guardian.models import Guardian from junior.models import Junior from guardian.utils import upload_image_to_alibaba from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User -"""Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, - UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) + UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer, + AdminLoginSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, ZOD, JUN, GRD @@ -346,6 +348,31 @@ class UserLogin(viewsets.ViewSet): data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'} return custom_response(None, data, response_status=status.HTTP_200_OK) + +class AdminLoginViewSet(viewsets.GenericViewSet): + """ + admin login api + """ + serializer_class = AdminLoginSerializer + + @action(methods=['post'], url_name='login', url_path='login', detail=False) + def admin_login(self, request, *args, **kwargs): + """ + :param request: + :return: + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.save() + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + refresh_token = str(refresh) + data = {"auth_token": access_token, "refresh_token": refresh_token, "username": user.username, + "email": user.email, "first_name": user.first_name, "last_name": user.last_name, + "is_active": user.is_active, "user_type": '3', "is_superuser": user.is_superuser} + return custom_response(None, data) + + class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer diff --git a/base/messages.py b/base/messages.py index e891ae1..2b57e4e 100644 --- a/base/messages.py +++ b/base/messages.py @@ -92,7 +92,8 @@ ERROR_CODE = { "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", - "2066": "Task does not exist or not in expired state" + "2066": "Task does not exist or not in expired state", + "2067": "Action not allowed. User type missing." } """Success message code""" SUCCESS_CODE = { diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 256248b..20184cd 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -13,7 +13,7 @@ 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.messages import SUCCESS_CODE +from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, UserManagementDetailSerializer, GuardianSerializer, @@ -36,6 +36,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, guardian_profile__isnull=True).order_by('date_joined') filter_backends = (SearchFilter,) search_fields = ['first_name', 'last_name'] + http_method_names = ['get', 'post', 'patch'] def get_queryset(self): if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): @@ -85,7 +86,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: success message """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: - return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + 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() @@ -110,7 +111,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: success message """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: - return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + 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() From f377e283fd55e4026cb568d8fbe82dd4add5cea9 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:25:01 +0530 Subject: [PATCH 177/372] guardian list api --- base/messages.py | 3 ++- guardian/serializers.py | 35 ++++++++++++++++++++++++++++- guardian/views.py | 50 +++++++++++++++++------------------------ junior/serializers.py | 6 +++-- junior/views.py | 5 +++-- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/base/messages.py b/base/messages.py index 979d970..470c674 100644 --- a/base/messages.py +++ b/base/messages.py @@ -92,7 +92,8 @@ ERROR_CODE = { "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", - "2066": "Task does not exist or not in expired state" + "2066": "Task does not exist or not in expired state", + "2067": "No guardian associated with this junior" } """Success message code""" SUCCESS_CODE = { diff --git a/guardian/serializers.py b/guardian/serializers.py index db465ad..04acb9d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -23,7 +23,7 @@ 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 -from junior.models import Junior, JuniorPoints +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 @@ -386,3 +386,36 @@ class ApproveTaskSerializer(serializers.ModelSerializer): junior_data.save() return junior_details +class GuardianDetailListSerializer(serializers.ModelSerializer): + """Guardian serializer""" + + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + image = serializers.SerializerMethodField('get_image') + guardian_id = serializers.SerializerMethodField('get_guardian_id') + + class Meta(object): + """Meta info""" + model = JuniorGuardianRelationship + fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'created_at', + 'updated_at'] + + def get_guardian_id(self,obj): + """first name of guardian""" + return obj.guardian.id + def get_first_name(self,obj): + """first name of guardian""" + return obj.guardian.user.first_name + + def get_last_name(self,obj): + """last name of guardian""" + return obj.guardian.user.last_name + + def get_email(self,obj): + """emailof guardian""" + return obj.guardian.user.email + + def get_image(self,obj): + """first name of guardian""" + return obj.guardian.image diff --git a/guardian/views.py b/guardian/views.py index 47fbdda..211d6ed 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -26,9 +26,10 @@ from django.utils import timezone # Import notification constant # Import send_notification function from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, - TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) + TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer, + GuardianDetailListSerializer) from .models import Guardian, JuniorTask -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email @@ -329,32 +330,21 @@ class ApproveTaskAPIView(viewsets.ViewSet): return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class GuardianListAPIView(viewsets.ModelViewSet): - """Junior list of assosicated guardian""" - pass + """Guardian list of assosicated junior""" - # serializer_class = JuniorDetailListSerializer - # queryset = Junior.objects.all() - # permission_classes = [IsAuthenticated] - # filter_backends = (SearchFilter,) - # search_fields = ['auth__first_name', 'auth__last_name'] - # http_method_names = ('get',) - # - # def get_queryset(self): - # queryset = self.filter_queryset(self.queryset) - # return queryset - # - # def list(self, request, *args, **kwargs): - # """ junior list""" - # try: - # update_positions_based_on_points() - # 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) - # 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: - # return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + serializer_class = GuardianDetailListSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def list(self, request, *args, **kwargs): + """ junior list""" + try: + guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user) + # fetch junior object + if guardian_data: + # use GuardianDetailListSerializer serializer + serializer = GuardianDetailListSerializer(guardian_data, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2067'], 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/junior/serializers.py b/junior/serializers.py index 316c1bf..1dc04da 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -89,7 +89,10 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """condition for guardian code""" if guardian_code: junior.guardian_code = guardian_code - junior.dob = validated_data.get('dob',junior.dob) + 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.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) junior.country_name = validated_data.get('country_name', junior.country_name) """Update country code and phone number""" @@ -289,7 +292,6 @@ class AddJuniorSerializer(serializers.ModelSerializer): UserNotification.objects.get_or_create(user=user_data) """Notification email""" junior_notification_email(email, full_name, email, password) - junior_approval_mail(guardian, full_name) # push notification send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) return junior_data diff --git a/junior/views.py b/junior/views.py index 3f3cd5a..15cd387 100644 --- a/junior/views.py +++ b/junior/views.py @@ -27,7 +27,7 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) @@ -144,7 +144,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" - queryset = Junior.objects.all() serializer_class = AddJuniorSerializer permission_classes = [IsAuthenticated] http_method_names = ('post',) @@ -172,6 +171,8 @@ class AddJuniorAPIView(viewsets.ModelViewSet): guardian = Guardian.objects.filter(user=self.request.user).first() junior.guardian_code = [guardian.guardian_code] junior.save() + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, + relationship=str(self.request.data['relationship'])) return True From b3b499e661a73b45cc97c22382cff36efab0eb83 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:41:33 +0530 Subject: [PATCH 178/372] guardian list api --- base/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index 470c674..9d20dfb 100644 --- a/base/messages.py +++ b/base/messages.py @@ -93,7 +93,7 @@ ERROR_CODE = { "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", "2066": "Task does not exist or not in expired state", - "2067": "No guardian associated with this junior" + "2068": "No guardian associated with this junior" } """Success message code""" SUCCESS_CODE = { From c20249730a0513ae1b5b6eff6ad9d751607b6b71 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:42:47 +0530 Subject: [PATCH 179/372] guardian list api --- guardian/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 211d6ed..1fa4e1b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE['2067'], response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From f2cf1488e9dbf5c75c20a202cdb25445f0cf95d3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 15:05:41 +0530 Subject: [PATCH 180/372] changes in edit api, included first name, last name and username --- .../serializers/user_management_serializer.py | 84 ++++++++++++++++--- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 9333155..4bb0709 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -97,14 +97,18 @@ class GuardianSerializer(serializers.ModelSerializer): guardian serializer """ name = serializers.SerializerMethodField() - email = serializers.CharField(required=False) + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + username = serializers.SerializerMethodField() + email = serializers.EmailField(required=False) class Meta: """ meta class """ model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', + 'is_active', 'country_name', 'image', 'email') def validate(self, attrs): """ @@ -137,25 +141,53 @@ class GuardianSerializer(serializers.ModelSerializer): @staticmethod def get_name(obj): """ - :param obj: user object + :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 + @staticmethod + def get_first_name(obj): + """ + :param obj: guardian object + :return: first name + """ + return obj.user.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: guardian object + :return: last name + """ + return obj.user.last_name + + @staticmethod + def get_username(obj): + """ + :param obj: guardian object + :return: email + """ + return obj.user.username + class JuniorSerializer(serializers.ModelSerializer): """ junior serializer """ name = serializers.SerializerMethodField() - email = serializers.CharField(required=False) + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + username = serializers.SerializerMethodField() + email = serializers.EmailField(required=False) class Meta: """ meta class """ model = Junior - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', + 'is_active', 'country_name', 'image', 'email') def validate(self, attrs): """ @@ -187,11 +219,35 @@ class JuniorSerializer(serializers.ModelSerializer): @staticmethod def get_name(obj): """ - :param obj: user object + :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 + @staticmethod + def get_first_name(obj): + """ + :param obj: junior object + :return: first name + """ + return obj.auth.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: junior object + :return: last name + """ + return obj.auth.last_name + + @staticmethod + def get_username(obj): + """ + :param obj: junior object + :return: email + """ + return obj.auth.username + class UserManagementDetailSerializer(serializers.ModelSerializer): """ @@ -229,10 +285,14 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): :return: associated user """ if profile := obj.guardian_profile.all().first(): - junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) - serializer = JuniorSerializer(junior, many=True) - return serializer.data + if profile.guardian_code: + junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) + serializer = JuniorSerializer(junior, many=True) + return serializer.data elif profile := obj.junior_profile.all().first(): - guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) - serializer = GuardianSerializer(guardian, many=True) - return serializer.data + if profile.guardian_code: + guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) + serializer = GuardianSerializer(guardian, many=True) + return serializer.data + else: + return None From ae0fc4fe8d02f2aff2f2594a3fb63ac041f3c25e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 17:06:31 +0530 Subject: [PATCH 181/372] article list api --- notifications/serializers.py | 4 --- notifications/views.py | 13 +++------ web_admin/serializers/article_serializer.py | 19 ++++++++++++ web_admin/urls.py | 4 +-- web_admin/views/article.py | 32 ++++++++++++++++++++- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index a085cf6..ff05dcd 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -41,7 +41,3 @@ class ReadNotificationSerializer(serializers.ModelSerializer): """Meta class""" model = Notification fields = ('id',) - def update(self, instance, validated_data): - instance.is_read = True - instance.save() - return instance diff --git a/notifications/views.py b/notifications/views.py index d5477b8..3d2ff4a 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -62,15 +62,10 @@ class ReadNotification(views.APIView): def put(self, request, format=None): try: - notification_id = str(self.request.data.get('notification_id')) - notification_queryset = Notification.objects.filter(id=notification_id, notification_to=self.request.user).last() + 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: - # use ReadNotificationSerializer serializer - serializer = ReadNotificationSerializer(notification_queryset, data=request.data, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + 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) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 427f20d..5b42686 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -214,3 +214,22 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image + +class ArticleListSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_cards = ArticleCardSerializer(many=True) + total_points = serializers.SerializerMethodField('get_total_points') + + class Meta: + """ + meta class + """ + model = Article + fields = ('id', 'title', 'description', 'article_cards', 'total_points') + + def get_total_points(self, obj): + """total points of article""" + total_question = ArticleSurvey.objects.filter(article=obj).count() + return total_question * 5 diff --git a/web_admin/urls.py b/web_admin/urls.py index 30645a2..065d30d 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,7 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet +from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -16,7 +16,7 @@ router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') - +router.register('article-list', ArticleListViewSet, basename='article-list') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 53da6e1..f334650 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -16,7 +16,7 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer) USER = get_user_model() @@ -195,3 +195,33 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m return custom_response(None, data=serializer.data) +class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): + """ + article api + """ + serializer_class = ArticleListSerializer + permission_classes = [IsAuthenticated,] + queryset = Article + 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) + return queryset + + def list(self, request, *args, **kwargs): + """ + article list api method + :param request: + :param args: + :param kwargs: + :return: list of article + """ + queryset = self.get_queryset() + count = queryset.count() + 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, count=count) From f91724426530c10ecdb6322a47875069b6f1936d Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 19:12:14 +0530 Subject: [PATCH 182/372] junior article points table --- guardian/views.py | 2 +- junior/migrations/0019_juniorarticlepoints.py | 30 +++++++++++++++++++ junior/models.py | 22 +++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 junior/migrations/0019_juniorarticlepoints.py diff --git a/guardian/views.py b/guardian/views.py index 1fa4e1b..ee86199 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) + return custom_response(ERROR_CODE['2068'], 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/junior/migrations/0019_juniorarticlepoints.py b/junior/migrations/0019_juniorarticlepoints.py new file mode 100644 index 0000000..1112702 --- /dev/null +++ b/junior/migrations/0019_juniorarticlepoints.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.2 on 2023-08-07 13:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0004_alter_surveyoption_survey'), + ('junior', '0018_remove_junior_relationship_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorArticlePoints', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('earn_points', models.IntegerField(blank=True, default=5, null=True)), + ('is_attempt', models.BooleanField(default=False)), + ('is_answer_correct', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles', to='web_admin.article')), + ('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_details', to='junior.junior', verbose_name='Junior')), + ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='web_admin.articlesurvey')), + ('submitted_answer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submitted_answer', to='web_admin.surveyoption')), + ], + ), + ] diff --git a/junior/models.py b/junior/models.py index b18cbf5..908822c 100644 --- a/junior/models.py +++ b/junior/models.py @@ -9,7 +9,8 @@ from django.contrib.postgres.fields import ArrayField from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP # Import guardian's model from guardian.models import Guardian - +# Import web admin's model +from web_admin.models import SurveyOption, ArticleSurvey, Article """Define User model""" User = get_user_model() # Create your models here. @@ -137,3 +138,22 @@ class JuniorGuardianRelationship(models.Model): return f'{self.guardian.user}' +class JuniorArticlePoints(models.Model): + """ + Survey Options model + """ + # associated junior with the task + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_details', verbose_name='Junior') + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles') + question = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='questions') + submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.CASCADE, related_name='submitted_answer') + # earn points""" + earn_points = models.IntegerField(blank=True, null=True, default=5) + is_attempt = models.BooleanField(default=False) + is_answer_correct = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.question}' From 659641467525ee06c6e2104f9d96788394cd97a3 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 11:46:27 +0530 Subject: [PATCH 183/372] guardian list api changes --- base/constants.py | 6 +++ guardian/serializers.py | 39 +++++++++++++++++-- guardian/views.py | 4 +- .../0020_junior_guardian_code_status.py | 18 +++++++++ junior/models.py | 5 ++- junior/serializers.py | 12 ++++-- web_admin/serializers/article_serializer.py | 7 +++- 7 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 junior/migrations/0020_junior_guardian_code_status.py diff --git a/base/constants.py b/base/constants.py index 6022f62..e38cb29 100644 --- a/base/constants.py +++ b/base/constants.py @@ -70,6 +70,12 @@ SIGNUP_METHODS = ( ('2', 'google'), ('3', 'apple') ) +# guardian code status +GUARDIAN_CODE_STATUS = ( + ('1', 'no guardian code'), + ('2', 'exist guardian code'), + ('3', 'request for guardian code') +) # relationship RELATIONSHIP = ( ('1', 'parent'), diff --git a/guardian/serializers.py b/guardian/serializers.py index 04acb9d..1fa385b 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -394,11 +394,20 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): email = serializers.SerializerMethodField('get_email') image = serializers.SerializerMethodField('get_image') guardian_id = serializers.SerializerMethodField('get_guardian_id') + guardian_code = serializers.SerializerMethodField('get_guardian_code') + gender = serializers.SerializerMethodField('get_gender') + phone = serializers.SerializerMethodField('get_phone') + country_name = serializers.SerializerMethodField('get_country_name') + dob = serializers.SerializerMethodField('get_dob') + guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status') + # code info + class Meta(object): """Meta info""" model = JuniorGuardianRelationship - fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'created_at', + fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'dob', + 'guardian_code', 'gender', 'phone', 'country_name', 'created_at', 'guardian_code_status', 'updated_at'] def get_guardian_id(self,obj): @@ -413,9 +422,33 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): return obj.guardian.user.last_name def get_email(self,obj): - """emailof guardian""" + """email of guardian""" return obj.guardian.user.email def get_image(self,obj): - """first name of guardian""" + """guardian image""" return obj.guardian.image + + def get_guardian_code(self,obj): + """ guardian code""" + return obj.guardian.guardian_code + + def get_gender(self,obj): + """ guardian gender""" + return obj.guardian.gender + + def get_phone(self,obj): + """guardian phone""" + return obj.guardian.phone + + def get_country_name(self,obj): + """ guardian country name """ + return obj.guardian.country_name + + def get_dob(self,obj): + """guardian dob """ + return obj.guardian.dob + + def get_guardian_code_status(self,obj): + """guardian code status""" + return obj.junior.guardian_code_status diff --git a/guardian/views.py b/guardian/views.py index ee86199..775932a 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -34,7 +34,7 @@ from account.models import UserEmailOtp, UserNotification 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 +from base.constants import NUMBER, GUARDIAN_CODE_STATUS from .utils import upload_image_to_alibaba from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING from notifications.utils import send_notification @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) + return custom_response({"status": GUARDIAN_CODE_STATUS['1']},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/junior/migrations/0020_junior_guardian_code_status.py b/junior/migrations/0020_junior_guardian_code_status.py new file mode 100644 index 0000000..62e09d3 --- /dev/null +++ b/junior/migrations/0020_junior_guardian_code_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-08-08 05:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0019_juniorarticlepoints'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='guardian_code_status', + field=models.CharField(blank=True, choices=[('1', 'no guardian code'), ('2', 'exist guardian code'), ('3', 'request for guardian code')], default='1', max_length=31, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 908822c..3adf459 100644 --- a/junior/models.py +++ b/junior/models.py @@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model """Import ArrayField""" from django.contrib.postgres.fields import ArrayField """Import django app""" -from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP +from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP, GUARDIAN_CODE_STATUS # Import guardian's model from guardian.models import Guardian # Import web admin's model @@ -74,6 +74,9 @@ class Junior(models.Model): 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) # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/junior/serializers.py b/junior/serializers.py index 1dc04da..178ba14 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,8 @@ from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship 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 +from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED, + GUARDIAN_CODE_STATUS) from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail @@ -280,7 +281,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): junior_code=generate_code(JUN, user_data.id), referral_code=generate_code(ZOD, user_data.id), referral_code_used=guardian_data.referral_code, - is_password_set=False, is_verified=True) + is_password_set=False, is_verified=True, + guardian_code_status=GUARDIAN_CODE_STATUS['2']) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Generate otp""" @@ -399,13 +401,15 @@ class AddGuardianSerializer(serializers.ModelSerializer): relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] junior_data = Junior.objects.filter(auth__username=junior).last() + junior_data.guardian_code_status = GUARDIAN_CODE_STATUS['3'] + junior_data.save() instance = User.objects.filter(username=email).last() if instance: guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True, - referral_code=generate_code(ZOD, + referral_code=generate_code(ZOD, instance.id), referral_code_used=junior_data.referral_code, - is_verified=True) + is_verified=True) UserNotification.objects.get_or_create(user=instance) return guardian_data else: diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 5b42686..c338d04 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -221,15 +221,20 @@ class ArticleListSerializer(serializers.ModelSerializer): """ article_cards = ArticleCardSerializer(many=True) total_points = serializers.SerializerMethodField('get_total_points') + is_completed = serializers.SerializerMethodField('get_is_completed') class Meta: """ meta class """ model = Article - fields = ('id', 'title', 'description', 'article_cards', 'total_points') + fields = ('id', 'title', 'description', 'article_cards', 'total_points', 'is_completed') def get_total_points(self, obj): """total points of article""" total_question = ArticleSurvey.objects.filter(article=obj).count() return total_question * 5 + + def get_is_completed(self, obj): + """complete all question""" + return False \ No newline at end of file From f57b111555aaca2e4c259c5bc53b45de2b534424 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 11:48:53 +0530 Subject: [PATCH 184/372] guardian list api changes --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index c338d04..695401e 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -233,7 +233,7 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_total_points(self, obj): """total points of article""" total_question = ArticleSurvey.objects.filter(article=obj).count() - return total_question * 5 + return total_question * NUMBER['five'] def get_is_completed(self, obj): """complete all question""" From 85e4ae876135775e1ac48424384cba335e40c0d0 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 13:54:04 +0530 Subject: [PATCH 185/372] junior position changes and decrease api response time --- account/views.py | 9 ---- guardian/serializers.py | 6 ++- guardian/views.py | 3 -- junior/admin.py | 6 ++- junior/urls.py | 4 +- junior/views.py | 49 +++++++++++++++++---- web_admin/models.py | 4 +- web_admin/serializers/article_serializer.py | 2 +- 8 files changed, 55 insertions(+), 28 deletions(-) diff --git a/account/views.py b/account/views.py index 61c4e0e..b36d75d 100644 --- a/account/views.py +++ b/account/views.py @@ -245,7 +245,6 @@ class ForgotPasswordAPIView(views.APIView): class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def create(self, request, *args, **kwargs): otp = generate_otp() @@ -262,7 +261,6 @@ class SendPhoneOtp(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def list(self, request, *args, **kwargs): try: @@ -376,7 +374,6 @@ class AdminLoginViewSet(viewsets.GenericViewSet): class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer - queryset = UserEmailOtp.objects.all() def list(self, request, *args, **kwargs): try: @@ -419,7 +416,6 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer permission_classes = [IsAuthenticated] @@ -442,7 +438,6 @@ class ReSendEmailOtp(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = User.objects.all() serializer_class = JuniorProfileSerializer permission_classes = [IsAuthenticated] @@ -461,7 +456,6 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): class UploadImageAPIViewSet(viewsets.ModelViewSet): """upload task image""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesSerializer def create(self, request, *args, **kwargs): """upload images""" @@ -480,7 +474,6 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet): class DefaultImageAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesDetailsSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -511,7 +504,6 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): class UserNotificationAPIViewSet(viewsets.ModelViewSet): """notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UserNotificationSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -523,7 +515,6 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): """Update notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UpdateUserNotificationSerializer permission_classes = [IsAuthenticated] diff --git a/guardian/serializers.py b/guardian/serializers.py index 1fa385b..65dd1ea 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -67,8 +67,10 @@ class UserSerializer(serializers.ModelSerializer): UserNotification.objects.get_or_create(user=user) if user_type == str(NUMBER['one']): # create junior profile - Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), - referral_code=generate_code(ZOD, user.id)) + junior = Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), + referral_code=generate_code(ZOD, user.id)) + position = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior, position=position) if user_type == str(NUMBER['two']): # create guardian profile Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id), diff --git a/guardian/views.py b/guardian/views.py index 775932a..9b55cf6 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -83,7 +83,6 @@ class SignupViewset(viewsets.ModelViewSet): class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" - queryset = Guardian.objects.all() serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] @@ -117,7 +116,6 @@ class UpdateGuardianProfile(viewsets.ViewSet): class AllTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer - queryset = JuniorTask.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -229,7 +227,6 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] - queryset = JuniorPoints.objects.all() def get_serializer_context(self): # context list diff --git a/junior/admin.py b/junior/admin.py index acd8734..5fdfcce 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior, JuniorPoints, JuniorGuardianRelationship +from .models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): @@ -27,3 +27,7 @@ class JuniorGuardianRelationshipAdmin(admin.ModelAdmin): """Junior Admin""" list_display = ['guardian', 'junior', 'relationship'] +@admin.register(JuniorArticlePoints) +class JuniorArticlePointsAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['junior', 'article', 'question', 'submitted_answer'] diff --git a/junior/urls.py b/junior/urls.py index f66fa30..267faef 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView) """Third party import""" from rest_framework import routers @@ -41,6 +41,8 @@ router.register('junior-points', JuniorPointsListAPIView, basename='junior-point router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') # invite guardian API""" router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') +# start article""" +router.register('start-article', StartArticleAPIView, basename='start-article') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 15cd387..449ac5f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -27,8 +27,8 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship -from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints +from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) from guardian.models import Guardian, JuniorTask @@ -40,7 +40,7 @@ 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 web_admin.models import Article, ArticleSurvey, SurveyOption """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -59,7 +59,6 @@ from notifications.constants import REMOVE_JUNIOR # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" - queryset = Junior.objects.all() serializer_class = CreateJuniorSerializer permission_classes = [IsAuthenticated] @@ -93,7 +92,6 @@ class UpdateJuniorProfile(viewsets.ViewSet): class ValidateGuardianCode(viewsets.ViewSet): """Check guardian code exist or not""" - queryset = Guardian.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -115,7 +113,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) search_fields = ['auth__first_name', 'auth__last_name'] @@ -180,7 +177,6 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination http_method_names = ('get',) @@ -210,7 +206,6 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = Junior.objects.all() http_method_names = ('get',) def get_queryset(self): @@ -268,7 +263,6 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsjuniorSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() http_method_names = ('get',) def list(self, request, *args, **kwargs): @@ -458,3 +452,40 @@ class ReAssignJuniorTaskAPIView(views.APIView): return custom_error_response(ERROR_CODE['2066'], 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 StartArticleAPIView(viewsets.ModelViewSet): + """Start article""" + serializer_class = AddGuardianSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('post',) + + def create(self, request, *args, **kwargs): + """ junior list""" + try: + junior_instance = Junior.objects.filter(auth=self.request.user).last() + article_id = request.data.get('article_id') + article_data = Article.objects.filter(id=article_id).last() + if article_data: + print("article_data====>", article_data,'===>',type(article_data)) + question_query = ArticleSurvey.objects.filter(article=article_id) + print("question_query====>",question_query,'===>',type(question_query)) + for question in question_query: + print("question====>", question, '===>', type(question)) + answer = SurveyOption.objects.filter(survey=question) + print("answer===>",answer,'====>',type(answer)) + for ans in answer: + JuniorArticlePoints.objects.create(junior=junior_instance, + article=article_data, + question=question, + submitted_answer=ans) + print("CREATED CREATED") + return custom_response(SUCCESS_CODE['3034'], response_status=status.HTTP_200_OK) + # use AddJuniorSerializer serializer + # serializer = AddGuardianSerializer(data=request.data, context=info) + # if serializer.is_valid(): + # # save serializer + # serializer.save() + # return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) + # return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/models.py b/web_admin/models.py index 8a3bb16..5dbef97 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -49,7 +49,7 @@ class ArticleSurvey(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.article}' + return f'{self.id} | {self.question}' class SurveyOption(models.Model): @@ -64,7 +64,7 @@ class SurveyOption(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.survey}' + return f'{self.id} | {self.option}' class DefaultArticleCardImage(models.Model): diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 695401e..b39d20d 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -237,4 +237,4 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" - return False \ No newline at end of file + return False From 10a1ea9b765728695bcc1fb622ffe67b17d7a2c2 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 8 Aug 2023 14:29:57 +0530 Subject: [PATCH 186/372] change in admin login --- account/views.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/account/views.py b/account/views.py index 61c4e0e..853c43a 100644 --- a/account/views.py +++ b/account/views.py @@ -329,24 +329,17 @@ class UserLogin(viewsets.ViewSet): @action(methods=['post'], detail=False) def admin_login(self, request): - username = request.data.get('username') + email = request.data.get('email') password = request.data.get('password') - user = authenticate(request, username=username, password=password) - try: - if user is not None: - login(request, user) - if user.is_superuser: - serializer = SuperUserSerializer(user) - return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) - else: - return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED) - except Exception as e: - logging.error(e) - refresh = RefreshToken.for_user(user) - access_token = str(refresh.access_token) - refresh_token = str(refresh) - data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'} - return custom_response(None, data, response_status=status.HTTP_200_OK) + user = User.objects.filter(email__iexact=email, is_superuser=True + ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + + if not user: + return custom_error_response(ERROR_CODE["2063"], response_status=status.HTTP_400_BAD_REQUEST) + elif not user.check_password(password): + return custom_error_response(ERROR_CODE["2031"], response_status=status.HTTP_400_BAD_REQUEST) + serializer = SuperUserSerializer(user) + return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) class AdminLoginViewSet(viewsets.GenericViewSet): From 4b23394569bc345d29de5e48d9758cf5bdedad46 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 15:03:24 +0530 Subject: [PATCH 187/372] top junior api --- guardian/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/guardian/views.py b/guardian/views.py index 9b55cf6..076f563 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -225,6 +225,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" + queryset = JuniorPoints.objects.all() serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] From 7d428f5eb5bc6b5776e955c4ac38043a8b3a1c8e Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 16:05:56 +0530 Subject: [PATCH 188/372] junior list api --- base/messages.py | 3 ++- ...er_juniorarticlepoints_submitted_answer.py | 20 ++++++++++++++++ junior/models.py | 3 ++- junior/serializers.py | 4 ++-- junior/views.py | 24 +++++-------------- 5 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 junior/migrations/0021_alter_juniorarticlepoints_submitted_answer.py diff --git a/base/messages.py b/base/messages.py index cf85f2b..05b4ec3 100644 --- a/base/messages.py +++ b/base/messages.py @@ -149,7 +149,8 @@ SUCCESS_CODE = { "3037": "Profile has been updated successfully.", "3038": "Status has been changed successfully.", # notification read - "3039": "Notification read successfully" + "3039": "Notification read successfully", + "3040": "Start article successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/junior/migrations/0021_alter_juniorarticlepoints_submitted_answer.py b/junior/migrations/0021_alter_juniorarticlepoints_submitted_answer.py new file mode 100644 index 0000000..45ce9a4 --- /dev/null +++ b/junior/migrations/0021_alter_juniorarticlepoints_submitted_answer.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.2 on 2023-08-08 09:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0004_alter_surveyoption_survey'), + ('junior', '0020_junior_guardian_code_status'), + ] + + operations = [ + migrations.AlterField( + model_name='juniorarticlepoints', + name='submitted_answer', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_answer', to='web_admin.surveyoption'), + ), + ] diff --git a/junior/models.py b/junior/models.py index 3adf459..3d2aaa4 100644 --- a/junior/models.py +++ b/junior/models.py @@ -149,7 +149,8 @@ class JuniorArticlePoints(models.Model): junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_details', verbose_name='Junior') article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles') question = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='questions') - submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.CASCADE, related_name='submitted_answer') + submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.SET_NULL, null=True, + related_name='submitted_answer') # earn points""" earn_points = models.IntegerField(blank=True, null=True, default=5) is_attempt = models.BooleanField(default=False) diff --git a/junior/serializers.py b/junior/serializers.py index 178ba14..f8be6aa 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -282,7 +282,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['2']) + guardian_code_status=GUARDIAN_CODE_STATUS[1][0]) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Generate otp""" @@ -401,7 +401,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] junior_data = Junior.objects.filter(auth__username=junior).last() - junior_data.guardian_code_status = GUARDIAN_CODE_STATUS['3'] + junior_data.guardian_code_status = GUARDIAN_CODE_STATUS[2][0] junior_data.save() instance = User.objects.filter(username=email).last() if instance: diff --git a/junior/views.py b/junior/views.py index 449ac5f..98428ce 100644 --- a/junior/views.py +++ b/junior/views.py @@ -113,6 +113,7 @@ class JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer + queryset = Junior.objects.all() permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) search_fields = ['auth__first_name', 'auth__last_name'] @@ -455,7 +456,6 @@ class ReAssignJuniorTaskAPIView(views.APIView): class StartArticleAPIView(viewsets.ModelViewSet): """Start article""" - serializer_class = AddGuardianSerializer permission_classes = [IsAuthenticated] http_method_names = ('post',) @@ -466,26 +466,14 @@ class StartArticleAPIView(viewsets.ModelViewSet): article_id = request.data.get('article_id') article_data = Article.objects.filter(id=article_id).last() if article_data: - print("article_data====>", article_data,'===>',type(article_data)) question_query = ArticleSurvey.objects.filter(article=article_id) - print("question_query====>",question_query,'===>',type(question_query)) for question in question_query: - print("question====>", question, '===>', type(question)) - answer = SurveyOption.objects.filter(survey=question) - print("answer===>",answer,'====>',type(answer)) - for ans in answer: + if not JuniorArticlePoints.objects.filter(junior=junior_instance, + article=article_data, + question=question): JuniorArticlePoints.objects.create(junior=junior_instance, article=article_data, - question=question, - submitted_answer=ans) - print("CREATED CREATED") - return custom_response(SUCCESS_CODE['3034'], response_status=status.HTTP_200_OK) - # use AddJuniorSerializer serializer - # serializer = AddGuardianSerializer(data=request.data, context=info) - # if serializer.is_valid(): - # # save serializer - # serializer.save() - # return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) - # return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + question=question) + return custom_response(SUCCESS_CODE['3040'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From f75201b3dd0114078133a25c9da683dbf7e125ca Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 16:36:03 +0530 Subject: [PATCH 189/372] changes in add junior api --- guardian/views.py | 2 +- junior/serializers.py | 5 +++-- junior/views.py | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index 076f563..3e265b0 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -343,6 +343,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_response({"status": GUARDIAN_CODE_STATUS['1']},response_status=status.HTTP_200_OK) + return custom_response({"status": GUARDIAN_CODE_STATUS[1][0]}, 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/junior/serializers.py b/junior/serializers.py index f8be6aa..24e8b21 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -254,11 +254,10 @@ class JuniorProfileSerializer(serializers.ModelSerializer): class AddJuniorSerializer(serializers.ModelSerializer): """Add junior serializer""" - class Meta(object): """Meta info""" model = Junior - fields = ['id', 'gender','dob', 'is_invited'] + fields = ['id', 'gender', 'dob', 'is_invited'] def create(self, validated_data): @@ -267,6 +266,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): email = self.context['email'] guardian = self.context['user'] relationship = self.context['relationship'] + profile_image = self.context['image'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] guardian_data = Guardian.objects.filter(user__username=guardian).last() user_data = User.objects.create(username=email, email=email, @@ -276,6 +276,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): user_data.set_password(password) user_data.save() junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), + image=profile_image, dob=validated_data.get('dob'), is_invited=True, guardian_code=[guardian_data.guardian_code], junior_code=generate_code(JUN, user_data.id), diff --git a/junior/views.py b/junior/views.py index 98428ce..e1cb8e0 100644 --- a/junior/views.py +++ b/junior/views.py @@ -149,8 +149,19 @@ class AddJuniorAPIView(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): """ junior list""" try: - info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], - 'last_name': request.data['last_name']} + info_data = {'user': request.user, 'relationship': str(request.data['relationship']), + 'email': request.data['email'], 'first_name': request.data['first_name'], + 'last_name': request.data['last_name'], 'image':None} + profile_image = request.data.get('image') + if profile_image: + # check image size + if profile_image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) + # convert into file + filename = f"images/{profile_image.name}" + # upload image on ali baba + 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) return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK) @@ -160,7 +171,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): # save serializer serializer.save() return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + 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) From d86f082e587fc78971faa7cdae8a615bbc66bd17 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 13:37:06 +0530 Subject: [PATCH 190/372] article card api, check answer api --- junior/serializers.py | 3 ++ junior/urls.py | 7 ++- junior/utils.py | 2 +- junior/views.py | 59 +++++++++++++++++++- web_admin/serializers/article_serializer.py | 60 +++++++++++++++++++-- web_admin/urls.py | 4 +- web_admin/views/article.py | 27 ++++++++-- 7 files changed, 152 insertions(+), 10 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 24e8b21..4014ee4 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -286,6 +286,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): guardian_code_status=GUARDIAN_CODE_STATUS[1][0]) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) + total_junior = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior_data, position=total_junior) """Generate otp""" otp_value = generate_otp() expiry_time = timezone.now() + timezone.timedelta(days=1) @@ -479,3 +481,4 @@ class ReAssignTaskSerializer(serializers.ModelSerializer): instance.requested_on = None instance.save() return instance + diff --git a/junior/urls.py b/junior/urls.py index 267faef..76e5b47 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,8 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView, + StartAssessmentAPIView, CheckAnswerAPIView) """Third party import""" from rest_framework import routers @@ -43,6 +44,10 @@ router.register('validate-referral-code', ValidateReferralCode, basename='valida router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') # start article""" router.register('start-article', StartArticleAPIView, basename='start-article') +# start assessment api""" +router.register('start-assessment', StartAssessmentAPIView, basename='start-assessment') +# check answer api""" +router.register('check-answer', CheckAnswerAPIView, basename='check-answer') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/utils.py b/junior/utils.py index fefe950..621a6dd 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -50,7 +50,7 @@ def junior_approval_mail(guardian, full_name): def update_positions_based_on_points(): """Update position of the junior""" # First, retrieve all the JuniorPoints instances ordered by total_points in descending order. - juniors_points = JuniorPoints.objects.order_by('-total_points') + juniors_points = JuniorPoints.objects.order_by('-total_points', 'updated_at') # Now, iterate through the queryset and update the position field based on the order. position = 1 diff --git a/junior/views.py b/junior/views.py index e1cb8e0..9a7ce35 100644 --- a/junior/views.py +++ b/junior/views.py @@ -6,6 +6,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from rest_framework.filters import SearchFilter +from django.db.models import F import datetime import requests @@ -41,6 +42,8 @@ from .utils import update_positions_based_on_points from notifications.utils import send_notification from notifications.constants import REMOVE_JUNIOR from web_admin.models import Article, ArticleSurvey, SurveyOption +from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, + StartAssessmentSerializer) """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -351,6 +354,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): def get_queryset(self): """get queryset""" + update_positions_based_on_points() return JuniorTask.objects.filter(junior__auth__email=self.request.user).last() def list(self, request, *args, **kwargs): """profile view""" @@ -358,7 +362,6 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): try: queryset = self.get_queryset() # update position of junior - update_positions_based_on_points() serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) except Exception as e: @@ -488,3 +491,57 @@ class StartArticleAPIView(viewsets.ModelViewSet): return custom_response(SUCCESS_CODE['3040'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class StartAssessmentAPIView(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = StartAssessmentSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + article_id = self.request.GET.get('article_id') + # if referral_code: + article = Article.objects.filter(id=article_id, is_deleted=False).prefetch_related( + 'article_cards', 'article_survey', 'article_survey__options' + ).order_by('-created_at') + return article + def list(self, request, *args, **kwargs): + """profile view""" + + try: + queryset = self.get_queryset() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, context={"user":request.user}, 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 CheckAnswerAPIView(viewsets.ModelViewSet): + """Junior Points viewset""" + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + question_id = self.request.GET.get('question_id') + article = ArticleSurvey.objects.filter(id=question_id).last() + return article + def list(self, request, *args, **kwargs): + """profile view""" + + try: + answer_id = self.request.GET.get('answer_id') + queryset = self.get_queryset() + submit_ans = SurveyOption.objects.filter(id=answer_id, is_answer=True).last() + junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user, + question=queryset) + if submit_ans: + junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True) + JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points= + F('total_points')+ queryset.points) + else: + junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, earn_points=0, + is_answer_correct=False) + return custom_response(None, 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/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index b39d20d..9458190 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -12,7 +12,7 @@ from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba 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 USER = get_user_model() @@ -64,7 +64,7 @@ class ArticleSurveySerializer(serializers.ModelSerializer): meta class """ model = ArticleSurvey - fields = ('id', 'question', 'options') + fields = ('id', 'question', 'options', 'points') class ArticleSerializer(serializers.ModelSerializer): @@ -220,6 +220,7 @@ class ArticleListSerializer(serializers.ModelSerializer): serializer for article API """ article_cards = ArticleCardSerializer(many=True) + article_survey = ArticleSurveySerializer(many=True) total_points = serializers.SerializerMethodField('get_total_points') is_completed = serializers.SerializerMethodField('get_is_completed') @@ -228,7 +229,7 @@ class ArticleListSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('id', 'title', 'description', 'article_cards', 'total_points', 'is_completed') + fields = ('id', 'title', 'description', 'article_cards', 'article_survey', 'total_points', 'is_completed') def get_total_points(self, obj): """total points of article""" @@ -238,3 +239,56 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" return False + +class ArticleQuestionSerializer(serializers.ModelSerializer): + """ + article survey serializer + """ + id = serializers.IntegerField(required=False) + options = SurveyOptionSerializer(many=True) + is_attempt = serializers.SerializerMethodField('get_is_attempt') + + def get_is_attempt(self, obj): + """attempt question or not""" + context_data = self.context.get('user') + junior_article_obj = JuniorArticlePoints.objects.filter(junior__auth=context_data, question=obj).last() + if junior_article_obj: + return junior_article_obj.is_attempt + return False + + class Meta: + """ + meta class + """ + model = ArticleSurvey + fields = ('id', 'question', 'options', 'points', 'is_attempt') + +class StartAssessmentSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_survey = ArticleQuestionSerializer(many=True) + + class Meta: + """ + meta class + """ + model = Article + fields = ('article_survey',) + + +class CheckAnswerSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_survey = ArticleSurveySerializer(many=True) + + class Meta: + """ + meta class + """ + model = ArticleSurvey + fields = ('id', 'article_survey') + + def create(self, validated_data): + """create function""" diff --git a/web_admin/urls.py b/web_admin/urls.py index 065d30d..6bf3b5e 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,8 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet +from web_admin.views.article import (ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet, + ArticleCardListViewSet) from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -17,6 +18,7 @@ router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') router.register('article-list', ArticleListViewSet, basename='article-list') +router.register('article-card-list', ArticleCardListViewSet, basename='article-card-list') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index f334650..d4cc843 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -4,7 +4,7 @@ web_admin views file # django imports from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework import status +from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated, AllowAny from django.contrib.auth import get_user_model @@ -16,7 +16,8 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer, ArticleListSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer + ) USER = get_user_model() @@ -224,4 +225,24 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): 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, count=count) + return custom_response(None, data=serializer.data) + +class ArticleCardListViewSet(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = ArticleCardSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + """get queryset""" + return ArticleCard.objects.filter(article=self.request.GET.get('article_id')) + def list(self, request, *args, **kwargs): + """profile view""" + + try: + queryset = self.get_queryset() + # article card list + serializer = ArticleCardSerializer(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) From 881bda739b4d3d40ea042329a649a46155dc669c Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 13:48:02 +0530 Subject: [PATCH 191/372] article card api, check answer api --- base/constants.py | 4 ++-- guardian/serializers.py | 4 ++-- guardian/utils.py | 2 +- guardian/views.py | 44 +++++++++++++++++++---------------------- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/base/constants.py b/base/constants.py index e38cb29..7efdb8b 100644 --- a/base/constants.py +++ b/base/constants.py @@ -112,7 +112,7 @@ MAX_ARTICLE_CARD = 6 MIN_ARTICLE_SURVEY = 5 MAX_ARTICLE_SURVEY = 10 -# real time url -time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" +# already register +Already_register_user = "duplicate key value violates unique constraint" ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' diff --git a/guardian/serializers.py b/guardian/serializers.py index 65dd1ea..172ccc4 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -22,7 +22,7 @@ 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 +from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from .utils import real_time, convert_timedelta_into_datetime, update_referral_points # notification's constant @@ -83,7 +83,7 @@ class UserSerializer(serializers.ModelSerializer): otp_verified = False if otp and otp.is_verified: otp_verified = True - raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":bool(otp_verified), + raise serializers.ValidationError({"details": ERROR_CODE['2021'], "otp_verified":bool(otp_verified), "code": 400, "status":"failed", }) diff --git a/guardian/utils.py b/guardian/utils.py index 9a6ae05..57e8080 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -11,7 +11,7 @@ import tempfile # Import date time module's function from datetime import datetime, time # import Number constant -from base.constants import NUMBER, time_url +from base.constants import NUMBER # Import Junior's model from junior.models import Junior, JuniorPoints # Import guardian's model diff --git a/guardian/views.py b/guardian/views.py index 3e265b0..75a16ff 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,7 +10,6 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User -from django.utils import timezone # Import guardian's model, # Import junior's model, @@ -57,29 +56,26 @@ class SignupViewset(viewsets.ModelViewSet): serializer_class = UserSerializer def create(self, request, *args, **kwargs): """Create user profile""" - try: - 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(): - user = serializer.save() - """Generate otp""" - otp = generate_otp() - # expire otp after 1 day - expiry = OTP_EXPIRY - # 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, {}) - 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) - except Exception as e: - return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + 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(): + user = serializer.save() + """Generate otp""" + otp = generate_otp() + # expire otp after 1 day + expiry = OTP_EXPIRY + # 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, {}) + 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): """Update guardian profile""" From c9ee482512be0ea68058f313d69ecd9d9c459493 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 9 Aug 2023 14:14:13 +0530 Subject: [PATCH 192/372] otp, verify otp and login bug resolved --- account/serializers.py | 4 ++-- account/views.py | 4 ++-- base/messages.py | 14 +++++++------- web_admin/views/auth.py | 6 +++--- web_admin/views/user_management.py | 7 ++++--- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 284ae09..5686931 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -155,9 +155,9 @@ class AdminLoginSerializer(serializers.ModelSerializer): ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() if not user: - raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + raise serializers.ValidationError({'details': ERROR_CODE['2002']}) elif not user.check_password(attrs['password']): - raise serializers.ValidationError({'details': ERROR_CODE['2031']}) + raise serializers.ValidationError({'details': ERROR_CODE['2002']}) self.context.update({'user': user}) return attrs diff --git a/account/views.py b/account/views.py index 853c43a..80530ad 100644 --- a/account/views.py +++ b/account/views.py @@ -335,9 +335,9 @@ class UserLogin(viewsets.ViewSet): ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() if not user: - return custom_error_response(ERROR_CODE["2063"], response_status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) elif not user.check_password(password): - return custom_error_response(ERROR_CODE["2031"], response_status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) serializer = SuperUserSerializer(user) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) diff --git a/base/messages.py b/base/messages.py index cf85f2b..f7d36dc 100644 --- a/base/messages.py +++ b/base/messages.py @@ -23,15 +23,15 @@ ERROR_CODE_REQUIRED = { # Error code ERROR_CODE = { - "2000": "Email not found.", + "2000": "Invalid email address. Please enter a registered email.", "2001": "This is your existing password. Please choose other one", - "2002": "Invalid login credentials.", + "2002": "Invalid username or password.", "2003": "An account already exists with this email address.", "2004": "User not found.", "2005": "Your account has been activated.", "2006": "Your account is not activated.", "2007": "Your account already activated.", - "2008": "Invalid OTP.", + "2008": "The OTP entered is not correct.", "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.", "2010": "Invalid Link.", "2011": "Your profile has not been completed yet.", @@ -54,7 +54,7 @@ ERROR_CODE = { "2026": "New password should not same as old password", "2027": "data should contain `identityToken`", "2028": "You are not authorized person to sign up on this platform", - "2029": "Validity of otp verification is expired", + "2029": "Validity of otp verification has expired. Please request a new one.", "2030": "Use correct user type and token", # invalid password "2031": "Invalid password", @@ -111,7 +111,7 @@ SUCCESS_CODE = { # Success code for link verified "3005": "Your account is deleted successfully.", # Success code for password reset - "3006": "Your password has been reset successfully.", + "3006": "Password reset successful. You can now log in with your new password.", # Success code for password update "3007": "Your password has been changed successfully.", # Success code for valid link @@ -124,8 +124,8 @@ SUCCESS_CODE = { "3012": "Phone OTP Verified successfully", "3013": "Valid Guardian code", "3014": "Password has been updated successfully.", - "3015": "Verification code sent on your email.", - "3016": "Send otp on your Email successfully", + "3015": "Verification code has been sent on your email.", + "3016": "An OTP has been sent on your email.", "3017": "Profile image update successfully", "3018": "Task created successfully", "3019": "Support Email sent successfully", diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index 0273a08..fae973e 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -33,7 +33,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3015']) - return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2000'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='verify-otp', url_path='verify-otp', detail=False, serializer_class=AdminVerifyOTPSerializer) @@ -45,7 +45,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): serializer = self.serializer_class(data=request.data) if serializer.is_valid(): return custom_response(SUCCESS_CODE['3011']) - return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2008'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='create-password', url_path='create-password', detail=False, serializer_class=AdminCreatePasswordSerializer) @@ -59,5 +59,5 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): user = USER.objects.filter(email=serializer.validated_data.get('email')).first() user.set_password(serializer.validated_data.get('new_password')) user.save() - return custom_response(SUCCESS_CODE['3007']) + return custom_response(SUCCESS_CODE['3006']) return custom_error_response(ERROR_CODE['2064'], status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 20184cd..3bd8f64 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -55,11 +55,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: """ queryset = self.get_queryset() - count = queryset.count() + queryset = queryset.filter( + (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True))) 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, count=count) + return custom_response(None, data=serializer.data, count=queryset.count()) def retrieve(self, request, *args, **kwargs): """ @@ -69,7 +70,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: user details """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: - return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + 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']) From 3cb0e3ed8b86784c6781e01ea3022ef104f04ad9 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 14:37:38 +0530 Subject: [PATCH 193/372] guardian code add in junior list api --- junior/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/serializers.py b/junior/serializers.py index 4014ee4..3f374cc 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -207,7 +207,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): fields = ['id', 'email', 'first_name', 'last_name', 'country_code', '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'] + 'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status'] class JuniorProfileSerializer(serializers.ModelSerializer): """junior serializer""" From 69ce6857e8bd95ee83b3525d1ac27c5d5ab1779c Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 16:36:33 +0530 Subject: [PATCH 194/372] financial learning section api --- base/constants.py | 6 ++ base/messages.py | 8 +- guardian/serializers.py | 2 - junior/admin.py | 22 +++++- junior/migrations/0022_juniorarticle.py | 27 +++++++ junior/migrations/0023_juniorarticlecard.py | 27 +++++++ junior/models.py | 39 +++++++++- junior/serializers.py | 2 +- junior/urls.py | 7 +- junior/views.py | 84 ++++++++++++++++++++- 10 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 junior/migrations/0022_juniorarticle.py create mode 100644 junior/migrations/0023_juniorarticlecard.py diff --git a/base/constants.py b/base/constants.py index 7efdb8b..36079b9 100644 --- a/base/constants.py +++ b/base/constants.py @@ -76,6 +76,12 @@ GUARDIAN_CODE_STATUS = ( ('2', 'exist guardian code'), ('3', 'request for guardian code') ) +# article status +ARTICLE_STATUS = ( + ('1', 'read'), + ('2', 'in_progress'), + ('3', 'completed') +) # relationship RELATIONSHIP = ( ('1', 'parent'), diff --git a/base/messages.py b/base/messages.py index 05b4ec3..1e3e146 100644 --- a/base/messages.py +++ b/base/messages.py @@ -150,7 +150,13 @@ SUCCESS_CODE = { "3038": "Status has been changed successfully.", # notification read "3039": "Notification read successfully", - "3040": "Start article successfully" + "3040": "Start article successfully", + # complete article + "3041": "Article completed successfully", + # submit assessment successfully + "3042": "Assessment completed successfully", + "3043": "Read article card successfully" + } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index 0a8b625..12d5383 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -387,8 +387,6 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # reject the task instance.task_status = str(NUMBER['three']) instance.is_approved = False - # update total task point - junior_data.total_points = junior_data.total_points - instance.points # update reject time of task # instance.rejected_on = real_time() instance.rejected_on = timezone.now().astimezone(pytz.utc) diff --git a/junior/admin.py b/junior/admin.py index 5fdfcce..6c6cdf9 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,8 +2,26 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints +from .models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle, + JuniorArticleCard) # Register your models here. +@admin.register(JuniorArticle) +class JuniorArticleAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['junior', 'article', 'status', 'is_completed'] + + def __str__(self): + """Return email id""" + return self.junior__auth__email + +@admin.register(JuniorArticleCard) +class JuniorArticleCardAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['junior', 'article', 'article_card', 'is_read'] + + def __str__(self): + """Return email id""" + return self.junior__auth__email @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): """Junior Admin""" @@ -30,4 +48,4 @@ class JuniorGuardianRelationshipAdmin(admin.ModelAdmin): @admin.register(JuniorArticlePoints) class JuniorArticlePointsAdmin(admin.ModelAdmin): """Junior Admin""" - list_display = ['junior', 'article', 'question', 'submitted_answer'] + list_display = ['junior', 'article', 'question', 'submitted_answer', 'is_answer_correct'] diff --git a/junior/migrations/0022_juniorarticle.py b/junior/migrations/0022_juniorarticle.py new file mode 100644 index 0000000..9dd794e --- /dev/null +++ b/junior/migrations/0022_juniorarticle.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.2 on 2023-08-09 09:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0004_alter_surveyoption_survey'), + ('junior', '0021_alter_juniorarticlepoints_submitted_answer'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorArticle', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_completed', models.BooleanField(default=False)), + ('status', models.CharField(blank=True, choices=[('1', 'read'), ('2', 'in_progress'), ('3', 'completed')], default='1', max_length=10, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles_details', to='web_admin.article')), + ('junior', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article', to='junior.junior', verbose_name='Junior')), + ], + ), + ] diff --git a/junior/migrations/0023_juniorarticlecard.py b/junior/migrations/0023_juniorarticlecard.py new file mode 100644 index 0000000..9b8a1af --- /dev/null +++ b/junior/migrations/0023_juniorarticlecard.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.2 on 2023-08-09 10:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0004_alter_surveyoption_survey'), + ('junior', '0022_juniorarticle'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorArticleCard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_read', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles_detail', to='web_admin.article')), + ('article_card', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_article_card', to='web_admin.articlecard')), + ('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article_card', to='junior.junior', verbose_name='Junior')), + ], + ), + ] diff --git a/junior/models.py b/junior/models.py index 3d2aaa4..180b425 100644 --- a/junior/models.py +++ b/junior/models.py @@ -6,11 +6,11 @@ from django.contrib.auth import get_user_model """Import ArrayField""" from django.contrib.postgres.fields import ArrayField """Import django app""" -from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP, GUARDIAN_CODE_STATUS +from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP, GUARDIAN_CODE_STATUS, ARTICLE_STATUS # Import guardian's model from guardian.models import Guardian # Import web admin's model -from web_admin.models import SurveyOption, ArticleSurvey, Article +from web_admin.models import SurveyOption, ArticleSurvey, Article, ArticleCard """Define User model""" User = get_user_model() # Create your models here. @@ -161,3 +161,38 @@ class JuniorArticlePoints(models.Model): def __str__(self): """Return title""" return f'{self.id} | {self.question}' + +class JuniorArticle(models.Model): + """ + Survey Options model + """ + # associated junior with the task + junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='juniors_article', verbose_name='Junior') + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_details') + # article completed""" + is_completed = models.BooleanField(default=False) + status = models.CharField(max_length=10, choices=ARTICLE_STATUS, null=True, blank=True, default='1') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.article}' + +class JuniorArticleCard(models.Model): + """ + Survey Options model + """ + # associated junior with the task + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article_card', verbose_name='Junior') + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_detail') + article_card = models.ForeignKey(ArticleCard, on_delete=models.CASCADE, related_name='junior_article_card') + + # article card read""" + is_read = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.article}' diff --git a/junior/serializers.py b/junior/serializers.py index bfd79d1..079031a 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,7 @@ 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 +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints 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, diff --git a/junior/urls.py b/junior/urls.py index 76e5b47..e494a3d 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -5,7 +5,8 @@ from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView, - StartAssessmentAPIView, CheckAnswerAPIView) + StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView, + CreateArticleCardAPIView) """Third party import""" from rest_framework import routers @@ -48,6 +49,8 @@ router.register('start-article', StartArticleAPIView, basename='start-article') router.register('start-assessment', StartAssessmentAPIView, basename='start-assessment') # check answer api""" router.register('check-answer', CheckAnswerAPIView, basename='check-answer') +# start article""" +router.register('create-article-card', CreateArticleCardAPIView, basename='create-article-card') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), @@ -55,4 +58,6 @@ urlpatterns = [ path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()), path('api/v1/start-task/', StartTaskAPIView.as_view()), 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()), ] diff --git a/junior/views.py b/junior/views.py index 9a7ce35..5926c47 100644 --- a/junior/views.py +++ b/junior/views.py @@ -28,20 +28,23 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints +from django.db.models import Sum +from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle, + JuniorArticleCard) from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, - AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) + AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer + ) 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 +from base.constants import NUMBER, ARTICLE_STATUS 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 web_admin.models import Article, ArticleSurvey, SurveyOption +from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, StartAssessmentSerializer) """ Define APIs """ @@ -479,6 +482,8 @@ class StartArticleAPIView(viewsets.ModelViewSet): junior_instance = Junior.objects.filter(auth=self.request.user).last() article_id = request.data.get('article_id') article_data = Article.objects.filter(id=article_id).last() + if not JuniorArticle.objects.filter(junior=junior_instance, article=article_data).last(): + JuniorArticle.objects.create(junior=junior_instance, article=article_data, status=str(NUMBER['two'])) if article_data: question_query = ArticleSurvey.objects.filter(article=article_id) for question in question_query: @@ -545,3 +550,74 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): return custom_response(None, response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class CompleteArticleAPIView(views.APIView): + """Remove junior API""" + permission_classes = [IsAuthenticated] + http_method_names = ('put', 'get',) + def put(self, request, format=None): + try: + article_id = self.request.data.get('article_id') + JuniorArticle.objects.filter(junior__auth=request.user, article__id=article_id).update( + is_completed=True, status=str(NUMBER['three']) + ) + return custom_response(SUCCESS_CODE['3041'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + def get(self, request, *args, **kwargs): + """ junior list""" + try: + article_id = self.request.GET.get('article_id') + total_earn_points = JuniorArticlePoints.objects.filter(junior__auth=request.user, + article__id=article_id, + is_answer_correct=True).aggregate( + total_earn_points=Sum('earn_points'))['total_earn_points'] + data = {"total_earn_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""" + permission_classes = [IsAuthenticated] + http_method_names = ('put',) + + def put(self, request, *args, **kwargs): + """ junior list""" + try: + junior_instance = Junior.objects.filter(auth=self.request.user).last() + article = self.request.data.get('article_id') + article_card = self.request.data.get('article_card') + JuniorArticleCard.objects.filter(junior=junior_instance, + article__id=article, + article_card__id=article_card).update(is_read=True) + return custom_response(SUCCESS_CODE['3043'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + +class CreateArticleCardAPIView(viewsets.ModelViewSet): + """Start article""" + permission_classes = [IsAuthenticated] + http_method_names = ('post',) + + def create(self, request, *args, **kwargs): + """ junior list""" + try: + junior_instance = Junior.objects.filter(auth=self.request.user).last() + article_id = request.data.get('article_id') + article_data = Article.objects.filter(id=article_id).last() + if article_data: + article_cards = ArticleCard.objects.filter(article=article_id) + for article_card in article_cards: + if not JuniorArticleCard.objects.filter(junior=junior_instance, + article=article_data, + article_card=article_card): + JuniorArticleCard.objects.create(junior=junior_instance, + article=article_data, + article_card=article_card) + return custom_response(None, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From 9c75cb1615729c468b6bfddb92ffbaf79b821d86 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 9 Aug 2023 16:57:19 +0530 Subject: [PATCH 195/372] analytics section api, users counts, new signup count, task report --- account/serializers.py | 5 +- account/views.py | 5 +- web_admin/serializers/analytics_serializer.py | 3 + web_admin/serializers/auth_serializer.py | 4 +- web_admin/urls.py | 4 + web_admin/views/analytics.py | 98 +++++++++++++++++++ web_admin/views/user_management.py | 13 ++- 7 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 web_admin/serializers/analytics_serializer.py create mode 100644 web_admin/views/analytics.py diff --git a/account/serializers.py b/account/serializers.py index 5686931..4065b5c 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -154,10 +154,9 @@ class AdminLoginSerializer(serializers.ModelSerializer): user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() - if not user: - raise serializers.ValidationError({'details': ERROR_CODE['2002']}) - elif not user.check_password(attrs['password']): + if not user or not user.check_password(attrs['password']): raise serializers.ValidationError({'details': ERROR_CODE['2002']}) + self.context.update({'user': user}) return attrs diff --git a/account/views.py b/account/views.py index 80530ad..c814100 100644 --- a/account/views.py +++ b/account/views.py @@ -334,10 +334,9 @@ class UserLogin(viewsets.ViewSet): user = User.objects.filter(email__iexact=email, is_superuser=True ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() - if not user: - return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) - elif not user.check_password(password): + if not user or not user.check_password(password): return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) + serializer = SuperUserSerializer(user) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py new file mode 100644 index 0000000..5b653a6 --- /dev/null +++ b/web_admin/serializers/analytics_serializer.py @@ -0,0 +1,3 @@ +""" +web_admin analytics serializer file +""" diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 9f603ce..712e284 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -87,9 +87,9 @@ class AdminVerifyOTPSerializer(serializers.Serializer): # fetch email otp object of the user user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last() if not user_otp_details: - raise serializers.ValidationError({'details': ERROR_CODE['2064']}) + raise serializers.ValidationError({'details': ERROR_CODE['2008']}) if user_otp_details.user_type != dict(USER_TYPE).get('3'): - raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + raise serializers.ValidationError({'details': ERROR_CODE['2008']}) if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): raise serializers.ValidationError({'details': ERROR_CODE['2029']}) user_otp_details.is_verified = True diff --git a/web_admin/urls.py b/web_admin/urls.py index 065d30d..d586e50 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,6 +6,7 @@ from django.urls import path, include from rest_framework import routers # local imports +from web_admin.views.analytics import AnalyticsViewSet from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -17,6 +18,9 @@ router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') router.register('article-list', ArticleListViewSet, basename='article-list') +router.register('analytics', AnalyticsViewSet, basename='user-analytics') +# router.register('task-analytics', TaskAnalyticsViewSet, basename='task-analytics') + # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py new file mode 100644 index 0000000..f769c18 --- /dev/null +++ b/web_admin/views/analytics.py @@ -0,0 +1,98 @@ +""" +web_admin analytics view file +""" +import datetime + +from rest_framework.viewsets import GenericViewSet +from rest_framework.decorators import action +from django.contrib.auth import get_user_model +from django.db.models import Q +from django.db.models import Count +from django.db.models.functions import TruncDate + +from account.utils import custom_response +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED +from guardian.models import JuniorTask + +USER = get_user_model() + + +class AnalyticsViewSet(GenericViewSet): + """ + analytics api view + """ + serializer_class = None + + def get_queryset(self): + user_qs = USER.objects.filter( + (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True)), + is_superuser=False + ).prefetch_related('guardian_profile', + 'junior_profile' + ).exclude(junior_profile__isnull=True, + guardian_profile__isnull=True).order_by('date_joined') + return user_qs + + @action(methods=['get'], url_name='users-count', url_path='users-count', detail=False) + def total_users_count(self, request, *args, **kwargs): + """ + api method to get total users, guardians and juniors + :param request: query params {start_date and 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'), '%Y-%m-%d') + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + + user_qs = self.get_queryset() + queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1)))) + + data = {'total_users': queryset.count(), + 'total_guardians': queryset.filter(junior_profile__isnull=True).count(), + 'total_juniors': queryset.filter(guardian_profile__isnull=True).count()} + + return custom_response(None, data) + + @action(methods=['get'], url_name='new-signups', url_path='new-signups', detail=False) + def new_signups(self, request, *args, **kwargs): + + 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'), '%Y-%m-%d') + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + + user_qs = self.get_queryset() + signup_data = user_qs.filter(date_joined__range=[start_date, (end_date + datetime.timedelta(days=1))] + ).annotate(date=TruncDate('date_joined') + ).values('date').annotate(signups=Count('id')).order_by('date') + + return custom_response(None, signup_data) + + @action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False) + def assign_tasks_report(self, request, *args, **kwargs): + + 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'), '%Y-%m-%d') + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + + 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_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(), + } + + return custom_response(None, data) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 3bd8f64..c16b62a 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -29,11 +29,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, """ serializer_class = UserManagementListSerializer permission_classes = [IsAuthenticated, AdminPermission] - queryset = USER.objects.filter(is_superuser=False - ).prefetch_related('guardian_profile', - 'junior_profile' - ).exclude(junior_profile__isnull=True, - guardian_profile__isnull=True).order_by('date_joined') + queryset = USER.objects.filter( + (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True)), + is_superuser=False).prefetch_related('guardian_profile', + 'junior_profile' + ).exclude(junior_profile__isnull=True, + guardian_profile__isnull=True).order_by('date_joined') filter_backends = (SearchFilter,) search_fields = ['first_name', 'last_name'] http_method_names = ['get', 'post', 'patch'] @@ -55,8 +56,6 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: """ queryset = self.get_queryset() - queryset = queryset.filter( - (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True))) paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) From 32eaa6c3f2a90d48c971f13532c553d497487bf2 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 17:18:07 +0530 Subject: [PATCH 196/372] guardian code update --- junior/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/junior/serializers.py b/junior/serializers.py index 079031a..3575ccf 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -96,6 +96,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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, {}) 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) From 19f56280e4a9e11d5237134c80e4f50c47716d25 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 18:53:18 +0530 Subject: [PATCH 197/372] points remove from serializer --- junior/serializers.py | 6 +++--- web_admin/serializers/article_serializer.py | 16 +--------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 3575ccf..1db1f44 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -96,9 +96,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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, {}) + 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, {}) 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) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 9458190..a55d4a9 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -58,6 +58,7 @@ class ArticleSurveySerializer(serializers.ModelSerializer): """ id = serializers.IntegerField(required=False) options = SurveyOptionSerializer(many=True) + points = serializers.IntegerField(required=False) class Meta: """ @@ -277,18 +278,3 @@ class StartAssessmentSerializer(serializers.ModelSerializer): fields = ('article_survey',) -class CheckAnswerSerializer(serializers.ModelSerializer): - """ - serializer for article API - """ - article_survey = ArticleSurveySerializer(many=True) - - class Meta: - """ - meta class - """ - model = ArticleSurvey - fields = ('id', 'article_survey') - - def create(self, validated_data): - """create function""" From af7582b9e2b085f63306caf5456c5f0022da1f77 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 9 Aug 2023 19:20:56 +0530 Subject: [PATCH 198/372] points --- web_admin/serializers/article_serializer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index a55d4a9..a3a7a46 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -58,14 +58,13 @@ class ArticleSurveySerializer(serializers.ModelSerializer): """ id = serializers.IntegerField(required=False) options = SurveyOptionSerializer(many=True) - points = serializers.IntegerField(required=False) class Meta: """ meta class """ model = ArticleSurvey - fields = ('id', 'question', 'options', 'points') + fields = ('id', 'question', 'options') class ArticleSerializer(serializers.ModelSerializer): From 22dd7fc10bfd42a03b7682b986508bb9e177fe30 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 10:55:33 +0530 Subject: [PATCH 199/372] remove guardian code request --- base/messages.py | 4 +++- junior/serializers.py | 12 ++++++++++++ junior/urls.py | 3 ++- junior/views.py | 26 ++++++++++++++++++++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/base/messages.py b/base/messages.py index 1e3e146..4e15d59 100644 --- a/base/messages.py +++ b/base/messages.py @@ -155,7 +155,9 @@ SUCCESS_CODE = { "3041": "Article completed successfully", # submit assessment successfully "3042": "Assessment completed successfully", - "3043": "Read article card successfully" + "3043": "Read article card successfully", + # remove guardian code request + "3044": "Remove guardian code request successfully", } """status code error""" diff --git a/junior/serializers.py b/junior/serializers.py index 1db1f44..003e563 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -492,3 +492,15 @@ class ReAssignTaskSerializer(serializers.ModelSerializer): instance.save() return instance + + +class RemoveGuardianCodeSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = Junior + fields = ('id', ) + def update(self, instance, validated_data): + instance.guardian_code_status = str(NUMBER['one']) + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index e494a3d..b145d4f 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) + CreateArticleCardAPIView, RemoveGuardianCodeAPIView) """Third party import""" from rest_framework import routers @@ -60,4 +60,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()), ] diff --git a/junior/views.py b/junior/views.py index 5926c47..8f4e53d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -33,8 +33,8 @@ from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, Jun JuniorArticleCard) from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, - AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer - ) + AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer, + RemoveGuardianCodeSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -621,3 +621,25 @@ class CreateArticleCardAPIView(viewsets.ModelViewSet): return custom_response(None, response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class RemoveGuardianCodeAPIView(views.APIView): + """Update junior task API""" + serializer_class = RemoveGuardianCodeSerializer + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + try: + 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) + if serializer.is_valid(): + # save serializer + serializer.save() + 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: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From b6b70af13ff909ef5a5a6f95ebe6d83f213d07fc Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 11:02:32 +0530 Subject: [PATCH 200/372] remove guardian code request --- base/messages.py | 2 ++ junior/models.py | 6 ++++-- junior/views.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/base/messages.py b/base/messages.py index 4e15d59..c98e0e0 100644 --- a/base/messages.py +++ b/base/messages.py @@ -150,11 +150,13 @@ SUCCESS_CODE = { "3038": "Status has been changed successfully.", # notification read "3039": "Notification read successfully", + # start article "3040": "Start article successfully", # complete article "3041": "Article completed successfully", # submit assessment successfully "3042": "Assessment completed successfully", + # read article "3043": "Read article card successfully", # remove guardian code request "3044": "Remove guardian code request successfully", diff --git a/junior/models.py b/junior/models.py index 180b425..12d8476 100644 --- a/junior/models.py +++ b/junior/models.py @@ -167,7 +167,8 @@ class JuniorArticle(models.Model): Survey Options model """ # associated junior with the task - junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='juniors_article', verbose_name='Junior') + junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='juniors_article', + verbose_name='Junior') article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_details') # article completed""" is_completed = models.BooleanField(default=False) @@ -184,7 +185,8 @@ class JuniorArticleCard(models.Model): Survey Options model """ # associated junior with the task - junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article_card', verbose_name='Junior') + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article_card', + verbose_name='Junior') article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_detail') article_card = models.ForeignKey(ArticleCard, on_delete=models.CASCADE, related_name='junior_article_card') diff --git a/junior/views.py b/junior/views.py index 8f4e53d..b41d2aa 100644 --- a/junior/views.py +++ b/junior/views.py @@ -260,7 +260,8 @@ class RemoveJuniorAPIView(views.APIView): junior_id = self.request.GET.get('id') guardian = Guardian.objects.filter(user__email=self.request.user).last() # fetch junior query - junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code)).last() + junior_queryset = Junior.objects.filter(id=junior_id, + guardian_code__icontains=str(guardian.guardian_code)).last() if junior_queryset: # use RemoveJuniorSerializer serializer serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) From b30643299f341c9d190a41a8b287ebafda1197f5 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 11:56:19 +0530 Subject: [PATCH 201/372] junior points --- guardian/serializers.py | 1 + guardian/views.py | 1 + junior/utils.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 12d5383..1dabc8d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -355,6 +355,7 @@ class ApproveJuniorSerializer(serializers.ModelSerializer): instance = self.context['junior'] instance.guardian_code = [self.context['guardian_code']] instance.guardian_code_approved = True + instance.guardian_code_status = str(NUMBER['two']) instance.save() return instance diff --git a/guardian/views.py b/guardian/views.py index 8d38395..c67037c 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -311,6 +311,7 @@ class ApproveJuniorAPIView(viewsets.ViewSet): 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() return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) except Exception as e: diff --git a/junior/utils.py b/junior/utils.py index 621a6dd..1afd8d5 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -50,7 +50,7 @@ def junior_approval_mail(guardian, full_name): def update_positions_based_on_points(): """Update position of the junior""" # First, retrieve all the JuniorPoints instances ordered by total_points in descending order. - juniors_points = JuniorPoints.objects.order_by('-total_points', 'updated_at') + juniors_points = JuniorPoints.objects.order_by('-total_points', 'created_at') # Now, iterate through the queryset and update the position field based on the order. position = 1 From 656f0da89a4646ba57315d343613d646ba6d277a Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 12:06:52 +0530 Subject: [PATCH 202/372] guardian code --- account/utils.py | 3 ++- junior/serializers.py | 1 + junior/views.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/account/utils.py b/account/utils.py index 4dc6acc..357f2d1 100644 --- a/account/utils.py +++ b/account/utils.py @@ -23,7 +23,7 @@ from guardian.models import Guardian from account.models import UserDelete from base.messages import ERROR_CODE from django.utils import timezone - +from base.constants import NUMBER # Define delete # user account condition, # Define delete @@ -91,6 +91,7 @@ def junior_account_update(user_tb): junior_data.is_active = False junior_data.is_verified = False junior_data.guardian_code = '{}' + junior_data.guardian_code_status = str(NUMBER['1']) junior_data.save() def guardian_account_update(user_tb): diff --git a/junior/serializers.py b/junior/serializers.py index 003e563..0f014fe 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -319,6 +319,7 @@ class RemoveJuniorSerializer(serializers.ModelSerializer): if instance: instance.is_invited = False instance.guardian_code = '{}' + instance.guardian_code_status = str(NUMBER['1']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index b41d2aa..471db0f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -185,6 +185,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior = Junior.objects.filter(auth=user).first() guardian = Guardian.objects.filter(user=self.request.user).first() junior.guardian_code = [guardian.guardian_code] + junior.guardian_code_status = str(NUMBER['two']) junior.save() JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, relationship=str(self.request.data['relationship'])) From 7a9be0326a6456ff5cec3e9ccb6a54148a44e60a Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 12:19:08 +0530 Subject: [PATCH 203/372] junior points --- junior/views.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/junior/views.py b/junior/views.py index 471db0f..ed42430 100644 --- a/junior/views.py +++ b/junior/views.py @@ -357,15 +357,11 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] http_method_names = ('get',) - def get_queryset(self): - """get queryset""" - update_positions_based_on_points() - return JuniorTask.objects.filter(junior__auth__email=self.request.user).last() def list(self, request, *args, **kwargs): """profile view""" - try: - queryset = self.get_queryset() + update_positions_based_on_points() + queryset = JuniorTask.objects.filter(junior__auth__email=self.request.user).last() # update position of junior serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 69723b362f47b2353861dc7d03aa3a560860a84d Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 15:30:46 +0530 Subject: [PATCH 204/372] current page update in article api --- guardian/views.py | 4 +-- ...uniorarticle_current_card_page_and_more.py | 23 ++++++++++++ junior/models.py | 2 ++ junior/serializers.py | 2 +- junior/utils.py | 5 +-- junior/views.py | 12 +++++-- web_admin/serializers/article_serializer.py | 36 +++++++++++++++++-- web_admin/views/article.py | 8 ++--- 8 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 junior/migrations/0024_juniorarticle_current_card_page_and_more.py diff --git a/guardian/views.py b/guardian/views.py index c67037c..c17c95b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -270,14 +270,12 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): """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, many=True) + serializer = self.get_serializer(junior_total_points[:15], 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) diff --git a/junior/migrations/0024_juniorarticle_current_card_page_and_more.py b/junior/migrations/0024_juniorarticle_current_card_page_and_more.py new file mode 100644 index 0000000..b00eba9 --- /dev/null +++ b/junior/migrations/0024_juniorarticle_current_card_page_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.2 on 2023-08-10 08:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0023_juniorarticlecard'), + ] + + operations = [ + migrations.AddField( + model_name='juniorarticle', + name='current_card_page', + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AddField( + model_name='juniorarticle', + name='current_que_page', + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 12d8476..d3912e0 100644 --- a/junior/models.py +++ b/junior/models.py @@ -173,6 +173,8 @@ class JuniorArticle(models.Model): # article completed""" is_completed = models.BooleanField(default=False) status = models.CharField(max_length=10, choices=ARTICLE_STATUS, null=True, blank=True, default='1') + current_card_page = models.IntegerField(blank=True, null=True, default=0) + current_que_page = models.IntegerField(blank=True, null=True, default=0) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/junior/serializers.py b/junior/serializers.py index 0f014fe..8b0f9be 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -146,7 +146,7 @@ class JuniorDetailSerializer(serializers.ModelSerializer): """Meta info""" model = Junior fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', - 'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at', + 'guardian_code', 'image', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at'] class JuniorDetailListSerializer(serializers.ModelSerializer): diff --git a/junior/utils.py b/junior/utils.py index 1afd8d5..ba177a8 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -4,6 +4,7 @@ from django.conf import settings """Third party Django app""" from templated_email import send_templated_mail from .models import JuniorPoints +from base.constants import NUMBER from django.db.models import F # junior notification # email for sending email @@ -53,8 +54,8 @@ def update_positions_based_on_points(): juniors_points = JuniorPoints.objects.order_by('-total_points', 'created_at') # Now, iterate through the queryset and update the position field based on the order. - position = 1 + position = NUMBER['one'] for junior_point in juniors_points: junior_point.position = position junior_point.save() - position += 1 + position += NUMBER['one'] diff --git a/junior/views.py b/junior/views.py index ed42430..3ffea4c 100644 --- a/junior/views.py +++ b/junior/views.py @@ -481,7 +481,8 @@ class StartArticleAPIView(viewsets.ModelViewSet): article_id = request.data.get('article_id') article_data = Article.objects.filter(id=article_id).last() if not JuniorArticle.objects.filter(junior=junior_instance, article=article_data).last(): - JuniorArticle.objects.create(junior=junior_instance, article=article_data, status=str(NUMBER['two'])) + JuniorArticle.objects.create(junior=junior_instance, article=article_data, status=str(NUMBER['two']), + current_card_page=NUMBER['zero'], current_que_page=NUMBER['zero']) if article_data: question_query = ArticleSurvey.objects.filter(article=article_id) for question in question_query: @@ -534,6 +535,7 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): 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() junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user, @@ -541,10 +543,13 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): if submit_ans: junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True) JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points= - F('total_points')+ queryset.points) + F('total_points') + queryset.points) else: junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, earn_points=0, is_answer_correct=False) + JuniorArticle.objects.filter(junior__auth=self.request.user, + article=queryset.article).update( + current_que_page=int(current_page) + NUMBER['one']) return custom_response(None, response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) @@ -588,9 +593,12 @@ class ReadArticleCardAPIView(views.APIView): junior_instance = Junior.objects.filter(auth=self.request.user).last() article = self.request.data.get('article_id') article_card = self.request.data.get('article_card') + current_page = self.request.data.get('current_page') JuniorArticleCard.objects.filter(junior=junior_instance, article__id=article, article_card__id=article_card).update(is_read=True) + JuniorArticle.objects.filter(junior=junior_instance, + article__id=article).update(current_card_page=int(current_page)+NUMBER['one']) return custom_response(SUCCESS_CODE['3043'], 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/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index a3a7a46..e78bd3e 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -12,7 +12,7 @@ from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba 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 +from junior.models import JuniorArticlePoints, JuniorArticle USER = get_user_model() @@ -268,12 +268,44 @@ class StartAssessmentSerializer(serializers.ModelSerializer): serializer for article API """ article_survey = ArticleQuestionSerializer(many=True) + current_page = serializers.SerializerMethodField('get_current_page') + def get_current_page(self, obj): + """current page""" + context_data = self.context.get('user') + data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last() + if data: + return data.current_que_page + return NUMBER['zero'] class Meta: """ meta class """ model = Article - fields = ('article_survey',) + fields = ('article_survey', 'current_page') + +class ArticleCardlistSerializer(serializers.ModelSerializer): + """ + Article Card serializer + """ + id = serializers.IntegerField(required=False) + image_name = serializers.CharField(required=False) + image_url = serializers.CharField(required=False) + current_page = serializers.SerializerMethodField('get_current_page') + + def get_current_page(self, obj): + """current page""" + context_data = self.context.get('user') + data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj.article).last() + if data: + return data.current_card_page + return NUMBER['zero'] + + class Meta: + """ + meta class + """ + model = ArticleCard + fields = ('id', 'title', 'description', 'image_name', 'image_url', 'current_page') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index d4cc843..e57577e 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -16,8 +16,8 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer, ArticleListSerializer - ) + DefaultArticleCardImageSerializer, ArticleListSerializer, + ArticleCardlistSerializer) USER = get_user_model() @@ -229,7 +229,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): class ArticleCardListViewSet(viewsets.ModelViewSet): """Junior Points viewset""" - serializer_class = ArticleCardSerializer + serializer_class = ArticleCardlistSerializer permission_classes = [IsAuthenticated] http_method_names = ('get',) @@ -242,7 +242,7 @@ class ArticleCardListViewSet(viewsets.ModelViewSet): try: queryset = self.get_queryset() # article card list - serializer = ArticleCardSerializer(queryset, many=True) + serializer = ArticleCardlistSerializer(queryset, context={"user": self.request.user}, 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) From 22afe7e5559eb4848b8f0c9652b8c75d0467052c Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 15:43:31 +0530 Subject: [PATCH 205/372] current page update in article api --- guardian/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index c17c95b..2194f4b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -275,7 +275,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): junior.position = index + 1 send_notification.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {}) junior.save() - serializer = self.get_serializer(junior_total_points[:15], many=True) + serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], 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) From 0a8c2cf56dd02aad0d05189a5abd53f6683cd464 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 10 Aug 2023 18:17:49 +0530 Subject: [PATCH 206/372] image name fix, remove '|' character --- web_admin/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/utils.py b/web_admin/utils.py index 6cdc240..9870b30 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -29,7 +29,7 @@ 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 = f"{data['title']} {data.pop('image_name')}" if 'image_name' in data else data['title'] filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image_name}" # upload image on ali baba image_url = upload_image_to_alibaba(base64_image, filename) From 4f02cef0f9408b53d4b93717feaa9f8934e46ae6 Mon Sep 17 00:00:00 2001 From: Ruman Siddiqui Date: Thu, 10 Aug 2023 18:43:03 +0530 Subject: [PATCH 207/372] Optimised login api --- account/serializers.py | 15 ++++++++--- account/utils.py | 19 ++++++++++++- account/views.py | 61 ++++++++++++++++++++++++++++-------------- base/constants.py | 7 +++++ base/messages.py | 3 ++- 5 files changed, 79 insertions(+), 26 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 4065b5c..7ae9c43 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -215,10 +215,17 @@ class GuardianSerializer(serializers.ModelSerializer): def get_user_type(self, obj): """user type""" - email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last() - if email_verified and email_verified.user_type is not None: - return email_verified.user_type - return str(NUMBER['two']) + if self.context.get('user_type', ''): + return self.context.get('user_type') + # remove the below code once user_type can be passed + # from everywhere from where this serializer is being called + else: + email_verified = UserEmailOtp.objects.filter( + email=obj.user.username + ).last() + if email_verified and email_verified.user_type is not None: + return email_verified.user_type + return str(NUMBER['two']) def get_auth(self, obj): """user email address""" diff --git a/account/utils.py b/account/utils.py index 357f2d1..d495163 100644 --- a/account/utils.py +++ b/account/utils.py @@ -1,4 +1,6 @@ """Account utils""" +from celery import shared_task + """Import django""" from django.conf import settings from rest_framework import viewsets, status @@ -20,7 +22,7 @@ from rest_framework import serializers # Import messages from base package""" from junior.models import Junior from guardian.models import Guardian -from account.models import UserDelete +from account.models import UserDelete, UserDeviceDetails from base.messages import ERROR_CODE from django.utils import timezone from base.constants import NUMBER @@ -107,6 +109,7 @@ def guardian_account_update(user_tb): for data in jun_data: data.guardian_code.remove(guardian_data.guardian_code) data.save() +@shared_task() def send_otp_email(recipient_email, otp): """Send otp on email with template""" from_email = settings.EMAIL_FROM_ADDRESS @@ -122,6 +125,20 @@ def send_otp_email(recipient_email, otp): ) return otp +@shared_task +def user_device_details(user, device_id): + """ + Used to store the device id of the user + user: user object + device_id: string + return + """ + device_details, created = UserDeviceDetails.objects.get_or_create(user=user) + if device_details: + device_details.device_id = device_id + device_details.save() + + def send_support_email(name, sender, subject, message): """Send otp on email with template""" to_email = [settings.EMAIL_FROM_ADDRESS] diff --git a/account/views.py b/account/views.py index 7979215..df273d6 100644 --- a/account/views.py +++ b/account/views.py @@ -1,4 +1,6 @@ """Account view """ +import threading + from notifications.utils import remove_fcm_token # django imports @@ -35,10 +37,10 @@ from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSeriali AdminLoginSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import NUMBER, ZOD, JUN, GRD +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) + generate_code, OTP_EXPIRY, user_device_details) from junior.serializers import JuniorProfileSerializer from guardian.serializers import GuardianProfileSerializer @@ -276,29 +278,38 @@ class UserPhoneVerification(viewsets.ModelViewSet): return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) - class UserLogin(viewsets.ViewSet): """User login""" @action(methods=['post'], detail=False) def login(self, request): username = request.data.get('username') password = request.data.get('password') + user_type = request.data.get('user_type') device_id = request.META.get('HTTP_DEVICE_ID') user = authenticate(request, username=username, password=password) try: if user is not None: login(request, user) - guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() - if guardian_data: - serializer = GuardianSerializer(guardian_data).data - junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() - if junior_data: - serializer = JuniorSerializer(junior_data).data - device_details, created = UserDeviceDetails.objects.get_or_create(user=user) - if device_details: - device_details.device_id = device_id - device_details.save() + if user_type == USER_TYPE_FLAG["FIRST"]: + guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() + if guardian_data: + serializer = GuardianSerializer( + guardian_data, context={'user_type': user_type} + ).data + elif user_type == USER_TYPE_FLAG["TWO"]: + junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() + if junior_data: + serializer = JuniorSerializer( + junior_data, context={'user_type': user_type} + ).data + else: + return custom_error_response( + 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)) 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) @@ -308,9 +319,12 @@ class UserLogin(viewsets.ViewSet): refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) refresh_token = str(refresh) - data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False, - "user_type": email_verified.user_type, - } + data = { + "auth_token":access_token, + "refresh_token":refresh_token, + "is_profile_complete": False, + "user_type": user_type, + } is_verified = False if email_verified: is_verified = email_verified.is_verified @@ -319,11 +333,18 @@ class UserLogin(viewsets.ViewSet): email_verified.otp = otp email_verified.save() data.update({"email_otp":otp}) - send_otp_email(username, otp) - return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, - response_status=status.HTTP_200_OK) + send_otp_email.delay(username, otp) + return custom_response( + ERROR_CODE['2024'], + {"email_otp": otp, "is_email_verified": is_verified}, + response_status=status.HTTP_200_OK + ) data.update({"is_email_verified": is_verified}) - return custom_response(SUCCESS_CODE['3003'], data, response_status=status.HTTP_200_OK) + return custom_response( + SUCCESS_CODE['3003'], + data, + response_status=status.HTTP_200_OK + ) @action(methods=['post'], detail=False) def admin_login(self, request): diff --git a/base/constants.py b/base/constants.py index 36079b9..b3b16d9 100644 --- a/base/constants.py +++ b/base/constants.py @@ -50,6 +50,13 @@ USER_TYPE = ( ('2', 'guardian'), ('3', 'superuser') ) + +USER_TYPE_FLAG = { + "FIRST" : "1", + "TWO" : "2", + "THREE": "3" +} + """gender""" GENDERS = ( ('1', 'Male'), diff --git a/base/messages.py b/base/messages.py index 87f5e9a..c7863c8 100644 --- a/base/messages.py +++ b/base/messages.py @@ -94,7 +94,8 @@ ERROR_CODE = { "2065": "Passwords do not match. Please try again.", "2066": "Task does not exist or not in expired state", "2067": "Action not allowed. User type missing.", - "2068": "No guardian associated with this junior" + "2068": "No guardian associated with this junior", + "2069": "Invalid user type" } """Success message code""" From 11c84208b753f8ef536bcaa0efbf55fbb450f503 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 19:00:37 +0530 Subject: [PATCH 208/372] task points --- junior/serializers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 8b0f9be..a6eff8d 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -184,8 +184,10 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): return data.position return 99999 def get_points(self, obj): - data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True)) - return data + data = JuniorPoints.objects.filter(junior=obj).last() + if data: + return data.total_points + return NUMBER['zero'] def get_in_progress_task(self, obj): data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count() @@ -210,7 +212,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = Junior - fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', + 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'] From fbd6f9ece52d456c3bc9d41a4d7be26395c1d6b2 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 19:05:33 +0530 Subject: [PATCH 209/372] task points --- junior/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/junior/serializers.py b/junior/serializers.py index a6eff8d..57a821b 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -182,7 +182,6 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): data = JuniorPoints.objects.filter(junior=obj).last() if data: return data.position - return 99999 def get_points(self, obj): data = JuniorPoints.objects.filter(junior=obj).last() if data: From 1e162255d7d3b60da0ecb6250df686367e970461 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 10 Aug 2023 20:17:24 +0530 Subject: [PATCH 210/372] create article --- .../0025_alter_juniorarticle_junior.py | 19 +++++++++++++++++++ junior/models.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 junior/migrations/0025_alter_juniorarticle_junior.py diff --git a/junior/migrations/0025_alter_juniorarticle_junior.py b/junior/migrations/0025_alter_juniorarticle_junior.py new file mode 100644 index 0000000..26c7966 --- /dev/null +++ b/junior/migrations/0025_alter_juniorarticle_junior.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.2 on 2023-08-10 14:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0024_juniorarticle_current_card_page_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='juniorarticle', + name='junior', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article', to='junior.junior', verbose_name='Junior'), + ), + ] diff --git a/junior/models.py b/junior/models.py index d3912e0..025843e 100644 --- a/junior/models.py +++ b/junior/models.py @@ -167,7 +167,7 @@ class JuniorArticle(models.Model): Survey Options model """ # associated junior with the task - junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='juniors_article', + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article', verbose_name='Junior') article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_details') # article completed""" From bb65f81200acfdda3453fbdaca0683886577e9c4 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 11:48:02 +0530 Subject: [PATCH 211/372] add correct and attempt answer --- web_admin/serializers/article_serializer.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index e78bd3e..29230eb 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -247,6 +247,9 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False) options = SurveyOptionSerializer(many=True) is_attempt = serializers.SerializerMethodField('get_is_attempt') + correct_answer = serializers.SerializerMethodField('get_correct_answer') + attempted_answer = serializers.SerializerMethodField('get_attempted_answer') + def get_is_attempt(self, obj): """attempt question or not""" @@ -256,12 +259,28 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): return junior_article_obj.is_attempt return False + def get_correct_answer(self, obj): + """attempt question or not""" + ans_obj = SurveyOption.objects.filter(survey=obj, is_answer=True).last() + if ans_obj: + return ans_obj.id + return str("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: + return junior_article_obj.submitted_answer.id + return None + class Meta: """ meta class """ model = ArticleSurvey - fields = ('id', 'question', 'options', 'points', 'is_attempt') + fields = ('id', 'question', 'options', 'points', 'is_attempt', 'correct_answer', 'attempted_answer') class StartAssessmentSerializer(serializers.ModelSerializer): """ From 8b0032f6d25b0c2dab51bd6f0166937392677ff1 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 12:18:35 +0530 Subject: [PATCH 212/372] junior point table --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index 3ffea4c..a86083d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -361,7 +361,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): """profile view""" try: update_positions_based_on_points() - queryset = JuniorTask.objects.filter(junior__auth__email=self.request.user).last() + queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last() # update position of junior serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 5eab8f49079ff3f4265b23501e8ccf1e48f63447 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 13:04:05 +0530 Subject: [PATCH 213/372] is complete article key --- web_admin/serializers/article_serializer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 29230eb..c58f97a 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -238,6 +238,9 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" + junior_article = JuniorArticle.objects.filter(article=obj).last() + if junior_article: + junior_article.is_completed return False class ArticleQuestionSerializer(serializers.ModelSerializer): From af8ea39200ac498481731b57e02322a7ea552070 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 13:35:12 +0530 Subject: [PATCH 214/372] leaderboard ranking --- celerybeat-schedule | Bin 16384 -> 16384 bytes web_admin/serializers/analytics_serializer.py | 50 ++++++++++++++++++ web_admin/views/analytics.py | 36 +++++++++++-- web_admin/views/user_management.py | 2 +- zod_bank/settings.py | 25 +++++++++ 5 files changed, 109 insertions(+), 4 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 4a51b891af30ec5d0a7f9b91777d2f6b8b1a45d1..f2510fc7dfb09da6514820cff292233776337c68 100644 GIT binary patch delta 33 pcmZo@U~Fh$+_2GzgM*t@UXD?{c=Bz7Kt|Tdx`vvYO^na*005(w36B5( delta 33 pcmZo@U~Fh$+_2GzgI$KjRG8~~^yJ$Hfs9O(bqzH)n;4(r0RXa_3TFTS diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 5b653a6..34316b7 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -1,3 +1,53 @@ """ web_admin analytics serializer file """ +from rest_framework import serializers + +from junior.models import JuniorPoints, Junior +from web_admin.serializers.user_management_serializer import JuniorSerializer + + +class JuniorLeaderboardSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = Junior + fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image') + + @staticmethod + def get_name(obj): + """ + :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 + + @staticmethod + def get_first_name(obj): + """ + :param obj: junior object + :return: first name + """ + return obj.auth.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: junior object + :return: last name + """ + return obj.auth.last_name + + +class LeaderboardSerializer(serializers.ModelSerializer): + junior = JuniorLeaderboardSerializer() + rank = serializers.IntegerField() + + class Meta: + model = JuniorPoints + fields = ('total_points', 'rank', 'junior') diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index f769c18..688e445 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -7,12 +7,16 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from django.contrib.auth import get_user_model from django.db.models import Q -from django.db.models import Count +from django.db.models import Count, OuterRef, Subquery, Sum from django.db.models.functions import TruncDate +from django.db.models import F, Window +from django.db.models.functions.window import Rank from account.utils import custom_response from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED from guardian.models import JuniorTask +from junior.models import JuniorPoints +from web_admin.serializers.analytics_serializer import LeaderboardSerializer USER = get_user_model() @@ -59,7 +63,11 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='new-signups', url_path='new-signups', detail=False) def new_signups(self, request, *args, **kwargs): - + """ + api method to get new signups + :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :return: + """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) @@ -76,7 +84,11 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False) def assign_tasks_report(self, request, *args, **kwargs): - + """ + api method to get assign tasks + :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :return: + """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) @@ -96,3 +108,21 @@ class AnalyticsViewSet(GenericViewSet): } return custom_response(None, data) + + @action(methods=['get'], url_name='junior-leaderboard', url_path='junior-leaderboard', detail=False, + serializer_class=LeaderboardSerializer) + def junior_leaderboard(self, request): + """ + + :param request: + :return: + """ + # queryset = JuniorPoints.objects.all().order_by('-total_points', 'junior__created_at') + 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') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index c16b62a..8f53a73 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -34,7 +34,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, is_superuser=False).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') filter_backends = (SearchFilter,) search_fields = ['first_name', 'last_name'] http_method_names = ['get', 'post', 'patch'] diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 3781f89..2d003c1 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -177,6 +177,31 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +LOGGING = { + "version": 1, + "filters": { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "filters": [ + "require_debug_true" + ], + "class": "logging.StreamHandler" + } + }, + "loggers": { + "django.db.backends": { + "level": "DEBUG", + "handlers": [ + "console" + ] + } + } +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 94c76b7f2e9c907082650f0715acefe296fd3f0c Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 13:39:25 +0530 Subject: [PATCH 215/372] remove deleted user from leaderboard --- account/utils.py | 4 +++- account/views.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/account/utils.py b/account/utils.py index 357f2d1..d33b267 100644 --- a/account/utils.py +++ b/account/utils.py @@ -24,6 +24,7 @@ from account.models import UserDelete from base.messages import ERROR_CODE from django.utils import timezone from base.constants import NUMBER +from junior.models import JuniorPoints # Define delete # user account condition, # Define delete @@ -91,8 +92,9 @@ def junior_account_update(user_tb): junior_data.is_active = False junior_data.is_verified = False junior_data.guardian_code = '{}' - junior_data.guardian_code_status = str(NUMBER['1']) + junior_data.guardian_code_status = str(NUMBER['one']) junior_data.save() + JuniorPoints.objects.filter(junior=junior_data).delete() def guardian_account_update(user_tb): """update guardian account after delete the user account""" diff --git a/account/views.py b/account/views.py index 0f9747e..593be1c 100644 --- a/account/views.py +++ b/account/views.py @@ -23,7 +23,7 @@ from django.conf import settings # local imports from guardian.models import Guardian -from junior.models import Junior +from junior.models import Junior, JuniorPoints from guardian.utils import upload_image_to_alibaba from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User @@ -97,6 +97,8 @@ class GoogleLoginMixin(object): referral_code=generate_code(ZOD, user_obj.id) ) serializer = JuniorSerializer(junior_query) + position = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior_query, position=position) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, image=profile_picture,signup_method='2', @@ -147,6 +149,8 @@ class SigninWithApple(views.APIView): junior_code=generate_code(JUN, user.id), referral_code=generate_code(ZOD, user.id)) serializer = JuniorSerializer(junior_query) + position = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior_query, position=position) if str(user_type) == '2': guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, signup_method='3', From 69c19cf09714846ff70232106ab6173b2002ce6a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 14:40:49 +0530 Subject: [PATCH 216/372] sonar issues --- account/serializers.py | 3 +- account/views.py | 3 +- base/constants.py | 2 ++ guardian/views.py | 32 ----------------- junior/serializers.py | 1 - web_admin/serializers/analytics_serializer.py | 1 - web_admin/views/analytics.py | 34 +++++++++++-------- 7 files changed, 26 insertions(+), 50 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 4065b5c..edf4de9 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -152,7 +152,8 @@ class AdminLoginSerializer(serializers.ModelSerializer): def validate(self, attrs): user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True - ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + ).only('id', 'first_name', 'last_name', 'email', + 'username', 'is_active', 'is_superuser').first() if not user or not user.check_password(attrs['password']): raise serializers.ValidationError({'details': ERROR_CODE['2002']}) diff --git a/account/views.py b/account/views.py index fca583b..8c2932d 100644 --- a/account/views.py +++ b/account/views.py @@ -334,7 +334,8 @@ class UserLogin(viewsets.ViewSet): email = request.data.get('email') password = request.data.get('password') user = User.objects.filter(email__iexact=email, is_superuser=True - ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + ).only('id', 'first_name', 'last_name', 'email', + 'username', 'is_active', 'is_superuser').first() if not user or not user.check_password(password): return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/constants.py b/base/constants.py index 36079b9..05fa8b8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -122,3 +122,5 @@ MAX_ARTICLE_SURVEY = 10 Already_register_user = "duplicate key value violates unique constraint" ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' + +DATE_FORMAT = '%Y-%m-%d' diff --git a/guardian/views.py b/guardian/views.py index 2194f4b..3948e6f 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -124,38 +124,6 @@ class AllTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) -# class TaskListAPIView(viewsets.ModelViewSet): -# """Update guardian profile""" -# serializer_class = TaskDetailsSerializer -# permission_classes = [IsAuthenticated] -# pagination_class = PageNumberPagination -# http_method_names = ('get',) -# -# def list(self, request, *args, **kwargs): -# """Create guardian profile""" -# try: -# status_value = self.request.GET.get('status') -# search = self.request.GET.get('search') -# if search and str(status_value) == '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user, -# task_name__icontains=search).order_by('due_date', 'created_at') -# elif search and str(status_value) != '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, -# task_status=status_value).order_by('due_date', 'created_at') -# if search is None and str(status_value) == '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') -# elif search is None and str(status_value) != '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user, -# task_status=status_value).order_by('due_date','created_at') -# paginator = self.pagination_class() -# # use Pagination -# paginated_queryset = paginator.paginate_queryset(queryset, request) -# # use TaskDetailsSerializer serializer -# serializer = TaskDetailsSerializer(paginated_queryset, many=True) -# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) -# except Exception as e: -# return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) - class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" diff --git a/junior/serializers.py b/junior/serializers.py index 57a821b..59fbe07 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -333,7 +333,6 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - # instance.requested_on = real_time() instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 34316b7..fd85118 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -4,7 +4,6 @@ web_admin analytics serializer file from rest_framework import serializers from junior.models import JuniorPoints, Junior -from web_admin.serializers.user_management_serializer import JuniorSerializer class JuniorLeaderboardSerializer(serializers.ModelSerializer): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 688e445..0133339 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -7,13 +7,13 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from django.contrib.auth import get_user_model from django.db.models import Q -from django.db.models import Count, OuterRef, Subquery, Sum +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 account.utils import custom_response -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT from guardian.models import JuniorTask from junior.models import JuniorPoints from web_admin.serializers.analytics_serializer import LeaderboardSerializer @@ -24,6 +24,10 @@ USER = get_user_model() class AnalyticsViewSet(GenericViewSet): """ analytics api view + to get user report (active users, guardians and juniors counts) + to get new user sign up report + to get task report (completed, in-progress, requested and rejected tasks count) + to get junior leaderboard and ranking """ serializer_class = None @@ -41,7 +45,8 @@ class AnalyticsViewSet(GenericViewSet): def total_users_count(self, request, *args, **kwargs): """ api method to get total users, guardians and juniors - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :param request: start_date: date format (yyyy-mm-dd) + :param request: end_date: date format (yyyy-mm-dd) :return: """ @@ -49,8 +54,8 @@ class AnalyticsViewSet(GenericViewSet): 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'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + 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) user_qs = self.get_queryset() queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1)))) @@ -65,15 +70,16 @@ class AnalyticsViewSet(GenericViewSet): def new_signups(self, request, *args, **kwargs): """ api method to get new signups - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :param request: start_date: date format (yyyy-mm-dd) + :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'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + 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) user_qs = self.get_queryset() signup_data = user_qs.filter(date_joined__range=[start_date, (end_date + datetime.timedelta(days=1))] @@ -85,16 +91,17 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False) def assign_tasks_report(self, request, *args, **kwargs): """ - api method to get assign tasks - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + api method to get assign tasks count for (completed, in-progress, requested and rejected) task + :param request: start_date: date format (yyyy-mm-dd) + :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'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + 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) assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] @@ -113,11 +120,10 @@ class AnalyticsViewSet(GenericViewSet): serializer_class=LeaderboardSerializer) def junior_leaderboard(self, request): """ - + to get junior leaderboard and rank :param request: :return: """ - # queryset = JuniorPoints.objects.all().order_by('-total_points', 'junior__created_at') queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] From 54200dba5276990c9771e05d8200a7de28b38f8b Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 15:39:25 +0530 Subject: [PATCH 217/372] is complete article key --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index c58f97a..f8c7f67 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -240,7 +240,7 @@ class ArticleListSerializer(serializers.ModelSerializer): """complete all question""" junior_article = JuniorArticle.objects.filter(article=obj).last() if junior_article: - junior_article.is_completed + return junior_article.is_completed return False class ArticleQuestionSerializer(serializers.ModelSerializer): From d5c30f2029d6ed4e6529c1e81f1fd8104c9ccac3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 15:54:54 +0530 Subject: [PATCH 218/372] added pagination in leaderboard admin --- web_admin/pagination.py | 13 +++++++++++++ web_admin/views/analytics.py | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 web_admin/pagination.py diff --git a/web_admin/pagination.py b/web_admin/pagination.py new file mode 100644 index 0000000..6a6aff4 --- /dev/null +++ b/web_admin/pagination.py @@ -0,0 +1,13 @@ +""" +web_admin pagination file +""" +from rest_framework.pagination import PageNumberPagination + + +class CustomPageNumberPagination(PageNumberPagination): + """ + custom paginator class + """ + page_size = 10 # Set the desired page size + page_size_query_param = 'page_size' + max_page_size = 100 # Set a maximum page size if needed diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 688e445..e8a8f5c 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -1,10 +1,15 @@ """ web_admin analytics view file """ +# python imports import datetime +# third party imports from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +# django imports from django.contrib.auth import get_user_model from django.db.models import Q from django.db.models import Count, OuterRef, Subquery, Sum @@ -12,10 +17,13 @@ from django.db.models.functions import TruncDate from django.db.models import F, Window from django.db.models.functions.window import Rank +# local imports from account.utils import custom_response from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED from guardian.models import JuniorTask 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 USER = get_user_model() @@ -26,6 +34,7 @@ class AnalyticsViewSet(GenericViewSet): analytics api view """ serializer_class = None + permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -122,7 +131,7 @@ class AnalyticsViewSet(GenericViewSet): expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] )).order_by('-total_points', 'junior__created_at') - paginator = self.pagination_class() + paginator = CustomPageNumberPagination() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, serializer.data) From 082f93ff9d135d39d0f868d202cb231699c168e0 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 16:54:27 +0530 Subject: [PATCH 219/372] guardian code chnages --- junior/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/junior/serializers.py b/junior/serializers.py index 57a821b..3101886 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -503,6 +503,7 @@ 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']) instance.save() return instance From 070637bf1d2ac0a5f408eebe332578946f6e7fc8 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 14 Aug 2023 11:43:59 +0530 Subject: [PATCH 220/372] is complete key --- web_admin/serializers/article_serializer.py | 3 ++- web_admin/views/article.py | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index f8c7f67..af07bd7 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -238,7 +238,8 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" - junior_article = JuniorArticle.objects.filter(article=obj).last() + context_data = self.context.get('user') + junior_article = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last() if junior_article: return junior_article.is_completed return False diff --git a/web_admin/views/article.py b/web_admin/views/article.py index e57577e..13c41c2 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -221,10 +221,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): :return: list of article """ queryset = self.get_queryset() - count = queryset.count() - paginator = self.pagination_class() - paginated_queryset = paginator.paginate_queryset(queryset, request) - serializer = self.serializer_class(paginated_queryset, many=True) + serializer = self.serializer_class(queryset, context={"user": request.user}, many=True) return custom_response(None, data=serializer.data) class ArticleCardListViewSet(viewsets.ModelViewSet): From 6373d1e02c78c7b5df2edfcad76c33d9026a7dd8 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 14 Aug 2023 12:13:14 +0530 Subject: [PATCH 221/372] sonar fixes --- account/utils.py | 2 +- junior/serializers.py | 4 ++-- notifications/views.py | 2 ++ web_admin/serializers/article_serializer.py | 8 ++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/account/utils.py b/account/utils.py index d33b267..fd2f40f 100644 --- a/account/utils.py +++ b/account/utils.py @@ -44,7 +44,7 @@ from junior.models import JuniorPoints # referral code, # Define function for generating # alphanumeric code - +# otp expiry def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num): """delete user account""" if user_type == '1' and user_type_data == '1': diff --git a/junior/serializers.py b/junior/serializers.py index f102972..dc8202f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -146,8 +146,8 @@ class JuniorDetailSerializer(serializers.ModelSerializer): """Meta info""" 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'] + 'guardian_code', 'image', 'is_invited', 'referral_code','is_active', 'is_complete_profile', + 'created_at', 'image', 'updated_at'] class JuniorDetailListSerializer(serializers.ModelSerializer): """junior serializer""" diff --git a/notifications/views.py b/notifications/views.py index 4644e62..c66d655 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -11,8 +11,10 @@ from rest_framework import viewsets, status, views from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE 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 diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index af07bd7..e125acf 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -224,7 +224,7 @@ class ArticleListSerializer(serializers.ModelSerializer): total_points = serializers.SerializerMethodField('get_total_points') is_completed = serializers.SerializerMethodField('get_is_completed') - class Meta: + class Meta(object): """ meta class """ @@ -279,7 +279,7 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): return junior_article_obj.submitted_answer.id return None - class Meta: + class Meta(object): """ meta class """ @@ -300,7 +300,7 @@ class StartAssessmentSerializer(serializers.ModelSerializer): if data: return data.current_que_page return NUMBER['zero'] - class Meta: + class Meta(object): """ meta class """ @@ -326,7 +326,7 @@ class ArticleCardlistSerializer(serializers.ModelSerializer): return data.current_card_page return NUMBER['zero'] - class Meta: + class Meta(object): """ meta class """ From 4bc16c56bdebd390ddd10684cd3c1fcd7516918a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 14 Aug 2023 13:57:29 +0530 Subject: [PATCH 222/372] csv/xls report --- web_admin/serializers/analytics_serializer.py | 70 +++++++ web_admin/views/analytics.py | 185 +++++++++++++++++- web_admin/views/user_management.py | 12 +- 3 files changed, 258 insertions(+), 9 deletions(-) diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index fd85118..4c0cbd5 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -2,11 +2,20 @@ web_admin analytics serializer file """ from rest_framework import serializers +from django.contrib.auth import get_user_model + +from base.constants import USER_TYPE from junior.models import JuniorPoints, Junior +from web_admin.serializers.user_management_serializer import JuniorSerializer, GuardianSerializer + +USER = get_user_model() class JuniorLeaderboardSerializer(serializers.ModelSerializer): + """ + junior leaderboard serializer + """ name = serializers.SerializerMethodField() first_name = serializers.SerializerMethodField() last_name = serializers.SerializerMethodField() @@ -44,9 +53,70 @@ class JuniorLeaderboardSerializer(serializers.ModelSerializer): class LeaderboardSerializer(serializers.ModelSerializer): + """ + leaderboard serializer + """ junior = JuniorLeaderboardSerializer() rank = serializers.IntegerField() class Meta: + """ + meta class + """ model = JuniorPoints fields = ('total_points', 'rank', 'junior') + + +class UserCSVReportSerializer(serializers.ModelSerializer): + """ + user csv/xls report serializer + """ + phone_number = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() + is_active = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('first_name', 'last_name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined') + + @staticmethod + def get_phone_number(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return f"+{profile.country_code}{profile.phone}" \ + if profile.country_code and profile.phone else profile.phone + elif profile := 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') + elif obj.junior_profile.all().first(): + return dict(USER_TYPE).get('1') + else: + return None + + @staticmethod + def get_is_active(obj): + """ + :param obj: user object + :return: user type + """ + if profile := obj.guardian_profile.all().first(): + return "Active" if profile.is_active else "Inactive" + elif profile := obj.junior_profile.all().first(): + return "Active" if profile.is_active else "Inactive" diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 8c21cb3..34d1204 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -3,6 +3,10 @@ web_admin analytics view file """ # python imports import datetime +import csv +import io +import pandas as pd +import xlsxwriter # third party imports from rest_framework.viewsets import GenericViewSet @@ -16,15 +20,17 @@ 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 base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT, TASK_STATUS from guardian.models import JuniorTask 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.serializers.user_management_serializer import UserManagementListSerializer USER = get_user_model() @@ -38,7 +44,7 @@ class AnalyticsViewSet(GenericViewSet): to get junior leaderboard and ranking """ serializer_class = None - permission_classes = [IsAuthenticated, AdminPermission] + # permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -141,3 +147,176 @@ class AnalyticsViewSet(GenericViewSet): 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='generate-report', url_path='generate-report', detail=False) + # def generate_report(self, request): + # + # response = HttpResponse(content_type='text/csv') + # response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.csv"' + # + # 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) + # + # buffer = io.StringIO() + # csv_writer = csv.writer(buffer) + # + # # sheet 1 for Total 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) + # + # # writer = csv.writer(response) + # csv_writer.writerow(['Users']) + # csv_writer.writerow([ + # 'First Name', 'Last Name', 'Email', 'Phone Number', 'User Type', 'status', 'Date Joined', + # # Add more fields as needed + # ]) + # + # for user_data in serializer.data: + # row = [ + # user_data['first_name'], user_data['last_name'], user_data['email'], + # user_data['phone_number'], user_data['user_type'], + # user_data['is_active'], user_data['date_joined'] + # ] + # csv_writer.writerow(row) + # + # response.write(buffer.getvalue()) + # buffer.close() + # + # # sheet 2 for Assign Task + # assign_tasks = JuniorTask.objects.filter( + # created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] + # ).exclude(task_status__in=[PENDING, EXPIRED]) + # buffer = io.StringIO() + # csv_writer = csv.writer(buffer) + # csv_writer.writerow([]) + # csv_writer.writerow(['Assign Tasks']) + # csv_writer.writerow([ + # 'Task Name', 'Task Status' + # # Add more fields as needed + # ]) + # + # for task in assign_tasks: + # row = [ + # task.task_name, dict(TASK_STATUS).get(task.task_status).capitalize(), + # ] + # csv_writer.writerow(row) + # response.write(buffer.getvalue()) + # buffer.close() + # + # # sheet 3 for Juniors Leaderboard and rank + # 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') + # buffer = io.StringIO() + # csv_writer = csv.writer(buffer) + # csv_writer.writerow([]) + # csv_writer.writerow(['Top 15 Juniors']) + # csv_writer.writerow([ + # 'Junior Name', 'Points', 'Rank' + # # Add more fields as needed + # ]) + # + # for junior in queryset: + # row = [ + # f"{junior.junior.auth.first_name}{junior.junior.auth.last_name}" if junior.junior.auth.last_name else junior.junior.auth.first_name, + # junior.total_points, junior.rank + # ] + # csv_writer.writerow(row) + # response.write(buffer.getvalue()) + # buffer.close() + # + # return response + + @action(methods=['get'], url_name='generate-report', url_path='generate-report', detail=False) + def generate_report(self, request): + + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.xlsx"' + + 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) + + buffer = io.BytesIO() # Use BytesIO for binary data + + # Create an XlsxWriter Workbook object + workbook = xlsxwriter.Workbook(buffer) + + # Add sheets + sheets = ['Users', 'Assign Tasks', 'Top 15 Juniors'] + + 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(serializer.data) + for idx, col in enumerate(df_users.columns): + worksheet.write(0, idx, col) # Write header + for row_num, row in enumerate(df_users.values, start=1): + for col_num, value in enumerate(row): + worksheet.write(row_num, col_num, value) + + # 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))] + ).exclude(task_status__in=[PENDING, EXPIRED]) + + df_tasks = pd.DataFrame([ + {'Task Name': task.task_name, 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()} + for task in assign_tasks + ]) + + for idx, col in enumerate(df_tasks.columns): + worksheet.write(0, idx, col) # Write header + for row_num, row in enumerate(df_tasks.values, start=1): + for col_num, value in enumerate(row): + worksheet.write(row_num, col_num, value) + + # sheet 3 for Juniors Leaderboard and rank + elif sheet_name == 'Top 15 Juniors': + 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') + + df_leaderboard = pd.DataFrame([ + { + 'Junior Name': f"{junior.junior.auth.first_name} {junior.junior.auth.last_name}" + if junior.junior.auth.last_name else junior.junior.auth.first_name, + 'Points': junior.total_points, + 'Rank': junior.rank + } + for junior in queryset + ]) + + for idx, col in enumerate(df_leaderboard.columns): + worksheet.write(0, idx, col) # Write header + for row_num, row in enumerate(df_leaderboard.values, start=1): + for col_num, value in enumerate(row): + worksheet.write(row_num, col_num, value) + + # 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() + + return response + diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 8f53a73..d1754aa 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -72,9 +72,9 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, 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']) + queryset = queryset.filter(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']) + queryset = queryset.filter(id=kwargs['pk']) serializer = UserManagementDetailSerializer(queryset, many=True) return custom_response(None, data=serializer.data) @@ -89,12 +89,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, 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() + user_obj = queryset.filter(id=kwargs['pk']).first() serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), 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() + user_obj = queryset.filter(id=kwargs['pk']).first() serializer = JuniorSerializer(user_obj.junior_profile.all().first(), request.data, context={'user_id': kwargs['pk']}) @@ -114,12 +114,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, 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() + user_obj = queryset.filter(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() + user_obj = queryset.filter(id=kwargs['pk']).first() obj = user_obj.junior_profile.all().first() obj.is_active = False if obj.is_active else True obj.save() From d3b0be953e4aafe2ae9a489d6c4c0a2d7b0f6118 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 14 Aug 2023 15:16:16 +0530 Subject: [PATCH 223/372] login api with user type --- account/views.py | 14 ++++++++++++-- base/messages.py | 4 +++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index cb2ee56..5b71b86 100644 --- a/account/views.py +++ b/account/views.py @@ -295,18 +295,28 @@ class UserLogin(viewsets.ViewSet): try: if user is not None: login(request, user) - if user_type == USER_TYPE_FLAG["FIRST"]: + if user_type == USER_TYPE_FLAG["TWO"]: guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() if guardian_data: serializer = GuardianSerializer( guardian_data, context={'user_type': user_type} ).data - elif user_type == USER_TYPE_FLAG["TWO"]: + else: + return custom_error_response( + ERROR_CODE["2070"], + response_status=status.HTTP_401_UNAUTHORIZED + ) + elif user_type == USER_TYPE_FLAG["FIRST"]: junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() if junior_data: serializer = JuniorSerializer( junior_data, context={'user_type': user_type} ).data + else: + return custom_error_response( + ERROR_CODE["2071"], + response_status=status.HTTP_401_UNAUTHORIZED + ) else: return custom_error_response( ERROR_CODE["2069"], diff --git a/base/messages.py b/base/messages.py index c7863c8..10fbfaa 100644 --- a/base/messages.py +++ b/base/messages.py @@ -95,7 +95,9 @@ ERROR_CODE = { "2066": "Task does not exist or not in expired state", "2067": "Action not allowed. User type missing.", "2068": "No guardian associated with this junior", - "2069": "Invalid user type" + "2069": "Invalid user type", + "2070": "You did not find as a guardian", + "2071": "You did not find as a junior" } """Success message code""" From a02dfd4e31a09edd3720f8a1a20a48d83cefdd02 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 14 Aug 2023 15:23:41 +0530 Subject: [PATCH 224/372] login api with user type --- account/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/account/views.py b/account/views.py index 5b71b86..7a26d9e 100644 --- a/account/views.py +++ b/account/views.py @@ -7,7 +7,6 @@ from notifications.utils import remove_fcm_token from datetime import datetime, timedelta from rest_framework import viewsets, status, views from rest_framework.decorators import action -import random import logging from django.utils import timezone import jwt From e46d649fdcd96a6c3766b0e00d9677679bfa4434 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 14 Aug 2023 16:03:36 +0530 Subject: [PATCH 225/372] notification modification added more payload --- account/utils.py | 7 +++++ notifications/constants.py | 2 +- notifications/serializers.py | 3 ++- notifications/utils.py | 50 +++++++++++++++++++++++++++--------- notifications/views.py | 3 ++- 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/account/utils.py b/account/utils.py index fd2f40f..dcfb4a2 100644 --- a/account/utils.py +++ b/account/utils.py @@ -234,3 +234,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.last_name else user_obj.first_name diff --git a/notifications/constants.py b/notifications/constants.py index b861142..46bfbef 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -67,6 +67,6 @@ NOTIFICATION_DICT = { }, 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..14f1b20 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -35,7 +35,8 @@ 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""" diff --git a/notifications/utils.py b/notifications/utils.py index ba980e6..38df5ba 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -10,6 +10,7 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati from django.contrib.auth import get_user_model from account.models import UserNotification +from account.utils import get_user_full_name from notifications.constants import NOTIFICATION_DICT from notifications.models import Notification @@ -39,30 +40,55 @@ 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] +def get_basic_detail(from_user_id, to_user_id): + """ + used to get the basic details + """ 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 + return from_user, to_user + + +def get_notification_data(notification_type, from_user, to_user, extra_data): + """ + get notification and push data + :param notification_type: notification_type + :param from_user: from_user obj + :param to_user: 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() + notification_data['to_user'] = get_user_full_name(to_user) + if from_user: + from_user_name = get_user_full_name(from_user) + push_data['body'] = push_data['body'].format(from_user=from_user_name) + notification_data['body'] = notification_data['body'].format(from_user=from_user_name) + notification_data['from_user'] = from_user_name + + notification_data.update(extra_data) + + return notification_data, push_data @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) + """ + used to send the push for the given notification type + """ + (from_user, to_user) = get_basic_detail(from_user_id, to_user_id) + notification_data, push_data = get_notification_data(notification_type, from_user, to_user, 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) + notification_to=to_user, data=notification_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) + 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) ) diff --git a/notifications/views.py b/notifications/views.py index c66d655..f932f0f 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -56,7 +56,8 @@ class NotificationViewSet(viewsets.GenericViewSet): to send test notification :return: """ - send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {}) + send_notification(TEST_NOTIFICATION, None, request.auth.payload['user_id'], + {'task_id': None}) return custom_response(SUCCESS_CODE["3000"]) @action(methods=['get'], detail=False, url_path='list', url_name='list', From 7c809776b60ab6939b3f8d93ad4b4051453c1f4c Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 14 Aug 2023 18:22:50 +0530 Subject: [PATCH 226/372] guardian code status --- junior/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/serializers.py b/junior/serializers.py index dc8202f..c3e2d72 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -320,7 +320,7 @@ class RemoveJuniorSerializer(serializers.ModelSerializer): if instance: instance.is_invited = False instance.guardian_code = '{}' - instance.guardian_code_status = str(NUMBER['1']) + instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance From 451c3bdae7b6b09aabfc6227aae77a615ea41120 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 16 Aug 2023 11:37:21 +0530 Subject: [PATCH 227/372] user type --- account/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/views.py b/account/views.py index 7a26d9e..d95f87a 100644 --- a/account/views.py +++ b/account/views.py @@ -294,7 +294,7 @@ class UserLogin(viewsets.ViewSet): try: if user is not None: login(request, user) - if user_type == USER_TYPE_FLAG["TWO"]: + if str(user_type) == USER_TYPE_FLAG["TWO"]: guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() if guardian_data: serializer = GuardianSerializer( @@ -305,7 +305,7 @@ class UserLogin(viewsets.ViewSet): ERROR_CODE["2070"], response_status=status.HTTP_401_UNAUTHORIZED ) - elif user_type == USER_TYPE_FLAG["FIRST"]: + elif str(user_type) == USER_TYPE_FLAG["FIRST"]: junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() if junior_data: serializer = JuniorSerializer( From 60af098e1e0f4b129bbacdb7464c36b53434a081 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 16 Aug 2023 16:16:05 +0530 Subject: [PATCH 228/372] email verfication and user notification bug --- account/views.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/account/views.py b/account/views.py index d95f87a..c17f853 100644 --- a/account/views.py +++ b/account/views.py @@ -401,12 +401,13 @@ class AdminLoginViewSet(viewsets.GenericViewSet): class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" 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 +421,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() @@ -535,7 +536,7 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): """profile view""" - queryset = self.queryset.filter(user=request.user) + queryset = UserNotification.objects.filter(user=request.user) serializer = UserNotificationSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 3017b0ece01dad24e74857da22516faaad40c3c2 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 16 Aug 2023 16:40:34 +0530 Subject: [PATCH 229/372] changes in notification method --- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 8 +++++--- guardian/utils.py | 6 ++++-- guardian/views.py | 13 ++++++------- junior/serializers.py | 16 +++++++++------- junior/views.py | 7 ++++--- notifications/constants.py | 18 +++++++++--------- notifications/utils.py | 33 ++++++++++++++++++++++++++++++--- notifications/views.py | 8 +++++--- 9 files changed, 72 insertions(+), 37 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index f2510fc7dfb09da6514820cff292233776337c68..ce5d8ac8aa39d618efaf5cf6434831370e993b16 100644 GIT binary patch delta 32 ocmZo@U~Fh$+_1@rU4X+%i%o6P;jO>$j4K+8L7@y$*0Hr($+W-In delta 32 ocmZo@U~Fh$+_1@rotsr&j#0gM@@<1aM%Kx?hMJpAjL+}@0G~n$hX4Qo diff --git a/guardian/serializers.py b/guardian/serializers.py index 1dabc8d..cbd1e4e 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -29,7 +29,7 @@ from .utils import real_time, convert_timedelta_into_datetime, update_referral_p # notification's constant from notifications.constants import TASK_POINTS, TASK_REJECTED # send notification function -from notifications.utils import send_notification +from notifications.utils import send_notification, send_notification_to_junior # In this serializer file @@ -383,7 +383,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_to_junior.delay(TASK_POINTS, None, junior_details.auth.id, + {'task_id': instance.id}) else: # reject the task instance.task_status = str(NUMBER['three']) @@ -391,7 +392,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_to_junior.delay(TASK_REJECTED, None, junior_details.auth.id, + {'task_id': instance.id}) instance.save() junior_data.save() return junior_details diff --git a/guardian/utils.py b/guardian/utils.py index 57e8080..d5081ac 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -21,7 +21,9 @@ from zod_bank.celery import app # notification's constant from notifications.constants import REFERRAL_POINTS # send notification function -from notifications.utils import send_notification +from notifications.utils import send_notification, send_notification_to_junior + + # Define upload image on # ali baba cloud # firstly save image @@ -92,7 +94,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_to_junior.delay(REFERRAL_POINTS, None, junior_queryset.auth.id, {}) diff --git a/guardian/views.py b/guardian/views.py index 3948e6f..0319938 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -38,8 +38,8 @@ from account.utils import custom_response, custom_error_response, OTP_EXPIRY, se from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, GUARDIAN_CODE_STATUS from .utils import upload_image_to_alibaba -from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING -from notifications.utils import send_notification +from notifications.constants import REGISTRATION, TASK_ASSIGNED, LEADERBOARD_RANKING +from notifications.utils import send_notification_to_junior """ Define APIs """ # Define Signup API, @@ -72,8 +72,6 @@ class SignupViewset(viewsets.ModelViewSet): 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, {}) 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) @@ -186,9 +184,10 @@ class CreateTaskAPIView(viewsets.ModelViewSet): serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) if serializer.is_valid(): # save serializer - serializer.save() + task = serializer.save() junior_id = Junior.objects.filter(id=junior).last() - send_notification.delay(TASK_CREATED, None, junior_id.auth.id, {}) + send_notification_to_junior.delay(TASK_ASSIGNED, request.auth.payload['user_id'], + 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) except Exception as e: @@ -241,7 +240,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): # 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, {}) + send_notification_to_junior.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {}) junior.save() serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) diff --git a/junior/serializers.py b/junior/serializers.py index c3e2d72..f932a26 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -21,7 +21,7 @@ from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime -from notifications.utils import send_notification +from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION, TASK_SUBMITTED) @@ -98,7 +98,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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_to_guardian.delay(APPROVED_JUNIOR, junior.auth.id, guardian_data.user.id, {}) 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) @@ -305,7 +305,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) # push notification - send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) + send_notification_to_junior.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) return junior_data @@ -337,8 +337,10 @@ 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_to_junior.delay(TASK_SUBMITTED, instance.guardian.user.id, + instance.junior.auth.id, {'task_id': instance.id}) + send_notification_to_guardian.delay(TASK_ACTION, instance.junior.auth.id, + instance.guardian.user.id, {'task_id': instance.id}) return instance class JuniorPointsSerializer(serializers.ModelSerializer): @@ -448,8 +450,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, {}) + send_notification_to_junior.delay(INVITED_GUARDIAN, guardian_data.user.id, junior_data.auth.id, {}) + send_notification_to_guardian.delay(APPROVED_JUNIOR, junior_data.auth.id, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): diff --git a/junior/views.py b/junior/views.py index a86083d..6b8bc2f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -42,7 +42,7 @@ from base.constants import NUMBER, ARTICLE_STATUS 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.utils import send_notification, send_notification_to_junior from notifications.constants import REMOVE_JUNIOR from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, @@ -269,7 +269,7 @@ class RemoveJuniorAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() - send_notification.delay(REMOVE_JUNIOR, None, junior_queryset.auth.id, {}) + send_notification_to_junior.delay(REMOVE_JUNIOR, 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: @@ -334,7 +334,8 @@ 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: # use CompleteTaskSerializer serializer if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]: diff --git a/notifications/constants.py b/notifications/constants.py index 46bfbef..59c2328 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -3,7 +3,7 @@ notification constants file """ from base.constants import NUMBER REGISTRATION = NUMBER['one'] -TASK_CREATED = NUMBER['two'] +TASK_ASSIGNED = NUMBER['two'] INVITED_GUARDIAN = NUMBER['three'] APPROVED_JUNIOR = NUMBER['four'] REFERRAL_POINTS = NUMBER['five'] @@ -21,17 +21,17 @@ 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." + TASK_ASSIGNED: { + "title": "New task assigned !!", + "body": "{from_user} has assigned you a new task." }, INVITED_GUARDIAN: { "title": "Invite guardian", "body": "Invite guardian successfully" }, APPROVED_JUNIOR: { - "title": "Approve junior", - "body": "You have request from junior to associate with you" + "title": "Approve junior !", + "body": "You have request from {from_user} to associate with you." }, REFERRAL_POINTS: { "title": "Earn Referral points", @@ -47,15 +47,15 @@ NOTIFICATION_DICT = { }, SKIPPED_PROFILE_SETUP: { "title": "Skipped profile setup!", - "body": "Your guardian has been setup your profile." + "body": "Your guardian {from_user} has setup your profile." }, TASK_SUBMITTED: { "title": "Task submitted!", "body": "Your task has been submitted successfully." }, TASK_ACTION: { - "title": "Task approval!", - "body": "You have request for task approval." + "title": "Task completion approval!", + "body": "You have request from {from_user} for task approval." }, LEADERBOARD_RANKING: { "title": "Leader board rank!", diff --git a/notifications/utils.py b/notifications/utils.py index 38df5ba..50dee81 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -11,6 +11,8 @@ from django.contrib.auth import get_user_model from account.models import UserNotification from account.utils import get_user_full_name +from guardian.models import Guardian +from junior.models import Junior from notifications.constants import NOTIFICATION_DICT from notifications.models import Notification @@ -72,12 +74,11 @@ def get_notification_data(notification_type, from_user, to_user, extra_data): return notification_data, push_data -@shared_task() -def send_notification(notification_type, from_user_id, to_user_id, extra_data): +def send_notification(notification_type, from_user, to_user, extra_data): """ used to send the push for the given notification type """ - (from_user, to_user) = get_basic_detail(from_user_id, to_user_id) + # (from_user, to_user) = get_basic_detail(from_user_id, to_user_id) notification_data, push_data = get_notification_data(notification_type, from_user, to_user, extra_data) user_notification_type = UserNotification.objects.filter(user=to_user).first() notification_data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()}) @@ -92,3 +93,29 @@ def send_push(user, data): user.fcmdevice_set.filter(active=True).send_message( Message(notification=FirebaseNotification(data['title'], data['body']), data=data) ) + + +@shared_task() +def send_notification_to_guardian(notification_type, from_user_id, to_user_id, extra_data): + from_user = None + if from_user_id: + from_user = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first() + extra_data['from_user_image'] = from_user.image + from_user = from_user.auth + to_user = Guardian.objects.filter(user_id=to_user_id).select_related('user').first() + extra_data['to_user_image'] = to_user.image + send_notification(notification_type, from_user, to_user.user, extra_data) + + +@shared_task() +def send_notification_to_junior(notification_type, from_user_id, to_user_id, extra_data): + from_user = None + if from_user_id: + from_user = Guardian.objects.filter(user_id=from_user_id).select_related('user').first() + extra_data['from_user_image'] = from_user.image + from_user = from_user.user + to_user = Junior.objects.filter(auth_id=to_user_id).select_related('auth').first() + extra_data['to_user_image'] = to_user.image + send_notification(notification_type, from_user, to_user.auth, extra_data) + + diff --git a/notifications/views.py b/notifications/views.py index f932f0f..dc6e891 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -13,7 +13,7 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from notifications.constants import TEST_NOTIFICATION # Import serializer from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer -from notifications.utils import send_notification +from notifications.utils import send_notification, send_notification_to_guardian, send_notification_to_junior # Import model from notifications.models import Notification @@ -56,8 +56,10 @@ class NotificationViewSet(viewsets.GenericViewSet): to send test notification :return: """ - send_notification(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) + send_notification_to_guardian(TEST_NOTIFICATION, None, request.auth.payload['user_id'], + {'task_id': None}) + send_notification_to_junior(TEST_NOTIFICATION, request.auth.payload['user_id'], None, + {'task_id': None}) return custom_response(SUCCESS_CODE["3000"]) @action(methods=['get'], detail=False, url_path='list', url_name='list', From c7c55f2a04d4add6c21e5e122d3a0283d4872ec7 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 16 Aug 2023 16:41:08 +0530 Subject: [PATCH 230/372] user notification issue --- account/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index d95f87a..227730e 100644 --- a/account/views.py +++ b/account/views.py @@ -535,7 +535,7 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): """profile view""" - queryset = self.queryset.filter(user=request.user) + queryset = UserNotification.objects.filter(user=request.user) serializer = UserNotificationSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 728d19da996eabac5dd85db3fb014f66dc63f17b Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 17 Aug 2023 12:21:13 +0530 Subject: [PATCH 231/372] FAQ model --- account/utils.py | 1 - junior/models.py | 38 ++++++++++++++++++++++++++++++++++++++ junior/urls.py | 4 +++- junior/views.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/account/utils.py b/account/utils.py index 1780a5d..8df5294 100644 --- a/account/utils.py +++ b/account/utils.py @@ -252,7 +252,6 @@ 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 diff --git a/junior/models.py b/junior/models.py index 025843e..784a753 100644 --- a/junior/models.py +++ b/junior/models.py @@ -158,6 +158,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 +184,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 +209,32 @@ 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""" + # Total earned points""" + questions = models.IntegerField(max_length=100) + # referral points""" + description = models.CharField(max_length=500) + 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.questions}' diff --git a/junior/urls.py b/junior/urls.py index b145d4f..08fe33a 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)), diff --git a/junior/views.py b/junior/views.py index 6b8bc2f..95a8ed8 100644 --- a/junior/views.py +++ b/junior/views.py @@ -10,6 +10,8 @@ from django.db.models import F import datetime import requests + +from rest_framework.viewsets import GenericViewSet, mixins """Django app import""" # Import guardian's model, @@ -30,7 +32,7 @@ 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, @@ -650,3 +652,44 @@ class RemoveGuardianCodeAPIView(views.APIView): 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 FAQViewSet(GenericViewSet, mixins.CreateModelMixin, + mixins.ListModelMixin): + """FAQ view set""" + + serializer_class = ArticleSerializer + permission_classes = [IsAuthenticated] + http_method_names = ['get', 'post'] + + def get_queryset(self): + queryset = FAQ.objects.all() + return queryset + + def create(self, request, *args, **kwargs): + """ + article create api method + :param request: + :param args: + :param kwargs: + :return: success message + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3027"]) + + def 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) + From 36427b50c1f9911846242db9100c5c0c669619ea Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 17 Aug 2023 12:24:33 +0530 Subject: [PATCH 232/372] print statement for expiry date --- account/utils.py | 3 ++- guardian/views.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/account/utils.py b/account/utils.py index 1780a5d..cc097ab 100644 --- a/account/utils.py +++ b/account/utils.py @@ -251,7 +251,8 @@ def generate_code(value, user_id): OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1) - +print("timezone.now()===>",timezone.now(),'====type===>',type(timezone.now())) +print("OTP_EXPIRY==>",OTP_EXPIRY,'====type===>',type(OTP_EXPIRY)) def get_user_full_name(user_obj): """ diff --git a/guardian/views.py b/guardian/views.py index 0319938..8aaaa69 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -67,6 +67,7 @@ class SignupViewset(viewsets.ModelViewSet): otp = generate_otp() # expire otp after 1 day expiry = OTP_EXPIRY + print("expiry==>", expiry,'====type===>',type(expiry)) # create user email otp object UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type']), expired_at=expiry) From 043c8c2f63358482b643121a768eca87f3ec78f7 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 16 Aug 2023 17:15:12 +0530 Subject: [PATCH 233/372] csv excel export api --- web_admin/pagination.py | 9 +- web_admin/serializers/analytics_serializer.py | 19 ++- web_admin/views/analytics.py | 109 +++--------------- zod_bank/settings.py | 7 ++ 4 files changed, 48 insertions(+), 96 deletions(-) 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 4c0cbd5..e87c25d 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -1,13 +1,16 @@ """ web_admin analytics serializer file """ +# third party imports from rest_framework import serializers + +# django imports from django.contrib.auth import get_user_model +# local imports from base.constants import USER_TYPE from junior.models import JuniorPoints, Junior -from web_admin.serializers.user_management_serializer import JuniorSerializer, GuardianSerializer USER = get_user_model() @@ -74,6 +77,7 @@ class UserCSVReportSerializer(serializers.ModelSerializer): phone_number = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() + date_joined = serializers.SerializerMethodField() class Meta: """ @@ -104,9 +108,9 @@ class UserCSVReportSerializer(serializers.ModelSerializer): :return: user type """ if obj.guardian_profile.all().first(): - return dict(USER_TYPE).get('2') + return dict(USER_TYPE).get('2').capitalize() elif obj.junior_profile.all().first(): - return dict(USER_TYPE).get('1') + return dict(USER_TYPE).get('1').capitalize() else: return None @@ -120,3 +124,12 @@ class UserCSVReportSerializer(serializers.ModelSerializer): return "Active" if profile.is_active else "Inactive" elif profile := 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/views/analytics.py b/web_admin/views/analytics.py index 34d1204..3a3360e 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -44,7 +44,7 @@ class AnalyticsViewSet(GenericViewSet): to get junior leaderboard and ranking """ serializer_class = None - # permission_classes = [IsAuthenticated, AdminPermission] + permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -148,93 +148,14 @@ class AnalyticsViewSet(GenericViewSet): serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, serializer.data) - # @action(methods=['get'], url_name='generate-report', url_path='generate-report', detail=False) - # def generate_report(self, request): - # - # response = HttpResponse(content_type='text/csv') - # response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.csv"' - # - # 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) - # - # buffer = io.StringIO() - # csv_writer = csv.writer(buffer) - # - # # sheet 1 for Total 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) - # - # # writer = csv.writer(response) - # csv_writer.writerow(['Users']) - # csv_writer.writerow([ - # 'First Name', 'Last Name', 'Email', 'Phone Number', 'User Type', 'status', 'Date Joined', - # # Add more fields as needed - # ]) - # - # for user_data in serializer.data: - # row = [ - # user_data['first_name'], user_data['last_name'], user_data['email'], - # user_data['phone_number'], user_data['user_type'], - # user_data['is_active'], user_data['date_joined'] - # ] - # csv_writer.writerow(row) - # - # response.write(buffer.getvalue()) - # buffer.close() - # - # # sheet 2 for Assign Task - # assign_tasks = JuniorTask.objects.filter( - # created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] - # ).exclude(task_status__in=[PENDING, EXPIRED]) - # buffer = io.StringIO() - # csv_writer = csv.writer(buffer) - # csv_writer.writerow([]) - # csv_writer.writerow(['Assign Tasks']) - # csv_writer.writerow([ - # 'Task Name', 'Task Status' - # # Add more fields as needed - # ]) - # - # for task in assign_tasks: - # row = [ - # task.task_name, dict(TASK_STATUS).get(task.task_status).capitalize(), - # ] - # csv_writer.writerow(row) - # response.write(buffer.getvalue()) - # buffer.close() - # - # # sheet 3 for Juniors Leaderboard and rank - # 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') - # buffer = io.StringIO() - # csv_writer = csv.writer(buffer) - # csv_writer.writerow([]) - # csv_writer.writerow(['Top 15 Juniors']) - # csv_writer.writerow([ - # 'Junior Name', 'Points', 'Rank' - # # Add more fields as needed - # ]) - # - # for junior in queryset: - # row = [ - # f"{junior.junior.auth.first_name}{junior.junior.auth.last_name}" if junior.junior.auth.last_name else junior.junior.auth.first_name, - # junior.total_points, junior.rank - # ] - # csv_writer.writerow(row) - # response.write(buffer.getvalue()) - # buffer.close() - # - # return response - - @action(methods=['get'], url_name='generate-report', url_path='generate-report', detail=False) - def generate_report(self, request): + @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"' @@ -252,7 +173,7 @@ class AnalyticsViewSet(GenericViewSet): workbook = xlsxwriter.Workbook(buffer) # Add sheets - sheets = ['Users', 'Assign Tasks', 'Top 15 Juniors'] + sheets = ['Users', 'Assign Tasks', 'Juniors leaderboard'] for sheet_name in sheets: worksheet = workbook.add_worksheet(name=sheet_name) @@ -263,7 +184,13 @@ class AnalyticsViewSet(GenericViewSet): queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1)))) serializer = UserCSVReportSerializer(queryset, many=True) - df_users = pd.DataFrame(serializer.data) + df_users = pd.DataFrame([ + {'Name': f"{user['first_name']} {user['last_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 + ]) for idx, col in enumerate(df_users.columns): worksheet.write(0, idx, col) # Write header for row_num, row in enumerate(df_users.values, start=1): @@ -288,7 +215,7 @@ class AnalyticsViewSet(GenericViewSet): worksheet.write(row_num, col_num, value) # sheet 3 for Juniors Leaderboard and rank - elif sheet_name == 'Top 15 Juniors': + elif sheet_name == 'Juniors leaderboard': queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 2d003c1..3e0713c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -177,6 +177,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 +196,7 @@ LOGGING = { "class": "logging.StreamHandler" } }, + # database logger "loggers": { "django.db.backends": { "level": "DEBUG", @@ -242,6 +246,7 @@ CORS_ALLOW_HEADERS = ( 'x-requested-with', ) +# CORS header settings CORS_EXPOSE_HEADERS = ( 'Access-Control-Allow-Origin: *', ) @@ -297,5 +302,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') From 4e8243c17ee478ba273dc79a899d8d7442a715f3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 17 Aug 2023 13:06:46 +0530 Subject: [PATCH 234/372] added required packages in reuirements fil --- requirements.txt | 3 +++ web_admin/views/analytics.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) 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/views/analytics.py b/web_admin/views/analytics.py index 3a3360e..b904af6 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -3,7 +3,6 @@ web_admin analytics view file """ # python imports import datetime -import csv import io import pandas as pd import xlsxwriter From 04ed9c668c09c742d7e81c666519169b812d6441 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 17 Aug 2023 13:24:20 +0530 Subject: [PATCH 235/372] remove print statement for expiry date --- account/utils.py | 2 -- guardian/views.py | 1 - 2 files changed, 3 deletions(-) diff --git a/account/utils.py b/account/utils.py index cc097ab..8df5294 100644 --- a/account/utils.py +++ b/account/utils.py @@ -251,8 +251,6 @@ def generate_code(value, user_id): OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1) -print("timezone.now()===>",timezone.now(),'====type===>',type(timezone.now())) -print("OTP_EXPIRY==>",OTP_EXPIRY,'====type===>',type(OTP_EXPIRY)) def get_user_full_name(user_obj): """ diff --git a/guardian/views.py b/guardian/views.py index 8aaaa69..0319938 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -67,7 +67,6 @@ class SignupViewset(viewsets.ModelViewSet): otp = generate_otp() # expire otp after 1 day expiry = OTP_EXPIRY - print("expiry==>", expiry,'====type===>',type(expiry)) # create user email otp object UserEmailOtp.objects.create(email=request.data['email'], otp=otp, user_type=str(request.data['user_type']), expired_at=expiry) From 3f6c9a2d997baa19927e12c471e128aea7064249 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 17 Aug 2023 15:48:26 +0530 Subject: [PATCH 236/372] FAQ list and creation --- base/messages.py | 2 + junior/admin.py | 3 +- ...aq_alter_juniorarticle_options_and_more.py | 39 ++++++++++++++ junior/migrations/0027_alter_faq_question.py | 18 +++++++ junior/models.py | 4 +- junior/serializers.py | 16 +++++- junior/views.py | 25 +++++---- zod_bank/settings.py | 52 +++++++++---------- 8 files changed, 119 insertions(+), 40 deletions(-) create mode 100644 junior/migrations/0026_faq_alter_juniorarticle_options_and_more.py create mode 100644 junior/migrations/0027_alter_faq_question.py diff --git a/base/messages.py b/base/messages.py index 10fbfaa..5113a4b 100644 --- a/base/messages.py +++ b/base/messages.py @@ -163,6 +163,8 @@ SUCCESS_CODE = { "3043": "Read article card successfully", # remove guardian code request "3044": "Remove guardian code request successfully", + # create faq + "3045": "Create FAQ data" } """status code error""" 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/models.py b/junior/models.py index 784a753..d3d2bcf 100644 --- a/junior/models.py +++ b/junior/models.py @@ -223,7 +223,7 @@ class JuniorArticleCard(models.Model): class FAQ(models.Model): """FAQ model""" # Total earned points""" - questions = models.IntegerField(max_length=100) + question = models.CharField(max_length=100) # referral points""" description = models.CharField(max_length=500) created_at = models.DateTimeField(auto_now_add=True) @@ -237,4 +237,4 @@ class FAQ(models.Model): def __str__(self): """Return email id""" - return f'{self.questions}' + return f'{self.question}' diff --git a/junior/serializers.py b/junior/serializers.py index f932a26..c03698e 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,7 @@ 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, @@ -508,3 +508,17 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance + +class FAQSerializer(serializers.ModelSerializer): + # FAQ Serializer + + class Meta(object): + # meta info + model = FAQ + fields = ('id', 'question', 'description') + + def create(self, validated_data): + # validate data + print("validated_data===>",validated_data) + faq = FAQ.objects.bulk_create(**validated_data) + return faq diff --git a/junior/views.py b/junior/views.py index 95a8ed8..7620963 100644 --- a/junior/views.py +++ b/junior/views.py @@ -36,7 +36,7 @@ from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, Jun from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer, - RemoveGuardianCodeSerializer) + RemoveGuardianCodeSerializer, FAQSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -658,26 +658,28 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.ListModelMixin): """FAQ view set""" - serializer_class = ArticleSerializer + serializer_class = FAQSerializer permission_classes = [IsAuthenticated] http_method_names = ['get', 'post'] def get_queryset(self): - queryset = FAQ.objects.all() - return queryset + return FAQ.objects.all() def create(self, request, *args, **kwargs): """ - article create api method + faq create api method :param request: :param args: :param kwargs: :return: success message """ - serializer = self.serializer_class(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return custom_response(SUCCESS_CODE["3027"]) + 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): """ @@ -691,5 +693,8 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, 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) + return custom_response(None, data=serializer.data, response_status=status.HTTP_200_OK) + + + diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 3e0713c..4e264f4 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -180,32 +180,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -LOGGING = { - "version": 1, - "filters": { - "require_debug_true": { - "()": "django.utils.log.RequireDebugTrue" - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "filters": [ - "require_debug_true" - ], - "class": "logging.StreamHandler" - } - }, - # database logger - "loggers": { - "django.db.backends": { - "level": "DEBUG", - "handlers": [ - "console" - ] - } - } -} +# LOGGING = { +# "version": 1, +# "filters": { +# "require_debug_true": { +# "()": "django.utils.log.RequireDebugTrue" +# } +# }, +# "handlers": { +# "console": { +# "level": "DEBUG", +# "filters": [ +# "require_debug_true" +# ], +# "class": "logging.StreamHandler" +# } +# }, +# # database logger +# "loggers": { +# "django.db.backends": { +# "level": "DEBUG", +# "handlers": [ +# "console" +# ] +# } +# } +# } # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From b46109e487c8fe35fb6c18fe0e65e08ed75f6521 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 17 Aug 2023 16:06:58 +0530 Subject: [PATCH 237/372] FAQ list and creation --- junior/migrations/0028_faq_status.py | 18 ++++++++++++++++++ junior/models.py | 6 ++++-- junior/serializers.py | 5 ----- 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 junior/migrations/0028_faq_status.py 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/models.py b/junior/models.py index d3d2bcf..c256800 100644 --- a/junior/models.py +++ b/junior/models.py @@ -222,10 +222,12 @@ class JuniorArticleCard(models.Model): class FAQ(models.Model): """FAQ model""" - # Total earned points""" + # questions""" question = models.CharField(max_length=100) - # referral points""" + # 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) diff --git a/junior/serializers.py b/junior/serializers.py index c03698e..1150bd8 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -517,8 +517,3 @@ class FAQSerializer(serializers.ModelSerializer): model = FAQ fields = ('id', 'question', 'description') - def create(self, validated_data): - # validate data - print("validated_data===>",validated_data) - faq = FAQ.objects.bulk_create(**validated_data) - return faq From dd2890bca69e6e20f3019ee3aca340730b649aa8 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 11:04:05 +0530 Subject: [PATCH 238/372] handle delete scenerio in approve task and junior --- account/serializers.py | 5 +- .../templated_email/support_mail.email | 4 +- account/utils.py | 2 + base/messages.py | 4 +- .../migrations/0021_guardian_is_deleted.py | 18 +++++++ guardian/models.py | 2 + guardian/serializers.py | 4 +- guardian/views.py | 47 +++++++++---------- junior/migrations/0029_junior_is_deleted.py | 18 +++++++ junior/models.py | 2 + junior/serializers.py | 9 ++-- web_admin/serializers/analytics_serializer.py | 2 +- .../serializers/user_management_serializer.py | 4 +- 13 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 guardian/migrations/0021_guardian_is_deleted.py create mode 100644 junior/migrations/0029_junior_is_deleted.py diff --git a/account/serializers.py b/account/serializers.py index ebdc263..0caeb8c 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -244,7 +244,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,7 +287,8 @@ 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""" 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/utils.py b/account/utils.py index 8df5294..e016940 100644 --- a/account/utils.py +++ b/account/utils.py @@ -95,6 +95,7 @@ def junior_account_update(user_tb): junior_data.is_verified = False junior_data.guardian_code = '{}' junior_data.guardian_code_status = str(NUMBER['one']) + 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""" diff --git a/base/messages.py b/base/messages.py index 5113a4b..37aa49a 100644 --- a/base/messages.py +++ b/base/messages.py @@ -97,7 +97,9 @@ ERROR_CODE = { "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" + "2071": "You did 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" } """Success message code""" 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 cbd1e4e..6e6e6af 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -214,7 +214,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""" @@ -340,7 +340,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): diff --git a/guardian/views.py b/guardian/views.py index 0319938..d06f5b9 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -253,31 +253,29 @@ class ApproveJuniorAPIView(viewsets.ViewSet): serializer_class = ApproveJuniorSerializer permission_classes = [IsAuthenticated] - def get_queryset(self): - """Get the queryset for the view""" - guardian = Guardian.objects.filter(user__email=self.request.user).last() - # fetch junior query - junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last() - return guardian, junior_queryset def create(self, request, *args, **kwargs): """ junior list""" 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: + 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() 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() + junior_queryset.guardian_code = None + junior_queryset.guardian_code_status = str(NUMBER['one']) + junior_queryset.save() 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) @@ -288,34 +286,31 @@ class ApproveTaskAPIView(viewsets.ViewSet): serializer_class = ApproveTaskSerializer permission_classes = [IsAuthenticated] - def get_queryset(self): - """Get the queryset for the view""" - guardian = Guardian.objects.filter(user__email=self.request.user).last() - # task query - task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'), - guardian=guardian, - junior=self.request.data.get('junior_id')).last() - return guardian, task_queryset - def create(self, request, *args, **kwargs): """ junior list""" # action 1 is use for approve and 2 for reject 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: + 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) 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/models.py b/junior/models.py index c256800..773557b 100644 --- a/junior/models.py +++ b/junior/models.py @@ -68,6 +68,8 @@ 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""" diff --git a/junior/serializers.py b/junior/serializers.py index 1150bd8..b4aa54f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -147,7 +147,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""" @@ -214,7 +214,8 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): 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 +258,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""" @@ -395,7 +396,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""" diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index e87c25d..8561e71 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -28,7 +28,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): diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 4bb0709..f2c95f3 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -108,7 +108,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): """ @@ -187,7 +187,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): """ From ef4c45922933716d6995c71a84a00fdf3f30e9f1 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 11:45:21 +0530 Subject: [PATCH 239/372] handle deleted user scenario in complete task api --- base/messages.py | 3 ++- junior/views.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index 37aa49a..db1fdd5 100644 --- a/base/messages.py +++ b/base/messages.py @@ -99,7 +99,8 @@ ERROR_CODE = { "2070": "You did not find as a guardian", "2071": "You did 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" + "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" } """Success message code""" diff --git a/junior/views.py b/junior/views.py index 7620963..7a83b0a 100644 --- a/junior/views.py +++ b/junior/views.py @@ -339,6 +339,8 @@ class CompleteJuniorTaskAPIView(views.APIView): 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: + 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 """ From ceaf58433224968d0890c4c28a38aff2cc63f318 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 18 Aug 2023 11:41:24 +0530 Subject: [PATCH 240/372] csv/excel method changed, uploading to alibaba cloud and providing link to frontend --- web_admin/serializers/analytics_serializer.py | 9 ++---- .../serializers/user_management_serializer.py | 31 +++++++++---------- web_admin/views/analytics.py | 31 ++++++++++++++++--- web_admin/views/user_management.py | 23 ++++++++------ zod_bank/settings.py | 1 + 5 files changed, 56 insertions(+), 39 deletions(-) diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index e87c25d..ce6c72e 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -92,10 +92,7 @@ class UserCSVReportSerializer(serializers.ModelSerializer): :param obj: user object :return: user phone number """ - if profile := obj.guardian_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" \ - if profile.country_code and profile.phone else profile.phone - elif profile := obj.junior_profile.all().first(): + 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: @@ -120,9 +117,7 @@ class UserCSVReportSerializer(serializers.ModelSerializer): :param obj: user object :return: user type """ - if profile := obj.guardian_profile.all().first(): - return "Active" if profile.is_active else "Inactive" - elif profile := obj.junior_profile.all().first(): + if profile := (obj.guardian_profile.all().first() or obj.junior_profile.all().first()): return "Active" if profile.is_active else "Inactive" @staticmethod diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 4bb0709..3544c66 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,7 +5,7 @@ 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 base.constants import USER_TYPE, GUARDIAN, JUNIOR # local imports from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.models import Guardian @@ -210,10 +210,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 @@ -265,33 +265,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/views/analytics.py b/web_admin/views/analytics.py index b904af6..a503356 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -6,6 +6,9 @@ import datetime import io import pandas as pd import xlsxwriter +import tempfile +import oss2 +from django.conf import settings # third party imports from rest_framework.viewsets import GenericViewSet @@ -166,7 +169,8 @@ class AnalyticsViewSet(GenericViewSet): 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) - buffer = io.BytesIO() # Use BytesIO for binary data + # Use BytesIO for binary data + buffer = io.BytesIO() # Create an XlsxWriter Workbook object workbook = xlsxwriter.Workbook(buffer) @@ -191,7 +195,8 @@ class AnalyticsViewSet(GenericViewSet): for user in serializer.data ]) for idx, col in enumerate(df_users.columns): - worksheet.write(0, idx, col) # Write header + # Write header + worksheet.write(0, idx, col) for row_num, row in enumerate(df_users.values, start=1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) @@ -208,7 +213,8 @@ class AnalyticsViewSet(GenericViewSet): ]) for idx, col in enumerate(df_tasks.columns): - worksheet.write(0, idx, col) # Write header + # Write header + worksheet.write(0, idx, col) for row_num, row in enumerate(df_tasks.values, start=1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) @@ -231,7 +237,8 @@ class AnalyticsViewSet(GenericViewSet): ]) for idx, col in enumerate(df_leaderboard.columns): - worksheet.write(0, idx, col) # Write header + # Write header + worksheet.write(0, idx, col) for row_num, row in enumerate(df_leaderboard.values, start=1): for col_num, value in enumerate(row): worksheet.write(row_num, col_num, value) @@ -244,5 +251,19 @@ class AnalyticsViewSet(GenericViewSet): response.write(buffer.getvalue()) buffer.close() - return response + filename = f"{'analytics'}/{'ZOD_Bank_Analytics.xlsx'}" + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + """write image in temporary file""" + temp_file.write(response.content) + """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') + link = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" + print(link) + return custom_response(None, link) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index d1754aa..ecc9771 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -14,6 +14,8 @@ from django.db.models import Q from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE +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 +72,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(id=kwargs['pk']) - elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): - queryset = queryset.filter(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 +91,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'): - user_obj = queryset.filter(id=kwargs['pk']).first() - serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), + guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).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(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) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 3e0713c..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'), From 9a5447bca297a16d113253bc7563b16f1182f6e0 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 18 Aug 2023 14:50:25 +0530 Subject: [PATCH 241/372] article bug fixed, changed image name in get image url --- guardian/views.py | 6 ++++-- web_admin/utils.py | 2 +- web_admin/views/analytics.py | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index 0319938..67fe6ef 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -62,7 +62,7 @@ class SignupViewset(viewsets.ModelViewSet): 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(): - user = serializer.save() + serializer.save() """Generate otp""" otp = generate_otp() # expire otp after 1 day @@ -79,7 +79,9 @@ class SignupViewset(viewsets.ModelViewSet): return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST) class UpdateGuardianProfile(viewsets.ViewSet): - """Update guardian profile""" + """ + Update guardian profile + """ serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] diff --git a/web_admin/utils.py b/web_admin/utils.py index 9870b30..a47bbff 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -29,7 +29,7 @@ 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) diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index a503356..2ff3773 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -224,8 +224,7 @@ class AnalyticsViewSet(GenericViewSet): 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') - + )).order_by('-total_points', 'junior__created_at')[:15] df_leaderboard = pd.DataFrame([ { 'Junior Name': f"{junior.junior.auth.first_name} {junior.junior.auth.last_name}" From 51d3b77ff76cb30540ae05e22a43ea2813f9e282 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 15:45:57 +0530 Subject: [PATCH 242/372] password validation, deactivated user's middleware --- account/custom_middleware.py | 32 ++++++++++++++++----- account/views.py | 2 +- base/messages.py | 8 +++--- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 39 +++++++++++++++++++++++++- zod_bank/settings.py | 52 +++++++++++++++++------------------ 6 files changed, 94 insertions(+), 39 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index 7a06e43..ec2e315 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -7,7 +7,9 @@ from rest_framework.renderers import JSONRenderer from account.utils import custom_error_response from account.models import UserDeviceDetails 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,16 @@ from base.messages import ERROR_CODE, SUCCESS_CODE # multiple devices only # user can login in single # device at a time""" + +def custom_response(custom_error): + """custom response""" + response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND) + # Set content type header to "application/json" + response['Content-Type'] = 'application/json' + # Render the response as JSON + renderer = JSONRenderer() + response.content = renderer.render(response.data) + return response class CustomMiddleware(object): """Custom middleware""" def __init__(self, get_response): @@ -26,15 +38,21 @@ 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') if request.user.is_authenticated: """device details""" device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() + 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 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) + response = custom_response(custom_error) return response diff --git a/account/views.py b/account/views.py index c17f853..f91f1fe 100644 --- a/account/views.py +++ b/account/views.py @@ -287,7 +287,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) diff --git a/base/messages.py b/base/messages.py index db1fdd5..cbcd559 100644 --- a/base/messages.py +++ b/base/messages.py @@ -96,12 +96,12 @@ 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" - + "2074": "You can not complete this task because you does not exist in the system", + "2075": "Your account is deactivated. Please contact with admin" } """Success message code""" SUCCESS_CODE = { diff --git a/celerybeat-schedule b/celerybeat-schedule index ce5d8ac8aa39d618efaf5cf6434831370e993b16..4a3bd313044e9169fbcb11bdbf27bec6aaff8fe8 100644 GIT binary patch delta 641 zcmZuuF>ljA6t%K#r*Q`T1QAzTs=c^kPR;;!^7sRQY#uWy& zxV7=KByJDWRFT1_xFwBvv77bIv6L9?-a1;!zmkYUU5j{O;axrIY~G1p556^Z{W)l-xX-z%=uBTPmIe9U+Tr$zA;Z1?ZnApVKlg7lL=pi=A z#J9;kCbWkj_n(5i)@MDUH-r8@6!65Lgj%}<{UsobQ1N?dwJel{A0Oci9{ZL}EyqL8 z2sJ2m5ft&unL{k+=?pz#WIG{H(#ou_?mKe}Pi+F_1t1kFTC$RzM+-OrkU%RP3Hf+=%@l0O*X*m&^egGb@I3SD11l#kILO{1q3 delta 163 zcmZo@U~Fh$oS?I@q?dnUgU4n@f&cuBY!eODCHWa3K-~aB_dxmClNA-jH%BP!VB!64 z2od83QX-pWRX=m^iWxzK*nyPJW?4OFMqXa10Zc$4p~<=WSNT{v^iuOmiZWA+8(5}_ mOjb0qoZM;fh)cA#P^_d-Jg87&a+itB=81+o7&jJf=K%nz1SJUo diff --git a/guardian/serializers.py b/guardian/serializers.py index 6e6e6af..f36bd46 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 @@ -30,7 +31,8 @@ from .utils import real_time, convert_timedelta_into_datetime, update_referral_p from notifications.constants import TASK_POINTS, TASK_REJECTED # send notification function from notifications.utils import send_notification, send_notification_to_junior - +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ # In this serializer file # define user serializer, @@ -42,10 +44,45 @@ from notifications.utils import send_notification, send_notification_to_junior # guardian profile serializer, # approve junior serializer, # approve task serializer, +from rest_framework import serializers + +class PasswordValidator: + 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): + 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: + 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): + raise serializers.ValidationError(_("Password must contain at least one uppercase letter.")) + + if self.require_numbers and not any(char.isdigit() for char in password): + 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): + 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""" diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 4e264f4..3e0713c 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -180,32 +180,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -# LOGGING = { -# "version": 1, -# "filters": { -# "require_debug_true": { -# "()": "django.utils.log.RequireDebugTrue" -# } -# }, -# "handlers": { -# "console": { -# "level": "DEBUG", -# "filters": [ -# "require_debug_true" -# ], -# "class": "logging.StreamHandler" -# } -# }, -# # database logger -# "loggers": { -# "django.db.backends": { -# "level": "DEBUG", -# "handlers": [ -# "console" -# ] -# } -# } -# } +LOGGING = { + "version": 1, + "filters": { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "filters": [ + "require_debug_true" + ], + "class": "logging.StreamHandler" + } + }, + # database logger + "loggers": { + "django.db.backends": { + "level": "DEBUG", + "handlers": [ + "console" + ] + } + } +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 5b236dfc818975d684b262180103ec2f4f1e2f45 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 16:37:42 +0530 Subject: [PATCH 243/372] remove threading from login api --- account/views.py | 4 ++-- celerybeat-schedule | Bin 16384 -> 16384 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account/views.py b/account/views.py index f91f1fe..c6fbb12 100644 --- a/account/views.py +++ b/account/views.py @@ -321,8 +321,8 @@ 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, device_id) 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) diff --git a/celerybeat-schedule b/celerybeat-schedule index 4a3bd313044e9169fbcb11bdbf27bec6aaff8fe8..cc8251c7500206755f61cdf0ff0b24e019ea84c5 100644 GIT binary patch delta 325 zcmZo@U~Fh$oS?I@q?dnUgU4n@f&ctcObigf3#AzhAPi2viBUnDa}<`aF!35}7E=Ak z!Rrc@Vh5^Ny;)Z81LNduL#fH?25OT7jm?-c7$?s%*0*3zPDw0DEy>JH<$^N`S)94p zpK}OtGiY)cMijCpCzWRAlw{@=bET)2B$kvEaTT&nW;7A0XOG|n8XjMoSCE;Uos(L~ z(N@SARLB*<3=|CwaW3TcW^60u2`c1mE946*Y8Da}bO=1MNg z&nrnx;wlu-@MiF4h+qV2)hZOs0K$?&p`b$H+Cq^e&DuiI+Cs6CLh+zNi3l;E60kXw grLx&%r9hA&5W27_Dr5*jQP+^LLi>rw zAU43lON|!bl+a85fFetPpi7qm?b@kRr%s(ZwNrsE1={y?M@w!y1V(}u!6R{ek9Xu9 z`MLLf5Gm>VCm&52b;B4GG-NBl;ZN{~~d`UUAtWEDP>iyp@XANwb z025#WOn?b60Vco%m;e)C0!)AjFoFM!z>*%BtpdRum;({uIN&(oIA9%c9^gE{d4Tf( z=K;czs8nrCYg^Ob-}m+Yr~3Q*m7J=$soOX8{>nqlU^ z925cOz#K*iD8F&{%Y{g~_Er7)lkzivo*Z)G{Ha(lhm&z|KL2$}1hSt!ua&Tyukjg= zd7g~tH_vaMm3xMI9>B{$@;y>>34l6!2bPS*o=0%juiH2KXxb`8HH(5rY7m%7o+au z^oCjbxin{9J5_yn9a}n)-;;iT$CvI2JX7{@sHD8N9SA@8cpKe2=-wh1>nnB??D+0h zPoxK_l_~(O9QjmQMpD-|5$da_j@|xtAe|1~L(9NpavQ1F@EQ`&h||t`j9c=+oz#~ix#Xz$F8MtIQYW{9-0&fSCo!S z=<QMriKv6)=f>Q@@+JbtkoZZ}!E=;Wo6D9|$7zFADobhm#FlE7X3~x}F&cyJh zgtHdRwr6n643gt+jf!H9L@|f!$PN8ag@%V$FmJ&*6vbpFiUlo-MH-7nTe4so^+2PY mkKw{GQCw7_Sg~L=YU233FaajO1egF5U;<2l2{3`zn!sPFW|t2D From 71a3e36bf3701bdf78e7ad21aeb31bfbb87df03a Mon Sep 17 00:00:00 2001 From: Ruman Siddiqui Date: Fri, 18 Aug 2023 16:57:42 +0530 Subject: [PATCH 244/372] [ZBKBCK-346] change password and forgot password api has been optimised --- account/serializers.py | 31 ++++++++++----- account/utils.py | 22 +++++++++++ account/views.py | 89 ++++++++++++++++++++++++------------------ guardian/tasks.py | 9 ++++- 4 files changed, 103 insertions(+), 48 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 0caeb8c..e946d15 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -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): + """ + + """ 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""" diff --git a/account/utils.py b/account/utils.py index e016940..3f8c687 100644 --- a/account/utils.py +++ b/account/utils.py @@ -129,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): """ diff --git a/account/views.py b/account/views.py index c6fbb12..b36c1f7 100644 --- a/account/views.py +++ b/account/views.py @@ -39,7 +39,7 @@ 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 @@ -193,15 +193,30 @@ 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 + """ + 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""" @@ -213,40 +228,40 @@ 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) + """ + Post method to validate serializer + """ + 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""" 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)) From b4026a4f118066fb36ab451687734dded8a750d1 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 18:12:14 +0530 Subject: [PATCH 245/372] middleware --- account/custom_middleware.py | 5 +++-- account/utils.py | 4 +++- account/views.py | 6 +++++- guardian/views.py | 9 +++++++-- junior/views.py | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index ec2e315..c2125cd 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -39,8 +39,9 @@ class CustomMiddleware(object): # 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') + api_endpoint = request.path if request.user.is_authenticated: - """device details""" + # device details device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() if user_type and str(user_type) == str(NUMBER['one']): junior = Junior.objects.filter(auth=request.user, is_active=False).last() @@ -52,7 +53,7 @@ class CustomMiddleware(object): if guardian: custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND) response = custom_response(custom_error) - if device_id and not device_details: + if device_id and 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) return response diff --git a/account/utils.py b/account/utils.py index e016940..ad910ba 100644 --- a/account/utils.py +++ b/account/utils.py @@ -137,10 +137,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): diff --git a/account/views.py b/account/views.py index c6fbb12..bf3a7d5 100644 --- a/account/views.py +++ b/account/views.py @@ -322,7 +322,11 @@ class UserLogin(viewsets.ViewSet): response_status=status.HTTP_401_UNAUTHORIZED ) # storing device id in using celery task so the time would be reduced - user_device_details.delay(user, device_id) + # 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) diff --git a/guardian/views.py b/guardian/views.py index d06f5b9..ad6a134 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -260,7 +260,7 @@ class ApproveJuniorAPIView(viewsets.ViewSet): 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: + 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': @@ -295,7 +295,12 @@ class ApproveTaskAPIView(viewsets.ViewSet): 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: + print("task_queryset.junior.is_deleted===>",task_queryset.junior.is_deleted) + print("task_queryset.junior.is_active===>",task_queryset.junior.is_active) + print("task_queryset.junior.is_deleted===>", type(task_queryset.junior.is_deleted)) + print("task_queryset.junior.is_active===>", type(task_queryset.junior.is_active)) + print("99999===>",(task_queryset.junior.is_deleted or not task_queryset.junior.is_active)) + 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": guardian.guardian_code, diff --git a/junior/views.py b/junior/views.py index 7a83b0a..1fdf2be 100644 --- a/junior/views.py +++ b/junior/views.py @@ -339,7 +339,7 @@ class CompleteJuniorTaskAPIView(views.APIView): 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: + 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'])]: From cdf1a7b74ea51beb70bd7647e3402fcca01aa267 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 18:29:46 +0530 Subject: [PATCH 246/372] add comma --- account/views.py | 4 ++-- guardian/serializers.py | 2 +- guardian/views.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/account/views.py b/account/views.py index bf3a7d5..f5cb15f 100644 --- a/account/views.py +++ b/account/views.py @@ -235,7 +235,7 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - expiry = OTP_EXPIRY + expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry @@ -454,7 +454,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): 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 diff --git a/guardian/serializers.py b/guardian/serializers.py index f36bd46..8db860b 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -251,7 +251,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', 'is_deleted' + 'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'is_deleted', 'updated_at'] class TaskDetailsSerializer(serializers.ModelSerializer): """Task detail serializer""" diff --git a/guardian/views.py b/guardian/views.py index ad6a134..e7a4b87 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -66,7 +66,7 @@ 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) From 3921f76f22642911d2ec7fa86c9442a5daef7179 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 18:31:27 +0530 Subject: [PATCH 247/372] remove print statement --- guardian/views.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index e7a4b87..9b9a0aa 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -295,11 +295,6 @@ class ApproveTaskAPIView(viewsets.ViewSet): task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'), guardian=guardian, junior=self.request.data.get('junior_id')).last() - print("task_queryset.junior.is_deleted===>",task_queryset.junior.is_deleted) - print("task_queryset.junior.is_active===>",task_queryset.junior.is_active) - print("task_queryset.junior.is_deleted===>", type(task_queryset.junior.is_deleted)) - print("task_queryset.junior.is_active===>", type(task_queryset.junior.is_active)) - print("99999===>",(task_queryset.junior.is_deleted or not task_queryset.junior.is_active)) 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 From 92e5104e3f8d8feb2d1dcad88cb8e82d696fe96b Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 18 Aug 2023 18:37:01 +0530 Subject: [PATCH 248/372] expiry date of otp --- account/views.py | 4 ++-- guardian/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index c17f853..2d8149d 100644 --- a/account/views.py +++ b/account/views.py @@ -235,7 +235,7 @@ class ForgotPasswordAPIView(views.APIView): 'verification_code': verification_code } ) - expiry = OTP_EXPIRY + expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created: user_data.expired_at = expiry @@ -450,7 +450,7 @@ class ReSendEmailOtp(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): 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 diff --git a/guardian/views.py b/guardian/views.py index 3948e6f..88f567c 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -66,7 +66,7 @@ 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) From 21e006ae2aff1bde295fc2beb5a12e667da3aa20 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 18 Aug 2023 18:34:48 +0530 Subject: [PATCH 249/372] added description for api, changes in upload image and file to alibaba method --- guardian/utils.py | 47 ++++++++++++++++++++++++-------- notifications/views.py | 8 +++--- web_admin/utils.py | 4 +-- web_admin/views/analytics.py | 17 ++---------- web_admin/views/article.py | 53 ++++++++++++++++++++++++++---------- web_admin/views/auth.py | 3 ++ 6 files changed, 85 insertions(+), 47 deletions(-) diff --git a/guardian/utils.py b/guardian/utils.py index d5081ac..14bd36a 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -43,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}" diff --git a/notifications/views.py b/notifications/views.py index dc6e891..5adb536 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -56,10 +56,10 @@ class NotificationViewSet(viewsets.GenericViewSet): to send test notification :return: """ - send_notification_to_guardian(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) - send_notification_to_junior(TEST_NOTIFICATION, request.auth.payload['user_id'], None, - {'task_id': None}) + send_notification_to_guardian.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], + {'task_id': None}) + send_notification_to_junior.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], + {'task_id': None}) return custom_response(SUCCESS_CODE["3000"]) @action(methods=['get'], detail=False, url_path='list', url_name='list', diff --git a/web_admin/utils.py b/web_admin/utils.py index a47bbff..4170cf9 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -4,7 +4,7 @@ web_utils file import base64 from base.constants import ARTICLE_CARD_IMAGE_FOLDER -from guardian.utils import upload_image_to_alibaba +from guardian.utils import upload_image_to_alibaba, upload_base64_image_to_alibaba def pop_id(data): @@ -32,7 +32,7 @@ def get_image_url(data): 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') diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 2ff3773..ddc4e0d 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -28,6 +28,7 @@ from django.http import HttpResponse from account.utils import custom_response 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 @@ -251,18 +252,6 @@ class AnalyticsViewSet(GenericViewSet): buffer.close() filename = f"{'analytics'}/{'ZOD_Bank_Analytics.xlsx'}" - with tempfile.NamedTemporaryFile(delete=False) as temp_file: - """write image in temporary file""" - temp_file.write(response.content) - """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') - link = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}" - print(link) - return custom_response(None, link) + file_link = upload_excel_file_to_alibaba(response, filename) + return custom_response(None, file_link) diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 13c41c2..49f76fa 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) 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) From 48b455e38a66a62334e952ef709dc98c41e0a33b Mon Sep 17 00:00:00 2001 From: jain Date: Sun, 20 Aug 2023 14:12:17 +0530 Subject: [PATCH 250/372] login issue --- account/views.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/account/views.py b/account/views.py index f5cb15f..3e09080 100644 --- a/account/views.py +++ b/account/views.py @@ -295,22 +295,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"], From 1adf0c2f703f5f31f2566fe23267bb70b3b6b6e9 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 12:24:01 +0530 Subject: [PATCH 251/372] resolve bugs --- base/constants.py | 2 +- base/messages.py | 6 +++++- guardian/views.py | 6 +++++- junior/views.py | 19 +++++++++++++++---- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/base/constants.py b/base/constants.py index 9688c0f..d376971 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" diff --git a/base/messages.py b/base/messages.py index cbcd559..d87b48e 100644 --- a/base/messages.py +++ b/base/messages.py @@ -101,7 +101,11 @@ ERROR_CODE = { "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", - "2075": "Your account is deactivated. Please contact with admin" + "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", + } """Success message code""" SUCCESS_CODE = { diff --git a/guardian/views.py b/guardian/views.py index 9b9a0aa..37b39cc 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -164,6 +164,10 @@ class CreateTaskAPIView(viewsets.ModelViewSet): try: image = request.data['default_image'] junior = request.data['junior'] + junior_id = Junior.objects.filter(id=junior).last() + guardian_data = Guardian.objects.filter(user=request.user).last() + if guardian_data.guardian_code in junior_id.guardian_code: + 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) @@ -185,7 +189,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): if serializer.is_valid(): # save serializer task = serializer.save() - junior_id = Junior.objects.filter(id=junior).last() + send_notification_to_junior.delay(TASK_ASSIGNED, request.auth.payload['user_id'], junior_id.auth.id, {'task_id': task.id}) return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) diff --git a/junior/views.py b/junior/views.py index 1fdf2be..8df8a09 100644 --- a/junior/views.py +++ b/junior/views.py @@ -40,7 +40,7 @@ from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, Ad 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 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 @@ -171,7 +171,11 @@ 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) return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK) # use AddJuniorSerializer serializer serializer = AddJuniorSerializer(data=request.data, context=info_data) @@ -184,9 +188,16 @@ 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] + if not junior: + return none + if guardian.guardian_code in junior.guardian_code: + return False + if type(junior.guardian_code) is list: + junior.guardian_code.append(guardian.guardian_code) + else: + junior.guardian_code = [guardian.guardian_code] junior.guardian_code_status = str(NUMBER['two']) junior.save() JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, From d202774df15608cd12f94225571aa88f4810ebb3 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 12:39:05 +0530 Subject: [PATCH 252/372] resolve bugs --- guardian/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 37b39cc..9656b9a 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -244,7 +244,7 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): # Update the position field for each JuniorPoints object for index, junior in enumerate(junior_total_points): junior.position = index + 1 - send_notification_to_junior.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {}) + # send_notification_to_junior.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {}) junior.save() serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) From 606c1fa6e62185af48e81fa5413ea1ce2a9381ce Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 21 Aug 2023 14:28:06 +0530 Subject: [PATCH 253/372] minor changes in excel import api method --- web_admin/utils.py | 21 +++++++++- web_admin/views/analytics.py | 74 ++++++++++++------------------------ 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/web_admin/utils.py b/web_admin/utils.py index 4170cf9..3dbb3b2 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -2,8 +2,9 @@ web_utils file """ import base64 +import datetime -from base.constants import ARTICLE_CARD_IMAGE_FOLDER +from base.constants import ARTICLE_CARD_IMAGE_FOLDER, DATE_FORMAT from guardian.utils import upload_image_to_alibaba, upload_base64_image_to_alibaba @@ -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 ddc4e0d..c0747d7 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -6,9 +6,6 @@ import datetime import io import pandas as pd import xlsxwriter -import tempfile -import oss2 -from django.conf import settings # third party imports from rest_framework.viewsets import GenericViewSet @@ -33,7 +30,7 @@ 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, UserCSVReportSerializer -from web_admin.serializers.user_management_serializer import UserManagementListSerializer +from web_admin.utils import get_dates USER = get_user_model() @@ -56,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) @@ -67,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)))) @@ -92,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))] @@ -114,12 +102,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')) assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] @@ -163,12 +147,8 @@ class AnalyticsViewSet(GenericViewSet): response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.xlsx"' - 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')) # Use BytesIO for binary data buffer = io.BytesIO() @@ -177,7 +157,7 @@ class AnalyticsViewSet(GenericViewSet): workbook = xlsxwriter.Workbook(buffer) # Add sheets - sheets = ['Users', 'Assign Tasks', 'Juniors leaderboard'] + sheets = ['Users', 'Assign Tasks', 'Juniors Leaderboard'] for sheet_name in sheets: worksheet = workbook.add_worksheet(name=sheet_name) @@ -195,12 +175,7 @@ class AnalyticsViewSet(GenericViewSet): 'Date Joined': user['date_joined']} for user in serializer.data ]) - for idx, col in enumerate(df_users.columns): - # Write header - worksheet.write(0, idx, col) - for row_num, row in enumerate(df_users.values, start=1): - for col_num, value in enumerate(row): - worksheet.write(row_num, col_num, value) + write_excel_worksheet(worksheet, df_users) # sheet 2 for Assign Task elif sheet_name == 'Assign Tasks': @@ -213,15 +188,10 @@ class AnalyticsViewSet(GenericViewSet): for task in assign_tasks ]) - for idx, col in enumerate(df_tasks.columns): - # Write header - worksheet.write(0, idx, col) - for row_num, row in enumerate(df_tasks.values, start=1): - for col_num, value in enumerate(row): - worksheet.write(row_num, col_num, value) + write_excel_worksheet(worksheet, df_tasks) # sheet 3 for Juniors Leaderboard and rank - elif sheet_name == 'Juniors leaderboard': + elif sheet_name == 'Juniors Leaderboard': queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] @@ -236,12 +206,7 @@ class AnalyticsViewSet(GenericViewSet): for junior in queryset ]) - for idx, col in enumerate(df_leaderboard.columns): - # Write header - worksheet.write(0, idx, col) - for row_num, row in enumerate(df_leaderboard.values, start=1): - for col_num, value in enumerate(row): - worksheet.write(row_num, col_num, value) + write_excel_worksheet(worksheet, df_leaderboard) # Close the workbook to save the content workbook.close() @@ -255,3 +220,12 @@ class AnalyticsViewSet(GenericViewSet): file_link = upload_excel_file_to_alibaba(response, filename) return custom_response(None, file_link) + +def write_excel_worksheet(worksheet, dataframe): + 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 From 1c3d4731c14c5765ddffba0515051d8389beed1e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 15:11:43 +0530 Subject: [PATCH 254/372] swagger fixes --- account/views.py | 8 +++++++- guardian/views.py | 34 ++++++++++++++++++++++++++-------- junior/serializers.py | 10 +++++++++- junior/views.py | 10 +++++++--- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/account/views.py b/account/views.py index 3e09080..a52dff7 100644 --- a/account/views.py +++ b/account/views.py @@ -518,7 +518,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]) diff --git a/guardian/views.py b/guardian/views.py index 9656b9a..6f4cb16 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -32,7 +32,7 @@ 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 @@ -59,6 +59,7 @@ class SignupViewset(viewsets.ModelViewSet): serializer_class = UserSerializer 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(): @@ -72,16 +73,21 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) + 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['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""" @@ -161,6 +167,9 @@ 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'] @@ -252,14 +261,17 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): 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] - + http_method_names = ('post',) def create(self, request, *args, **kwargs): - """ junior list""" + """ Use below param + {"junior_id":"75", + "action":"1"} + """ try: guardian = Guardian.objects.filter(user__email=self.request.user).last() # fetch junior query @@ -285,13 +297,19 @@ class ApproveJuniorAPIView(viewsets.ViewSet): 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] - + 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: guardian = Guardian.objects.filter(user__email=self.request.user).last() diff --git a/junior/serializers.py b/junior/serializers.py index b4aa54f..f79cd46 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -24,7 +24,7 @@ from guardian.utils import real_time, update_referral_points, convert_timedelta_ from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION, TASK_SUBMITTED) - +from web_admin.models import ArticleCard class ListCharField(serializers.ListField): """Serializer for Array field""" @@ -518,3 +518,11 @@ class FAQSerializer(serializers.ModelSerializer): 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/views.py b/junior/views.py index 8df8a09..5eb15bd 100644 --- a/junior/views.py +++ b/junior/views.py @@ -36,7 +36,7 @@ from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, Jun from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer, - RemoveGuardianCodeSerializer, FAQSerializer) + RemoveGuardianCodeSerializer, FAQSerializer, CreateArticleCardSerializer) from guardian.models import Guardian, JuniorTask from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from base.messages import ERROR_CODE, SUCCESS_CODE @@ -65,10 +65,11 @@ 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""" @@ -622,11 +623,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') From a7bce329930a5704552acd4ebd1e51f2c49c9c05 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 15:59:20 +0530 Subject: [PATCH 255/372] signup --- guardian/views.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index 6f4cb16..35fbb26 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -73,10 +73,7 @@ class SignupViewset(viewsets.ModelViewSet): user_type=str(request.data['user_type']), expired_at=expiry) """Send email to the register user""" send_otp_email(request.data['email'], otp) - device_details, created = UserDeviceDetails.objects.get_or_create(user=user) - if device_details: - device_details.device_id = device_id - device_details.save() + 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) From 82189d39534bddf6b1c2a198b4bb190fcaf92d90 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 16:00:39 +0530 Subject: [PATCH 256/372] signup --- guardian/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 109754b..0ec7217 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -63,7 +63,7 @@ class SignupViewset(viewsets.ModelViewSet): 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(): - serializer.save() + user = serializer.save() """Generate otp""" otp = generate_otp() # expire otp after 1 day From 99a59162a21ebb09a57fe230044f7e3de73a4860 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 18:12:03 +0530 Subject: [PATCH 257/372] swagger update --- account/serializers.py | 2 +- account/urls.py | 4 ++-- account/views.py | 6 +++++- guardian/urls.py | 4 +--- guardian/views.py | 2 ++ junior/views.py | 40 ++++++++++++++++++++++++++++++++++------ 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index e946d15..abfb8a6 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -308,7 +308,7 @@ class EmailVerificationSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = UserEmailOtp - fields = '__all__' + fields = ('email',) diff --git a/account/urls.py b/account/urls.py index 02ac124..49d2f53 100644 --- a/account/urls.py +++ b/account/urls.py @@ -39,8 +39,8 @@ 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') +# 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""" diff --git a/account/views.py b/account/views.py index ba99f95..d26703d 100644 --- a/account/views.py +++ b/account/views.py @@ -467,8 +467,11 @@ 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 = timezone.now() + timezone.timedelta(days=1) @@ -489,6 +492,7 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" serializer_class = JuniorProfileSerializer permission_classes = [IsAuthenticated] + http_method_names = ('get',) def list(self, request, *args, **kwargs): """profile view""" 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/views.py b/guardian/views.py index 0ec7217..042150c 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -57,6 +57,7 @@ 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') @@ -209,6 +210,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination + http_method_names = ('get',) def get_queryset(self): """Get the queryset for the view""" diff --git a/junior/views.py b/junior/views.py index 5eb15bd..1ed3972 100644 --- a/junior/views.py +++ b/junior/views.py @@ -13,7 +13,9 @@ import requests 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, @@ -99,7 +101,7 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) -class ValidateGuardianCode(viewsets.ViewSet): +class ValidateGuardianCode(viewsets.ModelViewSet): """Check guardian code exist or not""" permission_classes = [IsAuthenticated] @@ -156,7 +158,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'], @@ -235,12 +244,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', # Query parameter name + openapi.IN_QUERY, # Parameter location + description='title of the name', + type=openapi.TYPE_STRING, # Parameter type + ), + # Add more parameters as needed + ] + ) + def get_queryset(self): """Get the queryset for the view""" title = self.request.GET.get('title') @@ -386,7 +408,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',) @@ -421,7 +443,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) From e380f3f6ab308f12f0a8f033bf147b806ae46669 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 18:44:18 +0530 Subject: [PATCH 258/372] swagger update --- account/urls.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/account/urls.py b/account/urls.py index 49d2f53..c5cdb05 100644 --- a/account/urls.py +++ b/account/urls.py @@ -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""" From 214566ec8f3a9abeed5e4fe68db6eea5d6419c8d Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 21 Aug 2023 19:09:44 +0530 Subject: [PATCH 259/372] Error response --- account/utils.py | 2 +- guardian/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account/utils.py b/account/utils.py index cbdab1e..57fc273 100644 --- a/account/utils.py +++ b/account/utils.py @@ -204,7 +204,7 @@ def custom_error_response(detail, response_status): if not detail: """when details is empty""" detail = {} - return Response({"error": detail, "status": "failed", "code": response_status}) + return Response({"error": detail, "status": "failed", "code": response_status}, status=status.HTTP_400_BAD_REQUEST) def get_user_data(attrs): diff --git a/guardian/views.py b/guardian/views.py index 042150c..843ab45 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -337,7 +337,7 @@ class ApproveTaskAPIView(viewsets.ModelViewSet): 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) From 95dad86b12f17182d6210e430139cc8b1216827d Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 21 Aug 2023 20:07:39 +0530 Subject: [PATCH 260/372] notification changes --- junior/serializers.py | 6 +-- notifications/admin.py | 2 + notifications/constants.py | 80 +++++++++++++++++++++----------------- notifications/utils.py | 14 +++++++ 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index f79cd46..1ca98e4 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -22,7 +22,7 @@ from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian -from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION, +from notifications.constants import (INVITATION, ASSOCIATE_REQUEST, SKIPPED_PROFILE_SETUP, TASK_ACTION, TASK_SUBMITTED) from web_admin.models import ArticleCard @@ -451,8 +451,8 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail(email, full_name) - send_notification_to_junior.delay(INVITED_GUARDIAN, guardian_data.user.id, junior_data.auth.id, {}) - send_notification_to_guardian.delay(APPROVED_JUNIOR, junior_data.auth.id, guardian_data.user.id, {}) + send_notification_to_junior.delay(INVITATION, guardian_data.user.id, junior_data.auth.id, {}) + send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior_data.auth.id, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): diff --git a/notifications/admin.py b/notifications/admin.py index 382e97b..aa57aac 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -10,3 +10,5 @@ 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'] + \ No newline at end of file diff --git a/notifications/constants.py b/notifications/constants.py index 59c2328..f4f8b86 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -1,19 +1,22 @@ """ notification constants file """ -from base.constants import NUMBER -REGISTRATION = NUMBER['one'] -TASK_ASSIGNED = 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 +INVITATION = 2 +ASSOCIATE_REQUEST = 3 +REFERRAL_POINTS = 4 +SKIPPED_PROFILE_SETUP = 5 + +TASK_ASSIGNED = 6 +TASK_SUBMITTED = 7 +TASK_ACTION = 8 +TASK_REJECTED = 9 +TASK_APPROVED = 10 +TASK_POINTS = 11 + +LEADERBOARD_RANKING = 12 +REMOVE_JUNIOR = 13 + TEST_NOTIFICATION = 99 NOTIFICATION_DICT = { @@ -21,41 +24,46 @@ NOTIFICATION_DICT = { "title": "Successfully registered!", "body": "You have registered successfully. Now login and complete your profile." }, - TASK_ASSIGNED: { - "title": "New task assigned !!", - "body": "{from_user} has assigned you a new task." + INVITATION: { + "title": "Invitation sent!", + "body": "Invitation has been successfully sent." }, - INVITED_GUARDIAN: { - "title": "Invite guardian", - "body": "Invite guardian successfully" - }, - APPROVED_JUNIOR: { - "title": "Approve junior !", + ASSOCIATE_REQUEST: { + "title": "Associate request!", "body": "You have request from {from_user} to associate with you." }, 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." - }, - TASK_REJECTED: { - "title": "Task rejected!", - "body": "Your task has been rejected." - }, + # notification once any custodian adds junior in their account SKIPPED_PROFILE_SETUP: { - "title": "Skipped profile setup!", - "body": "Your guardian {from_user} has setup your profile." + "title": "Profile already setup!", + "body": "Your guardian has already setup your profile." + }, + TASK_ASSIGNED: { + "title": "New task assigned!", + "body": "{from_user} has assigned you a new task." }, TASK_SUBMITTED: { "title": "Task submitted!", - "body": "Your task has been submitted successfully." + "body": "Your task has been submitted for approval." }, TASK_ACTION: { "title": "Task completion approval!", - "body": "You have request from {from_user} for task approval." + "body": "You have request from {from_user} for task completion." + }, + TASK_REJECTED: { + "title": "Task completion rejected!", + "body": "Your task completion request has been rejected by {from_user}." + }, + TASK_APPROVED: { + "title": "Task completion approved!", + "body": "Your task completion request has been approved by {from_user}." + }, + TASK_POINTS: { + "title": "Earned Task points!", + "body": "You earn 5 points for task." }, LEADERBOARD_RANKING: { "title": "Leader board rank!", @@ -63,7 +71,7 @@ NOTIFICATION_DICT = { }, REMOVE_JUNIOR: { "title": "Disassociate by guardian!", - "body": "Your guardian disassociate you ." + "body": "Your guardian has disassociated you." }, TEST_NOTIFICATION: { "title": "Test Notification", diff --git a/notifications/utils.py b/notifications/utils.py index 50dee81..0d0b25b 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -97,6 +97,13 @@ def send_push(user, 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: + """ from_user = None if from_user_id: from_user = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first() @@ -109,6 +116,13 @@ def send_notification_to_guardian(notification_type, from_user_id, to_user_id, e @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: + """ from_user = None if from_user_id: from_user = Guardian.objects.filter(user_id=from_user_id).select_related('user').first() From 930c10b578e1add27da0bf280e9ae5e99f1d99d4 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 22 Aug 2023 11:28:52 +0530 Subject: [PATCH 261/372] swagger update --- account/views.py | 59 ++++++++++++++++++++++---- guardian/views.py | 17 +++++--- junior/views.py | 87 ++++++++++++++++++++++++++++---------- notifications/views.py | 6 ++- web_admin/views/article.py | 8 +++- 5 files changed, 137 insertions(+), 40 deletions(-) diff --git a/account/views.py b/account/views.py index d26703d..e4051f4 100644 --- a/account/views.py +++ b/account/views.py @@ -117,13 +117,22 @@ 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") @@ -202,6 +211,10 @@ class ChangePasswordAPIView(views.APIView): def post(self, request): """ POST request to change current login user password + Payload + { "current_password":"Demo@123", + "new_password":"Demo@123" + } """ serializer = ChangePasswordSerializer( context=request.user, @@ -219,7 +232,12 @@ class ChangePasswordAPIView(views.APIView): ) 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(): @@ -236,7 +254,10 @@ class ForgotPasswordAPIView(views.APIView): def post(self, request): """ - Post method to validate serializer + Payload + { + "email": "abc@yopmail.com" + } """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) @@ -420,7 +441,12 @@ 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',) @@ -495,7 +521,9 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): http_method_names = ('get',) def list(self, request, *args, **kwargs): - """profile view""" + """profile view + Params + user_type""" if str(self.request.GET.get('user_type')) == '1': junior_data = Junior.objects.filter(auth=self.request.user).last() if junior_data: @@ -510,6 +538,7 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): 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'] @@ -529,6 +558,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() @@ -565,8 +595,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) @@ -576,9 +607,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(): @@ -588,7 +624,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): diff --git a/guardian/views.py b/guardian/views.py index 843ab45..dd3a1ec 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -129,7 +129,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,) @@ -206,7 +210,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): 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 @@ -221,7 +225,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() @@ -235,10 +239,12 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): - """Top juniors list""" + """Top juniors list + No Params""" queryset = JuniorPoints.objects.all() serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] + http_method_names = ('get',) def get_serializer_context(self): # context list @@ -349,7 +355,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/views.py b/junior/views.py index 1ed3972..62c5fff 100644 --- a/junior/views.py +++ b/junior/views.py @@ -74,7 +74,7 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): 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') @@ -104,9 +104,13 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): 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: @@ -216,7 +220,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): class InvitedJuniorAPIView(viewsets.ModelViewSet): - """Junior list of assosicated guardian""" + """Invited Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer permission_classes = [IsAuthenticated] @@ -230,7 +234,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() @@ -273,7 +278,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() @@ -287,7 +292,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] @@ -315,14 +322,17 @@ class RemoveJuniorAPIView(views.APIView): class JuniorTaskListAPIView(viewsets.ModelViewSet): - """Update guardian profile""" + """Junior task list""" serializer_class = TaskDetailsjuniorSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination http_method_names = ('get',) 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') @@ -352,7 +362,9 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): class CompleteJuniorTaskAPIView(views.APIView): - """Update junior task API""" + """Payload + task_id + image""" serializer_class = CompleteTaskSerializer model = JuniorTask permission_classes = [IsAuthenticated] @@ -397,7 +409,8 @@ 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() queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last() @@ -467,7 +480,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] @@ -491,7 +508,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] @@ -520,7 +543,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') @@ -542,7 +568,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',) @@ -555,7 +581,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() @@ -567,7 +595,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',) @@ -576,7 +606,10 @@ 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') @@ -600,7 +633,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): @@ -614,7 +649,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, @@ -628,12 +664,16 @@ class CompleteArticleAPIView(views.APIView): 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') @@ -677,7 +717,8 @@ 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 + No Payload""" serializer_class = RemoveGuardianCodeSerializer permission_classes = [IsAuthenticated] @@ -714,7 +755,7 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, """ faq create api method :param request: - :param args: + :param args: question, description :param kwargs: :return: success message """ diff --git a/notifications/views.py b/notifications/views.py index 5adb536..154751a 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -77,7 +77,11 @@ class NotificationViewSet(viewsets.GenericViewSet): class ReadNotification(views.APIView): - """Update notification API""" + """Update notification API + Payload + { + "notification_id": [] + }""" serializer_class = ReadNotificationSerializer model = Notification permission_classes = [IsAuthenticated] diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 49f76fa..6570f6f 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -248,7 +248,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',) @@ -257,7 +259,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() From f8e529600b29c799bdb5a07ff6079d9b46433b90 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 22 Aug 2023 13:46:37 +0530 Subject: [PATCH 262/372] force update and mail by celery task --- account/custom_middleware.py | 6 +++++- account/migrations/0010_forceupdate.py | 28 ++++++++++++++++++++++++++ account/models.py | 24 +++++++++++++++++++++- account/views.py | 2 +- base/constants.py | 5 ++++- guardian/views.py | 2 +- junior/serializers.py | 6 +++--- junior/utils.py | 4 +++- 8 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 account/migrations/0010_forceupdate.py diff --git a/account/custom_middleware.py b/account/custom_middleware.py index c2125cd..3193fa9 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -5,7 +5,7 @@ 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 @@ -39,6 +39,8 @@ class CustomMiddleware(object): # 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 @@ -56,4 +58,6 @@ class CustomMiddleware(object): if device_id and 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) + force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last() + 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..c71181e 100644 --- a/account/models.py +++ b/account/models.py @@ -3,7 +3,7 @@ from django.db import models from django.contrib.auth.models import User """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 +165,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/views.py b/account/views.py index e4051f4..84b6aaa 100644 --- a/account/views.py +++ b/account/views.py @@ -509,7 +509,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) diff --git a/base/constants.py b/base/constants.py index d376971..1ab15a2 100644 --- a/base/constants.py +++ b/base/constants.py @@ -50,7 +50,10 @@ USER_TYPE = ( ('2', 'guardian'), ('3', 'superuser') ) - +DEVICE_TYPE = ( + ('1', 'android'), + ('2', 'ios') +) USER_TYPE_FLAG = { "FIRST" : "1", "TWO" : "2", diff --git a/guardian/views.py b/guardian/views.py index dd3a1ec..80c1d9b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -73,7 +73,7 @@ class SignupViewset(viewsets.ModelViewSet): 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_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) diff --git a/junior/serializers.py b/junior/serializers.py index 1ca98e4..01e5adc 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -97,7 +97,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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) + junior_approval_mail.delay(user.email, user.first_name) send_notification_to_guardian.delay(APPROVED_JUNIOR, junior.auth.id, guardian_data.user.id, {}) junior.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) @@ -304,7 +304,7 @@ 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_to_junior.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) return junior_data @@ -450,7 +450,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) - junior_approval_mail(email, full_name) + junior_approval_mail.delay(email, full_name) send_notification_to_junior.delay(INVITATION, guardian_data.user.id, junior_data.auth.id, {}) send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior_data.auth.id, guardian_data.user.id, {}) return guardian_data diff --git a/junior/utils.py b/junior/utils.py index ba177a8..4a6ee2b 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -14,6 +14,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 +34,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 From d573d56a357e51e49f8661c3cd3564bceee9db9d Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 22 Aug 2023 16:01:08 +0530 Subject: [PATCH 263/372] minor changes --- celerybeat-schedule | Bin 16384 -> 16384 bytes junior/serializers.py | 2 +- notifications/admin.py | 1 - notifications/constants.py | 10 +++++++++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index cc8251c7500206755f61cdf0ff0b24e019ea84c5..68297a7db074145de61828e2a94f36b892493ec5 100644 GIT binary patch delta 470 zcmYk2!Aiq07{?p!+~zEbP7jW;DTvNPTorwR;X!(vdht@)F51wp9c?CVFziW0a*=-C zckl&dkKn@ydjXG{(g+5U{QmiW-^ZUM2}%+)ZFUPvzLw;@)6PMYT9JBH>V2uX)SjNh zTsCYSzv|mFzxl3e-_ojxit4Ps_ftZRSsizfrpX}-!dXxIN7D^9PSm%eoXHojuR4%6 z+q;JPj7h*EN@C$xQC=_szCs=bG$K8dS+NJa;t-!iBWx3k0oN`am-L1{ITx)XNa4Um zyp_JRTaUm?J&O@W`{X~S&{-~il{S~_1@)5)#~xCj(I5t1yd%tFED~dQX&eTG2DhQW z8-W)YiubZP3gl{;n)f7%X&9KM_>1!2UlmUA3DyCB=yUBC>S={~ delta 390 zcmX|5F;Buk7`>w)?Lk4om4uKOsRPo%!I_w--Kr*yOVb{wTqrGHdl)exb|MZsnEn9< zHz&i7@+Uaz5gNa5crWkey}UHVX^L0vP62IC&k^;vM5}G_$9y+0*FpQEsOz~^kxqTx zI`~!b=epG!VEr(6Hf4G?IbJng_kqO;k3!c?6{jTildRqEoGULq7K)g&BvlrGx=vME z>~YFtHgtuTi~&>10v6AEKJx^*q9Zn(1?)_Q<^&4e5q1n2$=>?LGbnWE2`2c2k$(ck z0V#j0jS4bU(}5E8XFL!*OvpVGo)9sC9r_f7jE8p-xs~K%L!j(vve%u3Q%=VL19b_S z4a8BTdUHL1iiMX@wLxD(&4#*#=xIJQ$u}m1#!)CdpTKU{LKbqc%pSqsNWOrE4b27Y O`^EyYvHALCYPCNqqGy2s diff --git a/junior/serializers.py b/junior/serializers.py index 1ca98e4..abb0ded 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -98,7 +98,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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_to_guardian.delay(APPROVED_JUNIOR, junior.auth.id, guardian_data.user.id, {}) + send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior.auth.id, guardian_data.user.id, {}) 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) diff --git a/notifications/admin.py b/notifications/admin.py index aa57aac..c7cc895 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -11,4 +11,3 @@ class NotificationAdmin(admin.ModelAdmin): """Notification Admin""" list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read'] list_filter = ['notification_type'] - \ No newline at end of file diff --git a/notifications/constants.py b/notifications/constants.py index f4f8b86..8bb6a6d 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -32,15 +32,17 @@ NOTIFICATION_DICT = { "title": "Associate request!", "body": "You have request from {from_user} to associate with you." }, + # Juniors will receive Notifications for every Points earned by referrals REFERRAL_POINTS: { "title": "Earn Referral points!", "body": "You earn 5 points for referral." }, - # notification once any custodian adds junior in their account + # Juniors will receive notification once any custodians add them in their account SKIPPED_PROFILE_SETUP: { "title": "Profile already setup!", "body": "Your guardian has already setup your profile." }, + # 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." @@ -49,10 +51,12 @@ NOTIFICATION_DICT = { "title": "Task submitted!", "body": "Your task has been submitted for approval." }, + # Guardian will receive notification as soon as junior send task for approval TASK_ACTION: { "title": "Task completion approval!", "body": "You have request from {from_user} for task completion." }, + # Juniors will receive notification as soon as their task is approved or reject by custodians TASK_REJECTED: { "title": "Task completion rejected!", "body": "Your task completion request has been rejected by {from_user}." @@ -61,14 +65,18 @@ NOTIFICATION_DICT = { "title": "Task completion approved!", "body": "Your task completion request has been approved by {from_user}." }, + # Juniors will receive Notifications for every Points earned either by Task completion + # Juniors will receive notification as soon as their task is approved or reject by custodians TASK_POINTS: { "title": "Earned Task points!", "body": "You earn 5 points for task." }, + # Juniors will receive Notification related to Leaderboard progress LEADERBOARD_RANKING: { "title": "Leader board rank!", "body": "Your rank is ." }, + # Juniors will receive notification as soon as their custodians remove them from account REMOVE_JUNIOR: { "title": "Disassociate by guardian!", "body": "Your guardian has disassociated you." From 05fd76a50be3f5d3e4f036b68c176d9b215ecdff Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 22 Aug 2023 18:29:22 +0530 Subject: [PATCH 264/372] force update --- account/admin.py | 15 ++++++++++++++- account/custom_middleware.py | 6 +++++- account/models.py | 1 + account/serializers.py | 10 +++++++++- account/urls.py | 4 +++- account/views.py | 30 ++++++++++++++++++++++++++++-- base/messages.py | 2 ++ guardian/views.py | 2 +- junior/serializers.py | 4 ++-- junior/views.py | 3 --- 10 files changed, 65 insertions(+), 12 deletions(-) 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 3193fa9..f658fd3 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -41,6 +41,7 @@ class CustomMiddleware(object): 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 @@ -59,5 +60,8 @@ class CustomMiddleware(object): custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND) response = custom_response(custom_error) force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last() - + api_endpoint_checks = not any(endpoint in api_endpoint for endpoint in ['/admin/', '/api/v1/admin/']) + if not force_update and api_endpoint_checks: + custom_error = custom_error_response(ERROR_CODE['2079'], response_status=status.HTTP_404_NOT_FOUND) + response = custom_response(custom_error) return response diff --git a/account/models.py b/account/models.py index c71181e..d13762b 100644 --- a/account/models.py +++ b/account/models.py @@ -2,6 +2,7 @@ """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, DEVICE_TYPE # Create your models here. diff --git a/account/serializers.py b/account/serializers.py index abfb8a6..dbb52a0 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 @@ -390,3 +390,11 @@ 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/urls.py b/account/urls.py index c5cdb05..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() @@ -55,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/views.py b/account/views.py index 84b6aaa..9d87c93 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,14 +27,15 @@ 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 @@ -673,3 +675,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["3045"], 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/messages.py b/base/messages.py index d87b48e..ca9edb6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -105,6 +105,8 @@ ERROR_CODE = { "2076": "This junior already associate with you", "2077": "You can not add guardian", "2078": "This junior is not associate with you", + "2079": "Please update your app version for enjoying uninterrupted services", + "2080": "Can not add App version" } """Success message code""" diff --git a/guardian/views.py b/guardian/views.py index 80c1d9b..1bcf86b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -178,7 +178,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): junior = request.data['junior'] junior_id = Junior.objects.filter(id=junior).last() guardian_data = Guardian.objects.filter(user=request.user).last() - if guardian_data.guardian_code in junior_id.guardian_code: + if guardian_data.guardian_code not in junior_id.guardian_code: 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): diff --git a/junior/serializers.py b/junior/serializers.py index 01e5adc..4530350 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -292,7 +292,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): referral_code_used=guardian_data.referral_code, is_password_set=False, is_verified=True, guardian_code_status=GUARDIAN_CODE_STATUS[1][0]) - JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data, relationship=relationship) total_junior = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_data, position=total_junior) @@ -445,7 +445,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): user_type=str(NUMBER['two']), expired_at=expiry_time, is_verified=True) UserNotification.objects.get_or_create(user=user) - JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Notification email""" diff --git a/junior/views.py b/junior/views.py index 62c5fff..b53da2d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -781,6 +781,3 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, data=serializer.data, response_status=status.HTTP_200_OK) - - - From 19acef10ef0974e7401eb27a4a997bf085dd16d8 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 22 Aug 2023 19:03:39 +0530 Subject: [PATCH 265/372] added notitifcation for association rejected, approved, added mark as read api --- guardian/serializers.py | 6 ++-- guardian/views.py | 5 ++-- junior/serializers.py | 9 ++---- notifications/constants.py | 56 ++++++++++++++++---------------------- notifications/views.py | 41 +++------------------------- 5 files changed, 37 insertions(+), 80 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 8db860b..af11acc 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -28,7 +28,7 @@ from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user 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, send_notification_to_junior from django.core.exceptions import ValidationError @@ -420,7 +420,7 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update complete time of task # instance.completed_on = real_time() instance.completed_on = timezone.now().astimezone(pytz.utc) - send_notification_to_junior.delay(TASK_POINTS, None, junior_details.auth.id, + send_notification_to_junior.delay(TASK_APPROVED, instance.guardian.user.id, junior_details.auth.id, {'task_id': instance.id}) else: # reject the task @@ -429,7 +429,7 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update reject time of task # instance.rejected_on = real_time() instance.rejected_on = timezone.now().astimezone(pytz.utc) - send_notification_to_junior.delay(TASK_REJECTED, None, junior_details.auth.id, + send_notification_to_junior.delay(TASK_REJECTED, instance.guardian.user.id, junior_details.auth.id, {'task_id': instance.id}) instance.save() junior_data.save() diff --git a/guardian/views.py b/guardian/views.py index 80c1d9b..3f1b49b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -38,7 +38,7 @@ from account.utils import custom_response, custom_error_response, OTP_EXPIRY, se from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, GUARDIAN_CODE_STATUS from .utils import upload_image_to_alibaba -from notifications.constants import REGISTRATION, TASK_ASSIGNED, LEADERBOARD_RANKING +from notifications.constants import REGISTRATION, TASK_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED from notifications.utils import send_notification_to_junior """ Define APIs """ @@ -259,7 +259,6 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): # Update the position field for each JuniorPoints object for index, junior in enumerate(junior_total_points): junior.position = index + 1 - # send_notification_to_junior.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {}) junior.save() serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) @@ -293,11 +292,13 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): if serializer.is_valid(): # save serializer serializer.save() + send_notification_to_junior.delay(ASSOCIATE_APPROVED, guardian.user.id, junior_queryset.auth.id) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: junior_queryset.guardian_code = None junior_queryset.guardian_code_status = str(NUMBER['one']) junior_queryset.save() + send_notification_to_junior.delay(ASSOCIATE_REJECTED, guardian.user.id, 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) diff --git a/junior/serializers.py b/junior/serializers.py index 040896b..f105dc5 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -22,8 +22,8 @@ from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian -from notifications.constants import (INVITATION, ASSOCIATE_REQUEST, SKIPPED_PROFILE_SETUP, TASK_ACTION, - TASK_SUBMITTED) +from notifications.constants import (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION, + ) from web_admin.models import ArticleCard class ListCharField(serializers.ListField): @@ -307,7 +307,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, password) # push notification - send_notification_to_junior.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) + send_notification_to_junior.delay(JUNIOR_ADDED, None, junior_data.auth.id, {}) return junior_data @@ -339,8 +339,6 @@ class CompleteTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() - send_notification_to_junior.delay(TASK_SUBMITTED, instance.guardian.user.id, - instance.junior.auth.id, {'task_id': instance.id}) send_notification_to_guardian.delay(TASK_ACTION, instance.junior.auth.id, instance.guardian.user.id, {'task_id': instance.id}) return instance @@ -452,7 +450,6 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail.delay(email, full_name) - send_notification_to_junior.delay(INVITATION, guardian_data.user.id, junior_data.auth.id, {}) send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior_data.auth.id, guardian_data.user.id, {}) return guardian_data diff --git a/notifications/constants.py b/notifications/constants.py index 8bb6a6d..0cc5a0f 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -2,19 +2,17 @@ notification constants file """ REGISTRATION = 1 -INVITATION = 2 ASSOCIATE_REQUEST = 3 -REFERRAL_POINTS = 4 -SKIPPED_PROFILE_SETUP = 5 +ASSOCIATE_REJECTED = 4 +ASSOCIATE_APPROVED = 5 +REFERRAL_POINTS = 6 +JUNIOR_ADDED = 7 -TASK_ASSIGNED = 6 -TASK_SUBMITTED = 7 -TASK_ACTION = 8 -TASK_REJECTED = 9 -TASK_APPROVED = 10 -TASK_POINTS = 11 +TASK_ASSIGNED = 8 +TASK_ACTION = 9 +TASK_REJECTED = 10 +TASK_APPROVED = 11 -LEADERBOARD_RANKING = 12 REMOVE_JUNIOR = 13 TEST_NOTIFICATION = 99 @@ -24,21 +22,27 @@ NOTIFICATION_DICT = { "title": "Successfully registered!", "body": "You have registered successfully. Now login and complete your profile." }, - INVITATION: { - "title": "Invitation sent!", - "body": "Invitation has been successfully sent." - }, ASSOCIATE_REQUEST: { "title": "Associate request!", "body": "You have request from {from_user} to associate with you." }, + + ASSOCIATE_REJECTED: { + "title": "Associate request rejected!", + "body": "Your request to associate has been rejected by {from_user}." + }, + + 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!", "body": "You earn 5 points for referral." }, # Juniors will receive notification once any custodians add them in their account - SKIPPED_PROFILE_SETUP: { + JUNIOR_ADDED: { "title": "Profile already setup!", "body": "Your guardian has already setup your profile." }, @@ -47,34 +51,22 @@ NOTIFICATION_DICT = { "title": "New task assigned!", "body": "{from_user} has assigned you a new task." }, - TASK_SUBMITTED: { - "title": "Task submitted!", - "body": "Your task has been submitted for approval." - }, # Guardian will receive notification as soon as junior send task for approval TASK_ACTION: { "title": "Task completion approval!", "body": "You have request from {from_user} for task completion." }, - # Juniors will receive notification as soon as their task is approved or reject by custodians + # 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}." - }, - # Juniors will receive Notifications for every Points earned either by Task completion - # Juniors will receive notification as soon as their task is approved or reject by custodians - TASK_POINTS: { - "title": "Earned Task points!", - "body": "You earn 5 points for task." - }, - # Juniors will receive Notification related to Leaderboard progress - LEADERBOARD_RANKING: { - "title": "Leader board rank!", - "body": "Your rank is ." + "body": "Your task completion request has been approved by {from_user}. " + "Also you earned 5 points for successful completion." }, # Juniors will receive notification as soon as their custodians remove them from account REMOVE_JUNIOR: { diff --git a/notifications/views.py b/notifications/views.py index 154751a..8674e85 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -30,15 +30,8 @@ class NotificationViewSet(viewsets.GenericViewSet): 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) - @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) def fcm_registration(self, request): """ @@ -62,36 +55,10 @@ class NotificationViewSet(viewsets.GenericViewSet): {'task_id': None}) 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=['get'], url_path='mark-as-read', url_name='mark-as-read', detail=True, ) + 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 - Payload - { - "notification_id": [] - }""" - 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=kwargs['pk']).update(is_read=True) + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) From 5f3a9c35fa889675256cf262644ce03f0635a628 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 22 Aug 2023 19:14:57 +0530 Subject: [PATCH 266/372] junior guardiuan code --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index b53da2d..03f8ae7 100644 --- a/junior/views.py +++ b/junior/views.py @@ -206,7 +206,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): guardian = Guardian.objects.filter(user=self.request.user).first() if not junior: return none - if guardian.guardian_code in junior.guardian_code: + if junior.guardian_code and (guardian.guardian_code in junior.guardian_code): return False if type(junior.guardian_code) is list: junior.guardian_code.append(guardian.guardian_code) From 290089b9ef571f6de40209c4c92800d0bce4dcdd Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 22 Aug 2023 19:26:30 +0530 Subject: [PATCH 267/372] added notitifcation for association rejected, approved, added mark as read api --- notifications/urls.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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()), ] From 70685510503792312dd387017b01ceb18cea656a Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 11:16:46 +0530 Subject: [PATCH 268/372] force update not affect admin's api --- account/custom_middleware.py | 5 +++-- account/utils.py | 8 ++++++-- account/views.py | 2 +- base/messages.py | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index f658fd3..fb032dc 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -61,7 +61,8 @@ class CustomMiddleware(object): response = custom_response(custom_error) force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last() api_endpoint_checks = not any(endpoint in api_endpoint for endpoint in ['/admin/', '/api/v1/admin/']) - if not force_update and api_endpoint_checks: - custom_error = custom_error_response(ERROR_CODE['2079'], response_status=status.HTTP_404_NOT_FOUND) + if not force_update and version and device_type: + custom_error = custom_error_response(ERROR_CODE['2079'], + response_status=status.HTTP_308_PERMANENT_REDIRECT) response = custom_response(custom_error) return response diff --git a/account/utils.py b/account/utils.py index 57fc273..04c6791 100644 --- a/account/utils.py +++ b/account/utils.py @@ -204,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}, status=status.HTTP_400_BAD_REQUEST) - + 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): """ diff --git a/account/views.py b/account/views.py index 9d87c93..3b4c62c 100644 --- a/account/views.py +++ b/account/views.py @@ -696,6 +696,6 @@ class ForceUpdateViewSet(GenericViewSet, mixins.CreateModelMixin): obj_data = [ForceUpdate(**item) for item in request.data] try: ForceUpdate.objects.bulk_create(obj_data) - return custom_response(SUCCESS_CODE["3045"], response_status=status.HTTP_200_OK) + 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/messages.py b/base/messages.py index ca9edb6..8930bdc 100644 --- a/base/messages.py +++ b/base/messages.py @@ -173,7 +173,8 @@ SUCCESS_CODE = { # remove guardian code request "3044": "Remove guardian code request successfully", # create faq - "3045": "Create FAQ data" + "3045": "Create FAQ data", + "3046": "Add App version successfully" } """status code error""" From 8d159ac3a489c9c4327e611bac7871365ebe193f Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 11:45:38 +0530 Subject: [PATCH 269/372] changes in profile API --- account/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index 3b4c62c..e68732e 100644 --- a/account/views.py +++ b/account/views.py @@ -526,17 +526,18 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): """profile view Params user_type""" - if str(self.request.GET.get('user_type')) == '1': + 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 From 4e0c6a91f4918efbd290b3289bd8e467602f64d3 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 12:29:53 +0530 Subject: [PATCH 270/372] response 308 in force update --- account/custom_middleware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index fb032dc..ca89a86 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -18,9 +18,9 @@ from guardian.models import Guardian # user can login in single # device at a time""" -def custom_response(custom_error): +def custom_response(custom_error, response_status = status.HTTP_404_NOT_FOUND): """custom response""" - response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND) + 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 @@ -64,5 +64,5 @@ class CustomMiddleware(object): if not force_update and version and device_type: custom_error = custom_error_response(ERROR_CODE['2079'], response_status=status.HTTP_308_PERMANENT_REDIRECT) - response = custom_response(custom_error) + response = custom_response(custom_error, status.HTTP_308_PERMANENT_REDIRECT) return response From 56e1484b87464d508532482acae5b8001b12472d Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 23 Aug 2023 12:54:39 +0530 Subject: [PATCH 271/372] mark ad read api modified --- notifications/serializers.py | 2 ++ notifications/views.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index 14f1b20..2f0222f 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -40,6 +40,8 @@ class NotificationListSerializer(serializers.ModelSerializer): class ReadNotificationSerializer(serializers.ModelSerializer): """User task Serializer""" + id = serializers.ListSerializer(child=serializers.IntegerField()) + class Meta(object): """Meta class""" model = Notification diff --git a/notifications/views.py b/notifications/views.py index 8674e85..d60c429 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -55,10 +55,11 @@ class NotificationViewSet(viewsets.GenericViewSet): {'task_id': None}) return custom_response(SUCCESS_CODE["3000"]) - @action(methods=['get'], url_path='mark-as-read', url_name='mark-as-read', detail=True, ) + @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 """ - Notification.objects.filter(id=kwargs['pk']).update(is_read=True) + 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) From 20644a6293a0f48e88f3863defdbdde13f1435ef Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 17:15:54 +0530 Subject: [PATCH 272/372] google login with user type and is published article only display --- account/views.py | 44 ++++++++++++++++++++++++++++++++------ web_admin/views/article.py | 2 +- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/account/views.py b/account/views.py index e68732e..2785ae1 100644 --- a/account/views.py +++ b/account/views.py @@ -51,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) @@ -84,14 +85,24 @@ 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': 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) 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, @@ -109,6 +120,10 @@ class GoogleLoginMixin(object): referral_code=generate_code(ZOD, user_obj.id) ) serializer = GuardianSerializer(guardian_query) + 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) @@ -137,7 +152,8 @@ class SigninWithApple(views.APIView): }""" 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} @@ -145,11 +161,21 @@ 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) + 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) if str(user_type) == '2': - guardian_query = Guardian.objects.filter(user=user).last() - serializer = GuardianSerializer(guardian_query) + 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) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -169,6 +195,10 @@ class SigninWithApple(views.APIView): guardian_code=generate_code(GRD, user.id), referral_code=generate_code(ZOD, user.id)) serializer = GuardianSerializer(guardian_query) + device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_obj) + 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: diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 6570f6f..f362f8b 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -229,7 +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 = self.queryset.objects.filter(is_deleted=False, is_published=True).prefetch_related( 'article_cards', 'article_survey', 'article_survey__options' ).order_by('-created_at') queryset = self.filter_queryset(article) From 89dda61bff58e1afd5782fce695974c1a0f7a710 Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 17:46:20 +0530 Subject: [PATCH 273/372] google login with user type and is published article only display --- account/views.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/account/views.py b/account/views.py index 2785ae1..8dbb2f7 100644 --- a/account/views.py +++ b/account/views.py @@ -91,7 +91,7 @@ class GoogleLoginMixin(object): 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( @@ -99,6 +99,11 @@ class GoogleLoginMixin(object): 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) @@ -113,13 +118,19 @@ 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 @@ -168,7 +179,7 @@ class SigninWithApple(views.APIView): response_status=status.HTTP_400_BAD_REQUEST ) serializer = JuniorSerializer(junior_data) - if str(user_type) == '2': + elif str(user_type) == '2': guardian_data = Guardian.objects.filter(user=user).last() if not guardian_data: return custom_error_response( @@ -176,6 +187,11 @@ class SigninWithApple(views.APIView): 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) @@ -189,13 +205,19 @@ 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) - device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_obj) + 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() From bb3441e4eda4ec1e05ec9fad4e5fd679003316de Mon Sep 17 00:00:00 2001 From: jain Date: Wed, 23 Aug 2023 19:23:31 +0530 Subject: [PATCH 274/372] remove guardian code in both case --- junior/serializers.py | 7 +++++-- junior/views.py | 25 ++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 4530350..ec85608 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -505,8 +505,11 @@ 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 self.instance and guardian_code in self.instance.guardian_code: + instance.guardian_code.remove(guardian_code) + if len(instance.guardian_code) == 0: + instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index 03f8ae7..558d9e5 100644 --- a/junior/views.py +++ b/junior/views.py @@ -724,10 +724,29 @@ class RemoveGuardianCodeAPIView(views.APIView): def put(self, request, format=None): try: - junior_queryset = Junior.objects.filter(auth=self.request.user).last() - if junior_queryset: + user_type = request.META.get('HTTP_USER_TYPE') + junior_id = request.data.get('junior_id') + guardian_id = request.data.get('guardian_id') + my_dict = {} + if user_type and str(user_type) == str(NUMBER['one']) and guardian_id: + queryset = Junior.objects.filter(auth__email=self.request.user, is_active=True, is_deleted=False).last() + guardian_code = Guardian.objects.filter(id=guardian_id).last().guardian_code + my_dict.update({"guardian_code": guardian_code}) + elif user_type and str(user_type) == str(NUMBER['two']) and junior_id: + guardian_code = Guardian.objects.filter(user=self.request.user, + is_active=True, is_deleted=False).last().guardian_code + queryset = Junior.objects.filter(id=junior_id).last() + my_dict.update({"guardian_code": guardian_code}) + else: + return custom_error_response( + ERROR_CODE["2069"], + response_status=status.HTTP_400_BAD_REQUEST + ) + if queryset: # use RemoveGuardianCodeSerializer serializer - serializer = RemoveGuardianCodeSerializer(junior_queryset, data=request.data, partial=True) + serializer = RemoveGuardianCodeSerializer(queryset, data=request.data, + context=my_dict, + partial=True) if serializer.is_valid(): # save serializer serializer.save() From 8f9450b7432123513e514823fc31229ae48b8180 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 11:10:20 +0530 Subject: [PATCH 275/372] set guardian code is None --- account/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/utils.py b/account/utils.py index 04c6791..956ae23 100644 --- a/account/utils.py +++ b/account/utils.py @@ -93,7 +93,7 @@ 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 = None junior_data.guardian_code_status = str(NUMBER['one']) junior_data.is_deleted = True junior_data.save() From f54160865655df1f1a1ebccae5111697fc283985 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 12:10:18 +0530 Subject: [PATCH 276/372] changed notification method, added celery task to notify expiring task --- account/utils.py | 3 +- base/tasks.py | 29 ++++++++++++++ celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 12 +++--- guardian/utils.py | 4 +- guardian/views.py | 13 ++++--- junior/serializers.py | 14 +++---- junior/views.py | 4 +- notifications/constants.py | 26 ++++++++++--- notifications/utils.py | 77 ++++++++++++++++++++++--------------- notifications/views.py | 35 ++++++++++++----- zod_bank/celery.py | 7 ++++ 12 files changed, 153 insertions(+), 71 deletions(-) diff --git a/account/utils.py b/account/utils.py index 04c6791..59693c7 100644 --- a/account/utils.py +++ b/account/utils.py @@ -282,8 +282,9 @@ 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.last_name else user_obj.first_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/base/tasks.py b/base/tasks.py index 791be81..a737380 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -1,6 +1,8 @@ """ web_admin tasks file """ +import datetime + # third party imports from celery import shared_task from templated_email import send_templated_mail @@ -8,6 +10,12 @@ from templated_email import send_templated_mail # django imports from django.conf import settings +# local imports +from base.constants import PENDING, IN_PROGRESS, JUNIOR +from guardian.models import JuniorTask +from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING +from notifications.utils import send_notification + @shared_task def send_email_otp(email, verification_code): @@ -27,3 +35,24 @@ def send_email_otp(email, verification_code): } ) 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 diff --git a/celerybeat-schedule b/celerybeat-schedule index 68297a7db074145de61828e2a94f36b892493ec5..fef85ada273c6b1f27fa57f6340942760415d802 100644 GIT binary patch delta 28 kcmZo@U~Fh$+;GK!U4l)Po26~j;j7*z#4KMQn0EVLpBme*a delta 28 kcmZo@U~Fh$+;GK!U6f0fmE~~H;jEtLg4KMQn0EeOpH2?qr diff --git a/guardian/serializers.py b/guardian/serializers.py index af11acc..3e24265 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -24,13 +24,13 @@ 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_APPROVED, TASK_REJECTED # send notification function -from notifications.utils import send_notification, send_notification_to_junior +from notifications.utils import send_notification from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -420,8 +420,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_to_junior.delay(TASK_APPROVED, instance.guardian.user.id, junior_details.auth.id, - {'task_id': instance.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']) @@ -429,8 +429,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_to_junior.delay(TASK_REJECTED, instance.guardian.user.id, junior_details.auth.id, - {'task_id': instance.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 diff --git a/guardian/utils.py b/guardian/utils.py index 14bd36a..80cbc56 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -21,7 +21,7 @@ from zod_bank.celery import app # notification's constant from notifications.constants import REFERRAL_POINTS # send notification function -from notifications.utils import send_notification, send_notification_to_junior +from notifications.utils import send_notification # Define upload image on @@ -117,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_to_junior.delay(REFERRAL_POINTS, None, junior_queryset.auth.id, {}) + send_notification.delay(REFERRAL_POINTS, None, None, junior_queryset.auth.id, {}) diff --git a/guardian/views.py b/guardian/views.py index e49beaf..2cda5c1 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -36,10 +36,10 @@ 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_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED -from notifications.utils import send_notification_to_junior +from notifications.utils import send_notification """ Define APIs """ # Define Signup API, @@ -202,8 +202,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): # save serializer task = serializer.save() - send_notification_to_junior.delay(TASK_ASSIGNED, request.auth.payload['user_id'], - junior_id.auth.id, {'task_id': task.id}) + 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) except Exception as e: @@ -292,13 +292,14 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): if serializer.is_valid(): # save serializer serializer.save() - send_notification_to_junior.delay(ASSOCIATE_APPROVED, guardian.user.id, junior_queryset.auth.id) + 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: junior_queryset.guardian_code = None junior_queryset.guardian_code_status = str(NUMBER['one']) junior_queryset.save() - send_notification_to_junior.delay(ASSOCIATE_REJECTED, guardian.user.id, junior_queryset.auth.id) + 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) diff --git a/junior/serializers.py b/junior/serializers.py index 07a38cb..8c00bfb 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -16,12 +16,12 @@ from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, Juni 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 guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime -from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian +from notifications.utils import send_notification from notifications.constants import (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION, ) from web_admin.models import ArticleCard @@ -98,7 +98,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) junior.guardian_code_status = str(NUMBER['three']) junior_approval_mail.delay(user.email, user.first_name) - send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior.auth.id, guardian_data.user.id, {}) + send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) junior.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) @@ -307,7 +307,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, password) # push notification - send_notification_to_junior.delay(JUNIOR_ADDED, None, junior_data.auth.id, {}) + send_notification.delay(JUNIOR_ADDED, None, None, junior_data.auth.id, {}) return junior_data @@ -339,8 +339,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() - send_notification_to_guardian.delay(TASK_ACTION, instance.junior.auth.id, - instance.guardian.user.id, {'task_id': instance.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): @@ -450,7 +450,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail.delay(email, full_name) - send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior_data.auth.id, guardian_data.user.id, {}) + send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth.id, JUNIOR, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): diff --git a/junior/views.py b/junior/views.py index 03f8ae7..15aa7f9 100644 --- a/junior/views.py +++ b/junior/views.py @@ -46,7 +46,7 @@ from base.constants import NUMBER, ARTICLE_STATUS, none 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, send_notification_to_junior +from notifications.utils import send_notification from notifications.constants import REMOVE_JUNIOR from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, @@ -312,7 +312,7 @@ class RemoveJuniorAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() - send_notification_to_junior.delay(REMOVE_JUNIOR, None, junior_queryset.auth.id, {}) + 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: diff --git a/notifications/constants.py b/notifications/constants.py index 0cc5a0f..f94e4aa 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -12,8 +12,9 @@ TASK_ASSIGNED = 8 TASK_ACTION = 9 TASK_REJECTED = 10 TASK_APPROVED = 11 - -REMOVE_JUNIOR = 13 +PENDING_TASK_EXPIRING = 12 +IN_PROGRESS_TASK_EXPIRING = 13 +REMOVE_JUNIOR = 15 TEST_NOTIFICATION = 99 @@ -22,16 +23,18 @@ NOTIFICATION_DICT = { "title": "Successfully registered!", "body": "You have registered successfully. Now login and complete your profile." }, + # 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." }, - + # 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}." }, - + # 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}." @@ -54,7 +57,7 @@ NOTIFICATION_DICT = { # Guardian will receive notification as soon as junior send task for approval TASK_ACTION: { "title": "Task completion approval!", - "body": "You have request from {from_user} for task completion." + "body": "{from_user} completed her task {task_name}." }, # Juniors will receive notification as soon as their task is rejected by custodians TASK_REJECTED: { @@ -68,11 +71,24 @@ NOTIFICATION_DICT = { "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 as soon as their custodians remove them from account REMOVE_JUNIOR: { "title": "Disassociate by guardian!", "body": "Your guardian has disassociated you." }, + # Test notification TEST_NOTIFICATION: { "title": "Test Notification", "body": "This notification is for testing purpose from {from_user}." diff --git a/notifications/utils.py b/notifications/utils.py index 0d0b25b..bcb9749 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -11,7 +11,8 @@ from django.contrib.auth import get_user_model from account.models import UserNotification from account.utils import get_user_full_name -from guardian.models import Guardian +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 @@ -42,44 +43,64 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None: print(e) -def get_basic_detail(from_user_id, to_user_id): +def get_from_user_details(from_user_id, from_user_type): """ - used to get the basic details + used to get from user details """ - 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 from_user, to_user + 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, to_user, extra_data): +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: from_user obj - :param to_user: to_user obj + :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() - notification_data['to_user'] = get_user_full_name(to_user) - if from_user: - from_user_name = get_user_full_name(from_user) - push_data['body'] = push_data['body'].format(from_user=from_user_name) - notification_data['body'] = notification_data['body'].format(from_user=from_user_name) - notification_data['from_user'] = from_user_name + task_name = 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_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) + notification_data['body'] = notification_data['body'].format(from_user=from_user_name, task_name=task_name) + notification_data['from_user'] = from_user_name + notification_data['from_user_image'] = from_user_image notification_data.update(extra_data) - - return notification_data, push_data + to_user = User.objects.get(id=to_user_id) + return notification_data, push_data, from_user, to_user -def send_notification(notification_type, from_user, to_user, extra_data): +@shared_task() +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 """ - # (from_user, to_user) = get_basic_detail(from_user_id, to_user_id) - notification_data, push_data = get_notification_data(notification_type, from_user, to_user, extra_data) + 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() 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, @@ -104,14 +125,10 @@ def send_notification_to_guardian(notification_type, from_user_id, to_user_id, e :param extra_data: :return: """ - from_user = None if from_user_id: - from_user = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first() + from_user = Junior.objects.filter(auth_id=from_user_id).first() extra_data['from_user_image'] = from_user.image - from_user = from_user.auth - to_user = Guardian.objects.filter(user_id=to_user_id).select_related('user').first() - extra_data['to_user_image'] = to_user.image - send_notification(notification_type, from_user, to_user.user, extra_data) + send_notification(notification_type, from_user_id, to_user_id, extra_data) @shared_task() @@ -123,13 +140,9 @@ def send_notification_to_junior(notification_type, from_user_id, to_user_id, ext :param extra_data: :return: """ - from_user = None if from_user_id: - from_user = Guardian.objects.filter(user_id=from_user_id).select_related('user').first() + from_user = Guardian.objects.filter(user_id=from_user_id).first() extra_data['from_user_image'] = from_user.image - from_user = from_user.user - to_user = Junior.objects.filter(auth_id=to_user_id).select_related('auth').first() - extra_data['to_user_image'] = to_user.image - send_notification(notification_type, from_user, to_user.auth, extra_data) + send_notification(notification_type, from_user_id, to_user_id, extra_data) diff --git a/notifications/views.py b/notifications/views.py index d60c429..8c0e07c 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -4,27 +4,35 @@ 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.constants import JUNIOR, GUARDIAN from base.messages import SUCCESS_CODE, ERROR_CODE +from base.tasks import notify_task_expiry +from guardian.models import Guardian from notifications.constants import TEST_NOTIFICATION -# Import serializer from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer -from notifications.utils import send_notification, send_notification_to_guardian, send_notification_to_junior -# Import model +from notifications.utils import send_notification 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() @@ -49,10 +57,8 @@ class NotificationViewSet(viewsets.GenericViewSet): to send test notification :return: """ - send_notification_to_guardian.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) - send_notification_to_junior.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) + send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'], + {}) return custom_response(SUCCESS_CODE["3000"]) @action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False, @@ -63,3 +69,12 @@ class NotificationViewSet(viewsets.GenericViewSet): """ 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) + + @action(methods=['get'], url_path='task', url_name='task', detail=False, + permission_classes=[AllowAny]) + def task(self, request, *args, **kwargs): + """ + notification list + """ + notify_task_expiry() + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) diff --git a/zod_bank/celery.py b/zod_bank/celery.py index 5cc2829..93cfb8f 100644 --- a/zod_bank/celery.py +++ b/zod_bank/celery.py @@ -27,6 +27,13 @@ app.config_from_object('django.conf:settings') # Load task modules from all registered Django apps. app.autodiscover_tasks() +app.conf.beat_schedule = { + 'notify_task_expiry': { + 'task': 'base.tasks.notify_task_expiry', + 'schedule': crontab(minute='0', hour='*/1'), + }, +} + @app.task(bind=True) def debug_task(self): From 8cd4864748fde6f40ed78af9eafafdccb0928c1c Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 13:13:25 +0530 Subject: [PATCH 277/372] add three guardian at a time --- base/messages.py | 3 ++- junior/serializers.py | 13 +++++++++---- junior/views.py | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/base/messages.py b/base/messages.py index 8930bdc..9afbe4f 100644 --- a/base/messages.py +++ b/base/messages.py @@ -106,7 +106,8 @@ ERROR_CODE = { "2077": "You can not add guardian", "2078": "This junior is not associate with you", "2079": "Please update your app version for enjoying uninterrupted services", - "2080": "Can not add App version" + "2080": "Can not add App version", + "2081": "You can not add more than 3 guardian" } """Success message code""" diff --git a/junior/serializers.py b/junior/serializers.py index edeee71..48eaea9 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -88,11 +88,16 @@ 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 not junior.guardian_code: + junior.guardian_code = [] + junior.guardian_code.extend(guardian_code) + elif len(junior.guardian_code) < 3 and len(guardian_code) < 3: + junior.guardian_code.extend(guardian_code) + 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) diff --git a/junior/views.py b/junior/views.py index 558d9e5..8b44e93 100644 --- a/junior/views.py +++ b/junior/views.py @@ -99,7 +99,8 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): 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) + error_detail = e.detail.get('error', None) + return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST) class ValidateGuardianCode(viewsets.ModelViewSet): """Check guardian code exist or not""" From 11605540d77f95f2cfbd2b517dd818d45bf2ac81 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 13:18:23 +0530 Subject: [PATCH 278/372] remove guardian code request --- junior/serializers.py | 9 ++------- junior/views.py | 25 +++---------------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 48eaea9..379b393 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -499,8 +499,6 @@ class ReAssignTaskSerializer(serializers.ModelSerializer): instance.save() return instance - - class RemoveGuardianCodeSerializer(serializers.ModelSerializer): """User task Serializer""" class Meta(object): @@ -508,11 +506,8 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): model = Junior fields = ('id', ) def update(self, instance, validated_data): - guardian_code = self.context['guardian_code'] - if self.instance and guardian_code in self.instance.guardian_code: - instance.guardian_code.remove(guardian_code) - if len(instance.guardian_code) == 0: - instance.guardian_code_status = str(NUMBER['one']) + instance.guardian_code = None + instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index 8b44e93..2abd61d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -725,29 +725,10 @@ class RemoveGuardianCodeAPIView(views.APIView): def put(self, request, format=None): try: - user_type = request.META.get('HTTP_USER_TYPE') - junior_id = request.data.get('junior_id') - guardian_id = request.data.get('guardian_id') - my_dict = {} - if user_type and str(user_type) == str(NUMBER['one']) and guardian_id: - queryset = Junior.objects.filter(auth__email=self.request.user, is_active=True, is_deleted=False).last() - guardian_code = Guardian.objects.filter(id=guardian_id).last().guardian_code - my_dict.update({"guardian_code": guardian_code}) - elif user_type and str(user_type) == str(NUMBER['two']) and junior_id: - guardian_code = Guardian.objects.filter(user=self.request.user, - is_active=True, is_deleted=False).last().guardian_code - queryset = Junior.objects.filter(id=junior_id).last() - my_dict.update({"guardian_code": guardian_code}) - else: - return custom_error_response( - ERROR_CODE["2069"], - response_status=status.HTTP_400_BAD_REQUEST - ) - if queryset: + junior_queryset = Junior.objects.filter(auth=self.request.user).last() + if junior_queryset: # use RemoveGuardianCodeSerializer serializer - serializer = RemoveGuardianCodeSerializer(queryset, data=request.data, - context=my_dict, - partial=True) + serializer = RemoveGuardianCodeSerializer(junior_queryset, data=request.data, partial=True) if serializer.is_valid(): # save serializer serializer.save() From ab1a2be679617f456738bb08b3559b97177f90f8 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 13:32:08 +0530 Subject: [PATCH 279/372] remove junior by guardian --- junior/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/urls.py b/junior/urls.py index 08fe33a..3c597c3 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -62,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()) ] From cd3b385756f5bdbdfc106d440d84f5b670dd398d Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 13:33:34 +0530 Subject: [PATCH 280/372] added optional user as name --- base/tasks.py | 21 +++++++++++++++++++ notifications/utils.py | 5 ++--- notifications/views.py | 2 -- web_admin/serializers/analytics_serializer.py | 12 ++++++++++- web_admin/serializers/auth_serializer.py | 10 +++++---- .../serializers/user_management_serializer.py | 7 ++++--- web_admin/views/analytics.py | 13 ++++++------ 7 files changed, 51 insertions(+), 19 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index a737380..bd1892b 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -37,6 +37,27 @@ def send_email_otp(email, verification_code): return True +@shared_task +def send_mail(recipient_list, template, context: dict = None): + """ + used to send otp on email + :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 = recipient_list + send_templated_mail( + template_name=template, + from_email=from_email, + recipient_list=recipient_list, + context=context + ) + return True + + @shared_task() def notify_task_expiry(): """ diff --git a/notifications/utils.py b/notifications/utils.py index bcb9749..7623673 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -9,6 +9,7 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati # django imports from django.contrib.auth import get_user_model +# local imports from account.models import UserNotification from account.utils import get_user_full_name from base.constants import GUARDIAN, JUNIOR @@ -17,8 +18,6 @@ from junior.models import Junior from notifications.constants import NOTIFICATION_DICT from notifications.models import Notification -# local imports - User = get_user_model() @@ -105,7 +104,7 @@ def send_notification(notification_type, from_user_id, from_user_type, to_user_i 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=notification_data) - if user_notification_type.push_notification: + if user_notification_type and user_notification_type.push_notification: send_push(to_user, push_data) diff --git a/notifications/views.py b/notifications/views.py index 8c0e07c..4546317 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -10,10 +10,8 @@ from rest_framework import viewsets, status, views # local imports from account.utils import custom_response, custom_error_response -from base.constants import JUNIOR, GUARDIAN from base.messages import SUCCESS_CODE, ERROR_CODE from base.tasks import notify_task_expiry -from guardian.models import Guardian from notifications.constants import TEST_NOTIFICATION from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer from notifications.utils import send_notification diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 0e3418a..b0177d7 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -7,6 +7,7 @@ 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 @@ -74,6 +75,7 @@ class UserCSVReportSerializer(serializers.ModelSerializer): """ user csv/xls report serializer """ + name = serializers.SerializerMethodField() phone_number = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() @@ -84,7 +86,15 @@ class UserCSVReportSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('first_name', 'last_name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined') + 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): diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 712e284..585b2c9 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_mail 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_mail.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 21a0a3b..c8d0b1f 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,6 +5,7 @@ web_admin user_management serializers file from rest_framework import serializers from django.contrib.auth import get_user_model +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 @@ -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): @@ -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): @@ -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): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index face900..4867bc1 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -107,13 +107,15 @@ class AnalyticsViewSet(GenericViewSet): 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) @@ -169,10 +171,9 @@ class AnalyticsViewSet(GenericViewSet): serializer = UserCSVReportSerializer(queryset, many=True) df_users = pd.DataFrame([ - {'Name': f"{user['first_name']} {user['last_name']}", - 'Email': user['email'], 'Phone Number': user['phone_number'], - 'User Type': user['user_type'], 'Status': user['is_active'], - 'Date Joined': user['date_joined']} + {'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) @@ -181,7 +182,7 @@ class AnalyticsViewSet(GenericViewSet): elif sheet_name == 'Assign Tasks': assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] - ).exclude(task_status__in=[PENDING, EXPIRED]) + ) df_tasks = pd.DataFrame([ {'Task Name': task.task_name, 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()} From 930b58cf05910704c4d05cc7b2ca4ec7e53e80e6 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 14:51:24 +0530 Subject: [PATCH 281/372] added optional name as user --- web_admin/views/analytics.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 4867bc1..1908670 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -22,7 +22,7 @@ from django.db.models.functions.window import Rank from django.http import HttpResponse # local imports -from account.utils import custom_response +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 @@ -44,7 +44,7 @@ class AnalyticsViewSet(GenericViewSet): to get junior leaderboard and ranking """ serializer_class = None - permission_classes = [IsAuthenticated, AdminPermission] + # permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -182,10 +182,16 @@ class AnalyticsViewSet(GenericViewSet): 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, 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()} + {'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 ]) @@ -199,8 +205,7 @@ class AnalyticsViewSet(GenericViewSet): )).order_by('-total_points', 'junior__created_at')[:15] df_leaderboard = pd.DataFrame([ { - 'Junior Name': f"{junior.junior.auth.first_name} {junior.junior.auth.last_name}" - if junior.junior.auth.last_name else junior.junior.auth.first_name, + 'Junior Name': get_user_full_name(junior.junior.auth), 'Points': junior.total_points, 'Rank': junior.rank } From 3072bd5cdbf47190a0fb09c78c0c9408c3aecae0 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 14:53:43 +0530 Subject: [PATCH 282/372] added optional name as user --- web_admin/views/analytics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 1908670..4a010a3 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -44,7 +44,7 @@ class AnalyticsViewSet(GenericViewSet): to get junior leaderboard and ranking """ serializer_class = None - # permission_classes = [IsAuthenticated, AdminPermission] + permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -205,7 +205,7 @@ class AnalyticsViewSet(GenericViewSet): )).order_by('-total_points', 'junior__created_at')[:15] df_leaderboard = pd.DataFrame([ { - 'Junior Name': get_user_full_name(junior.junior.auth), + 'Name': get_user_full_name(junior.junior.auth), 'Points': junior.total_points, 'Rank': junior.rank } From e9aa2dfda98d527f7efdaa7122a92c728a726cdf Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 16:08:20 +0530 Subject: [PATCH 283/372] limit for 3 guardian code and article list API optimization --- base/constants.py | 3 ++- guardian/views.py | 5 +++-- junior/views.py | 8 ++++++-- web_admin/serializers/article_serializer.py | 10 +++++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/base/constants.py b/base/constants.py index 1ab15a2..da203ae 100644 --- a/base/constants.py +++ b/base/constants.py @@ -43,7 +43,8 @@ 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'), diff --git a/guardian/views.py b/guardian/views.py index e49beaf..b541660 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,7 +10,7 @@ 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 @@ -178,7 +178,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): junior = request.data['junior'] junior_id = Junior.objects.filter(id=junior).last() guardian_data = Guardian.objects.filter(user=request.user).last() - if guardian_data.guardian_code not in junior_id.guardian_code: + if (guardian_data.guardian_code not in junior_id.guardian_code or + junior_id.guardian_code_status in guardian_code_tuple): 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): diff --git a/junior/views.py b/junior/views.py index 2abd61d..96b9222 100644 --- a/junior/views.py +++ b/junior/views.py @@ -191,6 +191,8 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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) + if 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) @@ -209,10 +211,12 @@ class AddJuniorAPIView(viewsets.ModelViewSet): return none if junior.guardian_code and (guardian.guardian_code in junior.guardian_code): return False - if type(junior.guardian_code) is list: + if not junior.guardian_code: + junior.guardian_code = [guardian.guardian_code] + if type(junior.guardian_code) is list and len(junior.guardian_code) < 4: junior.guardian_code.append(guardian.guardian_code) else: - junior.guardian_code = [guardian.guardian_code] + return "Max" junior.guardian_code_status = str(NUMBER['two']) junior.save() JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index e125acf..62d09d4 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -219,8 +219,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,8 +228,13 @@ 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() From 1028822908ae731881aef25a3f35b963b83cb093 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 18:00:14 +0530 Subject: [PATCH 284/372] article-list optimization --- account/custom_middleware.py | 22 +++++++++++---------- web_admin/serializers/article_serializer.py | 5 ++--- web_admin/views/article.py | 5 +---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index ca89a86..42a3b0f 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -45,7 +45,11 @@ class CustomMiddleware(object): 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: + 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: @@ -56,13 +60,11 @@ class CustomMiddleware(object): if guardian: custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND) response = custom_response(custom_error) - if device_id and 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) - force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last() - api_endpoint_checks = not any(endpoint in api_endpoint for endpoint in ['/admin/', '/api/v1/admin/']) - if not force_update and version and device_type: - 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) + + 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/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 62d09d4..675591f 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -237,8 +237,7 @@ class ArticleListSerializer(serializers.ModelSerializer): 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""" @@ -278,7 +277,7 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): """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() + question=obj).last() if junior_article_obj: return junior_article_obj.submitted_answer.id return None diff --git a/web_admin/views/article.py b/web_admin/views/article.py index f362f8b..902f579 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -229,10 +229,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): http_method_names = ['get',] def get_queryset(self): - article = self.queryset.objects.filter(is_deleted=False, is_published=True).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): From f5a03e2fdf888abaad174665b1018ce4e0bff784 Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 18:26:15 +0530 Subject: [PATCH 285/372] assement id --- web_admin/serializers/article_serializer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 675591f..4e98e55 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -271,14 +271,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).last() - if junior_article_obj: + if junior_article_obj and junior_article_obj.submitted_answer: return junior_article_obj.submitted_answer.id return None From a65eb2f77d8146eacb3692075fd25f9e72a571ae Mon Sep 17 00:00:00 2001 From: jain Date: Thu, 24 Aug 2023 18:58:31 +0530 Subject: [PATCH 286/372] assessment answer --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index 96b9222..b91a8cd 100644 --- a/junior/views.py +++ b/junior/views.py @@ -620,7 +620,7 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): 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: From 2e0ceb8c920d5e3fc9891e5467698f99265fb432 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 19:34:59 +0530 Subject: [PATCH 287/372] modified top-list api, junior-list api and junior-points api, nodified rank method, added mail for deactivating user from admin --- .../templated_email/user_deactivate.email | 22 +++++++++++ base/tasks.py | 23 +---------- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 9 ++++- guardian/views.py | 18 +++++---- junior/serializers.py | 12 ++---- junior/utils.py | 19 ++++++++- junior/views.py | 4 +- web_admin/serializers/auth_serializer.py | 4 +- web_admin/views/user_management.py | 37 +++++++++++------- zod_bank/celery.py | 17 +++----- 11 files changed, 94 insertions(+), 71 deletions(-) create mode 100644 account/templates/templated_email/user_deactivate.email 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/base/tasks.py b/base/tasks.py index bd1892b..2056493 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -18,27 +18,7 @@ from notifications.utils import send_notification @shared_task -def send_email_otp(email, verification_code): - """ - used to send otp on email - :param email: e-mail - :param verification_code: otp - """ - 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 - } - ) - return True - - -@shared_task -def send_mail(recipient_list, template, context: dict = None): +def send_email(recipient_list, template, context: dict = None): """ used to send otp on email :param context: @@ -48,7 +28,6 @@ def send_mail(recipient_list, template, context: dict = None): if context is None: context = {} from_email = settings.EMAIL_FROM_ADDRESS - recipient_list = recipient_list send_templated_mail( template_name=template, from_email=from_email, diff --git a/celerybeat-schedule b/celerybeat-schedule index fef85ada273c6b1f27fa57f6340942760415d802..49e872897d5b39cf5fa0b1af41e7e7fe192562d5 100644 GIT binary patch delta 598 zcmZo@U~Fh$oS?(X%m4(F85Pu-W-?CpQc&9H(8|A=QGi`wvW9|!l^P>Rnt{R9079>U z^4A+f_}@7oG^aU)4&;H*>y0-jDXin=jW&hw*@4vZ&9ZtwnMG6cN{TX5iyK&`wnGi8 zouaWh(m;T*zDFo8za%rQGQK3SI6FSIq9C)Va!Lk^Muu{aY*J!zsvbzZSPz>b4rea* z=NuATk|s>X2d89+woM6|;?0mDrP0HzrEfSTLmC20fWoy?k~C|9B#2$Ahj7j;E@It- w)ty8;QGtQsfI7^H?0B3gi|NKW1LZ^}V}mI@Ooo=wkc5RJ7a|}x8h+;h0QYRWSpWb4 delta 61 zcmZo@U~Fh$oS?I@q?dnUgU4n@0d|3j77CL&6cm_V@lK3V+MJ`XgqPRVfB_8Hfu#Iq NS-qdk8w+3Y0suQY5h(xw diff --git a/guardian/serializers.py b/guardian/serializers.py index 3e24265..7146039 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -316,10 +316,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""" @@ -329,9 +330,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""" diff --git a/guardian/views.py b/guardian/views.py index e45b720..c755e87 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -6,6 +6,8 @@ # 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 @@ -242,7 +244,6 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list No Params""" - queryset = JuniorPoints.objects.all() serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] http_method_names = ('get',) @@ -253,15 +254,18 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): context.update({'view': self}) return context + def get_queryset(self): + 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') + 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 - 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) diff --git a/junior/serializers.py b/junior/serializers.py index 13735b3..1ef34f7 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -19,7 +19,7 @@ from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED 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 (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION, @@ -185,9 +185,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: @@ -365,10 +364,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() diff --git a/junior/utils.py b/junior/utils.py index 4a6ee2b..dacb426 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 @@ -61,3 +62,19 @@ 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.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') + + 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 12fef19..5e73562 100644 --- a/junior/views.py +++ b/junior/views.py @@ -142,7 +142,7 @@ 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: @@ -417,7 +417,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): """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) diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 585b2c9..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_mail +from base.tasks import send_email USER = get_user_model() @@ -54,7 +54,7 @@ class AdminOTPSerializer(serializers.ModelSerializer): data = { "verification_code": verification_code } - send_mail.delay([email], template, data) + 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/views/user_management.py b/web_admin/views/user_management.py index ecc9771..6980a7a 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -12,8 +12,9 @@ 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 @@ -92,12 +93,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) if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): - guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).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'): - junior = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True).select_related('auth').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']}) @@ -113,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(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(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 93cfb8f..5a3de03 100644 --- a/zod_bank/celery.py +++ b/zod_bank/celery.py @@ -28,9 +28,13 @@ app.config_from_object('django.conf:settings') app.autodiscover_tasks() 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='*/1'), + 'schedule': crontab(minute='30', hour='19'), }, } @@ -39,14 +43,3 @@ app.conf.beat_schedule = { 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), - }, -} From 464899f7d380d504ed82c96d0ef6fa1ab454a424 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 25 Aug 2023 12:26:38 +0530 Subject: [PATCH 288/372] added notification for top leaderboard junior, added related celery task, created method to send notification to multiple users --- base/tasks.py | 31 +++++++++++++++++++++++++++++-- celerybeat-schedule | Bin 16384 -> 16384 bytes notifications/constants.py | 30 ++++++++++++++++++++++-------- notifications/utils.py | 33 +++++++++++++++++++++++++++++++++ notifications/views.py | 4 ++-- zod_bank/celery.py | 6 +++++- 6 files changed, 91 insertions(+), 13 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index 2056493..98d0df0 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -9,12 +9,16 @@ 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 notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING -from notifications.utils import send_notification +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 @@ -56,3 +60,26 @@ def notify_task_expiry(): 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.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') + + 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: + to_user_list = [junior_point.junior.auth for junior_point in junior_points_qs] + send_notification_multiple_user(TOP_JUNIOR, new_top_position.junior.auth.id, JUNIOR, + to_user_list, {'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 49e872897d5b39cf5fa0b1af41e7e7fe192562d5..4ebacd4aa0047e3c5989eee717063f096d44e18e 100644 GIT binary patch delta 586 zcmZo@U~Fh$oS?(VHc?l5GKYeK)J#SOFc33>&+4a!_@emJog?lwWQO;a_Ki(3+N;lN8qR^0`|>`0PMRlWDV@-fND@>y5dX_?RZ| zHf~{;WK&gVSgSaBtx+H&^W^_VA(P*j*a(0{ALvcV5N(?hG{u`^@)I-V=pMnm{F2PH z%J`D}g7~b`yv+QfDH$vp8Ad%aNr}a&dL@a)*~NNT)NnX+u|MYko4N2a%uI$1DUBXx zEq%i&J&XpEKbT2^UByGHtNbZ;m6j>@{ADm#F;4C=)@70-)fI_2T!BrP*w8XzU^wy! Q<}PlayOcL`ns+h*0OKUD6#xJL delta 120 zcmZo@U~Fh$oS?(VJW*GhYbGND7>F5dT$sQ=afAJ4Mgew#i4h8uITRFBzjHujIn5z- xAP Date: Fri, 25 Aug 2023 12:57:15 +0530 Subject: [PATCH 289/372] remove code --- junior/serializers.py | 6 ++++-- junior/views.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/junior/serializers.py b/junior/serializers.py index 379b393..f4dd8ef 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -325,9 +325,11 @@ 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']) + instance.guardian_code.remove(guardian_code) + if not instance.guardian_code: + instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index b91a8cd..e1d03d1 100644 --- a/junior/views.py +++ b/junior/views.py @@ -313,7 +313,8 @@ 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() From c47f6222d938297aa39f0fa8e8c92bb1192d9843 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 25 Aug 2023 16:36:58 +0530 Subject: [PATCH 290/372] requested task not expired --- base/messages.py | 3 ++- guardian/utils.py | 2 +- junior/serializers.py | 13 +++++++++++-- junior/views.py | 16 ++++++++++++---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/base/messages.py b/base/messages.py index 9afbe4f..8ad5234 100644 --- a/base/messages.py +++ b/base/messages.py @@ -107,7 +107,8 @@ ERROR_CODE = { "2078": "This junior is not associate with you", "2079": "Please update your app version for enjoying uninterrupted services", "2080": "Can not add App version", - "2081": "You can not add more than 3 guardian" + "2081": "You can not add more than 3 guardian", + "2082": "Guardian code does not exist" } """Success message code""" diff --git a/guardian/utils.py b/guardian/utils.py index 14bd36a..ed4f338 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -127,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/junior/serializers.py b/junior/serializers.py index f4dd8ef..09d4241 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -508,8 +508,17 @@ 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: + instance.guardian_code.remove(guardian_code) + else: + raise serializers.ValidationError({"error":ERROR_CODE['2082'],"code":"400", "status":"failed"}) + if not instance.guardian_code: + instance.guardian_code_status = str(NUMBER['one']) + elif instance.guardian_code and (len(instance.guardian_code) == 1 and '-' in instance.guardian_code): + instance.guardian_code_status = str(NUMBER['one']) + else: + instance.guardian_code_status = str(NUMBER['two']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index e1d03d1..c4297e9 100644 --- a/junior/views.py +++ b/junior/views.py @@ -207,13 +207,15 @@ class AddJuniorAPIView(viewsets.ModelViewSet): def associate_guardian(self, user): junior = Junior.objects.filter(auth__email=self.request.data['email']).first() guardian = Guardian.objects.filter(user=self.request.user).first() + 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) < 4: + if type(junior.guardian_code) is list and len(junior.guardian_code) < 3: junior.guardian_code.append(guardian.guardian_code) else: return "Max" @@ -724,16 +726,21 @@ class CreateArticleCardAPIView(viewsets.ModelViewSet): class RemoveGuardianCodeAPIView(views.APIView): """Remove guardian code request API - No Payload""" + 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") 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() @@ -743,7 +750,8 @@ class RemoveGuardianCodeAPIView(views.APIView): # task in another state 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) + 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, From c46b2ef52c7729fc5b44c4bbf64347490f87c786 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 25 Aug 2023 16:59:17 +0530 Subject: [PATCH 291/372] sonar fixes --- account/custom_middleware.py | 2 ++ account/serializers.py | 5 +++-- base/messages.py | 3 +++ guardian/serializers.py | 7 +++++++ guardian/views.py | 3 ++- junior/serializers.py | 8 ++++---- junior/views.py | 6 +++--- 7 files changed, 24 insertions(+), 10 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index 42a3b0f..b3cc750 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -17,6 +17,8 @@ from guardian.models import Guardian # 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""" diff --git a/account/serializers.py b/account/serializers.py index dbb52a0..b783efd 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -123,7 +123,7 @@ class ChangePasswordSerializer(serializers.Serializer): def create(self, validated_data): """ - + change password """ new_password = validated_data.pop('new_password') current_password = validated_data.pop('current_password') @@ -392,7 +392,8 @@ class UserPhoneOtpSerializer(serializers.ModelSerializer): fields = '__all__' class ForceUpdateSerializer(serializers.ModelSerializer): - # ForceUpdate Serializer + """ ForceUpdate Serializer + """ class Meta(object): """ meta info """ diff --git a/base/messages.py b/base/messages.py index 8ad5234..b4abf7d 100644 --- a/base/messages.py +++ b/base/messages.py @@ -101,13 +101,16 @@ ERROR_CODE = { "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": "You can not add more than 3 guardian", + # guardian code not exist "2082": "Guardian code does not exist" } diff --git a/guardian/serializers.py b/guardian/serializers.py index af11acc..ae72bdc 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -36,6 +36,7 @@ from django.utils.translation import gettext as _ # In this serializer file # define user serializer, +# define password validation # create guardian serializer, # task serializer, # guardian serializer, @@ -47,6 +48,7 @@ from django.utils.translation import gettext as _ 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 @@ -57,6 +59,7 @@ class PasswordValidator: 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( @@ -64,16 +67,20 @@ class PasswordValidator: ) 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.")) diff --git a/guardian/views.py b/guardian/views.py index b541660..947f071 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -288,7 +288,8 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): if request.data['action'] == '1': # use ApproveJuniorSerializer serializer serializer = ApproveJuniorSerializer(context={"guardian_code": guardian.guardian_code, - "junior": junior_queryset, "action": request.data['action']}, + "junior": junior_queryset, + "action": request.data['action']}, data=request.data) if serializer.is_valid(): # save serializer diff --git a/junior/serializers.py b/junior/serializers.py index 09d4241..98616a2 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -523,18 +523,18 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): return instance class FAQSerializer(serializers.ModelSerializer): - # FAQ Serializer + """FAQ Serializer""" class Meta(object): - # meta info + """meta info""" model = FAQ fields = ('id', 'question', 'description') class CreateArticleCardSerializer(serializers.ModelSerializer): - # Article card Serializer + """Article card Serializer""" class Meta(object): - # meta info + """meta info""" model = ArticleCard fields = ('id', 'article') diff --git a/junior/views.py b/junior/views.py index c4297e9..4d20d57 100644 --- a/junior/views.py +++ b/junior/views.py @@ -266,10 +266,10 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): manual_parameters=[ # Example of a query parameter openapi.Parameter( - 'title', # Query parameter name - openapi.IN_QUERY, # Parameter location + 'title', + openapi.IN_QUERY, description='title of the name', - type=openapi.TYPE_STRING, # Parameter type + type=openapi.TYPE_STRING, ), # Add more parameters as needed ] From 21b92f8c74691dd049daf1d56aef8ef5ae18be3c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 25 Aug 2023 16:59:10 +0530 Subject: [PATCH 292/372] added notification when admin adds a new article --- base/tasks.py | 5 ++--- celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/views.py | 2 +- junior/utils.py | 2 +- notifications/constants.py | 16 ++++++++++++---- notifications/utils.py | 9 +++++++-- web_admin/serializers/article_serializer.py | 6 ++++++ zod_bank/celery.py | 5 +++-- 8 files changed, 32 insertions(+), 13 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index 98d0df0..65dff3b 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -68,7 +68,7 @@ def notify_top_junior(): task to send notification for top leaderboard junior to all junior's :return: """ - junior_points_qs = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + junior_points_qs = JuniorPoints.objects.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') @@ -76,9 +76,8 @@ def notify_top_junior(): 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: - to_user_list = [junior_point.junior.auth for junior_point in junior_points_qs] send_notification_multiple_user(TOP_JUNIOR, new_top_position.junior.auth.id, JUNIOR, - to_user_list, {'points': new_top_position.total_points}) + {'points': new_top_position.total_points}) for junior_point in junior_points_qs: junior_point.position = junior_point.rank junior_point.save() diff --git a/celerybeat-schedule b/celerybeat-schedule index 4ebacd4aa0047e3c5989eee717063f096d44e18e..4c54db222e0832de26f18d1b25337e66a5fdcf70 100644 GIT binary patch delta 226 zcmZo@U~Fh$T%aJt#sC44PM`n+03EJ!O2u^yIDxj zo|j3MY4UF4mdP{CUrBNCFmY7oPstE%n-Vm|n=?a7qleL8N)MBv!Q>BS;*+;ph)?FS xwHD_VV_;12hAEp0QpT*MZ#3D#RC=?Ubv2_j7IT Date: Fri, 25 Aug 2023 19:08:15 +0530 Subject: [PATCH 293/372] added notification for getting points after reading article --- junior/views.py | 4 +++- notifications/constants.py | 10 ++++++++-- notifications/utils.py | 6 ++++-- notifications/views.py | 2 +- zod_bank/celery.py | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/junior/views.py b/junior/views.py index b4a9d1d..e7f5815 100644 --- a/junior/views.py +++ b/junior/views.py @@ -47,7 +47,7 @@ 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 from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, StartAssessmentSerializer) @@ -666,6 +666,8 @@ 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) diff --git a/notifications/constants.py b/notifications/constants.py index dc6ea75..503e0f2 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -17,7 +17,8 @@ IN_PROGRESS_TASK_EXPIRING = 13 TOP_JUNIOR = 14 NEW_ARTICLE_PUBLISHED = 15 -REMOVE_JUNIOR = 16 +ARTICLE_REWARD_POINTS = 16 +REMOVE_JUNIOR = 17 TEST_NOTIFICATION = 99 @@ -66,7 +67,7 @@ NOTIFICATION_DICT = { # as junior send task for approval TASK_ACTION: { "title": "Task completion approval!", - "body": "{from_user} completed her task {task_name}." + "body": "{from_user} completed their task {task_name}." }, # Juniors will receive notification as soon # as their task is rejected by custodians @@ -105,6 +106,11 @@ NOTIFICATION_DICT = { "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!", diff --git a/notifications/utils.py b/notifications/utils.py index a2759e0..aad37c0 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -77,6 +77,7 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us 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 @@ -85,8 +86,9 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us 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) - notification_data['body'] = notification_data['body'].format(from_user=from_user_name, task_name=task_name) + 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 diff --git a/notifications/views.py b/notifications/views.py index 54adf5a..670635a 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -36,7 +36,7 @@ class NotificationViewSet(viewsets.GenericViewSet): paginator = self.pagination_class() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data) + 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): diff --git a/zod_bank/celery.py b/zod_bank/celery.py index cf71dc8..039ea03 100644 --- a/zod_bank/celery.py +++ b/zod_bank/celery.py @@ -35,11 +35,11 @@ app.conf.beat_schedule = { }, 'notify_task_expiry': { 'task': 'base.tasks.notify_task_expiry', - 'schedule': crontab(minute='5', hour='12'), + 'schedule': crontab(minute='0', hour='18'), }, 'notify_top_junior': { 'task': 'base.tasks.notify_top_junior', - 'schedule': crontab(minute='*/5',), + 'schedule': crontab(minute='0', hour='*/2'), }, } From cf9376663ca1447e0b14d69e24cc2adf76f2a1fe Mon Sep 17 00:00:00 2001 From: jain Date: Sat, 26 Aug 2023 17:09:10 +0530 Subject: [PATCH 294/372] list of guardian code status --- guardian/serializers.py | 19 ++++- guardian/views.py | 80 ++++++++++++------- ...0030_remove_junior_guardian_code_status.py | 17 ++++ .../0031_junior_guardian_code_status.py | 19 +++++ junior/models.py | 6 +- junior/serializers.py | 42 +++++++--- junior/views.py | 46 ++++++----- zod_bank/settings.py | 52 ++++++------ 8 files changed, 185 insertions(+), 96 deletions(-) create mode 100644 junior/migrations/0030_remove_junior_guardian_code_status.py create mode 100644 junior/migrations/0031_junior_guardian_code_status.py diff --git a/guardian/serializers.py b/guardian/serializers.py index 76b99b5..2914b79 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -402,9 +402,13 @@ 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'] + print("guardian_code==>", guardian_code, '==>', type(guardian_code)) + print("instance.guardian_code==>", instance.guardian_code, '==>', + type(instance.guardian_code)) + index = instance.guardian_code.index(guardian_code) + print("index==>", index, '==>', type(index)) + instance.guardian_code_status[index] = str(NUMBER['two']) instance.save() return instance @@ -512,4 +516,11 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): def get_guardian_code_status(self,obj): """guardian code status""" - return obj.junior.guardian_code_status + print("obj.guardian.guardian_code===>",obj.guardian.guardian_code,'===>',type(obj.guardian.guardian_code)) + print("obj.junior.guardian_code===>", obj.junior.guardian_code, '===>', type(obj.junior.guardian_code)) + if obj.guardian.guardian_code in obj.junior.guardian_code: + index = obj.junior.guardian_code.index(obj.guardian.guardian_code) + print("index===>", index, '===>', type(index)) + data = obj.junior.guardian_code_status[index] + print("data===>", data, '===>', type(data)) + return obj.junior.guardian_code_status diff --git a/guardian/views.py b/guardian/views.py index c565201..73ce914 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -179,36 +179,42 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image = request.data['default_image'] junior = request.data['junior'] junior_id = Junior.objects.filter(id=junior).last() - guardian_data = Guardian.objects.filter(user=request.user).last() - if (guardian_data.guardian_code not in junior_id.guardian_code or - junior_id.guardian_code_status in guardian_code_tuple): - 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() + if junior_id: + guardian_data = Guardian.objects.filter(user=request.user).last() + index = junior_id.guardian_code.index(guardian_data.guardian_code) + print("index===>", index, '===>', type(index)) + status_index = junior_id.guardian_code_status[index] + print("status_index===>", status_index, '===>', type(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) + 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: + 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) @@ -302,8 +308,20 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): junior_queryset.auth.id) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: - junior_queryset.guardian_code = None - junior_queryset.guardian_code_status = str(NUMBER['one']) + print("#############################################") + if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code): + print("777777777777777") + junior_queryset.guardian_code.remove('-') + if junior_queryset.guardian_code_status and ('-' in junior_queryset.guardian_code_status): + print("666666666666666666666666") + junior_queryset.guardian_code_status.remove('-') + print("guardian.guardian_code==>", guardian.guardian_code, '==>', type(guardian.guardian_code)) + print("junior_queryset.guardian_code==>", junior_queryset.guardian_code, '==>', type(junior_queryset.guardian_code)) + index = junior_queryset.guardian_code.index(guardian.guardian_code) + print("index==>", index, '==>', type(index)) + junior_queryset.guardian_code.remove(guardian.guardian_code) + data = junior_queryset.guardian_code_status.pop(index) + print("data==>", data, '==>', type(data)) 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) 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 773557b..559acfe 100644 --- a/junior/models.py +++ b/junior/models.py @@ -76,9 +76,9 @@ class Junior(models.Model): 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) diff --git a/junior/serializers.py b/junior/serializers.py index 6ba4218..3497b37 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -92,16 +92,23 @@ class CreateJuniorSerializer(serializers.ModelSerializer): # condition for guardian code if guardian_code: if not junior.guardian_code: + print("111111111") 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: + print("2222222222") 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']) + # print("junior.guardian_code.index(guardian_code)==>",junior.guardian_code.index(guardian_code),'===>',type(junior.guardian_code.index(guardian_code))) + print("junior.guardian_code==>", junior.guardian_code, '===>', type(junior.guardian_code)) + junior_approval_mail.delay(user.email, user.first_name) send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) @@ -296,9 +303,9 @@ 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.get_or_create(guardian=guardian_data, junior=junior_data, - relationship=relationship) + relationship=relationship) total_junior = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_data, position=total_junior) """Generate otp""" @@ -326,9 +333,18 @@ class RemoveJuniorSerializer(serializers.ModelSerializer): if instance: guardian_code = self.context['guardian_code'] instance.is_invited = False + if instance.guardian_code and ('-' in instance.guardian_code): + print("1111111111111") + instance.guardian_code.remove('-') + print("instance.guardian_code==>",instance.guardian_code,'==>',type(instance.guardian_code),'===>', + len(instance.guardian_code)) + print("instance.guardian_code_status==>", instance.guardian_code_status, '==>', type(instance.guardian_code_status), '===>', + len(instance.guardian_code_status)) + index = instance.guardian_code.index(guardian_code) + print("index==>",index,'==>',type(index)) instance.guardian_code.remove(guardian_code) - if not instance.guardian_code: - instance.guardian_code_status = str(NUMBER['one']) + data = instance.guardian_code_status.pop(index) + print("data==>", data, '==>', type(data)) instance.save() return instance @@ -506,15 +522,19 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): def update(self, instance, validated_data): guardian_code = self.context['guardian_code'] if guardian_code in instance.guardian_code: + if instance.guardian_code and ('-' in instance.guardian_code): + print("777777777777777") + instance.guardian_code.remove('-') + if instance.guardian_code_status and ('-' in instance.guardian_code_status): + print("666666666666666666666666") + instance.guardian_code_status.remove('-') + index = instance.guardian_code.index(guardian_code) + print("index===>",index,'===>',type(index)) instance.guardian_code.remove(guardian_code) + data = instance.guardian_code_status.pop(index) + print("data===>", data, '===>', type(data)) else: raise serializers.ValidationError({"error":ERROR_CODE['2082'],"code":"400", "status":"failed"}) - if not instance.guardian_code: - instance.guardian_code_status = str(NUMBER['one']) - elif instance.guardian_code and (len(instance.guardian_code) == 1 and '-' in instance.guardian_code): - instance.guardian_code_status = str(NUMBER['one']) - else: - instance.guardian_code_status = str(NUMBER['two']) instance.save() return instance diff --git a/junior/views.py b/junior/views.py index e7f5815..7e8831f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -99,7 +99,10 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): 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: - error_detail = e.detail.get('error', None) + 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.ModelViewSet): @@ -191,7 +194,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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) - if data == "Max": + 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 @@ -208,6 +211,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior = Junior.objects.filter(auth__email=self.request.data['email']).first() guardian = Guardian.objects.filter(user=self.request.user).first() if junior.guardian_code and ('-' in junior.guardian_code): + print("1111111111111") junior.guardian_code.remove('-') if not junior: return none @@ -219,7 +223,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior.guardian_code.append(guardian.guardian_code) else: return "Max" - junior.guardian_code_status = str(NUMBER['two']) + junior.guardian_code_status = [str(NUMBER['two'])] junior.save() JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, relationship=str(self.request.data['relationship'])) @@ -736,24 +740,24 @@ class RemoveGuardianCodeAPIView(views.APIView): permission_classes = [IsAuthenticated] def put(self, request, format=None): - try: - guardian_code = self.request.data.get("guardian_code") - junior_queryset = Junior.objects.filter(auth=self.request.user).last() - if junior_queryset: - # use RemoveGuardianCodeSerializer serializer - serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code}, - data=request.data, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - 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) + # try: + guardian_code = self.request.data.get("guardian_code") + junior_queryset = Junior.objects.filter(auth=self.request.user).last() + if junior_queryset: + # use RemoveGuardianCodeSerializer serializer + serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code}, + data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + 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, diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 781df80..74ca6e9 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -181,32 +181,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -LOGGING = { - "version": 1, - "filters": { - "require_debug_true": { - "()": "django.utils.log.RequireDebugTrue" - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "filters": [ - "require_debug_true" - ], - "class": "logging.StreamHandler" - } - }, - # database logger - "loggers": { - "django.db.backends": { - "level": "DEBUG", - "handlers": [ - "console" - ] - } - } -} +# LOGGING = { +# "version": 1, +# "filters": { +# "require_debug_true": { +# "()": "django.utils.log.RequireDebugTrue" +# } +# }, +# "handlers": { +# "console": { +# "level": "DEBUG", +# "filters": [ +# "require_debug_true" +# ], +# "class": "logging.StreamHandler" +# } +# }, +# # database logger +# "loggers": { +# "django.db.backends": { +# "level": "DEBUG", +# "handlers": [ +# "console" +# ] +# } +# } +# } # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From eb4e09964d05670cd4d0c56f5de54ddad48d046e Mon Sep 17 00:00:00 2001 From: jain Date: Sat, 26 Aug 2023 17:48:13 +0530 Subject: [PATCH 295/372] changes of guardian code status in necessary API --- guardian/serializers.py | 10 +------- guardian/views.py | 13 ++--------- junior/serializers.py | 26 ++++----------------- junior/views.py | 43 +++++++++++++++++----------------- zod_bank/settings.py | 52 ++++++++++++++++++++--------------------- 5 files changed, 56 insertions(+), 88 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 2914b79..4206d7a 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -403,11 +403,7 @@ class ApproveJuniorSerializer(serializers.ModelSerializer): """update guardian code""" instance = self.context['junior'] guardian_code = self.context['guardian_code'] - print("guardian_code==>", guardian_code, '==>', type(guardian_code)) - print("instance.guardian_code==>", instance.guardian_code, '==>', - type(instance.guardian_code)) index = instance.guardian_code.index(guardian_code) - print("index==>", index, '==>', type(index)) instance.guardian_code_status[index] = str(NUMBER['two']) instance.save() return instance @@ -516,11 +512,7 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): def get_guardian_code_status(self,obj): """guardian code status""" - print("obj.guardian.guardian_code===>",obj.guardian.guardian_code,'===>',type(obj.guardian.guardian_code)) - print("obj.junior.guardian_code===>", obj.junior.guardian_code, '===>', type(obj.junior.guardian_code)) if obj.guardian.guardian_code in obj.junior.guardian_code: index = obj.junior.guardian_code.index(obj.guardian.guardian_code) - print("index===>", index, '===>', type(index)) data = obj.junior.guardian_code_status[index] - print("data===>", data, '===>', type(data)) - return obj.junior.guardian_code_status + return data diff --git a/guardian/views.py b/guardian/views.py index 73ce914..ddd6231 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -182,9 +182,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): if junior_id: guardian_data = Guardian.objects.filter(user=request.user).last() index = junior_id.guardian_code.index(guardian_data.guardian_code) - print("index===>", index, '===>', type(index)) status_index = junior_id.guardian_code_status[index] - print("status_index===>", status_index, '===>', type(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'] @@ -308,20 +306,13 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): junior_queryset.auth.id) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: - print("#############################################") if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code): - print("777777777777777") junior_queryset.guardian_code.remove('-') if junior_queryset.guardian_code_status and ('-' in junior_queryset.guardian_code_status): - print("666666666666666666666666") junior_queryset.guardian_code_status.remove('-') - print("guardian.guardian_code==>", guardian.guardian_code, '==>', type(guardian.guardian_code)) - print("junior_queryset.guardian_code==>", junior_queryset.guardian_code, '==>', type(junior_queryset.guardian_code)) index = junior_queryset.guardian_code.index(guardian.guardian_code) - print("index==>", index, '==>', type(index)) junior_queryset.guardian_code.remove(guardian.guardian_code) - data = junior_queryset.guardian_code_status.pop(index) - print("data==>", data, '==>', type(data)) + 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) @@ -372,7 +363,7 @@ class ApproveTaskAPIView(viewsets.ModelViewSet): 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""" diff --git a/junior/serializers.py b/junior/serializers.py index 3497b37..33210d8 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -92,13 +92,11 @@ class CreateJuniorSerializer(serializers.ModelSerializer): # condition for guardian code if guardian_code: if not junior.guardian_code: - print("111111111") 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: - print("2222222222") junior.guardian_code.extend(guardian_code) junior.guardian_code_status.extend(str(NUMBER['three'])) else: @@ -106,9 +104,6 @@ class CreateJuniorSerializer(serializers.ModelSerializer): guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last() if guardian_data: JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) - # print("junior.guardian_code.index(guardian_code)==>",junior.guardian_code.index(guardian_code),'===>',type(junior.guardian_code.index(guardian_code))) - print("junior.guardian_code==>", junior.guardian_code, '===>', type(junior.guardian_code)) - junior_approval_mail.delay(user.email, user.first_name) send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) @@ -304,8 +299,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): referral_code_used=guardian_data.referral_code, is_password_set=False, is_verified=True, guardian_code_status=[str(NUMBER['two'])]) - JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data, - relationship=relationship) + JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, + relationship=relationship) total_junior = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_data, position=total_junior) """Generate otp""" @@ -334,17 +329,10 @@ class RemoveJuniorSerializer(serializers.ModelSerializer): guardian_code = self.context['guardian_code'] instance.is_invited = False if instance.guardian_code and ('-' in instance.guardian_code): - print("1111111111111") instance.guardian_code.remove('-') - print("instance.guardian_code==>",instance.guardian_code,'==>',type(instance.guardian_code),'===>', - len(instance.guardian_code)) - print("instance.guardian_code_status==>", instance.guardian_code_status, '==>', type(instance.guardian_code_status), '===>', - len(instance.guardian_code_status)) index = instance.guardian_code.index(guardian_code) - print("index==>",index,'==>',type(index)) instance.guardian_code.remove(guardian_code) - data = instance.guardian_code_status.pop(index) - print("data==>", data, '==>', type(data)) + instance.guardian_code_status.pop(index) instance.save() return instance @@ -463,7 +451,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): user_type=str(NUMBER['two']), expired_at=expiry_time, is_verified=True) UserNotification.objects.get_or_create(user=user) - JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data, + JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Notification email""" @@ -523,16 +511,12 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): guardian_code = self.context['guardian_code'] if guardian_code in instance.guardian_code: if instance.guardian_code and ('-' in instance.guardian_code): - print("777777777777777") instance.guardian_code.remove('-') if instance.guardian_code_status and ('-' in instance.guardian_code_status): - print("666666666666666666666666") instance.guardian_code_status.remove('-') index = instance.guardian_code.index(guardian_code) - print("index===>",index,'===>',type(index)) instance.guardian_code.remove(guardian_code) - data = instance.guardian_code_status.pop(index) - print("data===>", data, '===>', type(data)) + instance.guardian_code_status.pop(index) else: raise serializers.ValidationError({"error":ERROR_CODE['2082'],"code":"400", "status":"failed"}) instance.save() diff --git a/junior/views.py b/junior/views.py index 7e8831f..37c4e01 100644 --- a/junior/views.py +++ b/junior/views.py @@ -211,7 +211,6 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior = Junior.objects.filter(auth__email=self.request.data['email']).first() guardian = Guardian.objects.filter(user=self.request.user).first() if junior.guardian_code and ('-' in junior.guardian_code): - print("1111111111111") junior.guardian_code.remove('-') if not junior: return none @@ -225,8 +224,10 @@ class AddJuniorAPIView(viewsets.ModelViewSet): return "Max" junior.guardian_code_status = [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() return True @@ -740,24 +741,24 @@ class RemoveGuardianCodeAPIView(views.APIView): permission_classes = [IsAuthenticated] def put(self, request, format=None): - # try: - guardian_code = self.request.data.get("guardian_code") - junior_queryset = Junior.objects.filter(auth=self.request.user).last() - if junior_queryset: - # use RemoveGuardianCodeSerializer serializer - serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code}, - data=request.data, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - 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) + try: + guardian_code = self.request.data.get("guardian_code") + junior_queryset = Junior.objects.filter(auth=self.request.user).last() + if junior_queryset: + # use RemoveGuardianCodeSerializer serializer + serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code}, + data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + 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, diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 74ca6e9..781df80 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -181,32 +181,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -# LOGGING = { -# "version": 1, -# "filters": { -# "require_debug_true": { -# "()": "django.utils.log.RequireDebugTrue" -# } -# }, -# "handlers": { -# "console": { -# "level": "DEBUG", -# "filters": [ -# "require_debug_true" -# ], -# "class": "logging.StreamHandler" -# } -# }, -# # database logger -# "loggers": { -# "django.db.backends": { -# "level": "DEBUG", -# "handlers": [ -# "console" -# ] -# } -# } -# } +LOGGING = { + "version": 1, + "filters": { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "filters": [ + "require_debug_true" + ], + "class": "logging.StreamHandler" + } + }, + # database logger + "loggers": { + "django.db.backends": { + "level": "DEBUG", + "handlers": [ + "console" + ] + } + } +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 8be8d56036b67e898adcdcefb8e6956e2b7fea62 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Sat, 26 Aug 2023 21:36:01 +0530 Subject: [PATCH 296/372] fixed approve junior notification issue --- guardian/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index ddd6231..e5b984a 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -303,7 +303,7 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): # save serializer serializer.save() send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN, - junior_queryset.auth.id) + junior_queryset.auth.id, {}) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code): @@ -314,7 +314,7 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): 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) + 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) From 235fc9baac4d5fa45ef620e4b6e08fda9ec743f4 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 10:37:52 +0530 Subject: [PATCH 297/372] guardian code status in junior list API --- junior/serializers.py | 8 ++++++++ junior/views.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/junior/serializers.py b/junior/serializers.py index 33210d8..96f5fb6 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -171,6 +171,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): @@ -215,6 +216,13 @@ 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 diff --git a/junior/views.py b/junior/views.py index 37c4e01..7a95335 100644 --- a/junior/views.py +++ b/junior/views.py @@ -152,7 +152,8 @@ class JuniorListAPIView(viewsets.ModelViewSet): 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: From e3feab22a27ff32addc898072b41327517831843 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 28 Aug 2023 11:51:12 +0530 Subject: [PATCH 298/372] some changes --- guardian/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index c565201..c1a03da 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -299,13 +299,14 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): # save serializer serializer.save() send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN, - junior_queryset.auth.id) + junior_queryset.auth.id, {}) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: junior_queryset.guardian_code = None junior_queryset.guardian_code_status = str(NUMBER['one']) junior_queryset.save() - send_notification.delay(ASSOCIATE_REJECTED, guardian.user.id, GUARDIAN, junior_queryset.auth.id) + 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) From c2c9210a9a4cc5bb2d9b7511549689437de395b5 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 28 Aug 2023 11:54:38 +0530 Subject: [PATCH 299/372] some changes --- guardian/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guardian/views.py b/guardian/views.py index c1a03da..139f244 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -305,8 +305,7 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): junior_queryset.guardian_code = None junior_queryset.guardian_code_status = str(NUMBER['one']) junior_queryset.save() - send_notification.delay(ASSOCIATE_REJECTED, guardian.user.id, GUARDIAN, - junior_queryset.auth.id, {}) + 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) From 34359360e0e5dbb835af2346f75812b9edaa455f Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 12:10:13 +0530 Subject: [PATCH 300/372] guardian_status update --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index 7a95335..c1c110d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -223,7 +223,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior.guardian_code.append(guardian.guardian_code) else: return "Max" - junior.guardian_code_status = [str(NUMBER['two'])] + junior.guardian_code_status.append(str(NUMBER['two'])) junior.save() jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior) if jun_data: From 966f94eb1ec14cf78ba8a55a8e57e14f4e7669a1 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 12:41:29 +0530 Subject: [PATCH 301/372] change message --- base/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index b4abf7d..54c427f 100644 --- a/base/messages.py +++ b/base/messages.py @@ -109,7 +109,7 @@ ERROR_CODE = { # force update "2079": "Please update your app version for enjoying uninterrupted services", "2080": "Can not add App version", - "2081": "You can not add more than 3 guardian", + "2081": "A junior can only be associated with a maximum of 3 guardian", # guardian code not exist "2082": "Guardian code does not exist" From 8e529b292d8094bb7a4f35d0824643eff85fe6b1 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 13:38:35 +0530 Subject: [PATCH 302/372] add non in delete API --- account/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/utils.py b/account/utils.py index c24c833..60a5c44 100644 --- a/account/utils.py +++ b/account/utils.py @@ -94,7 +94,7 @@ def junior_account_update(user_tb): junior_data.is_active = False junior_data.is_verified = False junior_data.guardian_code = None - junior_data.guardian_code_status = str(NUMBER['one']) + junior_data.guardian_code_status = None junior_data.is_deleted = True junior_data.save() JuniorPoints.objects.filter(junior=junior_data).delete() From 5e17edcf3f8c47220b40c42b9677fae10e99e39d Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 28 Aug 2023 15:36:24 +0530 Subject: [PATCH 303/372] fixed add junior multiple device notification issue, changed send multiple user notification method, modified add fcm token method --- celerybeat-schedule | Bin 16384 -> 20480 bytes notifications/utils.py | 26 +++++++----------- notifications/views.py | 13 ++------- web_admin/serializers/analytics_serializer.py | 16 +++++++++-- web_admin/views/analytics.py | 4 +-- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 4c54db222e0832de26f18d1b25337e66a5fdcf70..f457bb55d9731edd43b34c596341385d6757e2ec 100644 GIT binary patch delta 427 zcmZo@U~E{xI6;vofB_8t074*{LMiM`*?X7 z93bNCK+1HptlknnDPd(fmjBzPWQev+37X=~l_90k!)P$2hsn@l@&_~V$y+VNCtKND zOM;YrG=nLd0aTWu*~6@*Zv<2&xw*`?i)oSp2MfzoEvCu4jV<|mcv340GK*4E;!6^X zv!_fBF!yAaVK-J|F|C=rz&wzVb+UntCbLY2%;W;II1Nr`F81dfVEHJ`DH&p5r*LOT v!<{lE1E>_Jc&UXt&^0FNPz_uR42-EWVH)NFHDqXk9R*WA)joUUhHPE{l#E_c delta 193 zcmZozz}V2hI6;xefdLHu07OL)Tf#T_ nqn$82qrsFOCPRbCAI!uj?{M^VzT= diff --git a/notifications/utils.py b/notifications/utils.py index aad37c0..bd2a8bd 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -19,14 +19,13 @@ 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 @@ -93,7 +92,7 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us notification_data['from_user_image'] = from_user_image notification_data.update(extra_data) - to_user = User.objects.get(id=to_user_id) + to_user = User.objects.filter(id=to_user_id).first() return notification_data, push_data, from_user, to_user @@ -135,21 +134,18 @@ def send_notification_multiple_user(notification_type, from_user_id, from_user_t to_user_list = User.objects.filter(junior_profile__is_verified=True, is_superuser=False ).exclude(junior_profile__isnull=True, guardian_profile__isnull=True) - push_data = NOTIFICATION_DICT[notification_type].copy() - notification_data = push_data.copy() - points = extra_data.get('points', None) - 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, points=points) - notification_data['body'] = notification_data['body'].format(from_user=from_user_name, - points=points) - notification_data['from_user'] = from_user_name - notification_data['from_user_image'] = from_user_image + 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_data)) + 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) @@ -183,5 +179,3 @@ def send_notification_to_junior(notification_type, from_user_id, to_user_id, ext 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 670635a..812502e 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -52,9 +52,11 @@ 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: """ + notify_task_expiry() + notify_top_junior() send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'], {}) return custom_response(SUCCESS_CODE["3000"]) @@ -67,12 +69,3 @@ class NotificationViewSet(viewsets.GenericViewSet): """ 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) - - @action(methods=['get'], url_path='task', url_name='task', detail=False, - permission_classes=[AllowAny]) - def task(self, request, *args, **kwargs): - """ - notification list - """ - notify_top_junior() - return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index b0177d7..7871615 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model from account.utils import get_user_full_name # local imports -from base.constants import USER_TYPE +from base.constants import USER_TYPE, JUNIOR from junior.models import JuniorPoints, Junior @@ -37,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): @@ -60,6 +60,8 @@ class LeaderboardSerializer(serializers.ModelSerializer): """ leaderboard serializer """ + user_id = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() junior = JuniorLeaderboardSerializer() rank = serializers.IntegerField() @@ -68,7 +70,15 @@ class LeaderboardSerializer(serializers.ModelSerializer): 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): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 4a010a3..c04bb98 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -128,7 +128,7 @@ class AnalyticsViewSet(GenericViewSet): :param request: :return: """ - queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + queryset = JuniorPoints.objects.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') @@ -199,7 +199,7 @@ class AnalyticsViewSet(GenericViewSet): # sheet 3 for Juniors Leaderboard and rank elif sheet_name == 'Juniors Leaderboard': - queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + queryset = JuniorPoints.objects.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] From d2242d4c647957c419a71e4e7dcc34b8cf8432b4 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 18:39:29 +0530 Subject: [PATCH 304/372] remove guardian request and resend otp --- account/views.py | 1 - junior/serializers.py | 6 ++++-- junior/views.py | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/account/views.py b/account/views.py index 8dbb2f7..e208163 100644 --- a/account/views.py +++ b/account/views.py @@ -546,7 +546,6 @@ 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 diff --git a/junior/serializers.py b/junior/serializers.py index 96f5fb6..d3d0b2f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -91,6 +91,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer): # Update guardian code""" # condition for guardian code if 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 = [] @@ -104,9 +107,8 @@ class CreateJuniorSerializer(serializers.ModelSerializer): guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last() if guardian_data: JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) + send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) junior_approval_mail.delay(user.email, user.first_name) - send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) - 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) diff --git a/junior/views.py b/junior/views.py index c1c110d..d7a3d5f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -326,6 +326,7 @@ class RemoveJuniorAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() + 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) @@ -744,6 +745,7 @@ class RemoveGuardianCodeAPIView(views.APIView): 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 @@ -752,6 +754,7 @@ class RemoveGuardianCodeAPIView(views.APIView): 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: From 219bae792e4f8c887ea1c06f6ba281a26290db58 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 28 Aug 2023 19:39:39 +0530 Subject: [PATCH 305/372] added notification for existing junior add, modified leaderboard method at every places --- base/tasks.py | 12 ++++++++---- guardian/views.py | 12 ++++++++---- junior/serializers.py | 4 ++-- junior/utils.py | 10 ++++++---- junior/views.py | 5 +++-- notifications/constants.py | 29 +++++++++++++++++------------ web_admin/views/analytics.py | 20 ++++++++++++-------- 7 files changed, 56 insertions(+), 36 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index 65dff3b..fbeca1d 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -68,10 +68,14 @@ def notify_top_junior(): task to send notification for top leaderboard junior to all junior's :return: """ - junior_points_qs = JuniorPoints.objects.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_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() diff --git a/guardian/views.py b/guardian/views.py index e5b984a..e120681 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -259,10 +259,14 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): return context def get_queryset(self): - queryset = JuniorPoints.objects.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') + 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): diff --git a/junior/serializers.py b/junior/serializers.py index d3d0b2f..de5f671 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -22,7 +22,7 @@ from account.models import UserEmailOtp, UserNotification 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 (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION, +from notifications.constants import (ASSOCIATE_REQUEST, ASSOCIATE_JUNIOR, TASK_ACTION, ) from web_admin.models import ArticleCard @@ -323,7 +323,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, password) # push notification - send_notification.delay(JUNIOR_ADDED, None, None, junior_data.auth.id, {}) + send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {}) return junior_data diff --git a/junior/utils.py b/junior/utils.py index 7533017..eac6ac9 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -70,10 +70,12 @@ def get_junior_leaderboard_rank(junior_obj): :param junior_obj: :return: junior's position/rank """ - queryset = JuniorPoints.objects.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') + 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) diff --git a/junior/views.py b/junior/views.py index d7a3d5f..cdb8874 100644 --- a/junior/views.py +++ b/junior/views.py @@ -42,12 +42,12 @@ from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, Ad 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, none +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, ARTICLE_REWARD_POINTS +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) @@ -229,6 +229,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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 diff --git a/notifications/constants.py b/notifications/constants.py index 503e0f2..85b0d2d 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -6,19 +6,20 @@ ASSOCIATE_REQUEST = 3 ASSOCIATE_REJECTED = 4 ASSOCIATE_APPROVED = 5 REFERRAL_POINTS = 6 -JUNIOR_ADDED = 7 +ASSOCIATE_JUNIOR = 7 +ASSOCIATE_EXISTING_JUNIOR = 8 -TASK_ASSIGNED = 8 -TASK_ACTION = 9 -TASK_REJECTED = 10 -TASK_APPROVED = 11 -PENDING_TASK_EXPIRING = 12 -IN_PROGRESS_TASK_EXPIRING = 13 -TOP_JUNIOR = 14 +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 = 15 -ARTICLE_REWARD_POINTS = 16 -REMOVE_JUNIOR = 17 +NEW_ARTICLE_PUBLISHED = 16 +ARTICLE_REWARD_POINTS = 17 +REMOVE_JUNIOR = 18 TEST_NOTIFICATION = 99 @@ -53,10 +54,14 @@ NOTIFICATION_DICT = { }, # Juniors will receive notification # once any custodians add them in their account - JUNIOR_ADDED: { + ASSOCIATE_JUNIOR: { "title": "Profile already setup!", "body": "Your guardian has already setup your profile." }, + ASSOCIATE_EXISTING_JUNIOR: { + "title": "Associated to guardian", + "body": "Your are associated to your guardian {from_user}." + }, # Juniors will receive Notification # for every Task Assign by Custodians TASK_ASSIGNED: { diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index c04bb98..926cd47 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -128,10 +128,12 @@ class AnalyticsViewSet(GenericViewSet): :param request: :return: """ - queryset = JuniorPoints.objects.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') + 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) @@ -199,10 +201,12 @@ class AnalyticsViewSet(GenericViewSet): # sheet 3 for Juniors Leaderboard and rank elif sheet_name == 'Juniors Leaderboard': - queryset = JuniorPoints.objects.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] + 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), From 63dfd731fc3e4728c0cbd9e77d7191428d8179b3 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 28 Aug 2023 20:09:39 +0530 Subject: [PATCH 306/372] condition in add junior --- junior/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index d7a3d5f..5d23894 100644 --- a/junior/views.py +++ b/junior/views.py @@ -223,7 +223,10 @@ class AddJuniorAPIView(viewsets.ModelViewSet): junior.guardian_code.append(guardian.guardian_code) else: return "Max" - junior.guardian_code_status.append(str(NUMBER['two'])) + if not junior.guardian_code_status: + junior.guardian_code_status = [str(NUMBER['two'])] + else: + junior.guardian_code_status.append(str(NUMBER['two'])) junior.save() jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior) if jun_data: From 37d191eef8685cc44b36bcecc1ea19a68dab086f Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 10:41:44 +0530 Subject: [PATCH 307/372] add message for blank search --- base/messages.py | 3 ++- junior/views.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index 54c427f..5a6e2e8 100644 --- a/base/messages.py +++ b/base/messages.py @@ -111,7 +111,8 @@ ERROR_CODE = { "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" + "2082": "Guardian code does not exist", + "2083": "Search should not be blank" } """Success message code""" diff --git a/junior/views.py b/junior/views.py index 5ca50e0..a560f4d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -223,6 +223,8 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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: @@ -355,6 +357,8 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): try: status_value = self.request.GET.get('status') search = self.request.GET.get('search') + if search.strip() == '': + return custom_error_response(ERROR_CODE['2083'], response_status=status.HTTP_400_BAD_REQUEST) if search and str(status_value) == '0': # search with title and for all task list queryset = JuniorTask.objects.filter(junior__auth=request.user, From e2c84eb83d42db13ebcce8fea20c0684e79cef6f Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 11:49:59 +0530 Subject: [PATCH 308/372] search junior task --- junior/views.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/junior/views.py b/junior/views.py index a560f4d..64aaffb 100644 --- a/junior/views.py +++ b/junior/views.py @@ -346,9 +346,19 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): """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): """Junior task list status=0 @@ -356,29 +366,14 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): page=1""" try: status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search.strip() == '': - return custom_error_response(ERROR_CODE['2083'], response_status=status.HTTP_400_BAD_REQUEST) - 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) From a8d291474a7614ac66ece27b4c5c908e4da25ac3 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 11:52:47 +0530 Subject: [PATCH 309/372] search junior task --- base/messages.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/messages.py b/base/messages.py index 5a6e2e8..54c427f 100644 --- a/base/messages.py +++ b/base/messages.py @@ -111,8 +111,7 @@ ERROR_CODE = { "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", - "2083": "Search should not be blank" + "2082": "Guardian code does not exist" } """Success message code""" From d4008d6cc210a410235b190da2af5421108ee834 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 17:50:58 +0530 Subject: [PATCH 310/372] elif for guardian code --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index 64aaffb..716ed70 100644 --- a/junior/views.py +++ b/junior/views.py @@ -219,7 +219,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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: + elif type(junior.guardian_code) is list and len(junior.guardian_code) < 3: junior.guardian_code.append(guardian.guardian_code) else: return "Max" From 8b0a5d9a8eca9d1c302b7764f512b0441493c2a1 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 18:33:47 +0530 Subject: [PATCH 311/372] otp expiry --- account/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index e208163..ff0a6e6 100644 --- a/account/views.py +++ b/account/views.py @@ -322,7 +322,7 @@ class ForgotPasswordAPIView(views.APIView): send_all_email.delay( 'email_reset_verification.email', email, verification_code ) - expiry = OTP_EXPIRY + expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.get_or_create( email=email ) From 3ad29e677d099bd12c205041334cd41efbc3cfac Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 18:56:51 +0530 Subject: [PATCH 312/372] article page --- junior/views.py | 4 ++-- web_admin/serializers/article_serializer.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/junior/views.py b/junior/views.py index 716ed70..433e410 100644 --- a/junior/views.py +++ b/junior/views.py @@ -595,8 +595,8 @@ class StartAssessmentAPIView(viewsets.ModelViewSet): article_id = self.request.GET.get('article_id') # if referral_code: article = Article.objects.filter(id=article_id, is_deleted=False).prefetch_related( - 'article_cards', 'article_survey', 'article_survey__options' - ).order_by('-created_at') + 'article_survey' + ) return article def list(self, request, *args, **kwargs): """Params diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 9fa5651..977e607 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -306,8 +306,9 @@ class StartAssessmentSerializer(serializers.ModelSerializer): """current page""" context_data = self.context.get('user') data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last() + total_count = obj.article_survey.all().count() if data: - return data.current_que_page + return data.current_que_page if data.current_que_page <= total_count else data.current_que_page - 1 return NUMBER['zero'] class Meta(object): """ From dc12b3584245053d9febf77bcb235feca1d85665 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 19:38:14 +0530 Subject: [PATCH 313/372] answer api --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 977e607..7e94e9b 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -308,7 +308,7 @@ class StartAssessmentSerializer(serializers.ModelSerializer): data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last() total_count = obj.article_survey.all().count() if data: - return data.current_que_page if data.current_que_page <= total_count else data.current_que_page - 1 + return data.current_que_page if data.current_que_page < total_count else data.current_que_page - 1 return NUMBER['zero'] class Meta(object): """ From d24f0751107df1b4421c302e9c2443f21beecd12 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 29 Aug 2023 21:28:36 +0530 Subject: [PATCH 314/372] answer api --- junior/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junior/views.py b/junior/views.py index 433e410..3e9eeed 100644 --- a/junior/views.py +++ b/junior/views.py @@ -211,12 +211,12 @@ class AddJuniorAPIView(viewsets.ModelViewSet): def associate_guardian(self, user): junior = Junior.objects.filter(auth__email=self.request.data['email']).first() guardian = Guardian.objects.filter(user=self.request.user).first() - if junior.guardian_code and ('-' in junior.guardian_code): - junior.guardian_code.remove('-') - if not junior: + if junior is None: return none if junior.guardian_code and (guardian.guardian_code in junior.guardian_code): return False + if junior.guardian_code and ('-' in junior.guardian_code): + junior.guardian_code.remove('-') if not junior.guardian_code: junior.guardian_code = [guardian.guardian_code] elif type(junior.guardian_code) is list and len(junior.guardian_code) < 3: From 0af2a352066fb63eb75dbe769e1f4f4fd5d383d7 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 1 Sep 2023 12:14:15 +0530 Subject: [PATCH 315/372] task assign to multiple junior --- guardian/serializers.py | 24 ++++++++++-- guardian/views.py | 83 ++++++++++++++++++++++++----------------- zod_bank/settings.py | 52 +++++++++++++------------- 3 files changed, 94 insertions(+), 65 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index 4206d7a..c26ad53 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -218,7 +218,7 @@ class TaskSerializer(serializers.ModelSerializer): class Meta(object): """Meta info""" model = JuniorTask - fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image'] + fields = ['id', 'task_name','task_description','points', 'due_date','default_image'] def validate_due_date(self, value): """validation on due date""" @@ -229,11 +229,27 @@ class TaskSerializer(serializers.ModelSerializer): return value def create(self, validated_data): """create default task image data""" - validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() + guardian = Guardian.objects.filter(user=self.context['user']).last() # update image of the task images = self.context['image'] - validated_data['default_image'] = images - instance = JuniorTask.objects.create(**validated_data) + junior_ids = self.context['junior_data'] + print("junior_ids==>", junior_ids, '==>', type(junior_ids)) + print() + junior_data = junior_ids[0].split(',') + print("junior_data[0==>", junior_data, '==>', type(junior_data)) + tasks_created = [] + + for junior_id in junior_data: + print("junior_id==>",junior_id,'==>',type(junior_id)) + task_data = validated_data.copy() + task_data['guardian'] = guardian + task_data['default_image'] = images + task_data['junior'] = Junior.objects.filter(id=junior_id).last() + print("task_data===>", task_data, '===>', type(task_data)) + print("task_data['junior']===>", task_data['junior'], '===>', type(task_data['junior'])) + instance = JuniorTask.objects.create(**task_data) + tasks_created.append(instance) + print("tasks_created==>", tasks_created, '==>', type(tasks_created)) return instance class GuardianDetailSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index e120681..2502a18 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -177,42 +177,55 @@ class CreateTaskAPIView(viewsets.ModelViewSet): """ try: image = request.data['default_image'] - junior = request.data['junior'] - 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() + juniors = request.data['junior'].split(',') + print("juniors===>", juniors, '===>', type(juniors)) + print() - 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: - return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + for junior in juniors: + print("junior===>", junior, '===>', type(junior)) + junior_id = Junior.objects.filter(id=junior).last() + print("junior_id===>", junior_id, '===>', type(junior_id)) + 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) + print("request.data===>", request.data, '===>', type(request.data)) + 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') + print("data===>",data,'===>',type(data)) + junior_data = data.pop('junior') + print() + print("data===>", data, '===>', type(data)) + print("junior_data===>", junior_data, '===>', type(junior_data)) + # use TaskSerializer serializer + serializer = TaskSerializer(context={"user":request.user, "image":image_data, + "junior_data":junior_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: + 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) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 781df80..74ca6e9 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -181,32 +181,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -LOGGING = { - "version": 1, - "filters": { - "require_debug_true": { - "()": "django.utils.log.RequireDebugTrue" - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "filters": [ - "require_debug_true" - ], - "class": "logging.StreamHandler" - } - }, - # database logger - "loggers": { - "django.db.backends": { - "level": "DEBUG", - "handlers": [ - "console" - ] - } - } -} +# LOGGING = { +# "version": 1, +# "filters": { +# "require_debug_true": { +# "()": "django.utils.log.RequireDebugTrue" +# } +# }, +# "handlers": { +# "console": { +# "level": "DEBUG", +# "filters": [ +# "require_debug_true" +# ], +# "class": "logging.StreamHandler" +# } +# }, +# # database logger +# "loggers": { +# "django.db.backends": { +# "level": "DEBUG", +# "handlers": [ +# "console" +# ] +# } +# } +# } # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 4f79a690c1584822c43d9f3a85e02c667940aa00 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 1 Sep 2023 16:25:05 +0530 Subject: [PATCH 316/372] unrestrict logout and refresh token api while login in multiple device --- account/custom_middleware.py | 3 ++- base/messages.py | 4 +-- guardian/serializers.py | 7 ----- guardian/views.py | 12 +-------- zod_bank/settings.py | 52 ++++++++++++++++++------------------ 5 files changed, 31 insertions(+), 47 deletions(-) diff --git a/account/custom_middleware.py b/account/custom_middleware.py index b3cc750..705cf49 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -45,11 +45,12 @@ class CustomMiddleware(object): device_type = str(request.META.get('HTTP_TYPE')) api_endpoint = request.path + unrestricted_api = ('/api/v1/user/login/', '/api/v1/logout/', '/api/v1/generate-token/') if request.user.is_authenticated: # 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/': + if not device_details and api_endpoint not in unrestricted_api: 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']): diff --git a/base/messages.py b/base/messages.py index 54c427f..feed804 100644 --- a/base/messages.py +++ b/base/messages.py @@ -147,8 +147,8 @@ SUCCESS_CODE = { "3018": "Task created successfully", "3019": "Support Email sent successfully", "3020": "Logged out successfully.", - "3021": "Add junior successfully", - "3022": "Remove junior successfully", + "3021": "Added junior successfully", + "3022": "Removed junior successfully", "3023": "Junior is approved successfully", "3024": "Junior request is rejected successfully", "3025": "Task is approved successfully", diff --git a/guardian/serializers.py b/guardian/serializers.py index c26ad53..bd2e43d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -233,23 +233,16 @@ class TaskSerializer(serializers.ModelSerializer): # update image of the task images = self.context['image'] junior_ids = self.context['junior_data'] - print("junior_ids==>", junior_ids, '==>', type(junior_ids)) - print() junior_data = junior_ids[0].split(',') - print("junior_data[0==>", junior_data, '==>', type(junior_data)) tasks_created = [] for junior_id in junior_data: - print("junior_id==>",junior_id,'==>',type(junior_id)) task_data = validated_data.copy() task_data['guardian'] = guardian task_data['default_image'] = images task_data['junior'] = Junior.objects.filter(id=junior_id).last() - print("task_data===>", task_data, '===>', type(task_data)) - print("task_data['junior']===>", task_data['junior'], '===>', type(task_data['junior'])) instance = JuniorTask.objects.create(**task_data) tasks_created.append(instance) - print("tasks_created==>", tasks_created, '==>', type(tasks_created)) return instance class GuardianDetailSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index 2502a18..b212faf 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -178,13 +178,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): try: image = request.data['default_image'] juniors = request.data['junior'].split(',') - print("juniors===>", juniors, '===>', type(juniors)) - print() - for junior in juniors: - print("junior===>", junior, '===>', type(junior)) junior_id = Junior.objects.filter(id=junior).last() - print("junior_id===>", junior_id, '===>', type(junior_id)) if junior_id: guardian_data = Guardian.objects.filter(user=request.user).last() index = junior_id.guardian_code.index(guardian_data.guardian_code) @@ -197,7 +192,6 @@ class CreateTaskAPIView(viewsets.ModelViewSet): if not junior.isnumeric(): """junior value must be integer""" return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) - print("request.data===>", request.data, '===>', type(request.data)) data = request.data if 'https' in str(image): image_data = image @@ -208,11 +202,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): image_url = upload_image_to_alibaba(image, filename) image_data = image_url data.pop('default_image') - print("data===>",data,'===>',type(data)) junior_data = data.pop('junior') - print() - print("data===>", data, '===>', type(data)) - print("junior_data===>", junior_data, '===>', type(junior_data)) # use TaskSerializer serializer serializer = TaskSerializer(context={"user":request.user, "image":image_data, "junior_data":junior_data}, data=data) @@ -222,7 +212,7 @@ class CreateTaskAPIView(viewsets.ModelViewSet): 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_response(SUCCESS_CODE['3018'], 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['2047'], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 74ca6e9..781df80 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -181,32 +181,32 @@ AUTH_PASSWORD_VALIDATORS = [ # database query logs settings # Allows us to check db hits # useful to optimize db query and hit -# LOGGING = { -# "version": 1, -# "filters": { -# "require_debug_true": { -# "()": "django.utils.log.RequireDebugTrue" -# } -# }, -# "handlers": { -# "console": { -# "level": "DEBUG", -# "filters": [ -# "require_debug_true" -# ], -# "class": "logging.StreamHandler" -# } -# }, -# # database logger -# "loggers": { -# "django.db.backends": { -# "level": "DEBUG", -# "handlers": [ -# "console" -# ] -# } -# } -# } +LOGGING = { + "version": 1, + "filters": { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "filters": [ + "require_debug_true" + ], + "class": "logging.StreamHandler" + } + }, + # database logger + "loggers": { + "django.db.backends": { + "level": "DEBUG", + "handlers": [ + "console" + ] + } + } +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 7b75a3233ca657a7cd4489f969df64f0a9d4867c Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 4 Sep 2023 12:52:04 +0530 Subject: [PATCH 317/372] add current page and total page in task list API --- account/utils.py | 5 +++-- guardian/views.py | 7 ++++++- junior/views.py | 7 ++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/account/utils.py b/account/utils.py index 60a5c44..f77e8e5 100644 --- a/account/utils.py +++ b/account/utils.py @@ -186,12 +186,13 @@ def send_support_email(name, sender, subject, message): return name -def custom_response(detail, data=None, response_status=status.HTTP_200_OK, count=None): +def custom_response(detail, data=None, total_pages=None, current_page=None, response_status=status.HTTP_200_OK, count=None): """Custom response code""" if not data: """when data is none""" data = None - return Response({"data": data, "message": detail, "status": "success", "code": response_status, "count": count}) + return Response({"data": data, "message": detail, "total_pages":total_pages, "current_page":current_page, + "status": "success", "code": response_status, "count": count}) def custom_error_response(detail, response_status): diff --git a/guardian/views.py b/guardian/views.py index b212faf..e115065 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -1,4 +1,5 @@ """Views of Guardian""" +import math # django imports # Import IsAuthenticated @@ -154,15 +155,19 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') + current_page = self.request.GET.get('page') queryset = self.get_queryset() if status_value and status_value != '0': queryset = queryset.filter(task_status=status_value) paginator = self.pagination_class() + total_count = len(queryset) + total_pages = math.ceil(total_count/10) # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetailsSerializer serializer serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_response(None, serializer.data, total_pages=total_pages, current_page=current_page, + response_status=status.HTTP_200_OK) class CreateTaskAPIView(viewsets.ModelViewSet): diff --git a/junior/views.py b/junior/views.py index 3e9eeed..1d2cac8 100644 --- a/junior/views.py +++ b/junior/views.py @@ -12,6 +12,7 @@ import datetime import requests from rest_framework.viewsets import GenericViewSet, mixins +import math """Django app import""" from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi @@ -366,15 +367,19 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): page=1""" try: status_value = self.request.GET.get('status') + current_page = self.request.GET.get('page') queryset = self.get_queryset() if status_value and status_value != '0': queryset = queryset.filter(task_status=status_value) paginator = self.pagination_class() + total_count = len(queryset) + total_pages = math.ceil(total_count / 10) # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetails juniorSerializer serializer serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_response(None, serializer.data, total_pages=total_pages, current_page=current_page, + response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From d62efa21395ad07963a90e050d986bb1b6791bda Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 4 Sep 2023 13:09:54 +0530 Subject: [PATCH 318/372] added and modifid pagination --- base/pagination.py | 46 ++++++++++++++++++++++++++++++++++++ web_admin/pagination.py | 18 -------------- web_admin/views/analytics.py | 4 ++-- 3 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 base/pagination.py delete mode 100644 web_admin/pagination.py diff --git a/base/pagination.py b/base/pagination.py new file mode 100644 index 0000000..085579c --- /dev/null +++ b/base/pagination.py @@ -0,0 +1,46 @@ +""" +web_admin pagination file +""" +# third party imports +from collections import OrderedDict +from rest_framework.pagination import PageNumberPagination + +from account.utils import custom_response +from base.constants import NUMBER + + +class CustomPageNumberPagination(PageNumberPagination): + """ + custom paginator class + """ + # Set the desired page size + page_size = NUMBER['ten'] + page_size_query_param = 'page_size' + # Set a maximum page size if needed + max_page_size = NUMBER['hundred'] + + def get_paginated_response(self, data): + """ + :param data: queryset to be paginated + :return: return a OrderedDict + """ + return custom_response(None, OrderedDict([ + ('count', self.page.paginator.count), + ('data', data), + ('current_page', self.page.number), + ('total_pages', self.page.paginator.num_pages), + + + ])) + + def get_paginated_dict_response(self, data): + """ + :param data: queryset to be paginated + :return: return a simple dict obj + """ + return { + 'current': self.page.number, + 'data': data, + 'total': self.page.paginator.count, + 'total_pages': self.page.paginator.num_pages, + } diff --git a/web_admin/pagination.py b/web_admin/pagination.py deleted file mode 100644 index c2eed5e..0000000 --- a/web_admin/pagination.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -web_admin pagination file -""" -# third party imports -from rest_framework.pagination import PageNumberPagination - -from base.constants import NUMBER - - -class CustomPageNumberPagination(PageNumberPagination): - """ - custom paginator class - """ - # Set the desired page size - page_size = NUMBER['ten'] - page_size_query_param = 'page_size' - # Set a maximum page size if needed - max_page_size = NUMBER['hundred'] diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 926cd47..b4f228f 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -23,11 +23,11 @@ from django.http import HttpResponse # local imports 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 base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, 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 base.pagination import CustomPageNumberPagination from web_admin.permission import AdminPermission from web_admin.serializers.analytics_serializer import LeaderboardSerializer, UserCSVReportSerializer from web_admin.utils import get_dates From ec585d35f3ec8cb7e30fe3264c76d302ef920280 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 4 Sep 2023 13:11:25 +0530 Subject: [PATCH 319/372] added and modifid pagination --- base/pagination.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/pagination.py b/base/pagination.py index 085579c..e060447 100644 --- a/base/pagination.py +++ b/base/pagination.py @@ -39,8 +39,8 @@ class CustomPageNumberPagination(PageNumberPagination): :return: return a simple dict obj """ return { - 'current': self.page.number, + 'count': self.page.paginator.count, 'data': data, - 'total': self.page.paginator.count, + 'current_page': self.page.number, 'total_pages': self.page.paginator.num_pages, } From 20fa6e43dadcd7f4640aa58a39595604bd5bf94f Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 4 Sep 2023 15:02:47 +0530 Subject: [PATCH 320/372] changes in task list API and add special character in password for added junior --- account/utils.py | 26 +++++++++++++++++++++++++- guardian/views.py | 15 ++++++++++++--- junior/serializers.py | 8 ++++---- junior/views.py | 9 +++++++-- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/account/utils.py b/account/utils.py index f77e8e5..63dfc17 100644 --- a/account/utils.py +++ b/account/utils.py @@ -1,6 +1,6 @@ """Account utils""" from celery import shared_task - +import random """Import django""" from django.conf import settings from rest_framework import viewsets, status @@ -289,3 +289,27 @@ 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" + +def make_special_password(length=10): + # Define character sets + lowercase_letters = string.ascii_lowercase + uppercase_letters = string.ascii_uppercase + digits = string.digits + special_characters = '!@#$%^&*()_-+=<>?/[]{}|' + + # Combine character sets + all_characters = lowercase_letters + uppercase_letters + digits + special_characters + + # Create a password with random characters + password = ( + random.choice(lowercase_letters) + + random.choice(uppercase_letters) + + random.choice(digits) + + random.choice(special_characters) + + ''.join(random.choice(all_characters) for _ in range(length - 4)) + ) + + # Shuffle the characters to make it more random + password_list = list(password) + random.shuffle(password_list) + return ''.join(password_list) diff --git a/guardian/views.py b/guardian/views.py index e115065..66d65e6 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -136,7 +136,8 @@ class TaskListAPIView(viewsets.ModelViewSet): Params status search - page""" + page + junior""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) @@ -156,9 +157,17 @@ class TaskListAPIView(viewsets.ModelViewSet): """Create guardian profile""" status_value = self.request.GET.get('status') current_page = self.request.GET.get('page') + junior = self.request.GET.get('junior') queryset = self.get_queryset() - if status_value and status_value != '0': - queryset = queryset.filter(task_status=status_value) + task_status = ['1'] + if str(status_value) == '2': + task_status = ['2', '4'] + elif str(status_value) == '3': + task_status = ['3', '5', '6'] + if status_value and not junior: + queryset = queryset.filter(task_status__in=task_status) + elif status_value and junior: + queryset = queryset.filter(task_status__in=task_status,junior=int(junior)) paginator = self.pagination_class() total_count = len(queryset) total_pages = math.ceil(total_count/10) diff --git a/junior/serializers.py b/junior/serializers.py index de5f671..aba9b12 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -11,7 +11,7 @@ from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken # local imports -from account.utils import send_otp_email, generate_code +from account.utils import send_otp_email, generate_code, make_special_password from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, FAQ from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE @@ -297,8 +297,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): user_data = User.objects.create(username=email, email=email, first_name=self.context['first_name'], last_name=self.context['last_name']) - password = User.objects.make_random_password() - user_data.set_password(password) + special_password = make_special_password() + user_data.set_password(special_password) user_data.save() junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), image=profile_image, @@ -321,7 +321,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): # add push notification UserNotification.objects.get_or_create(user=user_data) """Notification email""" - junior_notification_email.delay(email, full_name, email, password) + junior_notification_email.delay(email, full_name, email, special_password) # push notification send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {}) return junior_data diff --git a/junior/views.py b/junior/views.py index 1d2cac8..8d1390c 100644 --- a/junior/views.py +++ b/junior/views.py @@ -369,8 +369,13 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): status_value = self.request.GET.get('status') current_page = self.request.GET.get('page') queryset = self.get_queryset() - if status_value and status_value != '0': - queryset = queryset.filter(task_status=status_value) + task_status = ['1'] + if str(status_value) == '2': + task_status = ['2', '4'] + elif str(status_value) == '3': + task_status = ['3', '5', '6'] + if status_value: + queryset = queryset.filter(task_status__in=task_status) paginator = self.pagination_class() total_count = len(queryset) total_pages = math.ceil(total_count / 10) From 116fb003588f18f36f2f1896c14b5f4add75b27e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 4 Sep 2023 15:22:15 +0530 Subject: [PATCH 321/372] optimized task status code --- account/utils.py | 9 +++++++++ guardian/views.py | 8 ++------ junior/views.py | 8 ++------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/account/utils.py b/account/utils.py index 63dfc17..353042a 100644 --- a/account/utils.py +++ b/account/utils.py @@ -313,3 +313,12 @@ def make_special_password(length=10): password_list = list(password) random.shuffle(password_list) return ''.join(password_list) + +def task_status_fun(status_value): + """task status""" + task_status_value = ['1'] + if str(status_value) == '2': + task_status_value = ['2', '4'] + elif str(status_value) == '3': + task_status_value = ['3', '5', '6'] + return task_status_value diff --git a/guardian/views.py b/guardian/views.py index 66d65e6..082a9d7 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -37,7 +37,7 @@ from .models import Guardian, JuniorTask from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship 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 account.utils import custom_response, custom_error_response, send_otp_email, task_status_fun from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, GUARDIAN_CODE_STATUS, GUARDIAN from .utils import upload_image_to_alibaba @@ -159,11 +159,7 @@ class TaskListAPIView(viewsets.ModelViewSet): current_page = self.request.GET.get('page') junior = self.request.GET.get('junior') queryset = self.get_queryset() - task_status = ['1'] - if str(status_value) == '2': - task_status = ['2', '4'] - elif str(status_value) == '3': - task_status = ['3', '5', '6'] + task_status = task_status_fun(status_value) if status_value and not junior: queryset = queryset.filter(task_status__in=task_status) elif status_value and junior: diff --git a/junior/views.py b/junior/views.py index 8d1390c..fd917d2 100644 --- a/junior/views.py +++ b/junior/views.py @@ -44,7 +44,7 @@ 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, none, GUARDIAN -from account.utils import custom_response, custom_error_response +from account.utils import custom_response, custom_error_response, task_status_fun from guardian.utils import upload_image_to_alibaba from .utils import update_positions_based_on_points from notifications.utils import send_notification @@ -369,11 +369,7 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): status_value = self.request.GET.get('status') current_page = self.request.GET.get('page') queryset = self.get_queryset() - task_status = ['1'] - if str(status_value) == '2': - task_status = ['2', '4'] - elif str(status_value) == '3': - task_status = ['3', '5', '6'] + task_status = task_status_fun(status_value) if status_value: queryset = queryset.filter(task_status__in=task_status) paginator = self.pagination_class() From a211baa10ac52b7f54406a093b4bbf5a3b4d99b8 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 4 Sep 2023 15:46:36 +0530 Subject: [PATCH 322/372] added cors Allow specific origins setting, unpublish article api, pagination in notification list --- notifications/views.py | 5 +++-- web_admin/serializers/article_serializer.py | 2 +- web_admin/views/article.py | 16 ++++++++++++++++ zod_bank/settings.py | 14 ++++++++++++-- zod_bank/urls.py | 6 ++++-- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/notifications/views.py b/notifications/views.py index 812502e..c3a6751 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -11,6 +11,7 @@ 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.pagination import CustomPageNumberPagination from base.tasks import notify_task_expiry, notify_top_junior from notifications.constants import TEST_NOTIFICATION from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer @@ -33,10 +34,10 @@ class NotificationViewSet(viewsets.GenericViewSet): """ queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id'] ).select_related('notification_to').order_by('-id') - paginator = self.pagination_class() + paginator = CustomPageNumberPagination() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data, count=queryset.count()) + return paginator.get_paginated_response(serializer.data) @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) def fcm_registration(self, request): diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 7e94e9b..d030d41 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -81,7 +81,7 @@ class ArticleSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('id', 'title', 'description', 'article_cards', 'article_survey') + fields = ('id', 'title', 'description', 'is_published', 'article_cards', 'article_survey') def validate(self, attrs): """ diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 902f579..c9d4426 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -130,6 +130,22 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) + @action(methods=['get'], url_name='status-change', url_path='status-change', + detail=True) + def article_status_change(self, request, *args, **kwargs): + """ + article un-publish or publish api method + :param request: article id + :return: success message + """ + try: + article = Article.objects.filter(id=kwargs['pk']).first() + article.is_published = False if article.is_published else True + article.save(update_fields=['is_published']) + return custom_response(SUCCESS_CODE["3038"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2041"], response_status=status.HTTP_400_BAD_REQUEST) + @action(methods=['get'], url_name='remove-card', url_path='remove-card', detail=True) def remove_article_card(self, request, *args, **kwargs): diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 781df80..7ea1f74 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -37,7 +37,17 @@ SECRET_KEY = os.getenv('SECRET_KEY') DEBUG = os.getenv('DEBUG') # cors allow setting -CORS_ORIGIN_ALLOW_ALL = True +CORS_ORIGIN_ALLOW_ALL = False + +# Allow specific origins +CORS_ALLOWED_ORIGINS = [ + "https://dev-api.zodqaapp.com", + "https://qa-api.zodqaapp.com", + "https://stage-api.zodqaapp.com", + # Add more trusted origins as needed +] +# if DEBUG: +# CORS_ALLOWED_ORIGINS += ["http://localhost:3000"] # allow all host ALLOWED_HOSTS = ['*'] @@ -53,7 +63,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - # Add Django rest frame work apps here + # Add Django rest framework apps here 'django_extensions', 'storages', 'drf_yasg', diff --git a/zod_bank/urls.py b/zod_bank/urls.py index a34ef93..ba78b8f 100644 --- a/zod_bank/urls.py +++ b/zod_bank/urls.py @@ -20,12 +20,11 @@ from django.urls import path, include from drf_yasg import openapi from drf_yasg.views import get_schema_view from django.urls import path - +from django.conf import settings schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, ) urlpatterns = [ - path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), path('admin/', admin.site.urls), path('', include(('account.urls', 'account'), namespace='account')), path('', include('guardian.urls')), @@ -33,3 +32,6 @@ urlpatterns = [ path('', include(('notifications.urls', 'notifications'), namespace='notifications')), path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')), ] + +if settings.DEBUG: + urlpatterns += [(path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'))] From 9b14eedb1876286bdf7e96be30176bd58ec71c68 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 4 Sep 2023 16:49:18 +0530 Subject: [PATCH 323/372] handle scenerio for task after disassociate --- account/templates/templated_email/support_mail.email | 2 +- account/utils.py | 3 +-- account/views.py | 5 ++--- base/messages.py | 5 ++++- guardian/views.py | 2 ++ junior/views.py | 9 +++++++-- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/account/templates/templated_email/support_mail.email b/account/templates/templated_email/support_mail.email index 34d6156..20fcdb1 100644 --- a/account/templates/templated_email/support_mail.email +++ b/account/templates/templated_email/support_mail.email @@ -1,7 +1,7 @@ {% extends "templated_email/email_base.email" %} {% block subject %} - {{subject}} + Support Mail {% endblock %} {% block plain %} diff --git a/account/utils.py b/account/utils.py index 353042a..9ef0106 100644 --- a/account/utils.py +++ b/account/utils.py @@ -167,7 +167,7 @@ def user_device_details(user, device_id): return False -def send_support_email(name, sender, subject, message): +def send_support_email(name, sender, message): """Send otp on email with template""" to_email = [settings.EMAIL_FROM_ADDRESS] from_email = settings.DEFAULT_ADDRESS @@ -179,7 +179,6 @@ def send_support_email(name, sender, subject, message): context={ 'name': name.title(), 'sender': sender, - 'subject': subject, 'message': message } ) diff --git a/account/views.py b/account/views.py index ff0a6e6..a76693e 100644 --- a/account/views.py +++ b/account/views.py @@ -689,11 +689,10 @@ class SendSupportEmail(views.APIView): def post(self, request): name = request.data.get('name') sender = request.data.get('email') - subject = request.data.get('subject') message = request.data.get('message') - if name and sender and subject and message: + if name and sender and message: try: - send_support_email(name, sender, subject, message) + send_support_email(name, sender, message) return custom_response(SUCCESS_CODE['3019'], 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/messages.py b/base/messages.py index feed804..0af15a1 100644 --- a/base/messages.py +++ b/base/messages.py @@ -111,7 +111,10 @@ ERROR_CODE = { "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" + "2082": "Guardian code does not exist", + "2083": "You can not start this task because guardian is not associate with you", + "2084": "You can not complete this task because guardian is not associate with you", + "2085": "You can not take action on this task because junior is not associate with you" } """Success message code""" diff --git a/guardian/views.py b/guardian/views.py index 082a9d7..e083708 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -357,6 +357,8 @@ class ApproveTaskAPIView(viewsets.ModelViewSet): 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 guardian.guardian_code not in task_queryset.junior.guardian_code: + return custom_error_response(ERROR_CODE['2084'], response_status=status.HTTP_400_BAD_REQUEST) 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 diff --git a/junior/views.py b/junior/views.py index fd917d2..9a49345 100644 --- a/junior/views.py +++ b/junior/views.py @@ -409,10 +409,12 @@ class CompleteJuniorTaskAPIView(views.APIView): 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: + if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code: + return custom_error_response(ERROR_CODE['2085'], response_status=status.HTTP_400_BAD_REQUEST) + elif 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'])]: + elif task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]: """Already request send """ return custom_error_response(ERROR_CODE['2049'], response_status=status.HTTP_400_BAD_REQUEST) serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True) @@ -517,7 +519,10 @@ class StartTaskAPIView(views.APIView): try: task_id = self.request.data.get('task_id') task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() + print("task_queryset==>",task_queryset) if task_queryset and task_queryset.task_status == str(NUMBER['one']): + if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code: + return custom_error_response(ERROR_CODE['2083'], response_status=status.HTTP_400_BAD_REQUEST) # use StartTaskSerializer serializer serializer = StartTaskSerializer(task_queryset, data=request.data, partial=True) if serializer.is_valid(): From 65d0932893abe3bcb1dd1b511a1a67f43cd59431 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 4 Sep 2023 16:52:50 +0530 Subject: [PATCH 324/372] sonar issues --- guardian/serializers.py | 1 + guardian/views.py | 3 ++- notifications/constants.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index bd2e43d..0058bdd 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -237,6 +237,7 @@ class TaskSerializer(serializers.ModelSerializer): tasks_created = [] for junior_id in junior_data: + # create task task_data = validated_data.copy() task_data['guardian'] = guardian task_data['default_image'] = images diff --git a/guardian/views.py b/guardian/views.py index e083708..e832ec5 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -208,7 +208,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): 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) + 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') diff --git a/notifications/constants.py b/notifications/constants.py index 85b0d2d..c22f246 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -22,7 +22,7 @@ ARTICLE_REWARD_POINTS = 17 REMOVE_JUNIOR = 18 TEST_NOTIFICATION = 99 - +# notification dictionary NOTIFICATION_DICT = { REGISTRATION: { "title": "Successfully registered!", From be9f600bccfe8debeca22d5f668b5212d9271adc Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 4 Sep 2023 16:45:29 +0530 Subject: [PATCH 325/372] modified cord allowed origin values, added yml file for qa stage and prod --- docker-compose-prod.yml | 39 +++++++++++++++++++++++++++++++++++++++ docker-compose-qa.yml | 39 +++++++++++++++++++++++++++++++++++++++ docker-compose-stage.yml | 39 +++++++++++++++++++++++++++++++++++++++ zod_bank/settings.py | 13 +++++++++++-- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 docker-compose-prod.yml create mode 100644 docker-compose-qa.yml create mode 100644 docker-compose-stage.yml diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..436833b --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,39 @@ +version: '3' +services: + nginx: + image: nginx:latest + container_name: nginx + restart: always + ports: + - "8000:8000" + volumes: + - ./nginx:/etc/nginx/conf.d + - .:/usr/src/app + depends_on: + - web + web: + build: . + container_name: prod_django + restart: always + command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" + volumes: + - .:/usr/src/app + + broker: + image: rabbitmq:3.7 + container_name: prod_rabbitmq + volumes: + - .:/usr/src/app + ports: + - 5673:5673 + + worker: + build: . + image: celery + container_name: prod_celery + restart: "always" + command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E" + volumes: + - .:/usr/src/app + depends_on: + - broker diff --git a/docker-compose-qa.yml b/docker-compose-qa.yml new file mode 100644 index 0000000..6e3f4d5 --- /dev/null +++ b/docker-compose-qa.yml @@ -0,0 +1,39 @@ +version: '3' +services: + nginx: + image: nginx:latest + container_name: nginx + restart: always + ports: + - "8000:8000" + volumes: + - ./nginx:/etc/nginx/conf.d + - .:/usr/src/app + depends_on: + - web + web: + build: . + container_name: qa_django + restart: always + command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" + volumes: + - .:/usr/src/app + + broker: + image: rabbitmq:3.7 + container_name: qa_rabbitmq + volumes: + - .:/usr/src/app + ports: + - 5673:5673 + + worker: + build: . + image: celery + container_name: qa_celery + restart: "always" + command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E" + volumes: + - .:/usr/src/app + depends_on: + - broker diff --git a/docker-compose-stage.yml b/docker-compose-stage.yml new file mode 100644 index 0000000..39db221 --- /dev/null +++ b/docker-compose-stage.yml @@ -0,0 +1,39 @@ +version: '3' +services: + nginx: + image: nginx:latest + container_name: nginx + restart: always + ports: + - "8000:8000" + volumes: + - ./nginx:/etc/nginx/conf.d + - .:/usr/src/app + depends_on: + - web + web: + build: . + container_name: stage_django + restart: always + command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info" + volumes: + - .:/usr/src/app + + broker: + image: rabbitmq:3.7 + container_name: stage_rabbitmq + volumes: + - .:/usr/src/app + ports: + - 5673:5673 + + worker: + build: . + image: celery + container_name: stage_celery + restart: "always" + command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E" + volumes: + - .:/usr/src/app + depends_on: + - broker diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 7ea1f74..590bc9d 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -35,19 +35,28 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = os.getenv('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv('DEBUG') +ENV = os.getenv('ENV') # cors allow setting CORS_ORIGIN_ALLOW_ALL = False # Allow specific origins +# if ENV in ['dev', 'qa', 'stage']: CORS_ALLOWED_ORIGINS = [ + # backend base url "https://dev-api.zodqaapp.com", "https://qa-api.zodqaapp.com", "https://stage-api.zodqaapp.com", + + # frontend url + "http://localhost:3000", + "https://zod-dev.zodqaapp.com", + "https://zod-qa.zodqaapp.com", + "https://zod-stage.zodqaapp.com", # Add more trusted origins as needed ] -# if DEBUG: -# CORS_ALLOWED_ORIGINS += ["http://localhost:3000"] +if ENV == "prod": + CORS_ALLOWED_ORIGINS = [] # allow all host ALLOWED_HOSTS = ['*'] From 5524eeed643f4d6594ae27f9a2a1a692d1927440 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 5 Sep 2023 13:54:58 +0530 Subject: [PATCH 326/372] modified pagination in task list and junior task list api --- account/utils.py | 6 +++--- guardian/views.py | 19 ++++++++----------- junior/views.py | 15 +++++++-------- web_admin/views/article.py | 2 +- zod_bank/settings.py | 26 +++++++++++++------------- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/account/utils.py b/account/utils.py index 9ef0106..9122477 100644 --- a/account/utils.py +++ b/account/utils.py @@ -185,13 +185,13 @@ def send_support_email(name, sender, message): return name -def custom_response(detail, data=None, total_pages=None, current_page=None, response_status=status.HTTP_200_OK, count=None): +def custom_response(detail, data=None, response_status=status.HTTP_200_OK, count=None): """Custom response code""" if not data: """when data is none""" data = None - return Response({"data": data, "message": detail, "total_pages":total_pages, "current_page":current_page, - "status": "success", "code": response_status, "count": count}) + return Response({"data": data, "message": detail, "status": "success", + "code": response_status, "count": count}) def custom_error_response(detail, response_status): diff --git a/guardian/views.py b/guardian/views.py index e832ec5..9d6af06 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -17,6 +17,7 @@ from base.constants import guardian_code_tuple from rest_framework.filters import SearchFilter from django.utils import timezone +from base.pagination import CustomPageNumberPagination # Import guardian's model, # Import junior's model, # Import account's model, @@ -147,8 +148,8 @@ class TaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): queryset = JuniorTask.objects.filter(guardian__user=self.request.user - ).prefetch_related('junior', 'junior__auth' - ).order_by('due_date', 'created_at') + ).select_related('junior', 'junior__auth' + ).order_by('due_date', 'created_at') queryset = self.filter_queryset(queryset) return queryset @@ -156,23 +157,19 @@ class TaskListAPIView(viewsets.ModelViewSet): def list(self, request, *args, **kwargs): """Create guardian profile""" status_value = self.request.GET.get('status') - current_page = self.request.GET.get('page') junior = self.request.GET.get('junior') queryset = self.get_queryset() task_status = task_status_fun(status_value) - if status_value and not junior: + if status_value: queryset = queryset.filter(task_status__in=task_status) - elif status_value and junior: - queryset = queryset.filter(task_status__in=task_status,junior=int(junior)) - paginator = self.pagination_class() - total_count = len(queryset) - total_pages = math.ceil(total_count/10) + if junior: + queryset = queryset.filter(junior=int(junior)) + paginator = CustomPageNumberPagination() # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetailsSerializer serializer serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data, total_pages=total_pages, current_page=current_page, - response_status=status.HTTP_200_OK) + return paginator.get_paginated_response(serializer.data) class CreateTaskAPIView(viewsets.ModelViewSet): diff --git a/junior/views.py b/junior/views.py index 9a49345..fb4771d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -13,6 +13,9 @@ import requests from rest_framework.viewsets import GenericViewSet, mixins import math + +from base.pagination import CustomPageNumberPagination + """Django app import""" from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi @@ -354,8 +357,8 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): queryset = JuniorTask.objects.filter(junior__auth=self.request.user - ).prefetch_related('junior', 'junior__auth' - ).order_by('due_date', 'created_at') + ).select_related('junior', 'junior__auth' + ).order_by('due_date', 'created_at') queryset = self.filter_queryset(queryset) return queryset @@ -367,20 +370,16 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): page=1""" try: status_value = self.request.GET.get('status') - current_page = self.request.GET.get('page') queryset = self.get_queryset() task_status = task_status_fun(status_value) if status_value: queryset = queryset.filter(task_status__in=task_status) - paginator = self.pagination_class() - total_count = len(queryset) - total_pages = math.ceil(total_count / 10) + paginator = CustomPageNumberPagination() # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetails juniorSerializer serializer serializer = self.serializer_class(paginated_queryset, many=True) - return custom_response(None, serializer.data, total_pages=total_pages, current_page=current_page, - response_status=status.HTTP_200_OK) + return paginator.get_paginated_response(serializer.data) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/views/article.py b/web_admin/views/article.py index c9d4426..ab55d16 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -230,7 +230,7 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m :param request: :return: default article card images """ - queryset = self.queryset + queryset = self.get_queryset() serializer = self.serializer_class(queryset, many=True) return custom_response(None, data=serializer.data) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 590bc9d..cde1918 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -41,20 +41,20 @@ ENV = os.getenv('ENV') CORS_ORIGIN_ALLOW_ALL = False # Allow specific origins -# if ENV in ['dev', 'qa', 'stage']: -CORS_ALLOWED_ORIGINS = [ - # backend base url - "https://dev-api.zodqaapp.com", - "https://qa-api.zodqaapp.com", - "https://stage-api.zodqaapp.com", +if ENV in ['dev', 'qa', 'stage']: + CORS_ALLOWED_ORIGINS = [ + # backend base url + "https://dev-api.zodqaapp.com", + "https://qa-api.zodqaapp.com", + "https://stage-api.zodqaapp.com", - # frontend url - "http://localhost:3000", - "https://zod-dev.zodqaapp.com", - "https://zod-qa.zodqaapp.com", - "https://zod-stage.zodqaapp.com", - # Add more trusted origins as needed -] + # frontend url + "http://localhost:3000", + "https://zod-dev.zodqaapp.com", + "https://zod-qa.zodqaapp.com", + "https://zod-stage.zodqaapp.com", + # Add more trusted origins as needed + ] if ENV == "prod": CORS_ALLOWED_ORIGINS = [] From 8f214d11a7ec074ec33f3f44f458f98d88b13d42 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 5 Sep 2023 19:29:57 +0530 Subject: [PATCH 327/372] modified create task api, added badge count in notification list --- celerybeat-schedule | Bin 20480 -> 20480 bytes guardian/serializers.py | 13 ++++--- guardian/views.py | 71 +++++++++++++++++------------------ notifications/serializers.py | 7 +++- 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index f457bb55d9731edd43b34c596341385d6757e2ec..be892edea3778b38a0afe5c729adddc781b59706 100644 GIT binary patch delta 77 zcmZozz}T>Wal;%34o+4cK1IILkjV?o0~y&S8`x-0mUm1Ogb2?)Hzh-?ZA#D-Z=T81 T9E+f$i+fWal;%34h|W1V>K4jn#l{y0~uK-8`x-0mUm1Oga}7zPRS5!n-Vm|n|ty! W$0DF87Xt%h>P(pE+|80s&v^k;9Tw~W diff --git a/guardian/serializers.py b/guardian/serializers.py index 0058bdd..f78c237 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -28,7 +28,7 @@ from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIA 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_APPROVED, TASK_REJECTED +from notifications.constants import TASK_APPROVED, TASK_REJECTED, TASK_ASSIGNED # send notification function from notifications.utils import send_notification from django.core.exceptions import ValidationError @@ -229,21 +229,22 @@ class TaskSerializer(serializers.ModelSerializer): return value def create(self, validated_data): """create default task image data""" - guardian = Guardian.objects.filter(user=self.context['user']).last() + guardian = self.context['guardian'] # update image of the task images = self.context['image'] - junior_ids = self.context['junior_data'] - junior_data = junior_ids[0].split(',') + junior_data = self.context['junior_data'] tasks_created = [] - for junior_id in junior_data: + for junior in junior_data: # create task task_data = validated_data.copy() task_data['guardian'] = guardian task_data['default_image'] = images - task_data['junior'] = Junior.objects.filter(id=junior_id).last() + task_data['junior'] = junior instance = JuniorTask.objects.create(**task_data) tasks_created.append(instance) + send_notification.delay(TASK_ASSIGNED, guardian.user.id, GUARDIAN, + junior.auth.id, {'task_id': instance.id}) return instance class GuardianDetailSerializer(serializers.ModelSerializer): diff --git a/guardian/views.py b/guardian/views.py index 9d6af06..4a8a804 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -184,49 +184,48 @@ class CreateTaskAPIView(viewsets.ModelViewSet): """ try: image = request.data['default_image'] - juniors = request.data['junior'].split(',') - for junior in juniors: - 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] + junior_ids = request.data['junior'].split(',') + # if not junior.isnumeric(): + # """junior value must be integer""" + # return custom_error_response(ERROR_CODE['2047'], 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 '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_data = upload_image_to_alibaba(image, filename) + request.data.pop('default_image') + + guardian = Guardian.objects.filter(user=request.user).select_related('user').last() + junior_data = Junior.objects.filter(id__in=junior_ids).select_related('auth') + + for junior in junior_data: + if junior: + index = junior.guardian_code.index(guardian.guardian_code) + status_index = junior.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') - junior_data = data.pop('junior') - # use TaskSerializer serializer - serializer = TaskSerializer(context={"user":request.user, "image":image_data, - "junior_data":junior_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'], 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['2047'], response_status=status.HTTP_400_BAD_REQUEST) + + # use TaskSerializer serializer + serializer = TaskSerializer(context={"guardian": guardian, "image": image_data, + "junior_data": junior_data}, data=request.data) + if serializer.is_valid(): + # save serializer + serializer.save() + # removed send notification method and used in serializer + return custom_response(SUCCESS_CODE['3018'], 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) + class SearchTaskListAPIView(viewsets.ModelViewSet): """Filter task""" serializer_class = TaskDetailsSerializer diff --git a/notifications/serializers.py b/notifications/serializers.py index 2f0222f..e4fdb05 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -31,11 +31,16 @@ class RegisterDevice(serializers.Serializer): class NotificationListSerializer(serializers.ModelSerializer): """List of notification""" + badge = serializers.SerializerMethodField() class Meta(object): """meta info""" model = Notification - fields = ['id', 'data', 'is_read', 'created_at'] + fields = ['id', 'data', 'badge', 'is_read', 'created_at'] + + @staticmethod + def get_badge(obj): + return Notification.objects.filter(notification_to=obj.notification_to, is_read=False).count() class ReadNotificationSerializer(serializers.ModelSerializer): From ffb99f5099a53906f3481bcb1145ad0910b3022c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 6 Sep 2023 15:26:10 +0530 Subject: [PATCH 328/372] modified some success messages. --- base/messages.py | 52 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/base/messages.py b/base/messages.py index 0af15a1..e297137 100644 --- a/base/messages.py +++ b/base/messages.py @@ -125,11 +125,11 @@ SUCCESS_CODE = { # Success code for Thank you "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.", # Success code for account activation - "3003": "Log in successful", + "3003": "Log in successful.", # Success code for password reset - "3004": "Password reset link has been sent to your email address", + "3004": "Password reset link has been sent to your email address.", # Success code for link verified - "3005": "Your account is deleted successfully.", + "3005": "Your account has been deleted successfully.", # Success code for password reset "3006": "Password reset successful. You can now log in with your new password.", # Success code for password update @@ -137,11 +137,11 @@ SUCCESS_CODE = { # Success code for valid link "3008": "You have a valid link.", # Success code for logged out - "3009": "You have successfully logged out!", + "3009": "You have successfully logged out.", # Success code for check all fields - "3010": "All fields are valid", - "3011": "Email OTP Verified successfully", - "3012": "Phone OTP Verified successfully", + "3010": "All fields are valid.", + "3011": "Email OTP has been verified successfully.", + "3012": "Phone OTP has been verified successfully.", "3013": "Valid Guardian code", "3014": "Password has been updated successfully.", "3015": "Verification code has been sent on your email.", @@ -150,39 +150,39 @@ SUCCESS_CODE = { "3018": "Task created successfully", "3019": "Support Email sent successfully", "3020": "Logged out successfully.", - "3021": "Added junior successfully", - "3022": "Removed junior successfully", - "3023": "Junior is approved successfully", - "3024": "Junior request is rejected successfully", - "3025": "Task is approved successfully", - "3026": "Task is rejected successfully", + "3021": "Junior has been added successfully.", + "3022": "Junior has been removed successfully.", + "3023": "Junior has been approved successfully.", + "3024": "Junior request is rejected successfully.", + "3025": "Task is approved successfully.", + "3026": "Task is rejected successfully.", "3027": "Article has been created successfully.", "3028": "Article has been updated successfully.", "3029": "Article has been deleted successfully.", "3030": "Article Card has been removed successfully.", "3031": "Article Survey has been removed successfully.", - "3032": "Task request sent successfully", - "3033": "Valid Referral code", - "3034": "Invite guardian successfully", - "3035": "Task started successfully", - "3036": "Task reassign successfully", + "3032": "Task request sent successfully.", + "3033": "Valid Referral code.", + "3034": "Invite guardian successfully.", + "3035": "Task started successfully.", + "3036": "Task reassign successfully.", "3037": "Profile has been updated successfully.", "3038": "Status has been changed successfully.", # notification read - "3039": "Notification read successfully", + "3039": "Notification read successfully.", # start article - "3040": "Start article successfully", + "3040": "Start article successfully.", # complete article - "3041": "Article completed successfully", + "3041": "Article completed successfully.", # submit assessment successfully - "3042": "Assessment completed successfully", + "3042": "Assessment completed successfully.", # read article - "3043": "Read article card successfully", + "3043": "Read article card successfully.", # remove guardian code request - "3044": "Remove guardian code request successfully", + "3044": "Remove guardian code request successfully.", # create faq - "3045": "Create FAQ data", - "3046": "Add App version successfully" + "3045": "Create FAQ data.", + "3046": "Add App version successfully." } """status code error""" From bc18c6752799cc336b0e70072e4e19c429f1be30 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 7 Sep 2023 13:49:06 +0530 Subject: [PATCH 329/372] modified article publish and un-publish api, sonar issues, modification in create task api --- account/utils.py | 16 ++++++--- guardian/views.py | 18 +++++----- web_admin/serializers/article_serializer.py | 37 +++++++++++++++++---- web_admin/views/article.py | 24 ++++++------- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/account/utils.py b/account/utils.py index 9122477..428df15 100644 --- a/account/utils.py +++ b/account/utils.py @@ -289,7 +289,13 @@ def get_user_full_name(user_obj): """ return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.first_name or user_obj.last_name else "User" + def make_special_password(length=10): + """ + to make secured password + :param length: + :return: + """ # Define character sets lowercase_letters = string.ascii_lowercase uppercase_letters = string.ascii_uppercase @@ -301,11 +307,11 @@ def make_special_password(length=10): # Create a password with random characters password = ( - random.choice(lowercase_letters) + - random.choice(uppercase_letters) + - random.choice(digits) + - random.choice(special_characters) + - ''.join(random.choice(all_characters) for _ in range(length - 4)) + secrets.choice(lowercase_letters) + + secrets.choice(uppercase_letters) + + secrets.choice(digits) + + secrets.choice(special_characters) + + ''.join(secrets.choice(all_characters) for _ in range(length - 4)) ) # Shuffle the characters to make it more random diff --git a/guardian/views.py b/guardian/views.py index 4a8a804..635b8a0 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -185,9 +185,12 @@ class CreateTaskAPIView(viewsets.ModelViewSet): try: image = request.data['default_image'] junior_ids = request.data['junior'].split(',') - # if not junior.isnumeric(): - # """junior value must be integer""" - # return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + + invalid_junior_ids = [junior_id for junior_id in junior_ids if not junior_id.isnumeric()] + if invalid_junior_ids: + # At least one junior value is not an integer + return custom_error_response(ERROR_CODE['2047'], 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) @@ -203,15 +206,14 @@ class CreateTaskAPIView(viewsets.ModelViewSet): guardian = Guardian.objects.filter(user=request.user).select_related('user').last() junior_data = Junior.objects.filter(id__in=junior_ids).select_related('auth') - - for junior in junior_data: - if junior: + if junior_data: + for junior in junior_data: index = junior.guardian_code.index(guardian.guardian_code) status_index = junior.guardian_code_status[index] if status_index == str(NUMBER['three']): return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST) - else: - return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) # use TaskSerializer serializer serializer = TaskSerializer(context={"guardian": guardian, "image": image_data, diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index d030d41..c1bde0e 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -15,6 +15,7 @@ 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 + USER = get_user_model() @@ -90,10 +91,9 @@ class ArticleSerializer(serializers.ModelSerializer): """ article_cards = attrs.get('article_cards', None) article_survey = attrs.get('article_survey', None) - if article_cards is None or len(article_cards) > int(MAX_ARTICLE_CARD): + if not 0 < len(article_cards) <= int(MAX_ARTICLE_CARD): raise serializers.ValidationError({'details': ERROR_CODE['2039']}) - if article_survey is None or len(article_survey) < int(MIN_ARTICLE_SURVEY) or int( - MAX_ARTICLE_SURVEY) < len(article_survey): + if not int(MIN_ARTICLE_SURVEY) <= len(article_survey) <= int(MAX_ARTICLE_SURVEY): raise serializers.ValidationError({'details': ERROR_CODE['2040']}) return attrs @@ -185,6 +185,28 @@ class ArticleSerializer(serializers.ModelSerializer): return instance +class ArticleStatusChangeSerializer(serializers.ModelSerializer): + """ + Article status change serializer + """ + class Meta: + """ + meta class + """ + model = Article + fields = ('is_published', ) + + def update(self, instance, validated_data): + """ + :param instance: article object + :param validated_data: + :return: + """ + instance.is_published = validated_data['is_published'] + instance.save() + return instance + + class DefaultArticleCardImageSerializer(serializers.ModelSerializer): """ Article Card serializer @@ -221,6 +243,7 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image + class ArticleListSerializer(serializers.ModelSerializer): """ serializer for article API @@ -234,13 +257,14 @@ class ArticleListSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('id', 'title', 'description','image', '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""" return obj.article_survey.all().count() * NUMBER['five'] @@ -253,6 +277,7 @@ class ArticleListSerializer(serializers.ModelSerializer): return junior_article.is_completed return False + class ArticleQuestionSerializer(serializers.ModelSerializer): """ article survey serializer @@ -263,7 +288,6 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): correct_answer = serializers.SerializerMethodField('get_correct_answer') attempted_answer = serializers.SerializerMethodField('get_attempted_answer') - def get_is_attempt(self, obj): """attempt question or not""" context_data = self.context.get('user') @@ -295,6 +319,7 @@ class ArticleQuestionSerializer(serializers.ModelSerializer): model = ArticleSurvey fields = ('id', 'question', 'options', 'points', 'is_attempt', 'correct_answer', 'attempted_answer') + class StartAssessmentSerializer(serializers.ModelSerializer): """ serializer for article API @@ -310,6 +335,7 @@ class StartAssessmentSerializer(serializers.ModelSerializer): if data: return data.current_que_page if data.current_que_page < total_count else data.current_que_page - 1 return NUMBER['zero'] + class Meta(object): """ meta class @@ -318,7 +344,6 @@ class StartAssessmentSerializer(serializers.ModelSerializer): fields = ('article_survey', 'current_page') - class ArticleCardlistSerializer(serializers.ModelSerializer): """ Article Card serializer diff --git a/web_admin/views/article.py b/web_admin/views/article.py index ab55d16..e2cee07 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -17,7 +17,7 @@ from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticle from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, DefaultArticleCardImageSerializer, ArticleListSerializer, - ArticleCardlistSerializer) + ArticleCardlistSerializer, ArticleStatusChangeSerializer) USER = get_user_model() @@ -32,7 +32,6 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel queryset = Article filter_backends = (SearchFilter,) search_fields = ['title'] - http_method_names = ['get', 'post', 'put', 'delete'] def get_queryset(self): article = self.queryset.objects.filter(is_deleted=False).prefetch_related( @@ -130,21 +129,22 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='status-change', url_path='status-change', - detail=True) + @action(methods=['patch'], url_name='status-change', url_path='status-change', + detail=True, serializer_class=ArticleStatusChangeSerializer) def article_status_change(self, request, *args, **kwargs): """ article un-publish or publish api method - :param request: article id + :param request: article id and + { + "is_published": true/false + } :return: success message """ - try: - article = Article.objects.filter(id=kwargs['pk']).first() - article.is_published = False if article.is_published else True - article.save(update_fields=['is_published']) - return custom_response(SUCCESS_CODE["3038"]) - except AttributeError: - return custom_error_response(ERROR_CODE["2041"], response_status=status.HTTP_400_BAD_REQUEST) + article = Article.objects.filter(id=kwargs['pk']).first() + serializer = self.serializer_class(article, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response(SUCCESS_CODE["3038"]) @action(methods=['get'], url_name='remove-card', url_path='remove-card', detail=True) From f3478c972ee11efac56c6e83bc084f5b8c01841c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 7 Sep 2023 15:10:18 +0530 Subject: [PATCH 330/372] modification in create task api --- guardian/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 635b8a0..45ba54d 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -205,7 +205,9 @@ class CreateTaskAPIView(viewsets.ModelViewSet): request.data.pop('default_image') guardian = Guardian.objects.filter(user=request.user).select_related('user').last() - junior_data = Junior.objects.filter(id__in=junior_ids).select_related('auth') + junior_data = Junior.objects.filter(id__in=junior_ids, + guardian_code__contains=[guardian.guardian_code] + ).select_related('auth') if junior_data: for junior in junior_data: index = junior.guardian_code.index(guardian.guardian_code) From 441842df74edfd17028f4b446f5300790b7169b5 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 7 Sep 2023 19:24:37 +0530 Subject: [PATCH 331/372] test cases for web admin article --- .coverage | Bin 0 -> 86016 bytes .gitignore | 1 - account/tests.py | 62 +- coverage-reports/coverage.xml | 5450 +++++++++++++++++++++++++++++++ requirements.txt | 3 +- web_admin/tests.py | 6 - web_admin/tests/__init__.py | 0 web_admin/tests/test_article.py | 255 ++ web_admin/tests/test_set_up.py | 301 ++ 9 files changed, 6067 insertions(+), 11 deletions(-) create mode 100644 .coverage create mode 100644 coverage-reports/coverage.xml delete mode 100644 web_admin/tests.py create mode 100644 web_admin/tests/__init__.py create mode 100644 web_admin/tests/test_article.py create mode 100644 web_admin/tests/test_set_up.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..54b2bef06e7247a33767c8d51e38ad691ba51e31 GIT binary patch literal 86016 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 literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 63de945..7185040 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ media/ *.name *.iml *.log -*.xml *.pyo .DS_Store .idea diff --git a/account/tests.py b/account/tests.py index f2435b9..a58f7a3 100644 --- a/account/tests.py +++ b/account/tests.py @@ -1,5 +1,61 @@ -"""Test cases file of account""" -"""Django import""" +""" +test cases file of account +""" +# django imports from django.test import TestCase +from rest_framework.test import APIClient +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse +from rest_framework_simplejwt.tokens import RefreshToken + + +class UserLoginTestCase(TestCase): + """ + test cases for login + """ + def setUp(self): + """ + set up data + :return: + """ + self.client = APIClient() + self.user = User.objects.create_superuser( + username='admin@example.com', + email='admin@example.com', + password='admin@1234' + ) + + def test_admin_login_success(self): + """ + test admin login with valid credentials + :return: + """ + url = reverse('account:admin-login') + data = { + 'email': 'admin@example.com', + 'password': 'admin@1234', + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('auth_token', response.data['data']) + self.assertIn('refresh_token', response.data['data']) + self.assertEqual(response.data['data']['username'], data['email']) + + def test_admin_login_invalid_credentials(self): + """ + test admin login with invalid credentials + :return: + """ + url = reverse('account:admin-login') + data = { + 'email': 'admin@example.com', + 'password': 'admin@1235', + } + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertNotIn('auth_token', response.data) + self.assertNotIn('refresh_token', response.data) + + # Add more test cases as needed -# Create your tests here. diff --git a/coverage-reports/coverage.xml b/coverage-reports/coverage.xml new file mode 100644 index 0000000..dbf7607 --- /dev/null +++ b/coverage-reports/coverage.xml @@ -0,0 +1,5450 @@ + + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/requirements.txt b/requirements.txt index 624a176..1dddf86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -101,4 +101,5 @@ vine==5.0.0 wcwidth==0.2.6 pandas==2.0.3 -XlsxWriter==3.1.2 \ No newline at end of file +XlsxWriter==3.1.2 +coverage==7.3.1 diff --git a/web_admin/tests.py b/web_admin/tests.py deleted file mode 100644 index 8800281..0000000 --- a/web_admin/tests.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -web_admin test file -""" -from django.test import TestCase - -# Create your tests here. diff --git a/web_admin/tests/__init__.py b/web_admin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_admin/tests/test_article.py b/web_admin/tests/test_article.py new file mode 100644 index 0000000..728907c --- /dev/null +++ b/web_admin/tests/test_article.py @@ -0,0 +1,255 @@ +""" +web_admin test article file +""" +# django imports +from django.test import TestCase +from django.urls import reverse +from django.contrib.auth import get_user_model +from rest_framework.test import APITestCase +from rest_framework.test import APIClient +from rest_framework import status + +# local imports +from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage +from web_admin.tests.test_set_up import ArticleTestSetUp + +# user model +User = get_user_model() + + +class ArticleViewSetTestCase(ArticleTestSetUp): + """ + test cases for article create, update, list, retrieve, delete + """ + def setUp(self): + """ + inherit data here + :return: + """ + super(ArticleViewSetTestCase, self).setUp() + + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + + def test_article_create_with_default_card_image(self): + """ + test article create with default card_image + :return: + """ + url = reverse('web_admin:article-list') + response = self.client.post(url, self.article_data_with_default_card_image, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Check that a new article was created + self.assertEqual(Article.objects.count(), 2) + + def test_article_create_with_base64_card_image(self): + """ + test article create with base64 card image + :return: + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('web_admin:article-list') + response = self.client.post(url, self.article_data_with_base64_card_image, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Check that a new article was created + self.assertEqual(Article.objects.count(), 2) + + def test_article_update(self): + """ + test article update + :return: + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + response = self.client.put(url, self.article_update_data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.article.refresh_from_db() + self.assertEqual(self.article.title, self.article_update_data['title']) + self.assertEqual(self.article.article_cards.count(), 1) + self.assertEqual(self.article.article_survey.count(), 5) + self.assertEqual(self.article.article_survey.first().options.count(), 3) + + def test_articles_list(self): + """ + test articles list + :return: + """ + url = reverse('web_admin:article-list') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one article exists in the database + self.assertEqual(len(response.data['data']), 1) + + def test_article_retrieve(self): + """ + test article retrieve + :return: + """ + url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_article_delete(self): + """ + test article delete + :return: + """ + url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + response = self.client.delete(url) + self.article.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.article.is_deleted, True) + + def test_article_create_with_invalid_data(self): + """ + test article create with invalid data + :return: + """ + url = reverse('web_admin:article-list') + # Missing article_cards + invalid_data = { + "title": "Invalid Article", + "article_survey": [{"question": "Invalid Survey Question"}] + } + response = self.client.post(url, invalid_data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_article_status_change(self): + """ + test article status change (publish/un-publish) + :return: + """ + url = reverse('web_admin:article-status-change', kwargs={'pk': self.article.id}) + data = { + "is_published": False + } + response = self.client.patch(url, data, format='json') + self.article.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.article.is_published, False) + + def test_article_card_remove(self): + """ + test article card remove + :return: + """ + url = reverse('web_admin:article-remove-card', kwargs={'pk': self.article_card.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(ArticleCard.objects.count(), 0) + + def test_article_survey_remove(self): + """ + test article survey remove + :return: + """ + url = reverse('web_admin:article-remove-survey', kwargs={'pk': self.article_survey.id}) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(ArticleSurvey.objects.count(), 0) + + def test_article_card_create_with_default_card_image(self): + """ + test article card create with default card_image + :return: + """ + url = reverse('web_admin:article-test-add-card') + response = self.client.post(url, self.article_card_data_with_default_card_image, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Check that a new article card was created + self.assertEqual(ArticleCard.objects.count(), 2) + + def test_article_cards_list(self): + """ + test article cards list + :return: + """ + url = reverse('web_admin:article-test-list-card') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one article exists in the database + self.assertEqual(len(response.data['data']), 1) + + +class DefaultArticleCardImagesViewSetTestCase(APITestCase): + """ + test case for default article card image + """ + def setUp(self): + """ + data setup + :return: + """ + self.client = APIClient() + self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com', + password='admin@1234', is_staff=True, is_superuser=True) + self.default_image = DefaultArticleCardImage.objects.create( + image_name="card1.jpg", + image_url="https://example.com/updated_card1.jpg") + + def test_default_article_card_image_list(self): + """ + test default article card image list + :return: + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('web_admin:default-card-images-list') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one default article card image exists in the database + self.assertEqual(len(response.data['data']), 1) + + +class ArticleListViewSetTestCase(ArticleTestSetUp): + """ + test cases for article list for junior + """ + def setUp(self): + """ + data setup + :return: + """ + super(ArticleListViewSetTestCase, self).setUp() + + self.client.force_authenticate(user=self.user) + + def test_article_list(self): + """ + test article list + :return: + """ + url = reverse('web_admin:article-list-list') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one article exists in the database + self.assertEqual(len(response.data['data']), 1) + + +class ArticleCardListViewSetTestCase(ArticleTestSetUp): + """ + test cases for article card list for junior + """ + def setUp(self): + """ + data setup + :return: + """ + super(ArticleCardListViewSetTestCase, self).setUp() + + self.client.force_authenticate(user=self.user) + + def test_article_cards_list(self): + """ + test article cards list for junior + :return: + """ + url = reverse('web_admin:article-card-list-list') + query_params = { + 'article_id': self.article.id, + } + response = self.client.get(url, query_params) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one article exists in the database + self.assertEqual(len(response.data['data']), 1) + + # Add more test cases for edge cases, permissions, etc. diff --git a/web_admin/tests/test_set_up.py b/web_admin/tests/test_set_up.py new file mode 100644 index 0000000..aa6a664 --- /dev/null +++ b/web_admin/tests/test_set_up.py @@ -0,0 +1,301 @@ +""" +web_admin test set up file +""" +# django imports +from django.test import TestCase +from django.contrib.auth import get_user_model +from rest_framework.test import APITestCase +from rest_framework.test import APIClient + +# local imports +from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption + +# user model +User = get_user_model() + +# image data in base 64 string +base64_image = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBISEhIREhISEhgSERIREREYEhg" + "SGRERGRgZGhgYGBgcIS4lHB4rHxgYJjgmLC8xNTU1GiQ7QDszPy40NTEBDAwMEA8QGhISHjEhISE0NDQ0NDQ0N" + "DQ0NDQ0NDQxNDQ1NDQxNDQ0NDQ0NDQ0NDExNDE0MTQ0NDQ0NDQ0NDQ0P//AABEIALcBEwMBIgACEQEDEQH/xAAb" + "AAACAgMBAAAAAAAAAAAAAAAAAQIEAwUGB//EAEkQAAIBAgMEBgYGBgcIAwAAAAECAAMRBBIhBTFRYQYTIkFxkTJC" + "UoGhsRRDYnKCkhYjU8HR4QcVVGOD0vEkM5OissLT8ERVlP/EABgBAQEBAQEAAAAAAAAAAAAAAAABAgME/8QAIBEBA" + "QEBAAIDAQEBAQAAAAAAAAERAhIhMUFRAyJhE//aAAwDAQACEQMRAD8AuXiiJivAd4rxQgMGO8hHeQSvCRhKiUcheO" + "8CUJG8AZBK8d5G8cBxyMcCV4XkY7wHCKOA4RXheA4RQgOEUJQxHFCFOEUcBiSEhJQJRiREYgStCEIFGEIoBEYXheQ" + "AjkYAwJQihAccjHAcIoQHHIxwiQhFAGFSjkbwhEoCKOA4RQgOEUcAjvFKNPaSNdgjlL2VwV7YHeqneOBuL791pRfv" + "CVU2jRPrOn3qZP8A0ZpmTE0zuqU/A1FQ/lax+EDJGI8jWvlNuNtPORBhUowZGOBISUgJIQJQihAowvCK8AhFeEgcI" + "oQHCKOA7wvFCA4QhAcIrwgShIkxwHHIg90jSqBlzDcb/A2/dAyiCtfUd+4zFiKopo7nciM58ACZDZwtRpA7xSp38c" + "ohFmEIQHCKJmABJNgASSdwA3mBT2i5IWkpINS+YjetIemeRNwo5tfukGo303ACwG6wkab2DVn0NSxVTvSmPQXkbEs" + "RxYjulGviqjE5dBN5kWLRwR7jInDuOcqpiqi75Zw+0GYgWPlI2gKGU3ChTxXsHzGsyriqg3Van4m6z/rzTZCiGGsq" + "vhVJ3wmMa7Sqjvpt96nY/wDIyj4TMu1j61IeK1f+1k/7pWfBHumF8LUFgouWIVBxYmwHn38LwZG+w1ZaiB1DAEstm" + "ABBG/UEgzKJFECJTpqbimmUG1s7Elnc82YsffJCRlKEUIFGKEjeAQheK8gcIoxAJKRhAlHIR3gShISUBxyMcDG1TK" + "DmUkAbwM1x4DWURj1QqVcVKbNkJvc0mtcA99vHdNlNPtnZocGrTFnGrgfWKONt5kos47FCjUSo3oOOrc+yRcofi05" + "zAbYc5KTEj9eq3/uzmBB95kNt4rrKVDtf7tyHXnaynnpce+aYVwTfKFN9XF9bA917A+FpPJHc9KsRkwrjvqMtMeBN" + "2/5QZtaAsiDgqj4CcZtrH/SPoaA71zuPtk5B8m852GJxC01zMbDMqDmzEKAPeZZdGe8r4XFCo1TLqqMEze049Kx7w" + "NB5zT9INsFf9noHNUqHJcepfTfx+UnRqdXSTCYbtuq5Ge3YVvXYnS537o0b681m1cQMyUd+bt1B/dg6L+JhbwDS67" + "inTLOdES7NbuA1NpocPUNSobLnqVGD1ADpRX1VY8QthYbzc981BaqMahu3uEyJhXbcLTZ4fBqgzVDraZmxtNRYWm2" + "o1qbKJ3y2tGnTGttJixG0idFEqZKlQ3N98mLrLisffspvMwUsHUPaLHwmxw2BVe7WXGCqLnQCMNalcJV9qWcDS1NQ" + "nNkzU6Z4vudx4DsDmXlDE7UarUXD0Dq7ZS+8Io1Zj4AE+6blVVQqILKihUG85Rx4k7zzJk3TqYYkhIiSEjCUIQga6" + "8V4ooU4RQkDhFCBKO8jeECV4XkYQJXheRjgOSvIAxwJQvIwgcL0godXUqINASHX7p1+BvNUmthzM67pZhcypU4XRv" + "A7vj85ydGmSRyNjOPXrRbwDKlSm7g2QqzAbyVJNvMTNjsbUr1OsqNYJqiA2CcLc+cxV6eU3vpYHz3yuzm2gv47v5z" + "E6tRdwjqTmYM3BQcoP3mm42Vj6SEhKb1KjdyWyoL7hY7uJmowOw8RWsxXKvcXOUEclGpnR9VUwtJ3apSRKaliEo2" + "JPcAS2pJsNZ15lMU+kONqVGp4Yfq8xV6rXuyi/ZWw0uSL2ufRF9JtdkNTwyZFXmp3kt3knvM4vZuJZ6r16tRFLG7" + "FiMxOg7I3AWsL23Cb+njLgEeIuLXnWLG+frKlybgQXA8ZfwTh0Vh3j4ywEm1UqWDAlpKdtwmdUlbHY6nRQsxGm4c" + "4VOvXSmpZyBYTjdq7aeqSFuq/OV9qbVesddFvumDZeC+kVUp6hdXqMPVpr6R8ToBzYTn11vqOnPPjNro+i+C6uma" + "7DtVhlp/Zog7/AMTDyUcZuryJYdwCgABVG5VAsAOQFoXlkxyt26mDGDIXkgZUTvCLNFCNfETFeRkVK8LyMtYbChwT" + "1iLb1b3Y/hkFe8Ly+mzr31bQE+ja9pE4Rcpa7aWvuG+XE2KccylEHteY/hMbFR3N+Yfwg0oXgHS9u18DMtOhnNkLE" + "86Zt5i8isUInUqSDvBsfGK8Cd4XkLx3gTvC8xfSKanLULj7qhtPeRJtXo+q5P3iU+SkRpjBtKl1lKonFSR4jUTjMJ" + "SHWWPebTuhWTvQtySrTb4Egzkm2biDUY08PXK5mKE0z6N+zecf6y34WD6EHYM25QFVOLXOpm52fsdFIeoAzb1U7l5" + "24xbLwddNamFrOfV3Jl56jWdBRwVR99OrT+8EI8w9/hJ/P+d+eisF5xnTPaJd1wqXISz1Ld7n0VPgDf3jhOx26hwlC" + "pXcoQgAVbkFnOirqOP755W2Ie7OWOZmLseLHUmd2WajhqisGybt1wrfAmdZsSilU3rVFp2toWy33776i9vA9xM4s1G" + "be3mYUUJN8nWAd1jY+UsHrmGx+FT9XTqK9jlOTthT9phoPObLOoF7zy3AdY4ChkRF1NNcxPgb7p0CYmoqdltw3Humt" + "Tnqblb/AGnthKYOus4rH7Qaq12OncJDEVyxuxuZXYicuutevnnEGedh0fwfU0QzCz18rvxWn6i/HMfvDhNBsTALWqg" + "MLpTHWVeag9lPxNYeGbhOueoWJY7ybmXmfbn/AE6+k80eaYg0YaacmUNJBpiDRhoGa8JjvCBTvIVXyqW4SRMxV0zKR" + "5SB4Z6mW5eopOtkcoF8t58Zq9pF+su7FyR6R3kbhfnNwoBRXXdorD2WHcfGa3aydkNwNj4GZ6+G+flSSow3MR7zLaY" + "yoPrH/MZrc0yK8SpY2i42p+0b8xjOLf2jKC1JLrZpleXFVCfTbztMqVmZtWJ14zWo8vYLffhrBi4TC8jeF4ErwvI3h" + "eBRxnp+4TDeZsV6XuErmYt9tyegZ3ez79VT19RflOCvO+wY/Vp9wfKa5Ss+bnC8LTWdI9pjC4d6gIzt2KQPfUINjbv" + "AALHks0w4vpxtIV6woA3p4cnNro1e1m/KDl8S05g0U9kTLpvuTckknUkneSe8kxGFZcMqbsqg/dEvIk1qb78JfpV7/" + "wAJZXLufa1RpgHNuPfz8ZYaoRulQVIGrNMe2HFJrfjKz6C8z1nvLmwcKHqGq4ulCzkHc9U+gnPUFjyXnOPU9+nt46s" + "52t3s/CfR6S0zo72qVuTkdlPwrp4lpYzSu1Qkkk3JJJPEx5ppzt32sZow0r5pINKjOGkw0wBpNWgZ80Ux5oQJnCniI" + "voh9oeU2hpiRKiTK3/lrUwzISyMpuLMhHZccDMWJw9N1ZM4psynsOQtj3Wc6MLzcBRMeJwqOuV1BHxB4g90mU9OAOk" + "SvNptvZTUQHBzKTa/eOF5pC8zPXpeloPJq8pB5lR5WV6nqZvsDhxluTa81OysOXbkN/hN/cAWA3Rf+LJPtH6OvtHyh" + "1Ce0YZ+UYY8JP8AX61nJjDJ7Rk/oqcWgpPCZkvwjL+p6aTHoFqEC+4b/CUnmw2oP1jeC/ISg4mftpjG+eg4Ydhfuj5" + "Tz4DUeInodD0V8B8pvlz6ZQJ5Z0z2t9JxJRDdKGamnBnv228wFH3ec7fpdtY4bDNkNqlW9OlxW47T/hHxKzy6nTCjw" + "0m2TQaCNorxGQMGNHsbxWvIQq+tS8iX1tK+Gexliqljy3zU9xicyXRr3AkkgADUknQAc7zucFstaVGnSIBIu9Q39Kq" + "3peIAAUchNJ0QwBqVDWYdmibJ9qqRp+UG/iVnZmlymMei9StZ9DT2R5mH0RPZHmZsTT5RdXyjGdigMIvsiAwi+yJf6" + "rkfKMUjwPlGGxSGFX2RJfRl4CXOr5GHV8jGGqn0deAhLnVHgfKKMNXDhxxMRoLzjNE85gqU27rzbDMmGB3Bj4awNBR" + "vVvKaPGbSqYOotRaqWIAeg5sHW51HA67/AJ7p1Gy9o0cWmemdRbPTPpIeY7xz3RC+mj2slNqNRCrklGygKWOa3Zt77" + "TgDsyqfq6g8UInsL4YcpgbDLxXzEl52nk8i/qyt+zf8ss4fZdS+qMPwz1L6MnL5wGHTh8I8WfJzOx8OiIQ6sCSPVO6" + "bMYVDqB8xNqKKD/SZkVB/pHgvm0ybPU+qPOZBsq/qjzkdo9H2Japh6jAsSzUmqNkYnUlD6hPDd4b5z1Q1Q3Vv1qNTN" + "8pbK6Xtfc1iDbmpjxh5V1C7K5LJjZ9u5ZyVSs4Fn6zLcHODoCO863Q79b219LW0RrVFBDFqikEHXtW7wVuQ48LHkd8" + "vjDyq/tjYNarUzJURLCygG1xz01mpbo/i1+soN4syn4CTTDP6dMPUAJtqWsQdbPqQQQdD394tKdTDqxIAWmw9JHBU9" + "28W5bxp4zP/AJxrzqwmxMSWAJoXvuFRifLJOtFZlQEqNBr2rDzM4KojKynLlYEZXV7knuytv3A6aHwnQbP6QOoC4he" + "sXvqA5WUfaJ0O7vPf6RicyM3q1zvSgYjEYhn6tslNFSnZlcZd7EZTvJ4dwE583Ghnqf8AVNGqDUwrimd7Kq3Qk69un" + "3anepF+Jmo2pspDpiafVncKy6o3Dt27Pg4HK8uJrghrHpNxtDo7Up6p2xv0328P9ZpmUqbMCJMaPNEkRMZEBbtZdw9" + "6mVFF2YhUHFibASi06/8Ao/2TnqNinHZp3SlzcjtN7gbe88JdxHZbGwlPDUadEAkqLs3tOdWPnLpqLwMnlEXViTI2x" + "9YvsmLrF9k+cmaYiyCMgh1q8D5x9aOB848ghkEZE1HrR7J84Gry+MnkEMgjIqHXcvj/AChMmQQjIGzGYmvM5EiymbY" + "aTbWyVxFMq2+2jW1BnAK+K2ZWFy+QGyuu9VO+3EcjPV2QzX4/ZtOspSooYH4eElmrKsdGeklPFAU2qIXIuh3dYO8W3" + "Zhbd8Jvmp8J4rtnY1bZ9TrKRZ6ZOa2oynxG4852vRHputVVp4huCiqdCp4VP83nxidfVS8/jtDS5SJonhLQPf5GBmm" + "FPqTwh1J4S1FArimw3eXGYcbs6nWUCoCCPRcGzIfst+7ce+XYXHEQOLx+yatElizOg+sXeuvrp3feGm+4WUGVaah1q" + "hVNiAai9W191jc5O70dOVzeehF14jzE0+L2PSZ+spVFoPmzNaxVj7RUEENzBHO8DlKaI5DremxF75SM6g6HMpKuvO5" + "te1wbwq1FtlqqdPRqLVCqDpuLAMh+Hdczpf6opub18QatiCqhxTUaWvckvm78wYGQbZgFwMVTI1AzKrNY9zFXUH8sN" + "OQxVGrYgXqLuKsi1CRzUGzjwHumKmhAzKM3fY5l3cGBJB0Oh7+8Tqf0fojdXpr9kKoUeC5tPO3KA2DRvm+k2PENluN" + "wBs2tu6+6Qc9QJRr026t11y5hoN18oKmx4g28Zv8ABbfOiYhAL6ZwDY3G7dlbv0uDodDHV2PhdBUxIPeMzrpzUk6Hm" + "NZA7OwY/wDmDv8AWo6g9x7OsQWn2PTdc+FqKg9j06RPDJvQ/dIGtyDOe2ps1b5cTT6snRal7o7buxUta+ugYBuU3iU" + "sPRXrFx1RFGmc1EZbk20LKQBcgW3cpdp7ewdQij9Io1C4IyZg3WADW62sdATuhl5tj+jVRLtTOcezuPumjdGU2YEHm" + "J6i7bNps3+09WBvpB7oh+zmUlfAG3KafpZsFBQfG08W7IqhjTdFdHU2yhSigg6ixN9+8SWNSuIwuHarUSlT1aowVeX" + "EnkBc+6e0bLwKUKNOkm5FA8T3k8ydZxf9HOy87VMUV9ECmneATqSPh4C3Gegii3CSNI2ECBJ9SeERonhL6GMgSJAmU" + "0WkeobhAx2E5na3S+nRqNSp0alZkYo5DKiq47u8/CdV1DTgekn9H9avXqYinVS9RsxRhaxsBoR4SU9sn6Z4o6jZx/O" + "5+VOQPTTGDU7Nb3M/+SaM9DNr0/QZvwYgr8LiMbH24m41z/jB/mTJsXK3P6eYj/62p/xH/wDHCaf6Dtz++80jk0yvV" + "TIFhMxSRKTpiMVxItaZssWWBTxNBKilHUMDoRPOukPRl8O5r4fNbeRv04Ed4nqBSQeiGBBAIMzZqy48ewfSerS0/XL" + "9lajADwE3CdPrKAaddjxNSdXieimGdixpjXlMQ6JYQfViZ9xc5rmm6ff3NU/4n8pibp4f7O/vqfynVHozhR9WvlD+o" + "MMPq0/KI2njy5JunL92G86n8pH9Nqn9nH5iZ2I2Phx9Wn5ZlXZVAfVp5RtM5cOemlfuw6ebfxkW6Y4o7qFPyc/vnoK" + "bIo/s18hLC7Io/s18hH+jOXmn6W4zuo0/yt/GH6V439lT/I38Z6imyKPsL5CZP6rpD1F8hHszl5Q3SbHndTQf4ZMP0" + "j2kfUT/AIRnqpwNMeovkIhhE9lfyiPaenkWJ2ljapBqUaVQqCFL4ZWsO+2YTD1uL/s9D/8AHT/yT2Q4VPZHkJjxgSl" + "TqVOrz9WjPlA1awvYR7PTyX6VtDJ1YpqE3ZBhkC777gvHWYsMMcj56dMI1iMwoICAd+uWdRiemBqXFE06Q3dvD9Yb/" + "eFTn7M0+Ix+MqHTH0wDpYBqHxyD5xl/TZ+NZWw+Mdi1SmCSbsxoJr78s6LZeKxlWm+HxFOpiKDBUYIQjUgN1sgt3bmE" + "0VXZOIqEE1UxGouoxGcn3m9p1WB6GBaYr0atSjW9JUUE07g6KyvdmG/Um3KWc02fjpMDtzCYemKFOnVQU0zCmaToSt7M" + "13tnNyLnXfIVemPsUiebOB8AD84ld8Th8tWmKdanchCCoYi47Bb1XW45ZuU5tcG7OVSx4EkLcd2/v5TWYzrdP0txJOi0" + "1HDKT8SZJOl9YelTpt4XX95lGnsGqd5A/C3zIA+Mm+y6VP8A3ldF43dE+AzQjaJ0xHrUj+FwfgQJap9KqB39YnigPyJnN" + "ddgU+sap9xGf43t8Jmp45NOqwdd/tELTPwAMnlJ841JXXYba9Kp6FS/4WHzEuAk6g3B1B33E45Fx1Q6YWlTXddv1j28WG" + "hnZ4NMtNFIIIUAgm5v36gSSy/C5Z8l2odqZ4SjD2v/AERzLCRGIyBhCbChaEICKwKwhMjGyGYnQwhAr1FMwtTPKEJloivG" + "wgjjj8I4TNtVZpgnvlpFMITUZZ1TnApzihKqLU+cQpc4QhEhS5xVsMrqytqGBVhxB3whCOZxHQHAvuDJf2WImtxX9HFK3Y" + "rOORsR8oQkxdazE9A8QostdSBuBFvlKI6PY6ibg0z4VCscJi9WK3WzRiwmRqGHY3vndi5Hnebangse2hrpSHBEAjhE6rXj" + "GYdEC/arYms/HtkfAS7huhuDXXJmPEkn5whN4xrZ0NkYdPRpIPdLS0kG5QPdCEskKlYcIWHCEJUO3KFuUIQD3QhCB//Z") + + +class ArticleTestSetUp(APITestCase): + """ + test cases data set up for article create, update + """ + def setUp(self): + """ + set up data for test + :return: + """ + self.client = APIClient() + self.user = User.objects.create_user(username='user@example.com', password='user@1234') + self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com', + password='admin@1234', is_staff=True, is_superuser=True) + + self.article = Article.objects.create(title="Existing Article", description="Existing Description", + is_published=True) + self.article_card = ArticleCard.objects.create(article=self.article, title="Card 1", + description="Card 1 Description") + self.article_survey = ArticleSurvey.objects.create(article=self.article, points=5, + question="Survey Question 1") + SurveyOption.objects.create(survey=self.article_survey, option="Option 1", is_answer=True) + SurveyOption.objects.create(survey=self.article_survey, option="Option 2", is_answer=False) + + # article data with default card image + self.article_data_with_default_card_image = { + "title": "Test Article", + "description": "Test Description", + "article_cards": [ + { + "title": "Card 1", + "description": "Card 1 Description", + "image_name": "card1.jpg", + "image_url": "https://example.com/updated_card1.jpg" + } + ], + # minimum 5 article survey needed + "article_survey": [ + { + "question": "Survey Question 1", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 2", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 3", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 4", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 5", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + ] + } + + # article data with base64 card image + self.article_data_with_base64_card_image = { + "title": "Test Article", + "description": "Test Description", + "article_cards": [ + { + "title": "Card 1", + "description": "Card 1 Description", + "image_name": "card1.jpg", + "image_url": base64_image + } + ], + # minimum 5 article survey needed + "article_survey": [ + { + "question": "Survey Question 1", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 2", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 3", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 4", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + { + "question": "Survey Question 5", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + } + ] + } + + # article update data + self.article_update_data = { + "title": "Updated Article", + "description": "Updated Description", + + # updated article card + "article_cards": [ + { + "id": self.article_card.id, + "title": "Updated Card 1", + "description": "Updated Card 1 Description", + "image_name": "updated_card1.jpg", + "image_url": "https://example.com/updated_card1.jpg" + } + ], + # updated article survey + "article_survey": [ + # updated article survey + { + "id": self.article_survey.id, + "question": "Updated Survey Question 1", + "options": [ + {"id": self.article_survey.options.first().id, + "option": "Updated Option 1", "is_answer": False}, + # New option + {"option": "Option 3", "is_answer": True} + ] + }, + # new article survey + { + "question": "Survey Question 2", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + # new article survey + { + "question": "Survey Question 3", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + # new article survey + { + "question": "Survey Question 4", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + }, + # new article survey + { + "question": "Survey Question 5", + "options": [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + } + ] + } + # article card data with default card image + self.article_card_data_with_default_card_image = { + "title": "Card 1", + "description": "Card 1 Description", + "image_name": "card1.jpg", + "image_url": "https://example.com/card2.jpg" + } From 1e97d7bd6b10a56954037dcb758a95fedc8c7f28 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 8 Sep 2023 16:14:29 +0530 Subject: [PATCH 332/372] modified task description field, modified check answer api and create task api --- .../0022_alter_juniortask_task_description.py | 18 ++++++++++++++++++ guardian/models.py | 2 +- guardian/views.py | 13 ++++++------- junior/views.py | 2 +- web_admin/views/auth.py | 2 +- 5 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 guardian/migrations/0022_alter_juniortask_task_description.py diff --git a/guardian/migrations/0022_alter_juniortask_task_description.py b/guardian/migrations/0022_alter_juniortask_task_description.py new file mode 100644 index 0000000..27e003c --- /dev/null +++ b/guardian/migrations/0022_alter_juniortask_task_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-09-08 10:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0021_guardian_is_deleted'), + ] + + operations = [ + migrations.AlterField( + model_name='juniortask', + name='task_description', + field=models.CharField(blank=True, max_length=500, null=True), + ), + ] diff --git a/guardian/models.py b/guardian/models.py index 5c6457a..94cb4e6 100644 --- a/guardian/models.py +++ b/guardian/models.py @@ -97,7 +97,7 @@ class JuniorTask(models.Model): """task details""" task_name = models.CharField(max_length=100) """task description""" - task_description = models.CharField(max_length=500) + task_description = models.CharField(max_length=500, null=True, blank=True) """points of the task""" points = models.IntegerField(default=TASK_POINTS) """last date of the task""" diff --git a/guardian/views.py b/guardian/views.py index 45ba54d..c4e952e 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -208,14 +208,13 @@ class CreateTaskAPIView(viewsets.ModelViewSet): junior_data = Junior.objects.filter(id__in=junior_ids, guardian_code__contains=[guardian.guardian_code] ).select_related('auth') - if junior_data: - for junior in junior_data: - index = junior.guardian_code.index(guardian.guardian_code) - status_index = junior.guardian_code_status[index] - if status_index == str(NUMBER['three']): - return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST) - else: + if not junior_data: return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + for junior in junior_data: + index = junior.guardian_code.index(guardian.guardian_code) + status_index = junior.guardian_code_status[index] + if status_index == str(NUMBER['three']): + return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST) # use TaskSerializer serializer serializer = TaskSerializer(context={"guardian": guardian, "image": image_data, diff --git a/junior/views.py b/junior/views.py index fb4771d..0dd632d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -643,7 +643,7 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): 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).last() + submit_ans = SurveyOption.objects.filter(id=answer_id, is_answer=True).last() junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user, question=queryset) if submit_ans: diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index 73f19e5..36f058b 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -4,7 +4,7 @@ web_admin auth views file # django imports from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action -from rest_framework import status +from rest_framework import status from django.contrib.auth import get_user_model # local imports From 7db6502a89d7a610de16153d15d663a52c6f6820 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 8 Sep 2023 16:41:59 +0530 Subject: [PATCH 333/372] check answer api fixed --- junior/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junior/views.py b/junior/views.py index 0dd632d..71a09b0 100644 --- a/junior/views.py +++ b/junior/views.py @@ -643,10 +643,10 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): 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: + if submit_ans.is_answer: junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True) JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points= F('total_points') + queryset.points) From 4a554272a06489e16322229f9960121d10838ebd Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 8 Sep 2023 16:41:59 +0530 Subject: [PATCH 334/372] check answer api fixed --- junior/views.py | 4 ++-- notifications/utils.py | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/junior/views.py b/junior/views.py index 0dd632d..71a09b0 100644 --- a/junior/views.py +++ b/junior/views.py @@ -643,10 +643,10 @@ class CheckAnswerAPIView(viewsets.ModelViewSet): 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: + if submit_ans.is_answer: junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True) JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points= F('total_points') + queryset.points) diff --git a/notifications/utils.py b/notifications/utils.py index bd2a8bd..83e3e95 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -104,7 +104,6 @@ def send_notification(notification_type, from_user_id, from_user_type, to_user_i 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() - 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=notification_data) if user_notification_type and user_notification_type.push_notification: @@ -139,13 +138,10 @@ def send_notification_multiple_user(notification_type, from_user_id, from_user_t 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)) + data=notification_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) From 859d26d07364d9fbbd233f44601d14580894cac7 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 8 Sep 2023 19:07:06 +0530 Subject: [PATCH 335/372] test cases for web admin auth --- celerybeat-schedule | Bin 20480 -> 20480 bytes web_admin/tests/test_auth.py | 99 +++++++++++++++++++++++++++++++++ web_admin/tests/test_set_up.py | 22 ++++++-- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 web_admin/tests/test_auth.py diff --git a/celerybeat-schedule b/celerybeat-schedule index be892edea3778b38a0afe5c729adddc781b59706..7638e5252440a76df3a3df6b009f2805ccced487 100644 GIT binary patch delta 74 zcmZozz}T>Wal>2(b`B0XMov@S$qUQ_8QCWr*l13ccT5)mi+0sa$q;Ls5;Vn|ck(pH VVgU{=1_s9DYM9`H&5};fc>xRT7ApV% delta 74 zcmZozz}T>Wal>2(c2*ufMZVII$qUQ_8QCTq*l13ccT5)mi_SeaB}1%jO3)N None: + """ + + :return: + """ + self.client = APIClient() + self.user = User.objects.create_user(username='user@example.com', password='user@1234') + self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com', + password='admin@1234', is_staff=True, is_superuser=True) + + +class ArticleTestSetUp(BaseSetUp): """ test cases data set up for article create, update """ @@ -112,10 +127,7 @@ class ArticleTestSetUp(APITestCase): set up data for test :return: """ - self.client = APIClient() - self.user = User.objects.create_user(username='user@example.com', password='user@1234') - self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com', - password='admin@1234', is_staff=True, is_superuser=True) + super(ArticleTestSetUp, self).setUp() self.article = Article.objects.create(title="Existing Article", description="Existing Description", is_published=True) From 6b0ea9174275746083b1d960160776eab2521680 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 8 Sep 2023 20:55:32 +0530 Subject: [PATCH 336/372] api to check whether given user exist or not --- junior/urls.py | 4 +++- junior/views.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/junior/urls.py b/junior/urls.py index 3c597c3..4e35d7c 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, FAQViewSet) + CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet, CheckJuniorApiViewSet) """Third party import""" from rest_framework import routers @@ -29,6 +29,8 @@ router.register('create-junior-profile', UpdateJuniorProfile, basename='profile- router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code') # junior list API""" router.register('junior-list', JuniorListAPIView, basename='junior-list') + +router.register('check-junior', CheckJuniorApiViewSet, basename='check-junior') # Add junior list API""" router.register('add-junior', AddJuniorAPIView, basename='add-junior') # Invited junior list API""" diff --git a/junior/views.py b/junior/views.py index 71a09b0..6fa8e38 100644 --- a/junior/views.py +++ b/junior/views.py @@ -164,6 +164,29 @@ class JuniorListAPIView(viewsets.ModelViewSet): return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) +class CheckJuniorApiViewSet(viewsets.GenericViewSet): + """ + api to check whether given user exist or not + """ + serializer_class = None + permission_classes = [IsAuthenticated] + + def get_queryset(self): + junior = Junior.objects.filter(auth__email=self.request.data.get('email')).first() + return junior + + def create(self, request, *args, **kwargs): + """ + :param request: + :return: + """ + junior = self.get_queryset() + data = { + 'junior_exist': True if junior else False + } + return custom_response(None, data) + + class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" serializer_class = AddJuniorSerializer From 9d4e7b05e4930c8cae4acb34edc624017bdff691 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 11 Sep 2023 12:08:22 +0530 Subject: [PATCH 337/372] modified add junior api for exsiting junior. --- base/messages.py | 4 ++-- junior/views.py | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/base/messages.py b/base/messages.py index e297137..82a149b 100644 --- a/base/messages.py +++ b/base/messages.py @@ -103,9 +103,9 @@ ERROR_CODE = { "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", + "2076": "This junior already associated with you", "2077": "You can not add guardian", - "2078": "This junior is not associate with you", + "2078": "This junior is not associated with you", # force update "2079": "Please update your app version for enjoying uninterrupted services", "2080": "Can not add App version", diff --git a/junior/views.py b/junior/views.py index 6fa8e38..28ab9d6 100644 --- a/junior/views.py +++ b/junior/views.py @@ -203,6 +203,16 @@ class AddJuniorAPIView(viewsets.ModelViewSet): "email":"abc@yopmail.com" }""" try: + if user := User.objects.filter(username=request.data['email']).first(): + 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) + info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], 'last_name': request.data['last_name'], 'image':None} @@ -216,15 +226,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): # upload image on ali baba 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(): - 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) if serializer.is_valid(): From bf1004696ae09d098908033703f85129793a1cba Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 11 Sep 2023 14:39:01 +0530 Subject: [PATCH 338/372] added test cases for auth --- .coverage | Bin 86016 -> 86016 bytes account/tests.py | 17 +- coverage-reports/coverage.xml | 1007 +++++++++++++++++-------------- web_admin/tests/test_article.py | 16 +- web_admin/tests/test_auth.py | 93 ++- web_admin/tests/test_set_up.py | 230 +++---- 6 files changed, 730 insertions(+), 633 deletions(-) diff --git a/.coverage b/.coverage index 54b2bef06e7247a33767c8d51e38ad691ba51e31..c5c9742d688286628453e0f8b84e1ae94c05c24b 100644 GIT binary patch delta 5795 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 J&6mrT|36O - + @@ -29,7 +29,7 @@ - + @@ -509,24 +509,27 @@ - - - + + + - - - - + - - + + - + + + + + + + @@ -566,7 +569,7 @@ - + @@ -673,11 +676,11 @@ - + - + - + @@ -1421,7 +1424,7 @@ - + @@ -1867,13 +1870,13 @@ - + - - + + @@ -2108,66 +2111,67 @@ - - - - + + + + + - - + + - - - - - - + + + + + + - - + + - - + + - - + + - - - - - + + + + + - - + + - - + + - + - - - - + + + + - - + + @@ -2179,43 +2183,42 @@ - - + + - + - - + + - - + + - + - + - + - - + + - - + + - - + + - @@ -2416,13 +2419,22 @@ + + + + + + + + + - + @@ -3006,6 +3018,7 @@ + @@ -3045,7 +3058,7 @@ - + @@ -3139,401 +3152,411 @@ - - - - + + + + + - + - - - - - - - - - - - - - - + + + + + + + - + - - - - - - - + - + - - + - - - - - - + + + + + + + + + + + + - + + + - - - - + + + + - - - - + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - + + + + + + - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + - + + + + + - - - - - - - - - - + + + + + + + + - + + - - - + + - - - - - - - - - + + + + + + + - + - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - - - - - - - + + + + + + + + + + + - - + + + - - - + + + - - - - + + + - - - + + - - - - - + - - - - + + + + + + + + + + - + - - - - - - + + + + + - - + - - + + - - - + + + - - - - + + + + - + + + - - - - + + + - - - - + + + - - - - - + + + + + + - - - - - + + + + + - - - - + + - - - - - - - - - + + + + + + + + + + + + + - - - - + + + - - + + - - - - + - - - + + + - - - + + + + + - + - - - - - + + - - - - + + + + + + - + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -3837,7 +3860,7 @@ - + @@ -3953,7 +3976,7 @@ - + @@ -4016,37 +4039,34 @@ - + - - - - - + + + + + - - - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -4318,7 +4338,7 @@ - + @@ -4606,7 +4626,7 @@ - + @@ -4625,40 +4645,40 @@ - - - - + + + + - - + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - + - - - + + + @@ -4667,18 +4687,18 @@ - - - - + + + + - - - - + + + + - - + + @@ -4843,120 +4863,120 @@ - - - + - - - - - - - - - - - + - - - - - + - - - - + + - - + - + - - - - - - - - + + - - - - + - - + - - + - - + - - - + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -4964,27 +4984,108 @@ - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - + + + + + + + + + + + + + + + + + + + + + + + - + @@ -5216,7 +5317,7 @@ - + @@ -5231,26 +5332,26 @@ - - - - - + + + + + - - - - + + + + - - - - - - - + + + + + + + diff --git a/web_admin/tests/test_article.py b/web_admin/tests/test_article.py index 728907c..f77b26b 100644 --- a/web_admin/tests/test_article.py +++ b/web_admin/tests/test_article.py @@ -36,7 +36,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): test article create with default card_image :return: """ - url = reverse('web_admin:article-list') + url = reverse(self.article_list_url) response = self.client.post(url, self.article_data_with_default_card_image, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) # Check that a new article was created @@ -48,7 +48,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): :return: """ self.client.force_authenticate(user=self.admin_user) - url = reverse('web_admin:article-list') + url = reverse(self.article_list_url) response = self.client.post(url, self.article_data_with_base64_card_image, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) # Check that a new article was created @@ -60,13 +60,13 @@ class ArticleViewSetTestCase(ArticleTestSetUp): :return: """ self.client.force_authenticate(user=self.admin_user) - url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + url = reverse(self.article_detail_url, kwargs={'pk': self.article.id}) response = self.client.put(url, self.article_update_data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.article.refresh_from_db() self.assertEqual(self.article.title, self.article_update_data['title']) self.assertEqual(self.article.article_cards.count(), 1) - self.assertEqual(self.article.article_survey.count(), 5) + self.assertEqual(self.article.article_survey.count(), 6) self.assertEqual(self.article.article_survey.first().options.count(), 3) def test_articles_list(self): @@ -74,7 +74,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): test articles list :return: """ - url = reverse('web_admin:article-list') + url = reverse(self.article_list_url) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) # Assuming only one article exists in the database @@ -85,7 +85,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): test article retrieve :return: """ - url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + url = reverse(self.article_detail_url, kwargs={'pk': self.article.id}) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -94,7 +94,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): test article delete :return: """ - url = reverse('web_admin:article-detail', kwargs={'pk': self.article.id}) + url = reverse(self.article_detail_url, kwargs={'pk': self.article.id}) response = self.client.delete(url) self.article.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -105,7 +105,7 @@ class ArticleViewSetTestCase(ArticleTestSetUp): test article create with invalid data :return: """ - url = reverse('web_admin:article-list') + url = reverse(self.article_list_url) # Missing article_cards invalid_data = { "title": "Invalid Article", diff --git a/web_admin/tests/test_auth.py b/web_admin/tests/test_auth.py index 6033af1..fa16ced 100644 --- a/web_admin/tests/test_auth.py +++ b/web_admin/tests/test_auth.py @@ -19,7 +19,7 @@ User = get_user_model() class AdminOTPTestCase(BaseSetUp): """ - + test case to send otp to admin email """ def setUp(self): @@ -28,36 +28,35 @@ class AdminOTPTestCase(BaseSetUp): :return: """ super(AdminOTPTestCase, self).setUp() + self.url = reverse('web_admin:admin-otp') def test_admin_otp_for_valid_email(self): """ - + test admin otp for valid email :return: """ - url = reverse('web_admin:admin-otp') data = { - 'email': 'admin@example.com' + 'email': self.admin_email } - response = self.client.post(url, data, format='json') + response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(UserEmailOtp.objects.count(), 1) def test_admin_otp_for_invalid_email(self): """ - + test admin otp for invalid email :return: """ - url = reverse('web_admin:admin-otp') data = { 'email': 'notadmin@example.com' } - response = self.client.post(url, data, format='json') + response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class AdminVerifyOTPTestCase(BaseSetUp): """ - + test case to verify otp for admin email """ def setUp(self): @@ -68,32 +67,94 @@ class AdminVerifyOTPTestCase(BaseSetUp): super(AdminVerifyOTPTestCase, self).setUp() self.verification_code = generate_otp() expiry = timezone.now() + timezone.timedelta(days=1) - self.user_email_otp = UserEmailOtp.objects.create(email='admin@example.com', + self.user_email_otp = UserEmailOtp.objects.create(email=self.admin_email, otp=self.verification_code, expired_at=expiry, user_type=dict(USER_TYPE).get('3'), ) + self.url = reverse('web_admin:admin-verify-otp') def test_admin_verify_otp_with_valid_otp(self): - url = reverse('web_admin:admin-verify-otp') + """ + test admin verify otp with valid otp + :return: + """ + data = { - 'email': 'admin@example.com', + 'email': self.admin_email, "otp": self.verification_code } - response = self.client.post(url, data) + response = self.client.post(self.url, data) self.user_email_otp.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(self.user_email_otp.is_verified, True) def test_admin_verify_otp_with_invalid_otp(self): - url = reverse('web_admin:admin-verify-otp') + """ + test admin verify otp with invalid otp + :return: + """ data = { - 'email': 'admin@example.com', + 'email': self.admin_email, "otp": generate_otp() } - response = self.client.post(url, data) + response = self.client.post(self.url, data) self.user_email_otp.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(self.user_email_otp.is_verified, False) + + +class AdminCreateNewPassword(BaseSetUp): + """ + test case to create new password for admin email + """ + + def setUp(self): + """ + inherit data here + :return: + """ + super(AdminCreateNewPassword, self).setUp() + self.verification_code = generate_otp() + expiry = timezone.now() + timezone.timedelta(days=1) + self.user_email_otp = UserEmailOtp.objects.create(email=self.admin_email, + otp=self.verification_code, + expired_at=expiry, + user_type=dict(USER_TYPE).get('3'), + ) + self.url = reverse('web_admin:admin-create-password') + + def test_admin_create_new_password_after_verification(self): + """ + test admin create new password + :return: + """ + self.user_email_otp.is_verified = True + self.user_email_otp.save() + + data = { + 'email': self.admin_email, + "new_password": "New@1234", + "confirm_password": "New@1234" + } + + response = self.client.post(self.url, data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(UserEmailOtp.objects.count(), 0) + + def test_admin_create_new_password_without_verification(self): + """ + test admin create new password + :return: + """ + data = { + 'email': self.admin_email, + "new_password": "Some@1234", + "confirm_password": "Some@1234" + } + + response = self.client.post(self.url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(UserEmailOtp.objects.count(), 1) diff --git a/web_admin/tests/test_set_up.py b/web_admin/tests/test_set_up.py index f5cf1f6..5225969 100644 --- a/web_admin/tests/test_set_up.py +++ b/web_admin/tests/test_set_up.py @@ -105,23 +105,31 @@ base64_image = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBIS class BaseSetUp(APITestCase): """ - + basic setup """ + def setUp(self) -> None: """ - + user data :return: """ + self.user_email = 'user@example.com' + self.admin_email = 'admin@example.com' self.client = APIClient() - self.user = User.objects.create_user(username='user@example.com', password='user@1234') - self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com', - password='admin@1234', is_staff=True, is_superuser=True) + self.user = User.objects.create_user(username=self.user_email, email=self.user_email) + self.user.set_password('user@1234') + self.user.save() + self.admin_user = User.objects.create_user(username=self.admin_email, email=self.admin_email, + is_staff=True, is_superuser=True) + self.admin_user.set_password('admin@1234') + self.admin_user.save() class ArticleTestSetUp(BaseSetUp): """ test cases data set up for article create, update """ + def setUp(self): """ set up data for test @@ -131,63 +139,71 @@ class ArticleTestSetUp(BaseSetUp): self.article = Article.objects.create(title="Existing Article", description="Existing Description", is_published=True) - self.article_card = ArticleCard.objects.create(article=self.article, title="Card 1", - description="Card 1 Description") + self.article_card = ArticleCard.objects.create(article=self.article, title="Existing Card 1", + description="Existing Card 1 Description") self.article_survey = ArticleSurvey.objects.create(article=self.article, points=5, - question="Survey Question 1") - SurveyOption.objects.create(survey=self.article_survey, option="Option 1", is_answer=True) - SurveyOption.objects.create(survey=self.article_survey, option="Option 2", is_answer=False) + question="Existing Survey Question 1") + SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 1", is_answer=True) + SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 2", is_answer=False) + + self.article_list_url = 'web_admin:article-list' + self.article_detail_url = 'web_admin:article-detail' + + # article card data with default card image + self.article_card_data_with_default_card_image = { + "title": "Card 1", + "description": "Card 1 Description", + "image_name": "card1.jpg", + "image_url": "https://example.com/card1.jpg" + } + + # article card data with base64 image + self.article_card_data_with_base64_image = { + "title": "Card base64", + "description": "Card base64 Description", + "image_name": "base64_image.jpg", + "image_url": base64_image + } + + # article survey option data + self.article_survey_option_data = [ + {"option": "Option 1", "is_answer": True}, + {"option": "Option 2", "is_answer": False} + ] + + # article survey data + self.article_survey_data = [ + { + "question": "Survey Question 1", + "options": self.article_survey_option_data + }, + { + "question": "Survey Question 2", + "options": self.article_survey_option_data + }, + { + "question": "Survey Question 3", + "options": self.article_survey_option_data + }, + { + "question": "Survey Question 4", + "options": self.article_survey_option_data + }, + { + "question": "Survey Question 5", + "options": self.article_survey_option_data + }, + ] # article data with default card image self.article_data_with_default_card_image = { "title": "Test Article", "description": "Test Description", "article_cards": [ - { - "title": "Card 1", - "description": "Card 1 Description", - "image_name": "card1.jpg", - "image_url": "https://example.com/updated_card1.jpg" - } + self.article_card_data_with_default_card_image ], # minimum 5 article survey needed - "article_survey": [ - { - "question": "Survey Question 1", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 2", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 3", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 4", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 5", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - ] + "article_survey": self.article_survey_data } # article data with base64 card image @@ -195,51 +211,10 @@ class ArticleTestSetUp(BaseSetUp): "title": "Test Article", "description": "Test Description", "article_cards": [ - { - "title": "Card 1", - "description": "Card 1 Description", - "image_name": "card1.jpg", - "image_url": base64_image - } + self.article_card_data_with_base64_image ], # minimum 5 article survey needed - "article_survey": [ - { - "question": "Survey Question 1", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 2", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 3", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 4", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - { - "question": "Survey Question 5", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - } - ] + "article_survey": self.article_survey_data } # article update data @@ -259,55 +234,16 @@ class ArticleTestSetUp(BaseSetUp): ], # updated article survey "article_survey": [ - # updated article survey - { - "id": self.article_survey.id, - "question": "Updated Survey Question 1", - "options": [ - {"id": self.article_survey.options.first().id, - "option": "Updated Option 1", "is_answer": False}, - # New option - {"option": "Option 3", "is_answer": True} - ] - }, - # new article survey - { - "question": "Survey Question 2", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - # new article survey - { - "question": "Survey Question 3", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - # new article survey - { - "question": "Survey Question 4", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - }, - # new article survey - { - "question": "Survey Question 5", - "options": [ - {"option": "Option 1", "is_answer": True}, - {"option": "Option 2", "is_answer": False} - ] - } - ] - } - # article card data with default card image - self.article_card_data_with_default_card_image = { - "title": "Card 1", - "description": "Card 1 Description", - "image_name": "card1.jpg", - "image_url": "https://example.com/card2.jpg" + # updated article survey + { + "id": self.article_survey.id, + "question": "Updated Survey Question 1", + "options": [ + {"id": self.article_survey.options.first().id, + "option": "Updated Option 1", "is_answer": False}, + # New option + {"option": "New Option 3", "is_answer": True} + ] + # added new articles + }] + self.article_survey_data } From 373c1b70ab186c6934324e720e91186300fca1e5 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 11 Sep 2023 16:40:07 +0530 Subject: [PATCH 339/372] make special password method modified --- account/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/utils.py b/account/utils.py index 428df15..0d79483 100644 --- a/account/utils.py +++ b/account/utils.py @@ -303,7 +303,7 @@ def make_special_password(length=10): special_characters = '!@#$%^&*()_-+=<>?/[]{}|' # Combine character sets - all_characters = lowercase_letters + uppercase_letters + digits + special_characters + alphabets = lowercase_letters + uppercase_letters # Create a password with random characters password = ( @@ -311,7 +311,7 @@ def make_special_password(length=10): secrets.choice(uppercase_letters) + secrets.choice(digits) + secrets.choice(special_characters) + - ''.join(secrets.choice(all_characters) for _ in range(length - 4)) + ''.join(secrets.choice(alphabets) for _ in range(length - 4)) ) # Shuffle the characters to make it more random From c800853e9b41a684abc15077abb2df75d14dc644 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 11 Sep 2023 16:45:07 +0530 Subject: [PATCH 340/372] make special password method modified --- account/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account/utils.py b/account/utils.py index 428df15..0d79483 100644 --- a/account/utils.py +++ b/account/utils.py @@ -303,7 +303,7 @@ def make_special_password(length=10): special_characters = '!@#$%^&*()_-+=<>?/[]{}|' # Combine character sets - all_characters = lowercase_letters + uppercase_letters + digits + special_characters + alphabets = lowercase_letters + uppercase_letters # Create a password with random characters password = ( @@ -311,7 +311,7 @@ def make_special_password(length=10): secrets.choice(uppercase_letters) + secrets.choice(digits) + secrets.choice(special_characters) + - ''.join(secrets.choice(all_characters) for _ in range(length - 4)) + ''.join(secrets.choice(alphabets) for _ in range(length - 4)) ) # Shuffle the characters to make it more random From ca0041caa6e27649f0fefa369a8c1c4b2eb7f7c3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 11 Sep 2023 17:01:03 +0530 Subject: [PATCH 341/372] make special password method modified --- account/utils.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/account/utils.py b/account/utils.py index 0d79483..564472b 100644 --- a/account/utils.py +++ b/account/utils.py @@ -300,24 +300,21 @@ def make_special_password(length=10): lowercase_letters = string.ascii_lowercase uppercase_letters = string.ascii_uppercase digits = string.digits - special_characters = '!@#$%^&*()_-+=<>?/[]{}|' + special_characters = '@#$%&*?' # Combine character sets alphabets = lowercase_letters + uppercase_letters # Create a password with random characters - password = ( - secrets.choice(lowercase_letters) + + password = [ secrets.choice(uppercase_letters) + + secrets.choice(lowercase_letters) + secrets.choice(digits) + secrets.choice(special_characters) + ''.join(secrets.choice(alphabets) for _ in range(length - 4)) - ) + ] - # Shuffle the characters to make it more random - password_list = list(password) - random.shuffle(password_list) - return ''.join(password_list) + return ''.join(password) def task_status_fun(status_value): """task status""" From 2444b6f55e093775a6f9bd7deb9de2c8500b81ee Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 12 Sep 2023 14:40:50 +0530 Subject: [PATCH 342/372] test cases for admin user management, analytics and notification --- notifications/tests.py | 92 ++++++++- web_admin/tests/test_analytics.py | 93 +++++++++ web_admin/tests/test_set_up.py | 130 ++++++++++++ web_admin/tests/test_user_management.py | 255 ++++++++++++++++++++++++ web_admin/urls.py | 2 +- 5 files changed, 569 insertions(+), 3 deletions(-) create mode 100644 web_admin/tests/test_analytics.py create mode 100644 web_admin/tests/test_user_management.py diff --git a/notifications/tests.py b/notifications/tests.py index fd087b9..925d5a1 100644 --- a/notifications/tests.py +++ b/notifications/tests.py @@ -1,6 +1,94 @@ """ notification test file """ -from django.test import TestCase +# third party imports +from fcm_django.models import FCMDevice -# Create your tests here. +# django imports +from django.urls import reverse +from rest_framework import status + +# local imports +from account.serializers import GuardianSerializer +from notifications.models import Notification +from web_admin.tests.test_set_up import AnalyticsSetUp + + +class NotificationTestCase(AnalyticsSetUp): + """ + test notification + """ + def setUp(self) -> None: + """ + test data up + :return: + """ + super(NotificationTestCase, self).setUp() + + # notification create + self.notification = Notification.objects.create(notification_to=self.user) + + # to get guardian/user auth token + self.guardian_data = GuardianSerializer( + self.guardian, context={'user_type': 2} + ).data + self.auth_token = self.guardian_data['auth_token'] + + # api header + self.header = { + 'HTTP_AUTHORIZATION': f'Bearer {self.auth_token}', + 'Content-Type': "Application/json" + } + + def test_notification_list(self): + """ + test notification list + :return: + """ + + url = reverse('notifications:notifications-list') + response = self.client.get(url, **self.header) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one notification exists in the database + self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 1) + + def test_fcm_register(self): + """ + test fcm register + :return: + """ + url = reverse('notifications:notifications-device') + data = { + 'registration_id': 'registration_id', + 'device_id': 'device_id', + 'type': 'ios' + } + response = self.client.post(url, data, **self.header) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # device created for user + self.assertEqual(FCMDevice.objects.count(), 1) + + def test_send_test_notification(self): + """ + test send test notification + :return: + """ + url = reverse('notifications:notifications-list') + response = self.client.get(url, **self.header) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming only one notification exists in the database + self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 1) + + def test_mark_as_read(self): + """ + test mark as read + :return: + """ + url = reverse('notifications:notifications-mark-as-read') + data = { + 'id': [self.notification.id] + } + response = self.client.patch(url, data, **self.header) + self.notification.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.notification.is_read, True) diff --git a/web_admin/tests/test_analytics.py b/web_admin/tests/test_analytics.py new file mode 100644 index 0000000..81ff9b0 --- /dev/null +++ b/web_admin/tests/test_analytics.py @@ -0,0 +1,93 @@ +""" +web admin test analytics file +""" +# django imports +from django.urls import reverse +from rest_framework import status + +# local imports +from web_admin.tests.test_set_up import AnalyticsSetUp + + +class AnalyticsViewSetTestCase(AnalyticsSetUp): + """ + test cases for analytics, users count, new sign-ups, + assign tasks report, junior leaderboard, export excel + """ + def setUp(self) -> None: + """ + test data set up + :return: + """ + super(AnalyticsViewSetTestCase, self).setUp() + + def test_total_sign_up_count(self): + """ + test total sign up count + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-users-count') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming four users exists in the database + self.assertEqual(response.data['data']['total_users'], 4) + # Assuming two guardians exists in the database + self.assertEqual(response.data['data']['total_guardians'], 2) + # Assuming two juniors exists in the database + self.assertEqual(response.data['data']['total_juniors'], 2) + + def test_new_user_sign_ups(self): + """ + test new user sign-ups + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-new-signups') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming four users exists in the database + self.assertEqual(response.data['data'][0]['signups'], 4) + + def test_assign_tasks_report(self): + """ + test assign tasks report + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-assign-tasks') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming two completed tasks exists in the database + self.assertEqual(response.data['data']['task_completed'], 2) + # Assuming two pending tasks exists in the database + self.assertEqual(response.data['data']['task_pending'], 2) + # Assuming two in progress tasks exists in the database + self.assertEqual(response.data['data']['task_in_progress'], 2) + # Assuming two requested tasks exists in the database + self.assertEqual(response.data['data']['task_requested'], 2) + # Assuming two rejected tasks exists in the database + self.assertEqual(response.data['data']['task_rejected'], 2) + # Assuming two expired tasks exists in the database + self.assertEqual(response.data['data']['task_expired'], 2) + + def test_junior_leaderboard(self): + """ + test junior leaderboard + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-junior-leaderboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_export_excel(self): + """ + test export excel + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-export-excel') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertURLEqual(response.data['data'], self.export_excel_url) diff --git a/web_admin/tests/test_set_up.py b/web_admin/tests/test_set_up.py index 5225969..df522cd 100644 --- a/web_admin/tests/test_set_up.py +++ b/web_admin/tests/test_set_up.py @@ -4,10 +4,13 @@ web_admin test set up file # django imports from django.test import TestCase from django.contrib.auth import get_user_model +from django.conf import settings from rest_framework.test import APITestCase from rest_framework.test import APIClient # local imports +from guardian.models import Guardian, JuniorTask +from junior.models import Junior, JuniorPoints from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption # user model @@ -102,6 +105,9 @@ base64_image = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBIS "rOORsR8oQkxdazE9A8QostdSBuBFvlKI6PY6ibg0z4VCscJi9WK3WzRiwmRqGHY3vndi5Hnebangse2hrpSHBEAjhE6rXj" "GYdEC/arYms/HtkfAS7huhuDXXJmPEkn5whN4xrZ0NkYdPRpIPdLS0kG5QPdCEskKlYcIWHCEJUO3KFuUIQD3QhCB//Z") +export_excel_path = 'analytics/ZOD_Bank_Analytics.xlsx' +export_excel_url = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{export_excel_path}" + class BaseSetUp(APITestCase): """ @@ -146,7 +152,10 @@ class ArticleTestSetUp(BaseSetUp): SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 1", is_answer=True) SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 2", is_answer=False) + # article api url used for get api self.article_list_url = 'web_admin:article-list' + + # article api url used for post api self.article_detail_url = 'web_admin:article-detail' # article card data with default card image @@ -247,3 +256,124 @@ class ArticleTestSetUp(BaseSetUp): # added new articles }] + self.article_survey_data } + + +class UserManagementSetUp(BaseSetUp): + """ + test cases for user management + """ + + def setUp(self) -> None: + """ + data setup + :return: + """ + super(UserManagementSetUp, self).setUp() + # guardian codes + self.guardian_code_1 = 'GRD123' + self.guardian_code_2 = 'GRD456' + + # guardian 1 + self.guardian = Guardian.objects.create(user=self.user, country_code=91, phone='8765876565', + country_name='India', gender=2, is_verified=True, + guardian_code=self.guardian_code_1) + + # user 2 + self.user_email_2 = 'user2@yopmail.com' + self.user_2 = User.objects.create_user(username=self.user_email_2, email=self.user_email_2) + self.user_2.set_password('user2@1234') + self.user_2.save() + + # guardian 2 + self.guardian_2 = Guardian.objects.create(user=self.user_2, country_code=92, phone='8765876575', + country_name='India', gender=1, is_verified=True, + guardian_code=self.guardian_code_2) + + # user 3 + self.user_email_3 = 'user3@yopmail.com' + self.user_3 = User.objects.create_user(username=self.user_email_3, email=self.user_email_3) + self.user_3.set_password('user3@1234') + self.user_3.save() + + # junior 1 + self.junior = Junior.objects.create(auth=self.user_3, country_code=91, phone='8765887643', + country_name='India', gender=2, is_verified=True, + guardian_code=[self.guardian_code_1]) + + # user 4 + self.user_email_4 = 'user4@yopmail.com' + self.user_4 = User.objects.create_user(username=self.user_email_4, email=self.user_email_4) + self.user_4.set_password('user4@1234') + self.user_4.save() + + # junior 2 + self.junior_2 = Junior.objects.create(auth=self.user_4, country_code=92, phone='8768763443', + country_name='India', gender=1, is_verified=True, + guardian_code=[self.guardian_code_2]) + + +class AnalyticsSetUp(UserManagementSetUp): + """ + test analytics + """ + def setUp(self) -> None: + """ + test data set up + :return: + """ + super(AnalyticsSetUp, self).setUp() + + # pending tasks + self.pending_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='Pending Task 1', task_status=1, + due_date='2024-09-12') + self.pending_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='Pending Task 2', task_status=1, + due_date='2024-09-12') + + # in progress tasks + self.in_progress_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='In progress Task 1', task_status=2, + due_date='2024-09-12') + self.in_progress_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='In progress Task 2', task_status=2, + due_date='2024-09-12') + + # rejected tasks + self.rejected_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='Rejected Task 1', task_status=3, + due_date='2024-09-12') + self.rejected_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='Rejected Task 2', task_status=3, + due_date='2024-09-12') + + # requested task + self.requested_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='Requested Task 1', task_status=4, + due_date='2024-09-12') + self.requested_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='Requested Task 2', task_status=4, + due_date='2024-09-12') + + # completed task + self.completed_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='Completed Task 1', task_status=5, + due_date='2024-09-12') + self.completed_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='Completed Task 2', task_status=5, + due_date='2024-09-12') + + # expired task + self.expired_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, + task_name='Expired Task 1', task_status=6, + due_date='2024-09-11') + self.expired_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, + task_name='Expired Task 2', task_status=6, + due_date='2024-09-11') + + # junior point table data + JuniorPoints.objects.create(junior=self.junior_2, total_points=50) + JuniorPoints.objects.create(junior=self.junior, total_points=40) + + # export excel url + self.export_excel_url = export_excel_url diff --git a/web_admin/tests/test_user_management.py b/web_admin/tests/test_user_management.py new file mode 100644 index 0000000..c2e7adb --- /dev/null +++ b/web_admin/tests/test_user_management.py @@ -0,0 +1,255 @@ +""" +web admin test user management file +""" +# django imports +from django.contrib.auth import get_user_model +from rest_framework import status + +# local imports +from base.constants import GUARDIAN, JUNIOR +from web_admin.tests.test_set_up import UserManagementSetUp + +# user model +User = get_user_model() + + +class UserManagementViewSetTestCase(UserManagementSetUp): + """ + test cases for user management + """ + def setUp(self) -> None: + super(UserManagementViewSetTestCase, self).setUp() + + self.update_data = { + 'email': 'user5@yopmail.com', + 'country_code': 93, + 'phone': '8765454235' + } + self.user_management_endpoint = "/api/v1/user-management" + + def test_user_management_list_all_users(self): + """ + test user management list all users + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/" + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming four user exists in the database + self.assertEqual(len(response.data['data']), 4) + + def test_user_management_list_guardians(self): + """ + test user management list guardians + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/?user_type={GUARDIAN}" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming two guardians exists in the database + self.assertEqual(len(response.data['data']), 2) + + def test_user_management_list_juniors(self): + """ + test user management list juniors + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/?user_type={JUNIOR}" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming two juniors exists in the database + self.assertEqual(len(response.data['data']), 2) + + def test_user_management_list_with_unauthorised_user(self): + """ + test user management list with unauthorised user + :return: + """ + # user unauthorised access + self.client.force_authenticate(user=self.user) + url = f"{self.user_management_endpoint}/" + response = self.client.get(url, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_user_management_retrieve_guardian(self): + """ + test user management retrieve guardian + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 1) + + def test_user_management_retrieve_junior(self): + """ + test user management retrieve junior + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 1) + + def test_user_management_retrieve_without_user_type(self): + """ + test user management retrieve without user type + user status is mandatory + API will throw error + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_update_guardian(self): + """ + test user management update guardian + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}" + response = self.client.patch(url, self.update_data, format='json',) + self.user.refresh_from_db() + self.guardian.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.user.email, self.update_data['email']) + self.assertEqual(self.guardian.country_code, self.update_data['country_code']) + self.assertEqual(self.guardian.phone, self.update_data['phone']) + + def test_user_management_update_guardian_with_existing_email(self): + """ + test user management update guardian with existing email + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}" + data = { + 'email': self.user_email_2 + } + response = self.client.patch(url, data, format='json',) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_update_guardian_with_existing_phone(self): + """ + test user management update guardian with existing phone + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}" + data = { + 'phone': self.guardian_2.phone + } + response = self.client.patch(url, data, format='json',) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_update_junior(self): + """ + test user management update junior + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}" + response = self.client.patch(url, self.update_data, format='json',) + self.user_3.refresh_from_db() + self.junior.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.user_3.email, self.update_data['email']) + self.assertEqual(self.junior.country_code, self.update_data['country_code']) + self.assertEqual(self.junior.phone, self.update_data['phone']) + + def test_user_management_update_junior_with_existing_email(self): + """ + test user management update guardian with existing phone + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}" + data = { + 'email': self.user_email_4 + } + response = self.client.patch(url, data, format='json',) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_update_junior_with_existing_phone(self): + """ + test user management update junior with existing phone + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}" + data = { + 'phone': self.junior_2.phone + } + response = self.client.patch(url, data, format='json',) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_update_without_user_type(self): + """ + test user management update without user type + user status is mandatory + API will throw error + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/" + response = self.client.patch(url, self.update_data, format='json',) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_management_change_status_guardian(self): + """ + test user management change status guardian + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/change-status/?user_type={GUARDIAN}" + response = self.client.get(url) + self.guardian.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.guardian.is_active, False) + + def test_user_management_change_status_junior(self): + """ + test user management change status junior + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user_3.id}/change-status/?user_type={JUNIOR}" + response = self.client.get(url) + self.junior.refresh_from_db() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(self.junior.is_active, False) + + def test_user_management_change_status_without_user_type(self): + """ + test user management change status without user type + user status is mandatory + API will throw error + :return: + """ + # admin user authentication + self.client.force_authenticate(user=self.admin_user) + url = f"{self.user_management_endpoint}/{self.user.id}/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/urls.py b/web_admin/urls.py index 7f65fc8..4b0f480 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -18,7 +18,7 @@ router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') -router.register('analytics', AnalyticsViewSet, basename='user-analytics') +router.register('analytics', AnalyticsViewSet, basename='analytics') router.register('article-list', ArticleListViewSet, basename='article-list') router.register('article-card-list', ArticleCardListViewSet, basename='article-card-list') From 1dec5ace3f5b668f190b4808678693b6d86a351a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 12 Sep 2023 14:42:44 +0530 Subject: [PATCH 343/372] test cases for admin user management, analytics and notification --- account/utils.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/account/utils.py b/account/utils.py index 428df15..564472b 100644 --- a/account/utils.py +++ b/account/utils.py @@ -300,24 +300,21 @@ def make_special_password(length=10): lowercase_letters = string.ascii_lowercase uppercase_letters = string.ascii_uppercase digits = string.digits - special_characters = '!@#$%^&*()_-+=<>?/[]{}|' + special_characters = '@#$%&*?' # Combine character sets - all_characters = lowercase_letters + uppercase_letters + digits + special_characters + alphabets = lowercase_letters + uppercase_letters # Create a password with random characters - password = ( - secrets.choice(lowercase_letters) + + password = [ secrets.choice(uppercase_letters) + + secrets.choice(lowercase_letters) + secrets.choice(digits) + secrets.choice(special_characters) + - ''.join(secrets.choice(all_characters) for _ in range(length - 4)) - ) + ''.join(secrets.choice(alphabets) for _ in range(length - 4)) + ] - # Shuffle the characters to make it more random - password_list = list(password) - random.shuffle(password_list) - return ''.join(password_list) + return ''.join(password) def task_status_fun(status_value): """task status""" From 7aee372606bfdfcef94eb759506264e2b196a2ff Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 12 Sep 2023 14:51:28 +0530 Subject: [PATCH 344/372] test cases for admin user management, analytics and notification --- account/utils.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/account/utils.py b/account/utils.py index 0d79483..564472b 100644 --- a/account/utils.py +++ b/account/utils.py @@ -300,24 +300,21 @@ def make_special_password(length=10): lowercase_letters = string.ascii_lowercase uppercase_letters = string.ascii_uppercase digits = string.digits - special_characters = '!@#$%^&*()_-+=<>?/[]{}|' + special_characters = '@#$%&*?' # Combine character sets alphabets = lowercase_letters + uppercase_letters # Create a password with random characters - password = ( - secrets.choice(lowercase_letters) + + password = [ secrets.choice(uppercase_letters) + + secrets.choice(lowercase_letters) + secrets.choice(digits) + secrets.choice(special_characters) + ''.join(secrets.choice(alphabets) for _ in range(length - 4)) - ) + ] - # Shuffle the characters to make it more random - password_list = list(password) - random.shuffle(password_list) - return ''.join(password_list) + return ''.join(password) def task_status_fun(status_value): """task status""" From af06dddbebbc55e5d2b5ec91e35950404bf75c6c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 12 Sep 2023 15:03:12 +0530 Subject: [PATCH 345/372] updated coverage file --- .coverage | Bin 86016 -> 86016 bytes coverage-reports/coverage.xml | 781 ++++++++++++++++++++++------------ 2 files changed, 514 insertions(+), 267 deletions(-) diff --git a/.coverage b/.coverage index c5c9742d688286628453e0f8b84e1ae94c05c24b..cfe9abdb0be3e2dbddd74aecf82bf68af4e342d8 100644 GIT binary patch delta 5643 zcmZWtcUVq?#$+-{G@3FqUotUjOtm2B#EhBu6bFrH47{}usQKQzf1LZ04pfrm>33r~yn6?ifpwo2nq1yU3OZLJ1#yKk9+S~_15y1)|QCI z&Ol(X;R9dClidpPz`y&aAD;*$D?WH0quMu%xltCUFcORFXE_qBf__7PM}I@_&>Qti zJx`C&-E>*Ird`y|YM*HDX-Bm`XfJEKwawZZtxhY`^0h2&juxj))W&EdGU42!3QQe^$YP(vima2=@3^iGeRU_39)lYR*MderJuJV)8t$b6e ze6D4y98_LVwkhkCcBNJ+RTe85$}A;DiBLu=c14o=Wh}3hi{&&qPP!#^NiRrS zq)cg+6eXS%H;JucrI;rsi<8ArVL-Sed@Z~tydP3RVs@9l|9P~ZZ@|Z89h$B0%*Iovubw-1Zuf%~9~JdRPnj}hc~_Wy zz~OD!fIEtL{@U4u=7HPqcdNCFyKld5+}w}64XZh|3wLj}`{;lJn4Q;R_gO!9c4e

    V{r=>uwUsfO|9-SYF%+1yGuwjPy(}Sy41;;h z)@!kjfq$GH@cZtFaZ;G(nBISNea4q3A_wCiIMQx$4;4c)g`O*&cCt!0L>TxoTY@I9 zoO%C?nKna!aR9S{!TxA=#Y@QhKDs>*5KaflD$^LM8N6+i$8Ok zNo1t-RdmjC6`r?sPO;VrlfIp^GGE-Yatre4fKgb-z~Vf_up2|CTybZ(QB0J_N=jb& zpZhCk?z$dxD+V6czsLUQ+YaPu?bkjpdI($$2hQ)!u!aSeDK2ZHi_C4qo!bf>t-u%1 z8qYA&&x3=%IFuF`d$zF5z(aSr8L+)MweM9?ZvyDwwD+^Y2WK4vqSOdOx5m9UEK&nS z4XJ%duBY7f9n}Lisg4rsD6y6jYg7A9ic$?F)&Nnerl^{tDrzscDhewps-&obq6$Fj z3P9c~K;__7B$ZQgd1~KGc{w2aa;j7*1L#?{_lv<(7O9jnlu`{P6qQg^Oi?iiNktSD zrS@5+LW&9ji3Oa1WS4@}KK%OjB@Q_kChWPO3`q+p&jP9;hoT%nayFgL-h1PeMb4ry z3pj0JCP0_W)IKSLGGnQ1fJk#j`ZcFnhb>7}DL@AA;G^#QckUiC0F(rjk zQz&&VrOpLvfHa2^=TN1yDVm+y7a%25lngXw79h`AseM=9jgyinH3_KrZ#yPBtWq2u z$5F}Y6iufnmZDhLm>vVjKPG1CV8g)GM-5UmortC^(I`~nz>o{Nw9#=YPR!?4!M`7M3uXtfBkgQ`_ zVGR8r!%hu_|Ju-NEp8AK!@sD}kIqWJ;x%jhZ{M=M+W*Oe-xrl`yfNd)*t7d~ziR$m zWFdsghp^$n@V_h=hiwa1gBghZ(J+o5jkRq)88JbCn}dEv{fO!QBC^}7UuAkDT$o05 z;`>WMMbe%A`-PV5Jlo(bY0r#D{U6`9F-*cKrvHq^H4r3&1A|&ZgU`xGdjHh8Y4!2k zUB{5css4Ce+&vO;YA+yHmy?Pc&3G*1WSm1_H?#ZrgO{Z#HiVfmOo7FA#8zZ;BG5OMgW@G|yEud0&yHdKXt~2J5zerKdQQV=5w3584OCIPb)c~xRnX(W-euhW-C@AVT3vZdOG!mJURs5w;e-D&iD5#dfw&DkK3F)N+RYPADUo&?3gIWM;f&Q! z>0Ot{@)N5#Bkw6`=@kz&1vgzOLsK8?^Tgx2JkeyZc>{{ZN4ttq44m#x7{iQ4PKHae zd}%3Rzp>R>d#ok;_j;*zSL@Jx)lbIWJPP}aZQum>XuLk?!LB-FEnl5b0G(xzV3&r zz_bf-)IWXsXF52e1w?c3<)UC7D)0tZeC&rTZ2e&hKf^pK9li8}Cz^wsZZ5!sKlt!D zwVbik817Mgi`*nT5Z<;ma>FZyZJhZrl}RgHdv+J?+(P^;HecMOAfhb#QaZ zO`K6~N_Ab9@W_98qBxUv>%Y9vbU@X7!edP@$)IR4tX_jM@RgfIDE;`%pSB|uX1Xq% zQ^Bu1l(5f#U*wk!?d3u?*%g3%Pz`wcLNebE1qqeVOl+{32aUWn5tZYTTMkr&dv0w< z#klVF9%;D_UKhc9hQ;1wUtZ+?!c}oL%bS*5wvWZ^RCEz-L&3~vPUi~YnFx+CrV?i~ zN+bF_qzcQ!fsjFZ+)*-)xU1URTFRS?>PqV2sCDJ_t#I7TFns#w6dC-Vx^4ah9NCx8 zE*p<}BI9As?YL z6ArNwyWY$omk}DvR%g)3YPM;CU01Ww@zKz_5ZlCV-~3wkb|vCdctQXXJzEg)+LhL0;*%H5}wS22_^Bc zp%hXQj53L=AsgE~^TlJR*08Qf*A)-qtJhTz)4`k?

    bk$sRyi!3WhNK4u7VW#qdK8@1<>bESseM3 zE*g9}*cOt`^=u4DM>JfP!#KoT^oBQ@!IqU#!gw9;TAlq4o%^1t45!3jYcxVOK4qW?Ln+E`3bL>%I;o^?s<6 zt;?qyU1*kRQs56h05yOuTl_ROX|fe+7lCMj>4A{bSw|jq@C76y7=;Th3t_ItjMVJW zkXV=J!Z>5do&5umo5he9(2Wm;(MuePUZPmGGJ)&?q_=1T+IG#R{$7oQH|9C=C5y<< z5SqmTUpOzPB)fY!5LXlnA6DV(U4K<;q7jN1*%jg2@ggQd>-%zIgjP;?f>LW2=cQ=Q$+X5`Z`s2Xa;Kn8SLCRNgq3lDE?OsLZtgs*>T ze*W-)s!>%^TeYgZnI?W_OD1QGV8POfWOo>nL@+aF_ze-d!jQ-^>z}NBBpylp>fw5w zNoeNdQ_DFc!`!G}AaWOS;azxxXpYZ~L{X4IBLh(j!~~ReGYO+CVhcj6`Pst<%pzSu zXe63T27*v4S_ri=!Aysp` zep+wV{k1dNS}hDdu{^H^D_<&Wl`--~d7T_CotIWgx_De%CfpVF2qXEAk2xK@CEsv` z0y$iw#$B+Bjqt4la-v!INLDP;NL@5+%|;WSSwKnlTqKZ-(a4UhG}0@09R$M78m*MH zwN`>b7aJNm5`*%QN^8rI80ZS&6_b?~P;w#`*^ms2r%;VFdfNAq#L6j ztH>wQ;SL25^})^y@ha_Y>9yq=yjf03T^v$)4(7qF9;7J4y=|6ZAtebztE-R8bEUzO zkuy*gYas0MVD`{;r-R=yhM#%==5=Q|Ovxv`I-9u;_BNNiKNI$~mKJ#0^XbD2EnGuS z1!-XxZ3=B@wW%5WMCMnSo1%V2eVxtfqqIW`BS(wh_Yp*v@G=0mufQfcIu7^H~I4(MLAlLsr)D;i6DcSM zJxi>q5bPUenM*-YQbN87f;KaOl)aLdosd*8dS*#X=n)_ zVMZ^!k_`8Us{8-%D#LNn#rVhbq;?)!jwX=v^H2dlzKAoD=v&J0tfu{@r5r~h(@{Pj z@>`yvsJa5ZBN~g3Kt@Y^=uW23N6>lF_@`sDo=!lG#FhbBqS?$6Lo58I(xL{M6iQLk MJ0+Qr5>Wg90`WW`O8@`> delta 5368 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 - + @@ -29,7 +29,7 @@ - + @@ -265,7 +265,7 @@ - + @@ -384,26 +384,26 @@ - - - + + + - - - + + + - - + + - + - + - + @@ -569,7 +569,7 @@ - + @@ -713,7 +713,7 @@ - + @@ -721,16 +721,14 @@ - - - - + + + + + - - - @@ -1294,7 +1292,7 @@ - + @@ -1360,7 +1358,7 @@ - + @@ -1372,12 +1370,12 @@ - + - + @@ -1394,11 +1392,11 @@ - - - - - + + + + + @@ -1424,7 +1422,7 @@ - + @@ -1909,7 +1907,7 @@ - + @@ -1937,9 +1935,9 @@ - - - + + + @@ -3860,7 +3858,7 @@ - + @@ -3928,7 +3926,7 @@ - + @@ -3941,9 +3939,9 @@ - - - + + + @@ -3951,7 +3949,7 @@ - + @@ -3962,7 +3960,42 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3976,7 +4009,7 @@ - + @@ -3993,8 +4026,8 @@ - - + + @@ -4069,7 +4102,7 @@ - + @@ -4089,17 +4122,17 @@ - - - - - + + + + + - - - - + + + + @@ -4108,8 +4141,8 @@ - - + + @@ -4135,7 +4168,7 @@ - + @@ -4219,7 +4252,7 @@ - + @@ -4227,7 +4260,7 @@ - + @@ -4254,7 +4287,7 @@ - + @@ -4281,12 +4314,12 @@ - + - - - + + + @@ -4338,13 +4371,13 @@ - + - + @@ -4362,13 +4395,13 @@ - + - + - + @@ -4379,10 +4412,10 @@ - + - + @@ -4394,27 +4427,27 @@ - + - - + + - - - - + + + + - - + + - - + + @@ -4701,7 +4734,7 @@ - + @@ -4723,34 +4756,34 @@ - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + @@ -4762,33 +4795,33 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - + - + - + @@ -4799,33 +4832,33 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - + - + - + @@ -4835,20 +4868,20 @@ - + - - - - - - - - - - - - + + + + + + + + + + + + @@ -4860,6 +4893,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5050,48 +5130,215 @@ - + + + - - - + + + + - - - - + + + - - - - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -5122,72 +5369,72 @@ - - + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -5354,7 +5601,7 @@ - + @@ -5381,54 +5628,54 @@ - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + From 08f54f28a4d9301bfeca424cd5c48e5ab3417823 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 12 Sep 2023 23:08:17 +0530 Subject: [PATCH 346/372] added a test case, updated coverage file --- .coverage | Bin 86016 -> 86016 bytes base/pagination.py | 12 - coverage-reports/coverage.xml | 356 +++++++++--------- notifications/tests.py | 12 +- notifications/utils.py | 30 -- web_admin/serializers/analytics_serializer.py | 2 - web_admin/tests/test_analytics.py | 16 + web_admin/tests/test_set_up.py | 80 ++-- 8 files changed, 253 insertions(+), 255 deletions(-) diff --git a/.coverage b/.coverage index cfe9abdb0be3e2dbddd74aecf82bf68af4e342d8..ad3ab6617849e76cfbb418dc5a98f0bc4a21ebcd 100644 GIT binary patch delta 3420 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 ttB2B|#tMEc;?&1C4*@LaR@m{{tJHG4d(y= delta 3304 zcmY+Gc~I5Y6~}+)w{Pz~(8r=80vganSp_l3?gApaxFI|i5zxo-K#e>8NHm)!K5vq1 zrZwuc(`jpBRT53oNz63KWRi&?i`q6C6cb5Ir>KLO8XKYa{6yRG2lKh#d+xdCoO|#2 zy&Z?79fzc2w~Ea%ogBvnma=~Vu_sCnI$Lz_Lx?q zq(sZZ@=f`i{CoK&d5c^lC(4tK`+pM2 zxLv)%N5^W(KX#SnxGY)ZwQz3NzS)_=NFv9{0hYv+2yW+7@I~cTBxY1TDC$E@{c_fS!d)^;mW6;J7NUK ziLCme9B&ESdt43K^R!i@bua^WN8Zpu#o=QZ=G*_t$j`l9!;0F@hGW|I4+v@-Qd`~- zXsu}7x^d00OKm~11w}84-n^lA6jJ%j#9UCDaH@$-sf|dDjEvQcLRYhuBbPvJK)E4r zC|z5{$h`{J>h%nR>-T>)a#|p7J~Oc^UG<=uCvV89RwGq2QmPmQROJn+D^XgBn^YoI zBCSALfwUZHInpwuWk^ermLe@dT7p!8RKX%q7b7iZnpV!pUEaka0ksU(GPEp3D#iH{ zoG;lwa9Yrc5sR6nODSR)P{aZ&M8iT{vIuDrtDs)MD7XN#zpgGsb>aTc6g3|yA6@1# za_8AQEj5@%|^>C zq%5RNq)em?qzt5Vq;#Y?NOQ2vX-H|PJu0&O$2^KS8^zfy3r8xWu+-G#k+$JWzqF~d zaAFpkq#&g*(vlgulT#0jOweW`&O}s`a6XB}rzRpL;$8_z35>LOG>m7nf)9B& zf8(ipVqnhqvwL6Od(i&V$k9Fiv4N53Ei$SzdP=W$^u}xLJGb6&hI%3wj>^>j44cN#upikU|dsU?-)L&o4U3jSInYoCRXH#1eWGlf9>}yf{Nj z+$vgI{#Q8T`MCaSI0@EUeT(wZM*~Hqy;UaRYm%*6_dXYjF*G<)vpAG*oVz+&RL76?n(Hfves`wCdj@jL_(c92le zlm6pLURr7I<@1!#1JUfD){b6^5_)GUM9EF8|989Wtl|a}pn=p5c7ccV4?YVuq~-cP zb(Mo1Q!MuhQEpWG_cid#VoBhw1@xtS2<(}1^NKU?b4rg5MLJKgd;)$|2N(XH(@74aIgFQtK~JN zB4;fpf!7MLXncJM&5DJ3c}?+snNl6b@tcb1O$9c|YYIp8B061%P~NwQzG8q+t}bAD zHgi*ig%B)QNLy5}_@;bXBD1qemD$T#nQu>{r&I1(29eQgKobmD z%XiGi6{S=Rfk@et$~Md;hc6Uk+I;IQI$}Z_q+qeq|NCUtUx=3Ll9_3~ZTjaMF1~3d z7UQ0c{a*OyB#gG4uqUEeTp|lcvaQ*BleZ+W#arxQ-^HnXeLSAh=;FOjm;`aSws{fW zoG5Ms=UV4-I$w5%I{xUGXP!43jX`6h{)@g#`?t15y{>LogOp>+MEONIN%}x46fcTi zVOUth5AnPCaqwHn=04;$`27#l!3ZekH+SGkq?4>W27Rp%68Tymma&%aY^VMx=;VEE zG&~A?RGbWr^7S4=X#ic+jVFUusx9*6ozn;$e+WMyZ4)496mPR)AFoz{{+9T|0)~u#?r$LQiNe%RP z4D1xFfK@a+2KMsx^>&$<+i1D5j;$)kRfP3XzO5E_XcH{0hQ9j>+j!*Hz+pF<~uQeryGE)`>Y$`Up&sTb7T8)GW6w8^vOF zk2LIAeEm`kqYV27?_Gkad-<*kyvSXA=VC0OeY@SXWFl)1tIC;!Lh4hYh;A2P3=6Rg zLmahXziU!Y2EbE-RH3$VQU2`kQ%A - + @@ -1292,7 +1292,7 @@ - + @@ -1358,7 +1358,7 @@ - + @@ -1371,11 +1371,9 @@ - - - + @@ -1399,25 +1397,25 @@ - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + @@ -3858,7 +3856,7 @@ - + @@ -3963,39 +3961,41 @@ - + - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + - - - + + + - - + + - - + + + + @@ -4009,7 +4009,7 @@ - + @@ -4034,75 +4034,63 @@ - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - + + + + + - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + @@ -4135,10 +4123,10 @@ - - - - + + + + @@ -4168,7 +4156,7 @@ - + @@ -4287,7 +4275,7 @@ - + @@ -4315,8 +4303,8 @@ - - + + @@ -4371,13 +4359,13 @@ - + - + @@ -4439,15 +4427,14 @@ - - - - + + + + - - - - + + + @@ -4920,24 +4907,31 @@ - - + - - + - + + + - - + + - - - + + + + + + + + + + @@ -5136,75 +5130,75 @@ - - - - - - - + + + + - - - - - - - - - + + + + + + - + + + + + - - - - - - - - - + + + + + + - - - - + + - - - - + + + - - + + + - + + - - - - - + + + + + - - - + + + - - + - + + + + + + + + diff --git a/notifications/tests.py b/notifications/tests.py index 925d5a1..9cec499 100644 --- a/notifications/tests.py +++ b/notifications/tests.py @@ -8,6 +8,7 @@ from fcm_django.models import FCMDevice from django.urls import reverse from rest_framework import status +from account.models import UserNotification # local imports from account.serializers import GuardianSerializer from notifications.models import Notification @@ -25,8 +26,11 @@ class NotificationTestCase(AnalyticsSetUp): """ super(NotificationTestCase, self).setUp() + # notification settings create + UserNotification.objects.create(user=self.user) + # notification create - self.notification = Notification.objects.create(notification_to=self.user) + self.notification = Notification.objects.create(notification_to=self.user, notification_from=self.user_3) # to get guardian/user auth token self.guardian_data = GuardianSerializer( @@ -73,11 +77,11 @@ class NotificationTestCase(AnalyticsSetUp): test send test notification :return: """ - url = reverse('notifications:notifications-list') + url = reverse('notifications:notifications-test') response = self.client.get(url, **self.header) self.assertEqual(response.status_code, status.HTTP_200_OK) - # Assuming only one notification exists in the database - self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 1) + # Assuming one notification exists in the database and two created after api run + self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 3) def test_mark_as_read(self): """ diff --git a/notifications/utils.py b/notifications/utils.py index 83e3e95..a118cd7 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -145,33 +145,3 @@ def send_notification_multiple_user(notification_type, from_user_id, from_user_t 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/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 7871615..cc81f46 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -128,8 +128,6 @@ class UserCSVReportSerializer(serializers.ModelSerializer): 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): diff --git a/web_admin/tests/test_analytics.py b/web_admin/tests/test_analytics.py index 81ff9b0..02f401e 100644 --- a/web_admin/tests/test_analytics.py +++ b/web_admin/tests/test_analytics.py @@ -49,6 +49,22 @@ class AnalyticsViewSetTestCase(AnalyticsSetUp): # Assuming four users exists in the database self.assertEqual(response.data['data'][0]['signups'], 4) + def test_new_user_sign_ups_between_given_dates(self): + """ + test new user sign-ups + :return: + """ + self.client.force_authenticate(self.admin_user) + url = reverse('web_admin:analytics-new-signups') + query_params = { + 'start_date': '2023-09-12', + 'end_date': '2023-09-13' + } + response = self.client.get(url, query_params) + self.assertEqual(response.status_code, status.HTTP_200_OK) + # Assuming four users exists in the database + self.assertEqual(response.data['data'][0]['signups'], 4) + def test_assign_tasks_report(self): """ test assign tasks report diff --git a/web_admin/tests/test_set_up.py b/web_admin/tests/test_set_up.py index df522cd..f8e2e23 100644 --- a/web_admin/tests/test_set_up.py +++ b/web_admin/tests/test_set_up.py @@ -105,6 +105,8 @@ base64_image = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBIS "rOORsR8oQkxdazE9A8QostdSBuBFvlKI6PY6ibg0z4VCscJi9WK3WzRiwmRqGHY3vndi5Hnebangse2hrpSHBEAjhE6rXj" "GYdEC/arYms/HtkfAS7huhuDXXJmPEkn5whN4xrZ0NkYdPRpIPdLS0kG5QPdCEskKlYcIWHCEJUO3KFuUIQD3QhCB//Z") +# export excel path and +# export excel url export_excel_path = 'analytics/ZOD_Bank_Analytics.xlsx' export_excel_url = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{export_excel_path}" @@ -119,12 +121,17 @@ class BaseSetUp(APITestCase): user data :return: """ + # user and admin email self.user_email = 'user@example.com' self.admin_email = 'admin@example.com' self.client = APIClient() + + # create user self.user = User.objects.create_user(username=self.user_email, email=self.user_email) self.user.set_password('user@1234') self.user.save() + + # create admin self.admin_user = User.objects.create_user(username=self.admin_email, email=self.admin_email, is_staff=True, is_superuser=True) self.admin_user.set_password('admin@1234') @@ -133,22 +140,31 @@ class BaseSetUp(APITestCase): class ArticleTestSetUp(BaseSetUp): """ - test cases data set up for article create, update + test cases data set up + for article create, update, list, retrieve and + remove card, survey and add test card, list test card and + default image upload and list """ def setUp(self): """ set up data for test + create user and admin + create article, article card and article survey and survey options :return: """ super(ArticleTestSetUp, self).setUp() + # create article self.article = Article.objects.create(title="Existing Article", description="Existing Description", is_published=True) + # create article card self.article_card = ArticleCard.objects.create(article=self.article, title="Existing Card 1", description="Existing Card 1 Description") + # create article survey self.article_survey = ArticleSurvey.objects.create(article=self.article, points=5, question="Existing Survey Question 1") + # create article survey options SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 1", is_answer=True) SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 2", is_answer=False) @@ -261,11 +277,12 @@ class ArticleTestSetUp(BaseSetUp): class UserManagementSetUp(BaseSetUp): """ test cases for user management + users count, new sign-ups, """ - def setUp(self) -> None: """ data setup + create new guardian and junior :return: """ super(UserManagementSetUp, self).setUp() @@ -278,8 +295,9 @@ class UserManagementSetUp(BaseSetUp): country_name='India', gender=2, is_verified=True, guardian_code=self.guardian_code_1) - # user 2 + # user 2 email self.user_email_2 = 'user2@yopmail.com' + # create user 2 self.user_2 = User.objects.create_user(username=self.user_email_2, email=self.user_email_2) self.user_2.set_password('user2@1234') self.user_2.save() @@ -289,19 +307,20 @@ class UserManagementSetUp(BaseSetUp): country_name='India', gender=1, is_verified=True, guardian_code=self.guardian_code_2) - # user 3 + # user 3 email self.user_email_3 = 'user3@yopmail.com' + # create user 3 self.user_3 = User.objects.create_user(username=self.user_email_3, email=self.user_email_3) self.user_3.set_password('user3@1234') self.user_3.save() # junior 1 - self.junior = Junior.objects.create(auth=self.user_3, country_code=91, phone='8765887643', - country_name='India', gender=2, is_verified=True, - guardian_code=[self.guardian_code_1]) + self.junior = Junior.objects.create(auth=self.user_3, country_name='India', gender=2, + is_verified=True, guardian_code=[self.guardian_code_1]) - # user 4 + # user 4 email self.user_email_4 = 'user4@yopmail.com' + # create user 4 self.user_4 = User.objects.create_user(username=self.user_email_4, email=self.user_email_4) self.user_4.set_password('user4@1234') self.user_4.save() @@ -315,61 +334,70 @@ class UserManagementSetUp(BaseSetUp): class AnalyticsSetUp(UserManagementSetUp): """ test analytics + task assign report, junior leaderboard """ def setUp(self) -> None: """ test data set up + create task and assigned to junior + create junior points data :return: """ super(AnalyticsSetUp, self).setUp() - # pending tasks + # pending tasks 1 self.pending_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='Pending Task 1', task_status=1, - due_date='2024-09-12') + due_date='2023-09-12') + # pending tasks 2 self.pending_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='Pending Task 2', task_status=1, - due_date='2024-09-12') + due_date='2023-09-12') - # in progress tasks + # in progress tasks 1 self.in_progress_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='In progress Task 1', task_status=2, - due_date='2024-09-12') + due_date='2023-09-12') + # in progress tasks 2 self.in_progress_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='In progress Task 2', task_status=2, - due_date='2024-09-12') + due_date='2023-09-12') - # rejected tasks + # rejected tasks 1 self.rejected_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='Rejected Task 1', task_status=3, - due_date='2024-09-12') + due_date='2023-09-12') + # rejected tasks 2 self.rejected_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='Rejected Task 2', task_status=3, - due_date='2024-09-12') + due_date='2023-09-12') - # requested task + # requested task 1 self.requested_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='Requested Task 1', task_status=4, - due_date='2024-09-12') + due_date='2023-09-12') + # requested task 2 self.requested_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='Requested Task 2', task_status=4, - due_date='2024-09-12') + due_date='2023-09-12') - # completed task + # completed task 1 self.completed_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='Completed Task 1', task_status=5, - due_date='2024-09-12') + due_date='2023-09-12') + # completed task 2 self.completed_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='Completed Task 2', task_status=5, - due_date='2024-09-12') + due_date='2023-09-12') - # expired task + # expired task 1 self.expired_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior, task_name='Expired Task 1', task_status=6, - due_date='2024-09-11') + due_date='2023-09-11') + # expired task 2 self.expired_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2, task_name='Expired Task 2', task_status=6, - due_date='2024-09-11') + due_date='2023-09-11') # junior point table data JuniorPoints.objects.create(junior=self.junior_2, total_points=50) From 085607128b92429000d23298e1f864e2bdc56600 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 13 Sep 2023 13:36:25 +0530 Subject: [PATCH 347/372] modified get article card current page method --- web_admin/serializers/article_serializer.py | 3 ++- web_admin/views/article.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index c1bde0e..74cf793 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -357,8 +357,9 @@ class ArticleCardlistSerializer(serializers.ModelSerializer): """current page""" context_data = self.context.get('user') data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj.article).last() + total_count = self.context.get('card_count') if data: - return data.current_card_page + return data.current_que_page if data.current_que_page < total_count else data.current_que_page - 1 return NUMBER['zero'] class Meta(object): diff --git a/web_admin/views/article.py b/web_admin/views/article.py index e2cee07..1aa46a9 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -279,7 +279,8 @@ class ArticleCardListViewSet(viewsets.ModelViewSet): try: queryset = self.get_queryset() # article card list - serializer = ArticleCardlistSerializer(queryset, context={"user": self.request.user}, many=True) + serializer = ArticleCardlistSerializer(queryset, context={"user": self.request.user, + "card_count": queryset.count()}, 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) From eaf67b682fed2c64cd033f246489f226ce202fa0 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 13 Sep 2023 14:03:13 +0530 Subject: [PATCH 348/372] modified get article card current page method --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 74cf793..4c62973 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -359,7 +359,7 @@ class ArticleCardlistSerializer(serializers.ModelSerializer): data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj.article).last() total_count = self.context.get('card_count') if data: - return data.current_que_page if data.current_que_page < total_count else data.current_que_page - 1 + return data.current_card_page if data.current_card_page < total_count else data.current_card_page - 1 return NUMBER['zero'] class Meta(object): From a653518cfd1f2d5b22517a6943556237b9f322be Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 13 Sep 2023 14:38:18 +0530 Subject: [PATCH 349/372] fixed article reward points notification for 0 points earned --- junior/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junior/views.py b/junior/views.py index 28ab9d6..0c6a065 100644 --- a/junior/views.py +++ b/junior/views.py @@ -712,7 +712,7 @@ class CompleteArticleAPIView(views.APIView): 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}) + request.user.id, {'points': total_earn_points if total_earn_points else 0}) 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) From e157e98a177550b28ae1d023f6454a9ded57eeab Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 13 Sep 2023 14:45:47 +0530 Subject: [PATCH 350/372] fixed article reward points notification for 0 points earned --- junior/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/junior/views.py b/junior/views.py index 0c6a065..3d7ab31 100644 --- a/junior/views.py +++ b/junior/views.py @@ -711,8 +711,9 @@ 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 if total_earn_points else 0}) + if 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) From d1a4b86b092451ed2f9e2356150d6df9782a7d99 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 13 Sep 2023 18:15:07 +0530 Subject: [PATCH 351/372] fixed guardian reject issue, updated ids in notification method, remove unnecessary condition from analytics and user management --- base/tasks.py | 6 +++--- celerybeat-schedule | Bin 20480 -> 20480 bytes guardian/serializers.py | 12 ++++++------ guardian/utils.py | 2 +- guardian/views.py | 15 ++++++++++----- junior/serializers.py | 10 +++++----- junior/views.py | 4 ++-- web_admin/serializers/analytics_serializer.py | 2 -- .../serializers/user_management_serializer.py | 10 ---------- 9 files changed, 27 insertions(+), 34 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index fbeca1d..24893fc 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -53,11 +53,11 @@ def notify_task_expiry(): (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, + 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, + send_notification(IN_PROGRESS_TASK_EXPIRING, task.junior.auth_id, JUNIOR, task.guardian.user_id, {'task_id': task.id}) return True @@ -80,7 +80,7 @@ def notify_top_junior(): 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, + 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 diff --git a/celerybeat-schedule b/celerybeat-schedule index 7638e5252440a76df3a3df6b009f2805ccced487..2a485155d19184dae02b3e94bd235de358a96af7 100644 GIT binary patch delta 74 zcmZozz}T>Wal>2(c3vJ?Io{?MlNXo=GIC5du+f|>@0czC7Tu*ZB}1%jO3)N;H%mG_=LG;?!xzK= delta 74 zcmZozz}T>Wal>2(b`B0XMov@S$qUQ_8QCWr*l13ccT5)mi+0sa$q;Ls5;Vn|ck(pH VVgU{=1_s9DYM9`H&5};fc>xRT7ApV% diff --git a/guardian/serializers.py b/guardian/serializers.py index f78c237..a49286b 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -243,8 +243,8 @@ class TaskSerializer(serializers.ModelSerializer): task_data['junior'] = junior instance = JuniorTask.objects.create(**task_data) tasks_created.append(instance) - send_notification.delay(TASK_ASSIGNED, guardian.user.id, GUARDIAN, - junior.auth.id, {'task_id': instance.id}) + send_notification.delay(TASK_ASSIGNED, guardian.user_id, GUARDIAN, + junior.auth_id, {'task_id': instance.id}) return instance class GuardianDetailSerializer(serializers.ModelSerializer): @@ -443,8 +443,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_APPROVED, instance.guardian.user.id, GUARDIAN, - junior_details.auth.id, {'task_id': instance.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']) @@ -452,8 +452,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, instance.guardian.user.id, GUARDIAN, - junior_details.auth.id, {'task_id': instance.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 diff --git a/guardian/utils.py b/guardian/utils.py index 1d40c34..3360285 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -117,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, None, junior_queryset.auth.id, {}) + send_notification.delay(REFERRAL_POINTS, None, None, junior_queryset.auth_id, {}) diff --git a/guardian/views.py b/guardian/views.py index c4e952e..824f6ff 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -304,9 +304,13 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): "action":"1"} """ try: - guardian = Guardian.objects.filter(user__email=self.request.user).last() + relation_obj = JuniorGuardianRelationship.objects.filter( + guardian__user__email=self.request.user, + junior__id=self.request.data.get('junior_id') + ).select_related('guardian', 'junior').first() + guardian = relation_obj.guardian # fetch junior query - junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last() + junior_queryset = relation_obj.junior 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 @@ -319,8 +323,8 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): if serializer.is_valid(): # save serializer serializer.save() - send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN, - junior_queryset.auth.id, {}) + 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: if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code): @@ -331,7 +335,8 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): 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, {}) + send_notification.delay(ASSOCIATE_REJECTED, guardian.user_id, GUARDIAN, junior_queryset.auth_id, {}) + relation_obj.delete() 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) diff --git a/junior/serializers.py b/junior/serializers.py index aba9b12..6b765dc 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -107,7 +107,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last() if guardian_data: JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) - send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, 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) @@ -323,7 +323,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, special_password) # push notification - send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {}) + send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth_id, {}) return junior_data @@ -359,8 +359,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() - send_notification.delay(TASK_ACTION, instance.junior.auth.id, JUNIOR, - instance.guardian.user.id, {'task_id': instance.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): @@ -467,7 +467,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail.delay(email, full_name) - send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth.id, JUNIOR, guardian_data.user.id, {}) + send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth_id, JUNIOR, guardian_data.user_id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): diff --git a/junior/views.py b/junior/views.py index 3d7ab31..e6bd606 100644 --- a/junior/views.py +++ b/junior/views.py @@ -263,7 +263,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet): 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, {}) + send_notification.delay(ASSOCIATE_EXISTING_JUNIOR, guardian.user_id, GUARDIAN, junior.auth_id, {}) return True @@ -362,7 +362,7 @@ class RemoveJuniorAPIView(views.APIView): # save serializer serializer.save() JuniorGuardianRelationship.objects.filter(guardian=guardian, junior=junior_queryset).delete() - send_notification.delay(REMOVE_JUNIOR, None, None, junior_queryset.auth.id, {}) + 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: diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index cc81f46..d744674 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -115,8 +115,6 @@ class UserCSVReportSerializer(serializers.ModelSerializer): 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): diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index c8d0b1f..713d984 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -50,8 +50,6 @@ class UserManagementListSerializer(serializers.ModelSerializer): return profile.country_code if profile.country_code else None elif profile := obj.junior_profile.all().first(): return profile.country_code if profile.country_code else None - else: - return None @staticmethod def get_phone(obj): @@ -63,8 +61,6 @@ class UserManagementListSerializer(serializers.ModelSerializer): return profile.phone if profile.phone else None elif profile := obj.junior_profile.all().first(): return profile.phone if profile.phone else None - else: - return None @staticmethod def get_user_type(obj): @@ -76,8 +72,6 @@ class UserManagementListSerializer(serializers.ModelSerializer): return dict(USER_TYPE).get('2') elif obj.junior_profile.all().first(): return dict(USER_TYPE).get('1') - else: - return None @staticmethod def get_is_active(obj): @@ -89,8 +83,6 @@ class UserManagementListSerializer(serializers.ModelSerializer): return profile.is_active elif profile := obj.junior_profile.all().first(): return profile.is_active - else: - return obj.is_active class GuardianSerializer(serializers.ModelSerializer): @@ -292,5 +284,3 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): is_verified=True).select_related('user') serializer = GuardianSerializer(guardian, many=True) return serializer.data - else: - return None From 3afd7fecf35af89943c52223e8456273df59f9fb Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 22 Sep 2023 19:55:10 +0530 Subject: [PATCH 352/372] feedback changes and added mark all read --- base/messages.py | 2 +- guardian/views.py | 2 +- junior/serializers.py | 4 ++-- junior/views.py | 2 +- notifications/views.py | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/base/messages.py b/base/messages.py index 82a149b..e36921f 100644 --- a/base/messages.py +++ b/base/messages.py @@ -44,7 +44,7 @@ ERROR_CODE = { "2018": "Attached File not found", "2019": "Invalid Referral code", "2020": "Enter valid mobile number", - "2021": "Already register", + "2021": "User registered", "2022": "Invalid Guardian code", "2023": "Invalid user", # email not verified diff --git a/guardian/views.py b/guardian/views.py index 824f6ff..5e89417 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -149,7 +149,7 @@ class TaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): queryset = JuniorTask.objects.filter(guardian__user=self.request.user ).select_related('junior', 'junior__auth' - ).order_by('due_date', 'created_at') + ).order_by('-created_at') queryset = self.filter_queryset(queryset) return queryset diff --git a/junior/serializers.py b/junior/serializers.py index 6b765dc..615c949 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -108,7 +108,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): if guardian_data: JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) send_notification.delay(ASSOCIATE_REQUEST, junior.auth_id, JUNIOR, guardian_data.user_id, {}) - junior_approval_mail.delay(user.email, user.first_name) + # junior_approval_mail.delay(user.email, user.first_name) removed as per changes 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) @@ -466,7 +466,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) - junior_approval_mail.delay(email, full_name) + # junior_approval_mail.delay(email, full_name) removed as per changes send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth_id, JUNIOR, guardian_data.user_id, {}) return guardian_data diff --git a/junior/views.py b/junior/views.py index e6bd606..171c454 100644 --- a/junior/views.py +++ b/junior/views.py @@ -383,7 +383,7 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): queryset = JuniorTask.objects.filter(junior__auth=self.request.user ).select_related('junior', 'junior__auth' - ).order_by('due_date', 'created_at') + ).order_by('-created_at') queryset = self.filter_queryset(queryset) return queryset diff --git a/notifications/views.py b/notifications/views.py index c3a6751..cec50e4 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -68,5 +68,8 @@ class NotificationViewSet(viewsets.GenericViewSet): """ notification list """ - Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True) + if request.query_params.get('all'): + Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).update(is_read=True) + elif request.data.get('id'): + 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) From 6ba3d7d8dbb4b1ebad6e14d06ef657a70259f5fd Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 25 Sep 2023 11:44:04 +0530 Subject: [PATCH 353/372] token expire time 1 min for testing --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index cde1918..f318977 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -137,7 +137,7 @@ REST_FRAMEWORK = { } # define jwt token SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=0, minutes=59, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } From ea02d7f5bb168bcf99dd51c4f099891eabfd94f8 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 25 Sep 2023 11:45:50 +0530 Subject: [PATCH 354/372] token expire time 1 min for testing --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index f318977..96093b2 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -137,7 +137,7 @@ REST_FRAMEWORK = { } # define jwt token SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=0, minutes=59, seconds=59, microseconds=999999), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=0, minutes=0, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } From 32c35f86490100c966ae498d54f031cd92b2cc46 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 26 Sep 2023 17:24:40 +0530 Subject: [PATCH 355/372] added notification type in push data --- notifications/constants.py | 19 +++++++++++++++++++ notifications/views.py | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/notifications/constants.py b/notifications/constants.py index c22f246..6917327 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -22,79 +22,93 @@ ARTICLE_REWARD_POINTS = 17 REMOVE_JUNIOR = 18 TEST_NOTIFICATION = 99 + # notification dictionary NOTIFICATION_DICT = { REGISTRATION: { + "notification_type": REGISTRATION, "title": "Successfully registered!", "body": "You have registered successfully. Now login and complete your profile." }, # user will receive notification as soon junior # sign up application using their guardian code for association ASSOCIATE_REQUEST: { + "notification_type": ASSOCIATE_REQUEST, "title": "Associate request!", "body": "You have request from {from_user} to associate with you." }, # Juniors will receive notification when # custodians reject their request for associate ASSOCIATE_REJECTED: { + "notification_type": ASSOCIATE_REJECTED, "title": "Associate request rejected!", "body": "Your request to associate has been rejected by {from_user}." }, # Juniors will receive notification when # custodians approve their request for associate ASSOCIATE_APPROVED: { + "notification_type": 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: { + "notification_type": REFERRAL_POINTS, "title": "Earn Referral points!", "body": "You earn 5 points for referral." }, # Juniors will receive notification # once any custodians add them in their account ASSOCIATE_JUNIOR: { + "notification_type": ASSOCIATE_JUNIOR, "title": "Profile already setup!", "body": "Your guardian has already setup your profile." }, ASSOCIATE_EXISTING_JUNIOR: { + "notification_type": ASSOCIATE_EXISTING_JUNIOR, "title": "Associated to guardian", "body": "Your are associated to your guardian {from_user}." }, # Juniors will receive Notification # for every Task Assign by Custodians TASK_ASSIGNED: { + "notification_type": 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: { + "notification_type": TASK_ACTION, "title": "Task completion approval!", "body": "{from_user} completed their task {task_name}." }, # Juniors will receive notification as soon # as their task is rejected by custodians TASK_REJECTED: { + "notification_type": 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: { + "notification_type": 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: { + "notification_type": 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: { + "notification_type": 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." @@ -102,27 +116,32 @@ NOTIFICATION_DICT = { # Juniors will receive Notification # related to Leaderboard progress TOP_JUNIOR: { + "notification_type": 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: { + "notification_type": 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: { + "notification_type": 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: { + "notification_type": REMOVE_JUNIOR, "title": "Disassociate by guardian!", "body": "Your guardian has disassociated you." }, # Test notification TEST_NOTIFICATION: { + "notification_type": TEST_NOTIFICATION, "title": "Test Notification", "body": "This notification is for testing purpose from {from_user}." } diff --git a/notifications/views.py b/notifications/views.py index cec50e4..31fb9c1 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -58,7 +58,8 @@ class NotificationViewSet(viewsets.GenericViewSet): """ notify_task_expiry() notify_top_junior() - send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'], + notification_type = request.query_params.get('type', TEST_NOTIFICATION) + send_notification(int(notification_type), None, None, request.auth.payload['user_id'], {}) return custom_response(SUCCESS_CODE["3000"]) From f7bb83cebbf98acae3406713c15db198778a413e Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 26 Sep 2023 19:11:04 +0530 Subject: [PATCH 356/372] added notification type in serializer --- notifications/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index e4fdb05..8635970 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -36,7 +36,7 @@ class NotificationListSerializer(serializers.ModelSerializer): class Meta(object): """meta info""" model = Notification - fields = ['id', 'data', 'badge', 'is_read', 'created_at'] + fields = ['id', 'notification_type', 'data', 'badge', 'is_read', 'created_at'] @staticmethod def get_badge(obj): From 251a91294836d7c8e003fa61b2750c3864be94d1 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 27 Sep 2023 12:36:28 +0530 Subject: [PATCH 357/372] revert access token time to 3 hrs --- zod_bank/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 96093b2..cde1918 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -137,7 +137,7 @@ REST_FRAMEWORK = { } # define jwt token SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=0, minutes=0, seconds=59, microseconds=999999), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2, minutes=59, seconds=59, microseconds=999999), 'REFRESH_TOKEN_LIFETIME': timedelta(hours=71, minutes=59, seconds=59, microseconds=999999), } From 18143e02191ea51034e4ee93022257040a1854ef Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 27 Sep 2023 19:31:09 +0530 Subject: [PATCH 358/372] modified send push method --- notifications/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/notifications/utils.py b/notifications/utils.py index a118cd7..8941e5b 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -112,6 +112,7 @@ def send_notification(notification_type, from_user_id, from_user_type, to_user_i def send_push(user, data): """ used to send push notification to specific user """ + data['notification_type'] = str(data['notification_type']) user.fcmdevice_set.filter(active=True).send_message( Message(notification=FirebaseNotification(data['title'], data['body']), data=data) ) @@ -119,6 +120,7 @@ def send_push(user, data): def send_multiple_push(queryset, data): """ used to send same notification to multiple users """ + data['notification_type'] = str(data['notification_type']) FCMDevice.objects.filter(user__in=queryset, active=True).send_message( Message(notification=FirebaseNotification(data['title'], data['body']), data=data) ) From e121c92fb4b10f17dde0ca30a4cfca92245bdf70 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 28 Sep 2023 19:07:14 +0530 Subject: [PATCH 359/372] notification create modified to update or create --- base/tasks.py | 4 ++-- notifications/admin.py | 1 + notifications/utils.py | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/base/tasks.py b/base/tasks.py index 24893fc..ebc6672 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -13,7 +13,7 @@ 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 base.constants import PENDING, IN_PROGRESS, JUNIOR, GUARDIAN 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 @@ -53,7 +53,7 @@ def notify_task_expiry(): (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, + send_notification(PENDING_TASK_EXPIRING, task.guardian.user_id, GUARDIAN, 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: diff --git a/notifications/admin.py b/notifications/admin.py index c7cc895..4df035a 100644 --- a/notifications/admin.py +++ b/notifications/admin.py @@ -11,3 +11,4 @@ class NotificationAdmin(admin.ModelAdmin): """Notification Admin""" list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read'] list_filter = ['notification_type'] + search_fields = ['notification_to'] diff --git a/notifications/utils.py b/notifications/utils.py index 8941e5b..0ad6cda 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -88,6 +88,7 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us 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 @@ -104,8 +105,18 @@ def send_notification(notification_type, from_user_id, from_user_type, to_user_i 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() - Notification.objects.create(notification_type=notification_type, notification_from=from_user, - notification_to=to_user, data=notification_data) + + # notification create method changed on 28sep as per changes required + task_id = extra_data['task_id'] if 'task_id' in extra_data else None + Notification.objects.update_or_create(data__has_key='task_id', data__task_id=task_id, + notification_from=from_user, notification_to=to_user, + defaults={ + 'notification_type': notification_type, + 'notification_from': from_user, + 'notification_to': to_user, + 'data': notification_data + }) + if user_notification_type and user_notification_type.push_notification: send_push(to_user, push_data) From bd7eddb27537415fe0853a2e1a92985c3cc1ff87 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 29 Sep 2023 14:58:06 +0530 Subject: [PATCH 360/372] notification mark as read api modified for clear all, list sorting set to updated at field, added same field --- .../migrations/0002_notification_updated_at.py | 18 ++++++++++++++++++ notifications/models.py | 1 + notifications/serializers.py | 2 +- notifications/views.py | 16 ++++++++++++---- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 notifications/migrations/0002_notification_updated_at.py diff --git a/notifications/migrations/0002_notification_updated_at.py b/notifications/migrations/0002_notification_updated_at.py new file mode 100644 index 0000000..7a075fa --- /dev/null +++ b/notifications/migrations/0002_notification_updated_at.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-09-29 07:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/notifications/models.py b/notifications/models.py index 5d6d02c..1fb99e9 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -18,6 +18,7 @@ class Notification(models.Model): data = models.JSONField(default=dict, blank=True, null=True) is_read = models.BooleanField(default=False) created_at = models.DateTimeField(default=timezone.now) + updated_at = models.DateTimeField(auto_now=True) def __str__(self): """ string representation """ diff --git a/notifications/serializers.py b/notifications/serializers.py index 8635970..2c7dfec 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -36,7 +36,7 @@ class NotificationListSerializer(serializers.ModelSerializer): class Meta(object): """meta info""" model = Notification - fields = ['id', 'notification_type', 'data', 'badge', 'is_read', 'created_at'] + fields = ['id', 'notification_type', 'data', 'badge', 'is_read', 'updated_at'] @staticmethod def get_badge(obj): diff --git a/notifications/views.py b/notifications/views.py index 31fb9c1..5fcafe8 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -33,7 +33,7 @@ class NotificationViewSet(viewsets.GenericViewSet): :return: """ queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id'] - ).select_related('notification_to').order_by('-id') + ).select_related('notification_to').order_by('-updated_at', '-id') paginator = CustomPageNumberPagination() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) @@ -61,6 +61,8 @@ class NotificationViewSet(viewsets.GenericViewSet): notification_type = request.query_params.get('type', TEST_NOTIFICATION) send_notification(int(notification_type), None, None, request.auth.payload['user_id'], {}) + if notification_type and request.query_params.get('clear_all'): + Notification.objects.filter(notification_type=notification_type).delete() return custom_response(SUCCESS_CODE["3000"]) @action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False, @@ -69,8 +71,14 @@ class NotificationViewSet(viewsets.GenericViewSet): """ notification list """ - if request.query_params.get('all'): - Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).update(is_read=True) - elif request.data.get('id'): + + if request.data.get('id'): Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True) + + elif request.query_params.get('mark_all'): + Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).update(is_read=True) + + elif request.query_params.get('clear_all'): + Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).delete() + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) From 18a53e1c485c5f297501add91f78c3ac53225ca9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 29 Sep 2023 16:09:22 +0530 Subject: [PATCH 361/372] handled deactivated users for social login --- account/views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/account/views.py b/account/views.py index a76693e..45e662e 100644 --- a/account/views.py +++ b/account/views.py @@ -90,6 +90,11 @@ class GoogleLoginMixin(object): ERROR_CODE["2071"], response_status=status.HTTP_400_BAD_REQUEST ) + if not junior_query.is_active: + return custom_error_response( + ERROR_CODE["2075"], + response_status=status.HTTP_404_NOT_FOUND + ) serializer = JuniorSerializer(junior_query) elif str(user_type) == '2': guardian_query = Guardian.objects.filter(user=user_data.last()).last() @@ -98,6 +103,11 @@ class GoogleLoginMixin(object): ERROR_CODE["2070"], response_status=status.HTTP_400_BAD_REQUEST ) + if not guardian_query.is_active: + return custom_error_response( + ERROR_CODE["2075"], + response_status=status.HTTP_404_NOT_FOUND + ) serializer = GuardianSerializer(guardian_query) else: return custom_error_response( From ad4d782e726a4799398c557c4ab96abc19818371 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Tue, 3 Oct 2023 18:54:47 +0530 Subject: [PATCH 362/372] added mail for user activation, handled fcm token for deleted user --- .../templated_email/user_activate.email | 22 +++++++++++++++++++ base/tasks.py | 1 + notifications/utils.py | 1 + web_admin/views/user_management.py | 12 +++++++--- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 account/templates/templated_email/user_activate.email diff --git a/account/templates/templated_email/user_activate.email b/account/templates/templated_email/user_activate.email new file mode 100644 index 0000000..24ba5f8 --- /dev/null +++ b/account/templates/templated_email/user_activate.email @@ -0,0 +1,22 @@ +{% extends "templated_email/email_base.email" %} + +{% block subject %} + Account Activated +{% endblock %} + +{% block plain %} + + +

    + Hi User, +

    + + + + +

    + We're pleased to inform you that your account has been successfully reactivated by our admin team. Welcome back to ZOD !

    You can now access all the features and services as before. If you have any questions or need assistance, please feel free to reach out to our support team.

    Thank you for being a valued member of our community. +

    + + +{% endblock %} diff --git a/base/tasks.py b/base/tasks.py index ebc6672..edd3dd1 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -48,6 +48,7 @@ def notify_task_expiry(): :return: """ all_pending_tasks = JuniorTask.objects.filter( + junior__is_verified=True, task_status__in=[PENDING, IN_PROGRESS], due_date__range=[datetime.datetime.now().date(), (datetime.datetime.now().date() + datetime.timedelta(days=1))]) diff --git a/notifications/utils.py b/notifications/utils.py index 0ad6cda..46509eb 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -24,6 +24,7 @@ User = get_user_model() def register_fcm_token(user_id, registration_id, device_id, device_type): """ used to register the fcm device token""" + FCMDevice.objects.filter(registration_id=registration_id).delete() device, _ = FCMDevice.objects.update_or_create(user_id=user_id, defaults={'device_id': device_id, 'type': device_type, 'active': True, diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 6980a7a..b0b611e 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -120,17 +120,23 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, if user_type not in [GUARDIAN, JUNIOR]: return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) - 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 not obj: + return custom_error_response(ERROR_CODE['2004'], status.HTTP_400_BAD_REQUEST) + if obj.is_active: + deactivate_email_template = 'user_deactivate.email' obj.is_active = False - send_email([obj.user.email if user_type == GUARDIAN else obj.auth.email], email_template) + send_email([obj.user.email if user_type == GUARDIAN else obj.auth.email], + deactivate_email_template) else: + activate_email_template = 'user_activate.email' obj.is_active = True + send_email([obj.user.email if user_type == GUARDIAN else obj.auth.email], + activate_email_template) obj.save() return custom_response(SUCCESS_CODE['3038']) From 6e3166967efcdde868280d4c82f1315d4285dd76 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 5 Oct 2023 13:07:42 +0530 Subject: [PATCH 363/372] fixed login into another device issue --- account/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/account/views.py b/account/views.py index 45e662e..b240e44 100644 --- a/account/views.py +++ b/account/views.py @@ -114,6 +114,10 @@ class GoogleLoginMixin(object): ERROR_CODE["2069"], response_status=status.HTTP_400_BAD_REQUEST ) + device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_data.last()) + 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) @@ -202,6 +206,10 @@ class SigninWithApple(views.APIView): 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) From 13665f4c9a0716b0862d7f86811be17b3c1b97e9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 5 Oct 2023 17:38:57 +0530 Subject: [PATCH 364/372] fixed user notificatio setting issue --- account/serializers.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index b783efd..6356be5 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -373,17 +373,16 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): fields = ['push_notification', 'email_notification', 'sms_notification'] def create(self, validated_data): - instance = UserNotification.objects.filter(user=self.context).last() - if instance: - # change notification status - instance.push_notification = validated_data.get('push_notification',instance.push_notification) - instance.email_notification = validated_data.get('email_notification', instance.email_notification) - instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification) - instance.save() - else: - instance = UserNotification.objects.create(user=self.context) + instance, _ = UserNotification.objects.update_or_create( + user=self.context, + defaults={ + 'push_notification': validated_data.get('push_notification'), + 'email_notification': validated_data.get('email_notification'), + 'sms_notification': validated_data.get('sms_notification'), + }) return instance + class UserPhoneOtpSerializer(serializers.ModelSerializer): """User Phone serializers""" class Meta(object): From a1f9f93654f97e6411e02887b3e58ca2c84d5c5c Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 6 Oct 2023 17:29:41 +0530 Subject: [PATCH 365/372] added to user type as per required changes, handled apple signup for existing users --- account/views.py | 2 +- guardian/utils.py | 4 ++-- junior/serializers.py | 4 ++-- junior/views.py | 4 ++-- notifications/utils.py | 1 + notifications/views.py | 3 ++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/account/views.py b/account/views.py index b240e44..e2f211a 100644 --- a/account/views.py +++ b/account/views.py @@ -184,7 +184,7 @@ class SigninWithApple(views.APIView): user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True} if decoded_data.get("email"): try: - user = User.objects.get(email=decoded_data.get("email")) + user = User.objects.get(email__iexact=decoded_data.get("email")) if str(user_type) == '1': junior_data = Junior.objects.filter(auth=user).last() if not junior_data: diff --git a/guardian/utils.py b/guardian/utils.py index 3360285..6461912 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -11,7 +11,7 @@ import tempfile # Import date time module's function from datetime import datetime, time # import Number constant -from base.constants import NUMBER +from base.constants import NUMBER, GUARDIAN # Import Junior's model from junior.models import Junior, JuniorPoints # Import guardian's model @@ -117,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, None, junior_queryset.auth_id, {}) + send_notification.delay(REFERRAL_POINTS, None, GUARDIAN, junior_queryset.auth_id, {}) diff --git a/junior/serializers.py b/junior/serializers.py index 615c949..fadb55f 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -16,7 +16,7 @@ from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, Juni 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, JUNIOR) + GUARDIAN_CODE_STATUS, JUNIOR, GUARDIAN) from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail, get_junior_leaderboard_rank @@ -323,7 +323,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, special_password) # push notification - send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth_id, {}) + send_notification.delay(ASSOCIATE_JUNIOR, None, GUARDIAN, junior_data.auth_id, {}) return junior_data diff --git a/junior/views.py b/junior/views.py index 171c454..80cc89b 100644 --- a/junior/views.py +++ b/junior/views.py @@ -362,7 +362,7 @@ class RemoveJuniorAPIView(views.APIView): # save serializer serializer.save() JuniorGuardianRelationship.objects.filter(guardian=guardian, junior=junior_queryset).delete() - send_notification.delay(REMOVE_JUNIOR, None, None, junior_queryset.auth_id, {}) + send_notification.delay(REMOVE_JUNIOR, None, GUARDIAN, 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: @@ -712,7 +712,7 @@ class CompleteArticleAPIView(views.APIView): total_earn_points=Sum('earn_points'))['total_earn_points'] data = {"total_earn_points":total_earn_points} if total_earn_points: - send_notification.delay(ARTICLE_REWARD_POINTS, None, None, + send_notification.delay(ARTICLE_REWARD_POINTS, None, GUARDIAN, request.user.id, {'points': total_earn_points}) return custom_response(SUCCESS_CODE['3042'], data, response_status=status.HTTP_200_OK) except Exception as e: diff --git a/notifications/utils.py b/notifications/utils.py index 46509eb..0764db6 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -92,6 +92,7 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us notification_data['from_user'] = from_user_name notification_data['from_user_image'] = from_user_image + notification_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR notification_data.update(extra_data) to_user = User.objects.filter(id=to_user_id).first() diff --git a/notifications/views.py b/notifications/views.py index 5fcafe8..e11a63b 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -59,7 +59,8 @@ class NotificationViewSet(viewsets.GenericViewSet): notify_task_expiry() notify_top_junior() notification_type = request.query_params.get('type', TEST_NOTIFICATION) - send_notification(int(notification_type), None, None, request.auth.payload['user_id'], + from_user_type = request.query_params.get('from_user_type') + send_notification(int(notification_type), None, from_user_type, request.auth.payload['user_id'], {}) if notification_type and request.query_params.get('clear_all'): Notification.objects.filter(notification_type=notification_type).delete() From d1927b24ee09082ad08d4c786bd9a167b0891b9f Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 6 Oct 2023 18:10:56 +0530 Subject: [PATCH 366/372] to user type added --- notifications/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/notifications/utils.py b/notifications/utils.py index 0764db6..9e360df 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -90,9 +90,12 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us notification_data['body'] = notification_data['body'].format(from_user=from_user_name, task_name=task_name, points=points) + push_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR + notification_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR + notification_data['from_user'] = from_user_name notification_data['from_user_image'] = from_user_image - notification_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR + notification_data.update(extra_data) to_user = User.objects.filter(id=to_user_id).first() From b1d8949b0811aaeaf3334546ce11e12468af7817 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 27 Oct 2023 12:05:47 +0530 Subject: [PATCH 367/372] added status for login and sign up from google and apple --- account/serializers.py | 13 +++++++++++-- account/views.py | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 6356be5..e317c0c 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -216,6 +216,7 @@ class GuardianSerializer(serializers.ModelSerializer): last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') refresh_token = serializers.SerializerMethodField('get_refresh_token') + sign_up = serializers.SerializerMethodField() def get_auth_token(self, obj): refresh = RefreshToken.for_user(obj.user) @@ -253,12 +254,16 @@ class GuardianSerializer(serializers.ModelSerializer): """user last name""" return obj.user.last_name + def get_sign_up(self, obj): + return True if self.context.get('sign_up', '') else False + class Meta(object): """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', 'is_deleted', - 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name'] + 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', + 'country_name', 'sign_up'] class JuniorSerializer(serializers.ModelSerializer): @@ -269,6 +274,7 @@ class JuniorSerializer(serializers.ModelSerializer): last_name = serializers.SerializerMethodField('get_last_name') auth_token = serializers.SerializerMethodField('get_auth_token') refresh_token = serializers.SerializerMethodField('get_refresh_token') + sign_up = serializers.SerializerMethodField() def get_auth_token(self, obj): refresh = RefreshToken.for_user(obj.auth) @@ -295,13 +301,16 @@ class JuniorSerializer(serializers.ModelSerializer): def get_last_name(self, obj): return obj.auth.last_name + def get_sign_up(self, obj): + return True if self.context.get('sign_up', '') else False + class Meta(object): """Meta info""" model = Junior fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited', - 'is_deleted'] + 'is_deleted', 'sign_up'] class EmailVerificationSerializer(serializers.ModelSerializer): """Email verification serializer""" diff --git a/account/views.py b/account/views.py index e2f211a..1034d88 100644 --- a/account/views.py +++ b/account/views.py @@ -129,7 +129,7 @@ class GoogleLoginMixin(object): junior_code=generate_code(JUN, user_obj.id), referral_code=generate_code(ZOD, user_obj.id) ) - serializer = JuniorSerializer(junior_query) + serializer = JuniorSerializer(junior_query, context={'sign_up': True}) position = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_query, position=position) elif str(user_type) == '2': @@ -138,7 +138,7 @@ class GoogleLoginMixin(object): guardian_code=generate_code(GRD, user_obj.id), referral_code=generate_code(ZOD, user_obj.id) ) - serializer = GuardianSerializer(guardian_query) + serializer = GuardianSerializer(guardian_query, context={'sign_up': True}) else: user_obj.delete() return custom_error_response( @@ -220,7 +220,7 @@ class SigninWithApple(views.APIView): signup_method='3', junior_code=generate_code(JUN, user.id), referral_code=generate_code(ZOD, user.id)) - serializer = JuniorSerializer(junior_query) + serializer = JuniorSerializer(junior_query, context={'sign_up': True}) position = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_query, position=position) elif str(user_type) == '2': @@ -228,7 +228,7 @@ class SigninWithApple(views.APIView): signup_method='3', guardian_code=generate_code(GRD, user.id), referral_code=generate_code(ZOD, user.id)) - serializer = GuardianSerializer(guardian_query) + serializer = GuardianSerializer(guardian_query, context={'sign_up': True}) else: user.delete() return custom_error_response( From 9abf549ed43345c2a5765510ede81e15c3ba98af Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 8 Nov 2023 15:02:23 +0530 Subject: [PATCH 368/372] modified user notification setting, added optional value for sms notification as false --- account/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/serializers.py b/account/serializers.py index e317c0c..03160b6 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -387,7 +387,7 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer): defaults={ 'push_notification': validated_data.get('push_notification'), 'email_notification': validated_data.get('email_notification'), - 'sms_notification': validated_data.get('sms_notification'), + 'sms_notification': validated_data.get('sms_notification', False), }) return instance From bdc92163c36fa7e08afd98524b7c1c5dcd38f337 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 8 Nov 2023 18:47:28 +0530 Subject: [PATCH 369/372] added fixture file for faq's --- fixtures/faq.json | 112 ++++++++++++++++++++++++++++++++++++++++++++++ junior/views.py | 7 ++- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 fixtures/faq.json diff --git a/fixtures/faq.json b/fixtures/faq.json new file mode 100644 index 0000000..4f7ddb1 --- /dev/null +++ b/fixtures/faq.json @@ -0,0 +1,112 @@ +[ + { + "model": "junior.faq", + "pk": 1, + "fields": { + "question": "What is ZOD ?", + "description": "We are a future neobank for under 18. We aim to provide children with the ability to use debit cards under the watchfull eye of their parents.", + "status": 1, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 2, + "fields": { + "question": "What is financial literacy ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 3, + "fields": { + "question": "How can we win with Zod ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 4, + "fields": { + "question": "What is a budget ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 5, + "fields": { + "question": "What is the difference between stocks and bonds ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 6, + "fields": { + "question": "What is compound interest ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 7, + "fields": { + "question": "What is diversification ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 8, + "fields": { + "question": "What is a 401(k) ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 9, + "fields": { + "question": "What is an emergency fund ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + }, + { + "model": "junior.faq", + "pk": 10, + "fields": { + "question": "What is a mortgage ?", + "description": "", + "status": 2, + "created_at": "2023-11-08T12:32:55.291Z", + "updated_at": "2023-11-08T12:32:55.291Z" + } + } +] \ No newline at end of file diff --git a/junior/views.py b/junior/views.py index 80cc89b..9b1d67e 100644 --- a/junior/views.py +++ b/junior/views.py @@ -19,6 +19,7 @@ from base.pagination import CustomPageNumberPagination """Django app import""" from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi +from django.core.management import call_command from drf_yasg.views import get_schema_view # Import guardian's model, # Import junior's model, @@ -813,7 +814,7 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, http_method_names = ['get', 'post'] def get_queryset(self): - return FAQ.objects.all() + return FAQ.objects.filter(status=1).order_by('id') def create(self, request, *args, **kwargs): """ @@ -823,6 +824,10 @@ class FAQViewSet(GenericViewSet, mixins.CreateModelMixin, :param kwargs: :return: success message """ + load_fixture = request.query_params.get('load_fixture') + if load_fixture: + call_command('loaddata', 'fixtures/faq.json') + return custom_response(SUCCESS_CODE["3045"], response_status=status.HTTP_200_OK) obj_data = [FAQ(**item) for item in request.data] try: FAQ.objects.bulk_create(obj_data) From 5c05a988a5436487e2ab02005c0dea4db69134d4 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 8 Nov 2023 18:54:54 +0530 Subject: [PATCH 370/372] new line in faq_json file --- fixtures/faq.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fixtures/faq.json b/fixtures/faq.json index 4f7ddb1..3221c7e 100644 --- a/fixtures/faq.json +++ b/fixtures/faq.json @@ -109,4 +109,4 @@ "updated_at": "2023-11-08T12:32:55.291Z" } } -] \ No newline at end of file +] From 4f2b42dc08beb9cda92d887bc4ccb5c51ad18493 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 16 Nov 2023 17:07:46 +0530 Subject: [PATCH 371/372] changed message --- base/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/messages.py b/base/messages.py index e36921f..3977895 100644 --- a/base/messages.py +++ b/base/messages.py @@ -96,8 +96,8 @@ ERROR_CODE = { "2067": "Action not allowed. User type missing.", "2068": "No guardian associated with this junior", "2069": "Invalid user type", - "2070": "You do not find as a guardian", - "2071": "You do not find as a junior", + "2070": "You are not registered as a guardian in our system. Please try again as junior.", + "2071": "You are not registered as a junior in our system. Please try again as guardian.", "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", From d614d13136f935a787509b58692e11f2d85aa384 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 16 Nov 2023 17:30:32 +0530 Subject: [PATCH 372/372] added optional last name in google login --- account/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account/views.py b/account/views.py index 1034d88..a656e0b 100644 --- a/account/views.py +++ b/account/views.py @@ -74,7 +74,7 @@ class GoogleLoginMixin(object): user_info = response.json() email = user_info['email'] first_name = user_info['given_name'] - last_name = user_info['family_name'] + last_name = user_info['family_name'] if 'family_name' in user_info and user_info['family_name'] else user_info['given_name'] profile_picture = user_info['picture'] except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)

    qlZ)pW&#G;is-UE2m+6d?q&8-E#zW1PmH0c*&8 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�%?LP3o_bte9X2-iJxBmA7;NYWs2=U{Fk$woU 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 LIahYp@4^27zKv*M diff --git a/images/Screenshot_from_2023-06-28_15-16-09.png b/images/Screenshot_from_2023-06-28_15-16-09.png deleted file mode 100644 index e79b16aedeef85453fd3219184b892d0e782fc1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226054 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