diff --git a/account/serializers.py b/account/serializers.py index d4fd335..284ae09 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -137,6 +137,37 @@ class ForgotPasswordSerializer(serializers.Serializer): """Forget password serializer""" email = serializers.EmailField() + +class AdminLoginSerializer(serializers.ModelSerializer): + """admin login serializer""" + email = serializers.EmailField(required=True) + password = serializers.CharField(required=True) + + class Meta: + """ + meta class + """ + model = User + fields = ('email', 'password') + + def validate(self, attrs): + user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True + ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + + if not user: + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + elif not user.check_password(attrs['password']): + raise serializers.ValidationError({'details': ERROR_CODE['2031']}) + self.context.update({'user': user}) + return attrs + + def create(self, validated_data): + """ + used to return the user object after validation + """ + return self.context['user'] + + class SuperUserSerializer(serializers.ModelSerializer): """Super admin serializer""" user_type = serializers.SerializerMethodField('get_user_type') diff --git a/account/urls.py b/account/urls.py index ef3d026..02ac124 100644 --- a/account/urls.py +++ b/account/urls.py @@ -28,14 +28,15 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView) + UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView, + AdminLoginViewSet) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') """super admin login""" -router.register('admin', UserLogin, basename='admin') +router.register('admin', AdminLoginViewSet, basename='admin') """google login end point""" router.register('google-login', GoogleLoginViewSet, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') diff --git a/account/views.py b/account/views.py index c95aa7e..b36d75d 100644 --- a/account/views.py +++ b/account/views.py @@ -1,7 +1,7 @@ """Account view """ from notifications.utils import remove_fcm_token -"""Django import""" +# django imports from datetime import datetime, timedelta from rest_framework import viewsets, status, views from rest_framework.decorators import action @@ -18,19 +18,21 @@ import google.auth.transport.requests from rest_framework import status import requests from rest_framework.response import Response +from rest_framework import mixins from django.conf import settings -"""App Import""" + +# local imports from guardian.models import Guardian from junior.models import Junior from guardian.utils import upload_image_to_alibaba from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User -"""Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, - UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) + UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer, + AdminLoginSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, ZOD, JUN, GRD @@ -243,7 +245,6 @@ class ForgotPasswordAPIView(views.APIView): class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def create(self, request, *args, **kwargs): otp = generate_otp() @@ -260,7 +261,6 @@ class SendPhoneOtp(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def list(self, request, *args, **kwargs): try: @@ -346,10 +346,34 @@ class UserLogin(viewsets.ViewSet): data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'} return custom_response(None, data, response_status=status.HTTP_200_OK) + +class AdminLoginViewSet(viewsets.GenericViewSet): + """ + admin login api + """ + serializer_class = AdminLoginSerializer + + @action(methods=['post'], url_name='login', url_path='login', detail=False) + def admin_login(self, request, *args, **kwargs): + """ + :param request: + :return: + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.save() + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + refresh_token = str(refresh) + data = {"auth_token": access_token, "refresh_token": refresh_token, "username": user.username, + "email": user.email, "first_name": user.first_name, "last_name": user.last_name, + "is_active": user.is_active, "user_type": '3', "is_superuser": user.is_superuser} + return custom_response(None, data) + + class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer - queryset = UserEmailOtp.objects.all() def list(self, request, *args, **kwargs): try: @@ -392,7 +416,6 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer permission_classes = [IsAuthenticated] @@ -415,7 +438,6 @@ class ReSendEmailOtp(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = User.objects.all() serializer_class = JuniorProfileSerializer permission_classes = [IsAuthenticated] @@ -434,7 +456,6 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): class UploadImageAPIViewSet(viewsets.ModelViewSet): """upload task image""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesSerializer def create(self, request, *args, **kwargs): """upload images""" @@ -453,7 +474,6 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet): class DefaultImageAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesDetailsSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -484,7 +504,6 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): class UserNotificationAPIViewSet(viewsets.ModelViewSet): """notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UserNotificationSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -496,7 +515,6 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): """Update notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UpdateUserNotificationSerializer permission_classes = [IsAuthenticated] diff --git a/base/constants.py b/base/constants.py index 6022f62..e38cb29 100644 --- a/base/constants.py +++ b/base/constants.py @@ -70,6 +70,12 @@ SIGNUP_METHODS = ( ('2', 'google'), ('3', 'apple') ) +# guardian code status +GUARDIAN_CODE_STATUS = ( + ('1', 'no guardian code'), + ('2', 'exist guardian code'), + ('3', 'request for guardian code') +) # relationship RELATIONSHIP = ( ('1', 'parent'), diff --git a/base/messages.py b/base/messages.py index bff508d..cf85f2b 100644 --- a/base/messages.py +++ b/base/messages.py @@ -92,7 +92,10 @@ ERROR_CODE = { "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", - "2066": "Task does not exist or not in expired state" + "2066": "Task does not exist or not in expired state", + "2067": "Action not allowed. User type missing.", + "2068": "No guardian associated with this junior" + } """Success message code""" SUCCESS_CODE = { @@ -142,7 +145,11 @@ SUCCESS_CODE = { "3033": "Valid Referral code", "3034": "Invite guardian successfully", "3035": "Task started successfully", - "3036": "Task reassign successfully" + "3036": "Task reassign successfully", + "3037": "Profile has been updated successfully.", + "3038": "Status has been changed successfully.", + # notification read + "3039": "Notification read successfully" } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/serializers.py b/guardian/serializers.py index db465ad..27ab351 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 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), @@ -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 @@ -254,7 +260,10 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer): """ remaining time to complete task""" due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - current_datetime = real_time() + # current_datetime = real_time() + # new code + due_date_datetime = due_date_datetime.astimezone(pytz.utc) + current_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date_datetime > current_datetime: time_difference = due_date_datetime - current_datetime @@ -371,7 +380,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points + instance.points # update complete time of task - instance.completed_on = real_time() + # instance.completed_on = real_time() + instance.completed_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {}) else: # reject the task @@ -380,9 +390,76 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points - instance.points # update reject time of task - instance.rejected_on = real_time() + # instance.rejected_on = real_time() + instance.rejected_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {}) instance.save() junior_data.save() 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/views.py b/guardian/views.py index 15b29c5..ab876e7 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 @@ -82,7 +85,6 @@ class SignupViewset(viewsets.ModelViewSet): class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" - queryset = Guardian.objects.all() serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] @@ -116,7 +118,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,37 +127,69 @@ class AllTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# class TaskListAPIView(viewsets.ModelViewSet): +# """Update guardian profile""" +# serializer_class = TaskDetailsSerializer +# permission_classes = [IsAuthenticated] +# pagination_class = PageNumberPagination +# http_method_names = ('get',) +# +# def list(self, request, *args, **kwargs): +# """Create guardian profile""" +# try: +# status_value = self.request.GET.get('status') +# search = self.request.GET.get('search') +# if search and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_name__icontains=search).order_by('due_date', 'created_at') +# elif search and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, +# task_status=status_value).order_by('due_date', 'created_at') +# if search is None and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') +# elif search is None and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_status=status_value).order_by('due_date','created_at') +# paginator = self.pagination_class() +# # use Pagination +# paginated_queryset = paginator.paginate_queryset(queryset, request) +# # use TaskDetailsSerializer serializer +# serializer = TaskDetailsSerializer(paginated_queryset, many=True) +# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# except Exception as e: +# return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] + filter_backends = (SearchFilter,) + search_fields = ['task_name', ] pagination_class = PageNumberPagination http_method_names = ('get',) + def get_queryset(self): + queryset = JuniorTask.objects.filter(guardian__user=self.request.user + ).prefetch_related('junior', 'junior__auth' + ).order_by('due_date', 'created_at') + + queryset = self.filter_queryset(queryset) + return queryset + def list(self, request, *args, **kwargs): """Create guardian profile""" - try: - status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_name__icontains=search).order_by('due_date', 'created_at') - elif search and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, - task_status=status_value).order_by('due_date', 'created_at') - if search is None and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') - elif search is None and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=status_value).order_by('due_date','created_at') - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - except Exception as e: - return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + status_value = self.request.GET.get('status') + queryset = self.get_queryset() + if status_value and status_value != '0': + queryset = queryset.filter(task_status=status_value) + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" @@ -228,7 +261,6 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] - queryset = JuniorPoints.objects.all() def get_serializer_context(self): # context list @@ -328,3 +360,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']},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..5fdfcce 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior, JuniorPoints, JuniorGuardianRelationship +from .models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): @@ -27,3 +27,7 @@ class JuniorGuardianRelationshipAdmin(admin.ModelAdmin): """Junior Admin""" list_display = ['guardian', 'junior', 'relationship'] +@admin.register(JuniorArticlePoints) +class JuniorArticlePointsAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['junior', 'article', 'question', 'submitted_answer'] diff --git a/junior/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/models.py b/junior/models.py index b18cbf5..3adf459 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 # Import guardian's model from guardian.models import Guardian - +# Import web admin's model +from web_admin.models import SurveyOption, ArticleSurvey, Article """Define User model""" User = get_user_model() # Create your models here. @@ -73,6 +74,9 @@ class Junior(models.Model): is_verified = models.BooleanField(default=False) """guardian code is approved or not""" guardian_code_approved = models.BooleanField(default=False) + # guardian code status""" + guardian_code_status = models.CharField(max_length=31, choices=GUARDIAN_CODE_STATUS, default='1', + null=True, blank=True) # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -137,3 +141,22 @@ class JuniorGuardianRelationship(models.Model): return f'{self.guardian.user}' +class JuniorArticlePoints(models.Model): + """ + Survey Options model + """ + # associated junior with the task + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_details', verbose_name='Junior') + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles') + question = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='questions') + submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.CASCADE, related_name='submitted_answer') + # earn points""" + earn_points = models.IntegerField(blank=True, null=True, default=5) + is_attempt = models.BooleanField(default=False) + is_answer_correct = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.question}' diff --git a/junior/serializers.py b/junior/serializers.py index 316c1bf..ea79ed5 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 guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED +from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED, + GUARDIAN_CODE_STATUS) from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail @@ -89,7 +93,10 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """condition for guardian code""" if guardian_code: junior.guardian_code = guardian_code - junior.dob = validated_data.get('dob',junior.dob) + guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last() + if guardian_data: + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) + junior.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) junior.country_name = validated_data.get('country_name', junior.country_name) """Update country code and phone number""" @@ -277,7 +284,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): junior_code=generate_code(JUN, user_data.id), referral_code=generate_code(ZOD, user_data.id), referral_code_used=guardian_data.referral_code, - is_password_set=False, is_verified=True) + is_password_set=False, is_verified=True, + guardian_code_status=GUARDIAN_CODE_STATUS['2']) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Generate otp""" @@ -289,7 +297,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 @@ -318,7 +325,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - instance.requested_on = real_time() + # instance.requested_on = real_time() + instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() @@ -397,13 +405,15 @@ class AddGuardianSerializer(serializers.ModelSerializer): relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] junior_data = Junior.objects.filter(auth__username=junior).last() + junior_data.guardian_code_status = GUARDIAN_CODE_STATUS['3'] + junior_data.save() instance = User.objects.filter(username=email).last() if instance: guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True, - referral_code=generate_code(ZOD, + referral_code=generate_code(ZOD, instance.id), referral_code_used=junior_data.referral_code, - is_verified=True) + is_verified=True) UserNotification.objects.get_or_create(user=instance) return guardian_data else: @@ -443,7 +453,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 diff --git a/junior/urls.py b/junior/urls.py index f66fa30..267faef 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView) """Third party import""" from rest_framework import routers @@ -41,6 +41,8 @@ router.register('junior-points', JuniorPointsListAPIView, basename='junior-point router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') # invite guardian API""" router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') +# start article""" +router.register('start-article', StartArticleAPIView, basename='start-article') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 3f3cd5a..449ac5f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -27,8 +27,8 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints -from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints +from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) from guardian.models import Guardian, JuniorTask @@ -40,7 +40,7 @@ from guardian.utils import upload_image_to_alibaba from .utils import update_positions_based_on_points from notifications.utils import send_notification from notifications.constants import REMOVE_JUNIOR - +from web_admin.models import Article, ArticleSurvey, SurveyOption """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -59,7 +59,6 @@ from notifications.constants import REMOVE_JUNIOR # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" - queryset = Junior.objects.all() serializer_class = CreateJuniorSerializer permission_classes = [IsAuthenticated] @@ -93,7 +92,6 @@ class UpdateJuniorProfile(viewsets.ViewSet): class ValidateGuardianCode(viewsets.ViewSet): """Check guardian code exist or not""" - queryset = Guardian.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -115,7 +113,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) search_fields = ['auth__first_name', 'auth__last_name'] @@ -144,7 +141,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" - queryset = Junior.objects.all() serializer_class = AddJuniorSerializer permission_classes = [IsAuthenticated] http_method_names = ('post',) @@ -172,6 +168,8 @@ class AddJuniorAPIView(viewsets.ModelViewSet): guardian = Guardian.objects.filter(user=self.request.user).first() junior.guardian_code = [guardian.guardian_code] junior.save() + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, + relationship=str(self.request.data['relationship'])) return True @@ -179,7 +177,6 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination http_method_names = ('get',) @@ -209,7 +206,6 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = Junior.objects.all() http_method_names = ('get',) def get_queryset(self): @@ -267,7 +263,6 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsjuniorSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() http_method_names = ('get',) def list(self, request, *args, **kwargs): @@ -457,3 +452,40 @@ class ReAssignJuniorTaskAPIView(views.APIView): return custom_error_response(ERROR_CODE['2066'], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class StartArticleAPIView(viewsets.ModelViewSet): + """Start article""" + serializer_class = AddGuardianSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('post',) + + def create(self, request, *args, **kwargs): + """ junior list""" + try: + junior_instance = Junior.objects.filter(auth=self.request.user).last() + article_id = request.data.get('article_id') + article_data = Article.objects.filter(id=article_id).last() + if article_data: + print("article_data====>", article_data,'===>',type(article_data)) + question_query = ArticleSurvey.objects.filter(article=article_id) + print("question_query====>",question_query,'===>',type(question_query)) + for question in question_query: + print("question====>", question, '===>', type(question)) + answer = SurveyOption.objects.filter(survey=question) + print("answer===>",answer,'====>',type(answer)) + for ans in answer: + JuniorArticlePoints.objects.create(junior=junior_instance, + article=article_data, + question=question, + submitted_answer=ans) + print("CREATED CREATED") + return custom_response(SUCCESS_CODE['3034'], response_status=status.HTTP_200_OK) + # use AddJuniorSerializer serializer + # serializer = AddGuardianSerializer(data=request.data, context=info) + # if serializer.is_valid(): + # # save serializer + # serializer.save() + # return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) + # return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/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/models.py b/web_admin/models.py index 8a3bb16..5dbef97 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -49,7 +49,7 @@ class ArticleSurvey(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.article}' + return f'{self.id} | {self.question}' class SurveyOption(models.Model): @@ -64,7 +64,7 @@ class SurveyOption(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.survey}' + return f'{self.id} | {self.option}' class DefaultArticleCardImage(models.Model): diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 427f20d..b39d20d 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -214,3 +214,27 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image + +class ArticleListSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_cards = ArticleCardSerializer(many=True) + total_points = serializers.SerializerMethodField('get_total_points') + is_completed = serializers.SerializerMethodField('get_is_completed') + + class Meta: + """ + meta class + """ + model = Article + fields = ('id', 'title', 'description', 'article_cards', 'total_points', 'is_completed') + + def get_total_points(self, obj): + """total points of article""" + total_question = ArticleSurvey.objects.filter(article=obj).count() + return total_question * NUMBER['five'] + + def get_is_completed(self, obj): + """complete all question""" + return False diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 5293cc4..4bb0709 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,15 +5,11 @@ web_admin user_management serializers file from rest_framework import serializers from django.contrib.auth import get_user_model -from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, - USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) +from base.constants import USER_TYPE # local imports -from base.messages import ERROR_CODE +from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.models import Guardian -from guardian.utils import upload_image_to_alibaba from junior.models import Junior -from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage -from web_admin.utils import pop_id, get_image_url USER = get_user_model() @@ -23,7 +19,8 @@ class UserManagementListSerializer(serializers.ModelSerializer): user management serializer """ name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() + country_code = serializers.SerializerMethodField() + phone = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() @@ -32,7 +29,7 @@ class UserManagementListSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('id', 'name', 'email', 'phone_number', 'user_type', 'is_active') + fields = ('id', 'name', 'email', 'country_code', 'phone', 'user_type', 'is_active') @staticmethod def get_name(obj): @@ -43,15 +40,28 @@ class UserManagementListSerializer(serializers.ModelSerializer): return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name @staticmethod - def get_phone_number(obj): + def get_country_code(obj): """ :param obj: user object :return: user phone number """ if profile := obj.guardian_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None elif profile := obj.junior_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None + else: + return None + + @staticmethod + def get_phone(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return profile.phone if profile.phone else None + elif profile := obj.junior_profile.all().first(): + return profile.phone if profile.phone else None else: return None @@ -83,56 +93,166 @@ class UserManagementListSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer): + """ + guardian serializer + """ name = serializers.SerializerMethodField() - phone_number = 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', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + 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: user object + :param obj: guardian object :return: full name """ return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name @staticmethod - def get_phone_number(obj): + def get_first_name(obj): """ - :param obj: user object - :return: user phone number + :param obj: guardian object + :return: first name """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone + 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() - phone_number = 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', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + 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: user object + :param obj: junior object :return: full name """ return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name @staticmethod - def get_phone_number(obj): + def get_first_name(obj): """ - :param obj: user object - :return: user phone number + :param obj: junior object + :return: first name """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone + 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) @@ -151,7 +271,6 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): :param obj: user object :return: user type """ - profile = obj.guardian_profile.all().first() if obj.guardian_profile.all().first(): return dict(USER_TYPE).get('2') elif obj.junior_profile.all().first(): @@ -161,11 +280,19 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): @staticmethod def get_associated_users(obj): + """ + :param obj: user object + :return: associated user + """ if profile := obj.guardian_profile.all().first(): - junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) - serializer = JuniorSerializer(junior, many=True) - return serializer.data + if profile.guardian_code: + junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) + serializer = JuniorSerializer(junior, many=True) + return serializer.data elif profile := obj.junior_profile.all().first(): - guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) - serializer = GuardianSerializer(guardian, many=True) - return serializer.data + if profile.guardian_code: + guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) + serializer = GuardianSerializer(guardian, many=True) + return serializer.data + else: + return None diff --git a/web_admin/urls.py b/web_admin/urls.py index 30645a2..065d30d 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,7 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet +from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -16,7 +16,7 @@ router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') - +router.register('article-list', ArticleListViewSet, basename='article-list') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 314f36e..f334650 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -16,7 +16,7 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer) USER = get_user_model() @@ -29,7 +29,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel serializer_class = ArticleSerializer permission_classes = [IsAuthenticated, AdminPermission] queryset = Article - filter_backends = (OrderingFilter, SearchFilter,) + filter_backends = (SearchFilter,) search_fields = ['title'] http_method_names = ['get', 'post', 'put', 'delete'] @@ -109,7 +109,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_card', url_path='remove_card', + @action(methods=['get'], url_name='remove-card', url_path='remove-card', detail=True) def remove_article_card(self, request, *args, **kwargs): """ @@ -125,7 +125,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel except AttributeError: return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_survey', url_path='remove_survey', + @action(methods=['get'], url_name='remove-survey', url_path='remove-survey', detail=True) def remove_article_survey(self, request, *args, **kwargs): """ @@ -195,3 +195,33 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m return custom_response(None, data=serializer.data) +class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): + """ + article api + """ + serializer_class = ArticleListSerializer + permission_classes = [IsAuthenticated,] + queryset = Article + http_method_names = ['get',] + + def get_queryset(self): + article = self.queryset.objects.filter(is_deleted=False).prefetch_related( + 'article_cards', 'article_survey', 'article_survey__options' + ).order_by('-created_at') + queryset = self.filter_queryset(article) + return queryset + + def list(self, request, *args, **kwargs): + """ + article list api method + :param request: + :param args: + :param kwargs: + :return: list of article + """ + queryset = self.get_queryset() + count = queryset.count() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data, count=count) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 54dc874..20184cd 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -14,20 +14,21 @@ from django.db.models import Q from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, - UserManagementDetailSerializer) + UserManagementDetailSerializer, GuardianSerializer, + JuniorSerializer) USER = get_user_model() -class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin): +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, + mixins.RetrieveModelMixin, mixins.UpdateModelMixin): """ api to manage (list, view, edit) user """ serializer_class = UserManagementListSerializer - permission_classes = [] + permission_classes = [IsAuthenticated, AdminPermission] queryset = USER.objects.filter(is_superuser=False ).prefetch_related('guardian_profile', 'junior_profile' @@ -35,6 +36,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie 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'): @@ -49,7 +51,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie def list(self, request, *args, **kwargs): """ api method to list all the user - :param request: + :param request: user_type {'guardian' for Guardian list, 'junior' for Junior list} :return: """ queryset = self.get_queryset() @@ -60,6 +62,14 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie return custom_response(None, data=serializer.data, count=count) def retrieve(self, request, *args, **kwargs): + """ + to get details of single user + :param request: user_id along with + user_type {'guardian' for Guardian, 'junior' for Junior} mandatory + :return: user details + """ + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): queryset = queryset.filter(guardian_profile__user__id=kwargs['pk']) @@ -67,3 +77,50 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']) serializer = UserManagementDetailSerializer(queryset, many=True) return custom_response(None, data=serializer.data) + + def partial_update(self, request, *args, **kwargs): + """ + 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'])