diff --git a/account/serializers.py b/account/serializers.py index d4fd335..edf4de9 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', + 'username', 'is_active', 'is_superuser').first() + + if not user or not user.check_password(attrs['password']): + raise serializers.ValidationError({'details': ERROR_CODE['2002']}) + + 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/utils.py b/account/utils.py index d1e6e54..d33b267 100644 --- a/account/utils.py +++ b/account/utils.py @@ -23,7 +23,8 @@ 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 +from junior.models import JuniorPoints # Define delete # user account condition, # Define delete @@ -91,7 +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['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""" @@ -138,12 +141,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/account/views.py b/account/views.py index c95aa7e..8c2932d 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 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 -"""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 @@ -95,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', @@ -145,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', @@ -243,7 +249,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() @@ -260,7 +265,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: @@ -327,29 +331,46 @@ 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', + '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) + + serializer = SuperUserSerializer(user) + return custom_response(SUCCESS_CODE['3003'], serializer.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 - queryset = UserEmailOtp.objects.all() def list(self, request, *args, **kwargs): try: @@ -392,7 +413,6 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer permission_classes = [IsAuthenticated] @@ -415,7 +435,6 @@ class ReSendEmailOtp(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = User.objects.all() serializer_class = JuniorProfileSerializer permission_classes = [IsAuthenticated] @@ -434,7 +453,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""" @@ -453,7 +471,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): @@ -484,7 +501,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): @@ -496,7 +512,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/base/constants.py b/base/constants.py index 3c1501c..05fa8b8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -70,6 +70,18 @@ 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') +) +# article status +ARTICLE_STATUS = ( + ('1', 'read'), + ('2', 'in_progress'), + ('3', 'completed') +) # relationship RELATIONSHIP = ( ('1', 'parent'), @@ -83,6 +95,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' @@ -105,7 +118,9 @@ 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' + +DATE_FORMAT = '%Y-%m-%d' diff --git a/base/messages.py b/base/messages.py index 0407719..87f5e9a 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", @@ -91,7 +91,11 @@ 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", + "2067": "Action not allowed. User type missing.", + "2068": "No guardian associated with this junior" + } """Success message code""" SUCCESS_CODE = { @@ -107,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 @@ -120,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", @@ -140,7 +144,23 @@ 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", + "3037": "Profile has been updated successfully.", + "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", + } """status code error""" STATUS_CODE_ERROR = { diff --git a/celerybeat-schedule b/celerybeat-schedule index 71062ba..f2510fc 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ diff --git a/guardian/serializers.py b/guardian/serializers.py index df2178a..1dabc8d 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,14 +17,14 @@ 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 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 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 @@ -67,8 +68,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), @@ -81,7 +84,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", }) @@ -211,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', '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""" @@ -224,7 +227,10 @@ class TaskDetailsSerializer(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 @@ -244,6 +250,34 @@ 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() + # 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 + 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""" @@ -320,7 +354,8 @@ 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.guardian_code_status = str(NUMBER['two']) instance.save() return instance @@ -346,18 +381,84 @@ 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 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 = 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() 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') + 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', 'dob', + 'guardian_code', 'gender', 'phone', 'country_name', 'created_at', 'guardian_code_status', + '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): + """email of guardian""" + return obj.guardian.user.email + + def get_image(self,obj): + """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/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/utils.py b/guardian/utils.py index 920a119..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 @@ -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/guardian/views.py b/guardian/views.py index 9706e87..3948e6f 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, @@ -26,14 +28,15 @@ 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 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 @@ -56,33 +59,29 @@ 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""" - queryset = Guardian.objects.all() serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] @@ -116,7 +115,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): @@ -126,38 +124,37 @@ 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] + filter_backends = (SearchFilter,) + search_fields = ['task_name', ] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() 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""" @@ -227,9 +224,9 @@ class SearchTaskListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" + queryset = JuniorPoints.objects.all() serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] - queryset = JuniorPoints.objects.all() def get_serializer_context(self): # context list @@ -241,14 +238,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[: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) @@ -282,6 +277,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: @@ -329,3 +325,22 @@ 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): + """Guardian list of assosicated junior""" + + 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_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/admin.py b/junior/admin.py index acd8734..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 +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""" @@ -27,3 +45,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', 'is_answer_correct'] 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), + ), + ] 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/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/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/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/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/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 dc71c97..025843e 100644 --- a/junior/models.py +++ b/junior/models.py @@ -6,10 +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 +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, ArticleCard """Define User model""" User = get_user_model() # Create your models here. @@ -71,6 +72,11 @@ 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) + # 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) @@ -135,3 +141,62 @@ 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.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) + 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}' + +class JuniorArticle(models.Model): + """ + Survey Options model + """ + # associated junior with the task + 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""" + 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) + + 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 27a6808..f102972 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,12 +10,13 @@ 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 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 +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 @@ -89,7 +93,13 @@ 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.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) """Update country code and phone number""" @@ -136,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): @@ -172,10 +182,11 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): 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 + 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() @@ -200,10 +211,10 @@ 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'] + 'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status'] class JuniorProfileSerializer(serializers.ModelSerializer): """junior serializer""" @@ -246,15 +257,14 @@ 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""" - class Meta(object): """Meta info""" model = Junior - fields = ['id', 'gender','dob', 'is_invited'] + fields = ['id', 'gender', 'dob', 'is_invited'] def create(self, validated_data): @@ -263,6 +273,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, @@ -272,14 +283,18 @@ 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), 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[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) @@ -289,7 +304,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 @@ -306,6 +320,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 @@ -318,7 +333,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 = real_time() + instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() @@ -335,6 +350,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 +386,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""" @@ -393,13 +412,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[2][0] + 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: @@ -439,7 +460,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 @@ -454,3 +478,31 @@ 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 + + + +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 = None + instance.guardian_code_status = str(NUMBER['one']) + instance.save() + return instance diff --git a/junior/urls.py b/junior/urls.py index e4b2489..b145d4f 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,9 @@ 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, StartArticleAPIView, + StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView, + CreateArticleCardAPIView, RemoveGuardianCodeAPIView) """Third party import""" from rest_framework import routers @@ -41,10 +43,22 @@ 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') +# start assessment api""" +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)), 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()), + 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/utils.py b/junior/utils.py index fefe950..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 @@ -50,11 +51,11 @@ 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', '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 c9c8739..a86083d 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 @@ -27,20 +28,25 @@ import requests # 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,\ +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) + AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer, + RemoveGuardianCodeSerializer) 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 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, ArticleCard +from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, + StartAssessmentSerializer) """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -59,7 +65,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 +98,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): @@ -144,7 +148,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',) @@ -152,8 +155,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) @@ -163,7 +177,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) @@ -171,7 +185,10 @@ 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'])) return True @@ -179,7 +196,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',) @@ -209,7 +225,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): @@ -246,8 +261,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), - 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) @@ -265,10 +280,9 @@ 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() http_method_names = ('get',) def list(self, request, *args, **kwargs): @@ -295,7 +309,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) @@ -343,16 +357,12 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): permission_classes = [IsAuthenticated] http_method_names = ('get',) - 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""" - try: - queryset = self.get_queryset() - # update position of junior update_positions_based_on_points() + 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) except Exception as e: @@ -434,3 +444,208 @@ 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) + +class StartArticleAPIView(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 not JuniorArticle.objects.filter(junior=junior_instance, article=article_data).last(): + 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: + if not JuniorArticlePoints.objects.filter(junior=junior_instance, + article=article_data, + question=question): + JuniorArticlePoints.objects.create(junior=junior_instance, + article=article_data, + 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) + +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') + 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, + 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) + 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) + +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') + 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) + + +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) + +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) diff --git a/notifications/serializers.py b/notifications/serializers.py index 053096f..a061369 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -6,6 +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 +27,19 @@ 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 NotificationListSerializer(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',) 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..4644e62 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -3,24 +3,40 @@ 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, 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): """ @@ -40,3 +56,33 @@ 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=NotificationListSerializer) + def notification_list(self, request): + """ + notification list + """ + try: + queryset = Notification.objects.filter(notification_to=request.user) + serializer = NotificationListSerializer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + +class ReadNotification(views.APIView): + """Update notification API""" + serializer_class = ReadNotificationSerializer + model = Notification + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + try: + notification_id = self.request.data.get('notification_id') + notification_queryset = Notification.objects.filter(id__in=notification_id, + notification_to=self.request.user).update(is_read=True) + if notification_queryset: + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) 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..5dbef97 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -49,14 +49,14 @@ 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): """ 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) @@ -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/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/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py new file mode 100644 index 0000000..fd85118 --- /dev/null +++ b/web_admin/serializers/analytics_serializer.py @@ -0,0 +1,52 @@ +""" +web_admin analytics serializer file +""" +from rest_framework import serializers + +from junior.models import JuniorPoints, Junior + + +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/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index e374679..f8c7f67 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -11,8 +11,8 @@ 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 +from junior.models import JuniorArticlePoints, JuniorArticle USER = get_user_model() @@ -21,23 +21,20 @@ 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: """ meta class """ model = ArticleCard - fields = ('id', 'title', 'description', 'image', 'image_url') + fields = ('id', 'title', 'description', 'image_name', '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) - article_card = ArticleCard.objects.create(article_id='1', **validated_data) + validated_data['image_url'] = get_image_url(validated_data) + article = Article.objects.all().first() + article_card = ArticleCard.objects.create(article=article, **validated_data) return article_card @@ -111,11 +108,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,40 +132,36 @@ 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() + 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() 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: @@ -185,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 @@ -210,6 +200,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,62 +210,124 @@ 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 - -class UserManagementListSerializer(serializers.ModelSerializer): +class ArticleListSerializer(serializers.ModelSerializer): """ - user management serializer + serializer for article API """ - name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() - user_type = serializers.SerializerMethodField() + article_cards = ArticleCardSerializer(many=True) + article_survey = ArticleSurveySerializer(many=True) + total_points = serializers.SerializerMethodField('get_total_points') + is_completed = serializers.SerializerMethodField('get_is_completed') class Meta: """ meta class """ - model = USER - fields = ('name', 'email', 'phone_number', 'user_type', 'is_active') + model = Article + fields = ('id', 'title', 'description', 'article_cards', 'article_survey', 'total_points', 'is_completed') - @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 + def get_total_points(self, obj): + """total points of article""" + total_question = ArticleSurvey.objects.filter(article=obj).count() + return total_question * NUMBER['five'] - @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 + def get_is_completed(self, obj): + """complete all question""" + junior_article = JuniorArticle.objects.filter(article=obj).last() + if junior_article: + return junior_article.is_completed + return False - @staticmethod - def get_user_type(obj): +class ArticleQuestionSerializer(serializers.ModelSerializer): + """ + article survey serializer + """ + 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""" + 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 + + 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: """ - :param obj: user object - :return: user type + meta class """ - 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 + model = ArticleSurvey + fields = ('id', 'question', 'options', 'points', 'is_attempt', 'correct_answer', 'attempted_answer') + +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', '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/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/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py new file mode 100644 index 0000000..4bb0709 --- /dev/null +++ b/web_admin/serializers/user_management_serializer.py @@ -0,0 +1,298 @@ +""" +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 USER_TYPE +# local imports +from base.messages import ERROR_CODE, SUCCESS_CODE +from guardian.models import Guardian +from junior.models import Junior + +USER = get_user_model() + + +class UserManagementListSerializer(serializers.ModelSerializer): + """ + user management serializer + """ + name = serializers.SerializerMethodField() + country_code = serializers.SerializerMethodField() + phone = serializers.SerializerMethodField() + user_type = serializers.SerializerMethodField() + is_active = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = USER + fields = ('id', 'name', 'email', 'country_code', 'phone', '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_country_code(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + 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): + """ + :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 + + @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 profile.is_active + elif profile := obj.junior_profile.all().first(): + return profile.is_active + else: + return obj.is_active + + +class GuardianSerializer(serializers.ModelSerializer): + """ + guardian serializer + """ + name = serializers.SerializerMethodField() + 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', 'first_name', 'last_name', 'username', '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): + """ + 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) + instance.save() + return instance + + @staticmethod + def get_name(obj): + """ + :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() + 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', 'first_name', 'last_name', 'username', '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): + """ + :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) + instance.save() + return instance + + @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 + + @staticmethod + def get_username(obj): + """ + :param obj: junior object + :return: email + """ + return obj.auth.username + + +class UserManagementDetailSerializer(serializers.ModelSerializer): + """ + user management details serializer + """ + 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 = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users') + + @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_associated_users(obj): + """ + :param obj: user object + :return: associated user + """ + if profile := obj.guardian_profile.all().first(): + 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(): + 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 diff --git a/web_admin/urls.py b/web_admin/urls.py index 5fbe21e..7f65fc8 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,15 +6,22 @@ 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.analytics import AnalyticsViewSet +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 # 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') +router.register('analytics', AnalyticsViewSet, basename='user-analytics') + +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/utils.py b/web_admin/utils.py index 2e09e2f..9870b30 100644 --- a/web_admin/utils.py +++ b/web_admin/utils.py @@ -1,6 +1,10 @@ """ web_utils file """ +import base64 + +from base.constants import ARTICLE_CARD_IMAGE_FOLDER +from guardian.utils import upload_image_to_alibaba def pop_id(data): @@ -11,3 +15,28 @@ def pop_id(data): """ data.pop('id') if 'id' in data else data return data + + +def get_image_url(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}" + # upload image on ali baba + image_url = upload_image_to_alibaba(image, filename) + return image_url diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py new file mode 100644 index 0000000..8c21cb3 --- /dev/null +++ b/web_admin/views/analytics.py @@ -0,0 +1,143 @@ +""" +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 +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, DATE_FORMAT +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() + + +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 + permission_classes = [IsAuthenticated, AdminPermission] + + 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: 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'), 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)))) + + 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): + """ + api method to get new signups + :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'), 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))] + ).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): + """ + 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'), 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))] + ).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) + + @action(methods=['get'], url_name='junior-leaderboard', url_path='junior-leaderboard', detail=False, + serializer_class=LeaderboardSerializer) + def junior_leaderboard(self, request): + """ + to get junior leaderboard and rank + :param request: + :return: + """ + queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + expression=Rank(), + order_by=[F('total_points').desc(), 'junior__created_at'] + )).order_by('-total_points', 'junior__created_at') + paginator = CustomPageNumberPagination() + 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/article.py b/web_admin/views/article.py index 5aa88b3..e57577e 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,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, - UserManagementListSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer, + ArticleCardlistSerializer) USER = get_user_model() @@ -30,13 +30,13 @@ 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'] 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 @@ -77,10 +77,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): """ @@ -109,7 +110,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 +126,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): """ @@ -195,30 +196,53 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m return custom_response(None, data=serializer.data) -class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin): +class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): """ - api to manage (list, view, edit) user + article api """ - serializer_class = UserManagementListSerializer - permission_classes = [] - queryset = USER.objects.prefetch_related( - 'guardian_profile', 'junior_profile') + serializer_class = ArticleListSerializer + permission_classes = [IsAuthenticated,] + queryset = Article + http_method_names = ['get',] 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 + 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): """ - api method to list all the user + article list api method :param request: - :return: + :param args: + :param kwargs: + :return: list of article """ queryset = self.get_queryset() - serializer = self.serializer_class(queryset, many=True) + 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) +class ArticleCardListViewSet(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = ArticleCardlistSerializer + 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 = 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) 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 new file mode 100644 index 0000000..8f53a73 --- /dev/null +++ b/web_admin/views/user_management.py @@ -0,0 +1,126 @@ +""" +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 +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 web_admin.permission import AdminPermission +from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, + UserManagementDetailSerializer, GuardianSerializer, + JuniorSerializer) + +USER = get_user_model() + + +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, + mixins.RetrieveModelMixin, mixins.UpdateModelMixin): + """ + api to manage (list, view, edit) user + """ + serializer_class = UserManagementListSerializer + permission_classes = [IsAuthenticated, AdminPermission] + 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'] + + def get_queryset(self): + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + queryset = self.queryset.filter(junior_profile__isnull=True) + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + queryset = self.queryset.filter(guardian_profile__isnull=True) + else: + queryset = self.queryset + queryset = self.filter_queryset(queryset) + return queryset + + def list(self, request, *args, **kwargs): + """ + api method to list all the user + :param request: user_type {'guardian' for Guardian list, 'junior' for Junior list} + :return: + """ + 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, count=queryset.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(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) + queryset = self.queryset + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + queryset = queryset.filter(guardian_profile__user__id=kwargs['pk']) + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']) + serializer = UserManagementDetailSerializer(queryset, many=True) + 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(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) + queryset = self.queryset + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), + request.data, context={'user_id': kwargs['pk']}) + + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + serializer = JuniorSerializer(user_obj.junior_profile.all().first(), + request.data, context={'user_id': kwargs['pk']}) + + serializer.is_valid(raise_exception=True) + serializer.save() + 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(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) + queryset = self.queryset + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + obj = user_obj.guardian_profile.all().first() + obj.is_active = False if obj.is_active else True + obj.save() + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + obj = user_obj.junior_profile.all().first() + obj.is_active = False if obj.is_active else True + obj.save() + return custom_response(SUCCESS_CODE['3038']) 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/