From 685f62770752ac8a136cf95ea6412a097a6207f3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 2 Aug 2023 19:16:29 +0530 Subject: [PATCH 01/17] change method to get real time --- guardian/serializers.py | 21 ++++++++++++--------- junior/serializers.py | 15 +++++++++++---- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index c81744a..4d0ea65 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -1,5 +1,5 @@ """Serializer of Guardian""" -"""Third party Django app""" +# third party imports import logging from rest_framework import serializers # Import Refresh token of jwt @@ -7,7 +7,8 @@ from rest_framework_simplejwt.tokens import RefreshToken from django.db import transaction from django.contrib.auth.models import User from datetime import datetime, time -"""Import Django app""" +import pytz +from django.utils import timezone # Import guardian's model, # Import junior's model, # Import account's model, @@ -16,7 +17,7 @@ from datetime import datetime, time # Import messages from # base package, # Import some functions -# from utils file""" +# local imports from .models import Guardian, JuniorTask from account.models import UserProfile, UserEmailOtp, UserNotification from account.utils import generate_code @@ -222,12 +223,9 @@ class TaskDetailsSerializer(serializers.ModelSerializer): def get_remaining_time(self, obj): """ remaining time to complete task""" - import pytz - from django.utils import timezone due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time # current_datetime = real_time() - print(due_date_datetime.astimezone(pytz.utc), datetime.now(pytz.utc), timezone.now().astimezone(pytz.utc)) # new code due_date_datetime = due_date_datetime.astimezone(pytz.utc) current_datetime = timezone.now().astimezone(pytz.utc) @@ -260,7 +258,10 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer): """ remaining time to complete task""" due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - current_datetime = real_time() + # current_datetime = real_time() + # new code + due_date_datetime = due_date_datetime.astimezone(pytz.utc) + current_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date_datetime > current_datetime: time_difference = due_date_datetime - current_datetime @@ -377,7 +378,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points + instance.points # update complete time of task - instance.completed_on = real_time() + # instance.completed_on = real_time() + instance.completed_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {}) else: # reject the task @@ -386,7 +388,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update total task point junior_data.total_points = junior_data.total_points - instance.points # update reject time of task - instance.rejected_on = real_time() + # instance.rejected_on = real_time() + instance.rejected_on = timezone.now().astimezone(pytz.utc) send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {}) instance.save() junior_data.save() diff --git a/junior/serializers.py b/junior/serializers.py index 1dc04da..bdedbb5 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -1,5 +1,8 @@ """Serializer file for junior""" -"""Import Django 3rd party app""" +# third party imports +import pytz + +# django imports from rest_framework import serializers from django.contrib.auth.models import User from django.db import transaction @@ -7,7 +10,7 @@ from datetime import datetime from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken -"""Import django app""" +# local imports from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from guardian.tasks import generate_otp @@ -320,7 +323,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - instance.requested_on = real_time() + # instance.requested_on = real_time() + instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() @@ -445,7 +449,10 @@ class StartTaskSerializer(serializers.ModelSerializer): """ remaining time to complete task""" due_date = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - real_datetime = real_time() + # real_datetime = real_time() + # new code + due_date = due_date.astimezone(pytz.utc) + real_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date > real_datetime: time_difference = due_date - real_datetime From f3e2ab9a346ca28e3acc7b9b37db6fc32167906b Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Wed, 2 Aug 2023 20:45:46 +0530 Subject: [PATCH 02/17] notification list modified and mark as read method changed --- notifications/serializers.py | 4 +++- notifications/views.py | 25 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index ff05dcd..a061369 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -8,6 +8,7 @@ from rest_framework import serializers from notifications.utils import register_fcm_token from notifications.models import Notification + class RegisterDevice(serializers.Serializer): """ used to create and validate register device token @@ -27,7 +28,8 @@ class RegisterDevice(serializers.Serializer): return register_fcm_token(self.context['user_id'], registration_id, validated_data['device_id'], device_type) -class NotificationListSerailizer(serializers.ModelSerializer): + +class NotificationListSerializer(serializers.ModelSerializer): """List of notification""" class Meta(object): diff --git a/notifications/views.py b/notifications/views.py index 3d2ff4a..4644e62 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -11,15 +11,32 @@ from rest_framework import viewsets, status, views from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE from notifications.constants import TEST_NOTIFICATION -from notifications.serializers import RegisterDevice, NotificationListSerailizer, ReadNotificationSerializer +from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer from notifications.utils import send_notification from notifications.models import Notification + class NotificationViewSet(viewsets.GenericViewSet): """ used to do the notification actions """ - serializer_class = RegisterDevice + serializer_class = NotificationListSerializer permission_classes = [IsAuthenticated, ] + def list(self, request, *args, **kwargs) -> Response: + """ list the notifications """ + queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id'] + ).select_related('notification_to').order_by('-id') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + self.mark_notifications_as_read(serializer.data) + return custom_response(None, serializer.data) + + @staticmethod + def mark_notifications_as_read(data): + """ used to mark notification queryset as read """ + ids = [obj['id'] for obj in data] + Notification.objects.filter(id__in=ids).update(is_read=True) + @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) def fcm_registration(self, request): """ @@ -41,14 +58,14 @@ class NotificationViewSet(viewsets.GenericViewSet): return custom_response(SUCCESS_CODE["3000"]) @action(methods=['get'], detail=False, url_path='list', url_name='list', - serializer_class=NotificationListSerailizer) + serializer_class=NotificationListSerializer) def notification_list(self, request): """ notification list """ try: queryset = Notification.objects.filter(notification_to=request.user) - serializer = NotificationListSerailizer(queryset, many=True) + serializer = NotificationListSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From dd0f2b027a84d9e510c6c1fd3b94da3dfef65dab Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 3 Aug 2023 18:17:01 +0530 Subject: [PATCH 03/17] task list api modified --- guardian/serializers.py | 8 ++++- guardian/views.py | 78 +++++++++++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/guardian/serializers.py b/guardian/serializers.py index db465ad..d92b637 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -222,9 +222,15 @@ class TaskDetailsSerializer(serializers.ModelSerializer): def get_remaining_time(self, obj): """ remaining time to complete task""" + import pytz + from django.utils import timezone due_date_datetime = datetime.combine(obj.due_date, datetime.max.time()) # fetch real time - current_datetime = real_time() + # current_datetime = real_time() + print(due_date_datetime.astimezone(pytz.utc), datetime.now(pytz.utc), timezone.now().astimezone(pytz.utc)) + # new code + due_date_datetime = due_date_datetime.astimezone(pytz.utc) + current_datetime = timezone.now().astimezone(pytz.utc) # Perform the subtraction if due_date_datetime > current_datetime: time_difference = due_date_datetime - current_datetime diff --git a/guardian/views.py b/guardian/views.py index 15b29c5..612f837 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,6 +10,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, status from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User +from rest_framework.filters import SearchFilter + from django.utils import timezone # Import guardian's model, @@ -126,37 +128,69 @@ class AllTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# class TaskListAPIView(viewsets.ModelViewSet): +# """Update guardian profile""" +# serializer_class = TaskDetailsSerializer +# permission_classes = [IsAuthenticated] +# pagination_class = PageNumberPagination +# http_method_names = ('get',) +# +# def list(self, request, *args, **kwargs): +# """Create guardian profile""" +# try: +# status_value = self.request.GET.get('status') +# search = self.request.GET.get('search') +# if search and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_name__icontains=search).order_by('due_date', 'created_at') +# elif search and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, +# task_status=status_value).order_by('due_date', 'created_at') +# if search is None and str(status_value) == '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') +# elif search is None and str(status_value) != '0': +# queryset = JuniorTask.objects.filter(guardian__user=request.user, +# task_status=status_value).order_by('due_date','created_at') +# paginator = self.pagination_class() +# # use Pagination +# paginated_queryset = paginator.paginate_queryset(queryset, request) +# # use TaskDetailsSerializer serializer +# serializer = TaskDetailsSerializer(paginated_queryset, many=True) +# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) +# except Exception as e: +# return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] + filter_backends = (SearchFilter,) + search_fields = ['task_name', ] pagination_class = PageNumberPagination http_method_names = ('get',) + def get_queryset(self): + queryset = JuniorTask.objects.filter(guardian__user=self.request.user + ).prefetch_related('junior', 'junior__auth' + ).order_by('due_date', 'created_at') + + queryset = self.filter_queryset(queryset) + return queryset + def list(self, request, *args, **kwargs): """Create guardian profile""" - try: - status_value = self.request.GET.get('status') - search = self.request.GET.get('search') - if search and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_name__icontains=search).order_by('due_date', 'created_at') - elif search and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, - task_status=status_value).order_by('due_date', 'created_at') - if search is None and str(status_value) == '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') - elif search is None and str(status_value) != '0': - queryset = JuniorTask.objects.filter(guardian__user=request.user, - task_status=status_value).order_by('due_date','created_at') - paginator = self.pagination_class() - # use Pagination - paginated_queryset = paginator.paginate_queryset(queryset, request) - # use TaskDetailsSerializer serializer - serializer = TaskDetailsSerializer(paginated_queryset, many=True) - return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - except Exception as e: - return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + status_value = self.request.GET.get('status') + queryset = self.get_queryset() + if status_value and status_value != '0': + queryset = queryset.filter(task_status=status_value) + paginator = self.pagination_class() + # use Pagination + paginated_queryset = paginator.paginate_queryset(queryset, request) + # use TaskDetailsSerializer serializer + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + class CreateTaskAPIView(viewsets.ModelViewSet): """create task for junior""" From 5f1c645e3a26627a7d683dd4d2f626289e022213 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 4 Aug 2023 16:27:46 +0530 Subject: [PATCH 04/17] user list api --- .../serializers/user_management_serializer.py | 38 +++++++++---------- web_admin/views/article.py | 2 +- web_admin/views/user_management.py | 35 +++++++++++++++-- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 5293cc4..78a1136 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -84,11 +84,18 @@ class UserManagementListSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() class Meta: model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + + def update(self, instance, validated_data): + instance.user.email = self.context.get('email', instance.user.email) + instance.user.save() + instance.country_code = validated_data.get('country_code', instance.country_code) + instance.phone = validated_data.get('phone', instance.phone) + instance.save() + return instance @staticmethod def get_name(obj): @@ -98,22 +105,21 @@ class GuardianSerializer(serializers.ModelSerializer): """ return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name - @staticmethod - def get_phone_number(obj): - """ - :param obj: user object - :return: user phone number - """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone - class JuniorSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() class Meta: model = Junior - fields = ('id', 'name', 'dob', 'gender', 'phone_number', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + + def update(self, instance, validated_data): + instance.auth.email = self.context.get('email', instance.auth.email) + instance.auth.save() + instance.country_code = validated_data.get('country_code', instance.country_code) + instance.phone = validated_data.get('phone', instance.phone) + instance.save() + return instance @staticmethod def get_name(obj): @@ -123,14 +129,6 @@ class JuniorSerializer(serializers.ModelSerializer): """ return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name - @staticmethod - def get_phone_number(obj): - """ - :param obj: user object - :return: user phone number - """ - return f"+{obj.country_code}{obj.phone}" if obj.country_code and obj.phone else obj.phone - class UserManagementDetailSerializer(serializers.ModelSerializer): user_type = serializers.SerializerMethodField() diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 314f36e..be93b4c 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -29,7 +29,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel serializer_class = ArticleSerializer permission_classes = [IsAuthenticated, AdminPermission] queryset = Article - filter_backends = (OrderingFilter, SearchFilter,) + filter_backends = (SearchFilter,) search_fields = ['title'] http_method_names = ['get', 'post', 'put', 'delete'] diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 54dc874..db41855 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -14,15 +14,16 @@ from django.db.models import Q from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE from base.messages import SUCCESS_CODE, ERROR_CODE -from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, - UserManagementDetailSerializer) + UserManagementDetailSerializer, GuardianSerializer, + JuniorSerializer) USER = get_user_model() -class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin): +class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, + mixins.RetrieveModelMixin, mixins.UpdateModelMixin): """ api to manage (list, view, edit) user """ @@ -49,7 +50,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie def list(self, request, *args, **kwargs): """ api method to list all the user - :param request: + :param request: user_type {'guardian' for Guardian list, 'junior' for Junior list} :return: """ queryset = self.get_queryset() @@ -60,6 +61,14 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie return custom_response(None, data=serializer.data, count=count) def retrieve(self, request, *args, **kwargs): + """ + to get details of single user + :param request: user_id along with + user_type {'guardian' for Guardian, 'junior' for Junior} mandatory + :return: user details + """ + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): queryset = queryset.filter(guardian_profile__user__id=kwargs['pk']) @@ -67,3 +76,21 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, mixins.Retrie queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']) serializer = UserManagementDetailSerializer(queryset, many=True) return custom_response(None, data=serializer.data) + + def partial_update(self, request, *args, **kwargs): + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + queryset = self.queryset + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + queryset = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + serializer = GuardianSerializer(queryset.guardian_profile.all().first(), + request.data, context={'email': request.data.get('email', queryset.email)}) + + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + serializer = JuniorSerializer(queryset.junior_profile.all().first(), + request.data, context={'email': request.data.get('email', queryset.email)}) + + serializer.is_valid(raise_exception=True) + serializer.save() + return custom_response('Profile updated successfully.') From baacb1a18fac585be1e67b6e3743792c53720eb9 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 4 Aug 2023 16:28:58 +0530 Subject: [PATCH 05/17] user edit api --- .../serializers/user_management_serializer.py | 24 +++++++++++++++---- web_admin/views/user_management.py | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 78a1136..5221e12 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -23,7 +23,8 @@ class UserManagementListSerializer(serializers.ModelSerializer): user management serializer """ name = serializers.SerializerMethodField() - phone_number = serializers.SerializerMethodField() + country_code = serializers.SerializerMethodField() + phone = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() @@ -32,7 +33,7 @@ class UserManagementListSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('id', 'name', 'email', 'phone_number', 'user_type', 'is_active') + fields = ('id', 'name', 'email', 'country_code', 'phone', 'user_type', 'is_active') @staticmethod def get_name(obj): @@ -43,15 +44,28 @@ class UserManagementListSerializer(serializers.ModelSerializer): return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name @staticmethod - def get_phone_number(obj): + def get_country_code(obj): """ :param obj: user object :return: user phone number """ if profile := obj.guardian_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None elif profile := obj.junior_profile.all().first(): - return f"+{profile.country_code}{profile.phone}" if profile.country_code and profile.phone else profile.phone + return profile.country_code if profile.country_code else None + else: + return None + + @staticmethod + def get_phone(obj): + """ + :param obj: user object + :return: user phone number + """ + if profile := obj.guardian_profile.all().first(): + return profile.phone if profile.phone else None + elif profile := obj.junior_profile.all().first(): + return profile.phone if profile.phone else None else: return None diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index db41855..e4018ac 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -28,7 +28,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, api to manage (list, view, edit) user """ serializer_class = UserManagementListSerializer - permission_classes = [] + permission_classes = [IsAuthenticated, AdminPermission] queryset = USER.objects.filter(is_superuser=False ).prefetch_related('guardian_profile', 'junior_profile' From 4a2f36cde801a3fba3dd6154bca884c4de195368 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 10:55:34 +0530 Subject: [PATCH 06/17] edit user validations, active-inactive api --- base/messages.py | 4 +- .../serializers/user_management_serializer.py | 77 ++++++++++++++++--- web_admin/views/article.py | 4 +- web_admin/views/user_management.py | 45 +++++++++-- 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/base/messages.py b/base/messages.py index bff508d..e891ae1 100644 --- a/base/messages.py +++ b/base/messages.py @@ -142,7 +142,9 @@ SUCCESS_CODE = { "3033": "Valid Referral code", "3034": "Invite guardian successfully", "3035": "Task started successfully", - "3036": "Task reassign successfully" + "3036": "Task reassign successfully", + "3037": "Profile has been updated successfully.", + "3038": "Status has been changed successfully." } """status code error""" STATUS_CODE_ERROR = { diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 5221e12..9333155 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,15 +5,11 @@ web_admin user_management serializers file from rest_framework import serializers from django.contrib.auth import get_user_model -from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER, - USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER) +from base.constants import USER_TYPE # local imports -from base.messages import ERROR_CODE +from base.messages import ERROR_CODE, SUCCESS_CODE from guardian.models import Guardian -from guardian.utils import upload_image_to_alibaba from junior.models import Junior -from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage -from web_admin.utils import pop_id, get_image_url USER = get_user_model() @@ -97,14 +93,41 @@ class UserManagementListSerializer(serializers.ModelSerializer): class GuardianSerializer(serializers.ModelSerializer): + """ + guardian serializer + """ name = serializers.SerializerMethodField() + email = serializers.CharField(required=False) class Meta: + """ + meta class + """ model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + + def validate(self, attrs): + """ + to validate request data + :return: validated attrs + """ + email = attrs.get('email') + phone = attrs.get('phone') + if USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2003']}) + if Guardian.objects.filter(phone=phone).exclude(user__id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2012']}) + return attrs def update(self, instance, validated_data): - instance.user.email = self.context.get('email', instance.user.email) + """ + to update user and its related profile + :param instance: user's guardian object + :param validated_data: + :return: guardian object + """ + instance.user.email = self.validated_data.get('email', instance.user.email) + instance.user.username = self.validated_data.get('email', instance.user.username) instance.user.save() instance.country_code = validated_data.get('country_code', instance.country_code) instance.phone = validated_data.get('phone', instance.phone) @@ -121,14 +144,40 @@ class GuardianSerializer(serializers.ModelSerializer): class JuniorSerializer(serializers.ModelSerializer): + """ + junior serializer + """ name = serializers.SerializerMethodField() + email = serializers.CharField(required=False) class Meta: + """ + meta class + """ model = Junior - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image') + fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + + def validate(self, attrs): + """ + to validate request data + :return: validated attrs + """ + email = attrs.get('email') + phone = attrs.get('phone') + if email and USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2003']}) + if phone and Junior.objects.filter(phone=phone).exclude(auth__id=self.context.get('user_id')).exists(): + raise serializers.ValidationError({'details': ERROR_CODE['2012']}) + return attrs def update(self, instance, validated_data): - instance.auth.email = self.context.get('email', instance.auth.email) + """ + :param instance: user's junior object + :param validated_data: validated data + :return: instance + """ + instance.auth.email = self.validated_data.get('email', instance.auth.email) + instance.auth.username = self.validated_data.get('email', instance.auth.username) instance.auth.save() instance.country_code = validated_data.get('country_code', instance.country_code) instance.phone = validated_data.get('phone', instance.phone) @@ -145,6 +194,9 @@ class JuniorSerializer(serializers.ModelSerializer): class UserManagementDetailSerializer(serializers.ModelSerializer): + """ + user management details serializer + """ user_type = serializers.SerializerMethodField() guardian_profile = GuardianSerializer(many=True) junior_profile = JuniorSerializer(many=True) @@ -163,7 +215,6 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): :param obj: user object :return: user type """ - profile = obj.guardian_profile.all().first() if obj.guardian_profile.all().first(): return dict(USER_TYPE).get('2') elif obj.junior_profile.all().first(): @@ -173,6 +224,10 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): @staticmethod def get_associated_users(obj): + """ + :param obj: user object + :return: associated user + """ if profile := obj.guardian_profile.all().first(): junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) serializer = JuniorSerializer(junior, many=True) diff --git a/web_admin/views/article.py b/web_admin/views/article.py index be93b4c..53da6e1 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -109,7 +109,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel return custom_response(SUCCESS_CODE["3029"]) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_card', url_path='remove_card', + @action(methods=['get'], url_name='remove-card', url_path='remove-card', detail=True) def remove_article_card(self, request, *args, **kwargs): """ @@ -125,7 +125,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel except AttributeError: return custom_error_response(ERROR_CODE["2042"], response_status=status.HTTP_400_BAD_REQUEST) - @action(methods=['get'], url_name='remove_survey', url_path='remove_survey', + @action(methods=['get'], url_name='remove-survey', url_path='remove-survey', detail=True) def remove_article_survey(self, request, *args, **kwargs): """ diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index e4018ac..256248b 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -13,7 +13,7 @@ from django.db.models import Q # local imports from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE -from base.messages import SUCCESS_CODE, ERROR_CODE +from base.messages import SUCCESS_CODE from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, UserManagementDetailSerializer, GuardianSerializer, @@ -78,19 +78,48 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, return custom_response(None, data=serializer.data) def partial_update(self, request, *args, **kwargs): + """ + api method to update user detail (email, phone number) + :param request: user_id along with + user_type {'guardian' for Guardian, 'junior' for Junior} mandatory + :return: success message + """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): - queryset = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() - serializer = GuardianSerializer(queryset.guardian_profile.all().first(), - request.data, context={'email': request.data.get('email', queryset.email)}) + user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), + request.data, context={'user_id': kwargs['pk']}) elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): - queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() - serializer = JuniorSerializer(queryset.junior_profile.all().first(), - request.data, context={'email': request.data.get('email', queryset.email)}) + user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + serializer = JuniorSerializer(user_obj.junior_profile.all().first(), + request.data, context={'user_id': kwargs['pk']}) serializer.is_valid(raise_exception=True) serializer.save() - return custom_response('Profile updated successfully.') + return custom_response(SUCCESS_CODE['3037']) + + @action(methods=['get'], url_name='change-status', url_path='change-status', detail=True) + def change_status(self, request, *args, **kwargs): + """ + api to change user status (mark as active or inactive) + :param request: user_id along with + user_type {'guardian' for Guardian, 'junior' for Junior} mandatory + :return: success message + """ + if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: + return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + queryset = self.queryset + if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): + user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() + obj = user_obj.guardian_profile.all().first() + obj.is_active = False if obj.is_active else True + obj.save() + elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): + user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() + obj = user_obj.junior_profile.all().first() + obj.is_active = False if obj.is_active else True + obj.save() + return custom_response(SUCCESS_CODE['3038']) From e85334691064be94beb4f9b5af72b6e436b7d47a Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 11:26:23 +0530 Subject: [PATCH 07/17] notifications API --- base/messages.py | 4 +++- guardian/urls.py | 5 +++- guardian/views.py | 30 +++++++++++++++++++++++ notifications/serializers.py | 21 +++++++++++++++- notifications/urls.py | 3 ++- notifications/views.py | 46 +++++++++++++++++++++++++++++++----- 6 files changed, 99 insertions(+), 10 deletions(-) diff --git a/base/messages.py b/base/messages.py index bff508d..9f36be6 100644 --- a/base/messages.py +++ b/base/messages.py @@ -142,7 +142,9 @@ SUCCESS_CODE = { "3033": "Valid Referral code", "3034": "Invite guardian successfully", "3035": "Task started successfully", - "3036": "Task reassign successfully" + "3036": "Task reassign successfully", + # notification read + "3037": "Notification read successfully", } """status code error""" STATUS_CODE_ERROR = { diff --git a/guardian/urls.py b/guardian/urls.py index b14aa77..e95ea8e 100644 --- a/guardian/urls.py +++ b/guardian/urls.py @@ -2,7 +2,8 @@ """Django import""" from django.urls import path, include from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, - SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView) + SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView, + GuardianListAPIView) """Third party import""" from rest_framework import routers @@ -36,6 +37,8 @@ router.register('filter-task', SearchTaskListAPIView, basename='filter-task') router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior') # Approve junior API""" router.register('approve-task', ApproveTaskAPIView, basename='approve-task') +# guardian list API""" +router.register('guardian-list', GuardianListAPIView, basename='guardian-list') # Define Url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/guardian/views.py b/guardian/views.py index 15b29c5..47fbdda 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -328,3 +328,33 @@ class ApproveTaskAPIView(viewsets.ViewSet): except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) +class GuardianListAPIView(viewsets.ModelViewSet): + """Junior list of assosicated guardian""" + pass + + # serializer_class = JuniorDetailListSerializer + # queryset = Junior.objects.all() + # permission_classes = [IsAuthenticated] + # filter_backends = (SearchFilter,) + # search_fields = ['auth__first_name', 'auth__last_name'] + # http_method_names = ('get',) + # + # def get_queryset(self): + # queryset = self.filter_queryset(self.queryset) + # return queryset + # + # def list(self, request, *args, **kwargs): + # """ junior list""" + # try: + # update_positions_based_on_points() + # guardian_data = Guardian.objects.filter(user__email=request.user).last() + # # fetch junior object + # if guardian_data: + # queryset = self.get_queryset() + # queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code)) + # # use JuniorDetailListSerializer serializer + # serializer = JuniorDetailListSerializer(queryset, many=True) + # return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + # return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) + # except Exception as e: + # return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/notifications/serializers.py b/notifications/serializers.py index 053096f..a085cf6 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers # local imports from notifications.utils import register_fcm_token - +from notifications.models import Notification class RegisterDevice(serializers.Serializer): """ @@ -26,3 +26,22 @@ class RegisterDevice(serializers.Serializer): device_type = validated_data['type'] return register_fcm_token(self.context['user_id'], registration_id, validated_data['device_id'], device_type) + +class NotificationListSerailizer(serializers.ModelSerializer): + """List of notification""" + + class Meta(object): + """meta info""" + model = Notification + fields = ['id', 'data', 'is_read'] + +class ReadNotificationSerializer(serializers.ModelSerializer): + """User task Serializer""" + class Meta(object): + """Meta class""" + model = Notification + fields = ('id',) + def update(self, instance, validated_data): + instance.is_read = True + instance.save() + return instance diff --git a/notifications/urls.py b/notifications/urls.py index b184d02..713aae3 100644 --- a/notifications/urls.py +++ b/notifications/urls.py @@ -6,7 +6,7 @@ from django.urls import path, include from rest_framework import routers # local imports -from notifications.views import NotificationViewSet +from notifications.views import NotificationViewSet, ReadNotification # initiate router router = routers.SimpleRouter() @@ -15,4 +15,5 @@ router.register('notifications', NotificationViewSet, basename='notifications') urlpatterns = [ path('api/v1/', include(router.urls)), + path('api/v1/read-notification/', ReadNotification.as_view()), ] diff --git a/notifications/views.py b/notifications/views.py index a8659e3..d4dec80 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -3,18 +3,17 @@ notifications views file """ # django imports from django.db.models import Q -from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response - +from rest_framework import viewsets, status, views # local imports -from account.utils import custom_response -from base.messages import SUCCESS_CODE +from account.utils import custom_response, custom_error_response +from base.messages import SUCCESS_CODE, ERROR_CODE from notifications.constants import TEST_NOTIFICATION -from notifications.serializers import RegisterDevice +from notifications.serializers import RegisterDevice, NotificationListSerailizer, ReadNotificationSerializer from notifications.utils import send_notification - +from notifications.models import Notification class NotificationViewSet(viewsets.GenericViewSet): """ used to do the notification actions """ @@ -40,3 +39,38 @@ class NotificationViewSet(viewsets.GenericViewSet): """ send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {}) return custom_response(SUCCESS_CODE["3000"]) + + @action(methods=['get'], detail=False, url_path='list', url_name='list', + serializer_class=NotificationListSerailizer) + def notification_list(self, request): + """ + notification list + """ + try: + queryset = Notification.objects.filter(notification_to=request.user) + serializer = NotificationListSerailizer(queryset, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + + +class ReadNotification(views.APIView): + """Update notification API""" + serializer_class = ReadNotificationSerializer + model = Notification + permission_classes = [IsAuthenticated] + + def put(self, request, format=None): + try: + notification_id = str(self.request.data.get('notification_id')) + notification_queryset = Notification.objects.filter(id=notification_id, notification_to=self.request.user).last() + if notification_queryset: + # use ReadNotificationSerializer serializer + serializer = ReadNotificationSerializer(notification_queryset, data=request.data, partial=True) + if serializer.is_valid(): + # save serializer + serializer.save() + return custom_response(SUCCESS_CODE['3037'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From 83ec922584df163b05f523983be2e5027f1a3310 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 12:00:58 +0530 Subject: [PATCH 08/17] admin login api modified --- account/serializers.py | 31 ++++++++++++++++++++++++++ account/urls.py | 5 +++-- account/views.py | 35 ++++++++++++++++++++++++++---- base/messages.py | 3 ++- web_admin/views/user_management.py | 7 +++--- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index d4fd335..284ae09 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -137,6 +137,37 @@ class ForgotPasswordSerializer(serializers.Serializer): """Forget password serializer""" email = serializers.EmailField() + +class AdminLoginSerializer(serializers.ModelSerializer): + """admin login serializer""" + email = serializers.EmailField(required=True) + password = serializers.CharField(required=True) + + class Meta: + """ + meta class + """ + model = User + fields = ('email', 'password') + + def validate(self, attrs): + user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True + ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + + if not user: + raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + elif not user.check_password(attrs['password']): + raise serializers.ValidationError({'details': ERROR_CODE['2031']}) + self.context.update({'user': user}) + return attrs + + def create(self, validated_data): + """ + used to return the user object after validation + """ + return self.context['user'] + + class SuperUserSerializer(serializers.ModelSerializer): """Super admin serializer""" user_type = serializers.SerializerMethodField('get_user_type') diff --git a/account/urls.py b/account/urls.py index ef3d026..02ac124 100644 --- a/account/urls.py +++ b/account/urls.py @@ -28,14 +28,15 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, - UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView) + UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView, + AdminLoginViewSet) """Router""" router = routers.SimpleRouter() """API End points with router""" router.register('user', UserLogin, basename='user') """super admin login""" -router.register('admin', UserLogin, basename='admin') +router.register('admin', AdminLoginViewSet, basename='admin') """google login end point""" router.register('google-login', GoogleLoginViewSet, basename='admin') router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp') diff --git a/account/views.py b/account/views.py index c95aa7e..61c4e0e 100644 --- a/account/views.py +++ b/account/views.py @@ -1,7 +1,7 @@ """Account view """ from notifications.utils import remove_fcm_token -"""Django import""" +# django imports from datetime import datetime, timedelta from rest_framework import viewsets, status, views from rest_framework.decorators import action @@ -18,19 +18,21 @@ import google.auth.transport.requests from rest_framework import status import requests from rest_framework.response import Response +from rest_framework import mixins from django.conf import settings -"""App Import""" + +# local imports from guardian.models import Guardian from junior.models import Junior from guardian.utils import upload_image_to_alibaba from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification from django.contrib.auth.models import User -"""Account serializer""" from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, - UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer) + UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer, + AdminLoginSerializer) from rest_framework_simplejwt.tokens import RefreshToken from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, ZOD, JUN, GRD @@ -346,6 +348,31 @@ class UserLogin(viewsets.ViewSet): data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'} return custom_response(None, data, response_status=status.HTTP_200_OK) + +class AdminLoginViewSet(viewsets.GenericViewSet): + """ + admin login api + """ + serializer_class = AdminLoginSerializer + + @action(methods=['post'], url_name='login', url_path='login', detail=False) + def admin_login(self, request, *args, **kwargs): + """ + :param request: + :return: + """ + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.save() + refresh = RefreshToken.for_user(user) + access_token = str(refresh.access_token) + refresh_token = str(refresh) + data = {"auth_token": access_token, "refresh_token": refresh_token, "username": user.username, + "email": user.email, "first_name": user.first_name, "last_name": user.last_name, + "is_active": user.is_active, "user_type": '3', "is_superuser": user.is_superuser} + return custom_response(None, data) + + class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer diff --git a/base/messages.py b/base/messages.py index e891ae1..2b57e4e 100644 --- a/base/messages.py +++ b/base/messages.py @@ -92,7 +92,8 @@ ERROR_CODE = { "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", - "2066": "Task does not exist or not in expired state" + "2066": "Task does not exist or not in expired state", + "2067": "Action not allowed. User type missing." } """Success message code""" SUCCESS_CODE = { diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 256248b..20184cd 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -13,7 +13,7 @@ from django.db.models import Q # local imports from account.utils import custom_response, custom_error_response from base.constants import USER_TYPE -from base.messages import SUCCESS_CODE +from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.permission import AdminPermission from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, UserManagementDetailSerializer, GuardianSerializer, @@ -36,6 +36,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, guardian_profile__isnull=True).order_by('date_joined') filter_backends = (SearchFilter,) search_fields = ['first_name', 'last_name'] + http_method_names = ['get', 'post', 'patch'] def get_queryset(self): if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): @@ -85,7 +86,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: success message """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: - return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() @@ -110,7 +111,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: success message """ if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: - return custom_error_response('Action not allowed', status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() From f377e283fd55e4026cb568d8fbe82dd4add5cea9 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:25:01 +0530 Subject: [PATCH 09/17] guardian list api --- base/messages.py | 3 ++- guardian/serializers.py | 35 ++++++++++++++++++++++++++++- guardian/views.py | 50 +++++++++++++++++------------------------ junior/serializers.py | 6 +++-- junior/views.py | 5 +++-- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/base/messages.py b/base/messages.py index 979d970..470c674 100644 --- a/base/messages.py +++ b/base/messages.py @@ -92,7 +92,8 @@ ERROR_CODE = { "2063": "Unauthorized access.", "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", - "2066": "Task does not exist or not in expired state" + "2066": "Task does not exist or not in expired state", + "2067": "No guardian associated with this junior" } """Success message code""" SUCCESS_CODE = { diff --git a/guardian/serializers.py b/guardian/serializers.py index db465ad..04acb9d 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -23,7 +23,7 @@ from account.utils import generate_code from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE from base.constants import NUMBER, JUN, ZOD, GRD -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from .utils import real_time, convert_timedelta_into_datetime, update_referral_points # notification's constant from notifications.constants import TASK_POINTS, TASK_REJECTED @@ -386,3 +386,36 @@ class ApproveTaskSerializer(serializers.ModelSerializer): junior_data.save() return junior_details +class GuardianDetailListSerializer(serializers.ModelSerializer): + """Guardian serializer""" + + first_name = serializers.SerializerMethodField('get_first_name') + last_name = serializers.SerializerMethodField('get_last_name') + email = serializers.SerializerMethodField('get_email') + image = serializers.SerializerMethodField('get_image') + guardian_id = serializers.SerializerMethodField('get_guardian_id') + + class Meta(object): + """Meta info""" + model = JuniorGuardianRelationship + fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'created_at', + 'updated_at'] + + def get_guardian_id(self,obj): + """first name of guardian""" + return obj.guardian.id + def get_first_name(self,obj): + """first name of guardian""" + return obj.guardian.user.first_name + + def get_last_name(self,obj): + """last name of guardian""" + return obj.guardian.user.last_name + + def get_email(self,obj): + """emailof guardian""" + return obj.guardian.user.email + + def get_image(self,obj): + """first name of guardian""" + return obj.guardian.image diff --git a/guardian/views.py b/guardian/views.py index 47fbdda..211d6ed 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -26,9 +26,10 @@ from django.utils import timezone # Import notification constant # Import send_notification function from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer, - TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer) + TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer, + GuardianDetailListSerializer) from .models import Guardian, JuniorTask -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email @@ -329,32 +330,21 @@ class ApproveTaskAPIView(viewsets.ViewSet): return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) class GuardianListAPIView(viewsets.ModelViewSet): - """Junior list of assosicated guardian""" - pass + """Guardian list of assosicated junior""" - # serializer_class = JuniorDetailListSerializer - # queryset = Junior.objects.all() - # permission_classes = [IsAuthenticated] - # filter_backends = (SearchFilter,) - # search_fields = ['auth__first_name', 'auth__last_name'] - # http_method_names = ('get',) - # - # def get_queryset(self): - # queryset = self.filter_queryset(self.queryset) - # return queryset - # - # def list(self, request, *args, **kwargs): - # """ junior list""" - # try: - # update_positions_based_on_points() - # guardian_data = Guardian.objects.filter(user__email=request.user).last() - # # fetch junior object - # if guardian_data: - # queryset = self.get_queryset() - # queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code)) - # # use JuniorDetailListSerializer serializer - # serializer = JuniorDetailListSerializer(queryset, many=True) - # return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - # return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) - # except Exception as e: - # return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + serializer_class = GuardianDetailListSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def list(self, request, *args, **kwargs): + """ junior list""" + try: + guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user) + # fetch junior object + if guardian_data: + # use GuardianDetailListSerializer serializer + serializer = GuardianDetailListSerializer(guardian_data, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2067'], response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/serializers.py b/junior/serializers.py index 316c1bf..1dc04da 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -89,7 +89,10 @@ class CreateJuniorSerializer(serializers.ModelSerializer): """condition for guardian code""" if guardian_code: junior.guardian_code = guardian_code - junior.dob = validated_data.get('dob',junior.dob) + guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last() + if guardian_data: + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) + junior.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) junior.country_name = validated_data.get('country_name', junior.country_name) """Update country code and phone number""" @@ -289,7 +292,6 @@ class AddJuniorSerializer(serializers.ModelSerializer): UserNotification.objects.get_or_create(user=user_data) """Notification email""" junior_notification_email(email, full_name, email, password) - junior_approval_mail(guardian, full_name) # push notification send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) return junior_data diff --git a/junior/views.py b/junior/views.py index 3f3cd5a..15cd387 100644 --- a/junior/views.py +++ b/junior/views.py @@ -27,7 +27,7 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) @@ -144,7 +144,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): class AddJuniorAPIView(viewsets.ModelViewSet): """Add Junior by guardian""" - queryset = Junior.objects.all() serializer_class = AddJuniorSerializer permission_classes = [IsAuthenticated] http_method_names = ('post',) @@ -172,6 +171,8 @@ class AddJuniorAPIView(viewsets.ModelViewSet): guardian = Guardian.objects.filter(user=self.request.user).first() junior.guardian_code = [guardian.guardian_code] junior.save() + JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, + relationship=str(self.request.data['relationship'])) return True From b3b499e661a73b45cc97c22382cff36efab0eb83 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:41:33 +0530 Subject: [PATCH 10/17] guardian list api --- base/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/messages.py b/base/messages.py index 470c674..9d20dfb 100644 --- a/base/messages.py +++ b/base/messages.py @@ -93,7 +93,7 @@ ERROR_CODE = { "2064": "To change your password first request an OTP and get it verify then change your password.", "2065": "Passwords do not match. Please try again.", "2066": "Task does not exist or not in expired state", - "2067": "No guardian associated with this junior" + "2068": "No guardian associated with this junior" } """Success message code""" SUCCESS_CODE = { From c20249730a0513ae1b5b6eff6ad9d751607b6b71 Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 13:42:47 +0530 Subject: [PATCH 11/17] guardian list api --- guardian/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardian/views.py b/guardian/views.py index 211d6ed..1fa4e1b 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE['2067'], response_status=status.HTTP_200_OK) + return custom_error_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) From f2cf1488e9dbf5c75c20a202cdb25445f0cf95d3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Mon, 7 Aug 2023 15:05:41 +0530 Subject: [PATCH 12/17] changes in edit api, included first name, last name and username --- .../serializers/user_management_serializer.py | 84 ++++++++++++++++--- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 9333155..4bb0709 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -97,14 +97,18 @@ class GuardianSerializer(serializers.ModelSerializer): guardian serializer """ name = serializers.SerializerMethodField() - email = serializers.CharField(required=False) + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + username = serializers.SerializerMethodField() + email = serializers.EmailField(required=False) class Meta: """ meta class """ model = Guardian - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', + 'is_active', 'country_name', 'image', 'email') def validate(self, attrs): """ @@ -137,25 +141,53 @@ class GuardianSerializer(serializers.ModelSerializer): @staticmethod def get_name(obj): """ - :param obj: user object + :param obj: guardian object :return: full name """ return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name + @staticmethod + def get_first_name(obj): + """ + :param obj: guardian object + :return: first name + """ + return obj.user.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: guardian object + :return: last name + """ + return obj.user.last_name + + @staticmethod + def get_username(obj): + """ + :param obj: guardian object + :return: email + """ + return obj.user.username + class JuniorSerializer(serializers.ModelSerializer): """ junior serializer """ name = serializers.SerializerMethodField() - email = serializers.CharField(required=False) + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + username = serializers.SerializerMethodField() + email = serializers.EmailField(required=False) class Meta: """ meta class """ model = Junior - fields = ('id', 'name', 'dob', 'gender', 'country_code', 'phone', 'is_active', 'country_name', 'image', 'email') + fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', + 'is_active', 'country_name', 'image', 'email') def validate(self, attrs): """ @@ -187,11 +219,35 @@ class JuniorSerializer(serializers.ModelSerializer): @staticmethod def get_name(obj): """ - :param obj: user object + :param obj: junior object :return: full name """ return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name + @staticmethod + def get_first_name(obj): + """ + :param obj: junior object + :return: first name + """ + return obj.auth.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: junior object + :return: last name + """ + return obj.auth.last_name + + @staticmethod + def get_username(obj): + """ + :param obj: junior object + :return: email + """ + return obj.auth.username + class UserManagementDetailSerializer(serializers.ModelSerializer): """ @@ -229,10 +285,14 @@ class UserManagementDetailSerializer(serializers.ModelSerializer): :return: associated user """ if profile := obj.guardian_profile.all().first(): - junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) - serializer = JuniorSerializer(junior, many=True) - return serializer.data + if profile.guardian_code: + junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) + serializer = JuniorSerializer(junior, many=True) + return serializer.data elif profile := obj.junior_profile.all().first(): - guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) - serializer = GuardianSerializer(guardian, many=True) - return serializer.data + if profile.guardian_code: + guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) + serializer = GuardianSerializer(guardian, many=True) + return serializer.data + else: + return None From ae0fc4fe8d02f2aff2f2594a3fb63ac041f3c25e Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 17:06:31 +0530 Subject: [PATCH 13/17] article list api --- notifications/serializers.py | 4 --- notifications/views.py | 13 +++------ web_admin/serializers/article_serializer.py | 19 ++++++++++++ web_admin/urls.py | 4 +-- web_admin/views/article.py | 32 ++++++++++++++++++++- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/notifications/serializers.py b/notifications/serializers.py index a085cf6..ff05dcd 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -41,7 +41,3 @@ class ReadNotificationSerializer(serializers.ModelSerializer): """Meta class""" model = Notification fields = ('id',) - def update(self, instance, validated_data): - instance.is_read = True - instance.save() - return instance diff --git a/notifications/views.py b/notifications/views.py index d5477b8..3d2ff4a 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -62,15 +62,10 @@ class ReadNotification(views.APIView): def put(self, request, format=None): try: - notification_id = str(self.request.data.get('notification_id')) - notification_queryset = Notification.objects.filter(id=notification_id, notification_to=self.request.user).last() + notification_id = self.request.data.get('notification_id') + notification_queryset = Notification.objects.filter(id__in=notification_id, + notification_to=self.request.user).update(is_read=True) if notification_queryset: - # use ReadNotificationSerializer serializer - serializer = ReadNotificationSerializer(notification_queryset, data=request.data, partial=True) - if serializer.is_valid(): - # save serializer - serializer.save() - return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 427f20d..5b42686 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -214,3 +214,22 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer): card_image = DefaultArticleCardImage.objects.create(**validated_data) return card_image + +class ArticleListSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_cards = ArticleCardSerializer(many=True) + total_points = serializers.SerializerMethodField('get_total_points') + + class Meta: + """ + meta class + """ + model = Article + fields = ('id', 'title', 'description', 'article_cards', 'total_points') + + def get_total_points(self, obj): + """total points of article""" + total_question = ArticleSurvey.objects.filter(article=obj).count() + return total_question * 5 diff --git a/web_admin/urls.py b/web_admin/urls.py index 30645a2..065d30d 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,7 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet +from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -16,7 +16,7 @@ router = routers.SimpleRouter() router.register('article', ArticleViewSet, basename='article') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('user-management', UserManagementViewSet, basename='user') - +router.register('article-list', ArticleListViewSet, basename='article-list') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 53da6e1..f334650 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -16,7 +16,7 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer) USER = get_user_model() @@ -195,3 +195,33 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m return custom_response(None, data=serializer.data) +class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): + """ + article api + """ + serializer_class = ArticleListSerializer + permission_classes = [IsAuthenticated,] + queryset = Article + http_method_names = ['get',] + + def get_queryset(self): + article = self.queryset.objects.filter(is_deleted=False).prefetch_related( + 'article_cards', 'article_survey', 'article_survey__options' + ).order_by('-created_at') + queryset = self.filter_queryset(article) + return queryset + + def list(self, request, *args, **kwargs): + """ + article list api method + :param request: + :param args: + :param kwargs: + :return: list of article + """ + queryset = self.get_queryset() + count = queryset.count() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, data=serializer.data, count=count) From f91724426530c10ecdb6322a47875069b6f1936d Mon Sep 17 00:00:00 2001 From: jain Date: Mon, 7 Aug 2023 19:12:14 +0530 Subject: [PATCH 14/17] junior article points table --- guardian/views.py | 2 +- junior/migrations/0019_juniorarticlepoints.py | 30 +++++++++++++++++++ junior/models.py | 22 +++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 junior/migrations/0019_juniorarticlepoints.py diff --git a/guardian/views.py b/guardian/views.py index 1fa4e1b..ee86199 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) + return custom_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/migrations/0019_juniorarticlepoints.py b/junior/migrations/0019_juniorarticlepoints.py new file mode 100644 index 0000000..1112702 --- /dev/null +++ b/junior/migrations/0019_juniorarticlepoints.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.2 on 2023-08-07 13:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_admin', '0004_alter_surveyoption_survey'), + ('junior', '0018_remove_junior_relationship_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='JuniorArticlePoints', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('earn_points', models.IntegerField(blank=True, default=5, null=True)), + ('is_attempt', models.BooleanField(default=False)), + ('is_answer_correct', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles', to='web_admin.article')), + ('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_details', to='junior.junior', verbose_name='Junior')), + ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='web_admin.articlesurvey')), + ('submitted_answer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submitted_answer', to='web_admin.surveyoption')), + ], + ), + ] diff --git a/junior/models.py b/junior/models.py index b18cbf5..908822c 100644 --- a/junior/models.py +++ b/junior/models.py @@ -9,7 +9,8 @@ from django.contrib.postgres.fields import ArrayField from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP # Import guardian's model from guardian.models import Guardian - +# Import web admin's model +from web_admin.models import SurveyOption, ArticleSurvey, Article """Define User model""" User = get_user_model() # Create your models here. @@ -137,3 +138,22 @@ class JuniorGuardianRelationship(models.Model): return f'{self.guardian.user}' +class JuniorArticlePoints(models.Model): + """ + Survey Options model + """ + # associated junior with the task + junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_details', verbose_name='Junior') + article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles') + question = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='questions') + submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.CASCADE, related_name='submitted_answer') + # earn points""" + earn_points = models.IntegerField(blank=True, null=True, default=5) + is_attempt = models.BooleanField(default=False) + is_answer_correct = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + """Return title""" + return f'{self.id} | {self.question}' From 659641467525ee06c6e2104f9d96788394cd97a3 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 11:46:27 +0530 Subject: [PATCH 15/17] guardian list api changes --- base/constants.py | 6 +++ guardian/serializers.py | 39 +++++++++++++++++-- guardian/views.py | 4 +- .../0020_junior_guardian_code_status.py | 18 +++++++++ junior/models.py | 5 ++- junior/serializers.py | 12 ++++-- web_admin/serializers/article_serializer.py | 7 +++- 7 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 junior/migrations/0020_junior_guardian_code_status.py diff --git a/base/constants.py b/base/constants.py index 6022f62..e38cb29 100644 --- a/base/constants.py +++ b/base/constants.py @@ -70,6 +70,12 @@ SIGNUP_METHODS = ( ('2', 'google'), ('3', 'apple') ) +# guardian code status +GUARDIAN_CODE_STATUS = ( + ('1', 'no guardian code'), + ('2', 'exist guardian code'), + ('3', 'request for guardian code') +) # relationship RELATIONSHIP = ( ('1', 'parent'), diff --git a/guardian/serializers.py b/guardian/serializers.py index 04acb9d..1fa385b 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -394,11 +394,20 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): email = serializers.SerializerMethodField('get_email') image = serializers.SerializerMethodField('get_image') guardian_id = serializers.SerializerMethodField('get_guardian_id') + guardian_code = serializers.SerializerMethodField('get_guardian_code') + gender = serializers.SerializerMethodField('get_gender') + phone = serializers.SerializerMethodField('get_phone') + country_name = serializers.SerializerMethodField('get_country_name') + dob = serializers.SerializerMethodField('get_dob') + guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status') + # code info + class Meta(object): """Meta info""" model = JuniorGuardianRelationship - fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'created_at', + fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'dob', + 'guardian_code', 'gender', 'phone', 'country_name', 'created_at', 'guardian_code_status', 'updated_at'] def get_guardian_id(self,obj): @@ -413,9 +422,33 @@ class GuardianDetailListSerializer(serializers.ModelSerializer): return obj.guardian.user.last_name def get_email(self,obj): - """emailof guardian""" + """email of guardian""" return obj.guardian.user.email def get_image(self,obj): - """first name of guardian""" + """guardian image""" return obj.guardian.image + + def get_guardian_code(self,obj): + """ guardian code""" + return obj.guardian.guardian_code + + def get_gender(self,obj): + """ guardian gender""" + return obj.guardian.gender + + def get_phone(self,obj): + """guardian phone""" + return obj.guardian.phone + + def get_country_name(self,obj): + """ guardian country name """ + return obj.guardian.country_name + + def get_dob(self,obj): + """guardian dob """ + return obj.guardian.dob + + def get_guardian_code_status(self,obj): + """guardian code status""" + return obj.junior.guardian_code_status diff --git a/guardian/views.py b/guardian/views.py index ee86199..775932a 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -34,7 +34,7 @@ from account.models import UserEmailOtp, UserNotification from .tasks import generate_otp from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import NUMBER +from base.constants import NUMBER, GUARDIAN_CODE_STATUS from .utils import upload_image_to_alibaba from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING from notifications.utils import send_notification @@ -345,6 +345,6 @@ class GuardianListAPIView(viewsets.ModelViewSet): # use GuardianDetailListSerializer serializer serializer = GuardianDetailListSerializer(guardian_data, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) - return custom_response(ERROR_CODE['2068'], response_status=status.HTTP_200_OK) + return custom_response({"status": GUARDIAN_CODE_STATUS['1']},response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/junior/migrations/0020_junior_guardian_code_status.py b/junior/migrations/0020_junior_guardian_code_status.py new file mode 100644 index 0000000..62e09d3 --- /dev/null +++ b/junior/migrations/0020_junior_guardian_code_status.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-08-08 05:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('junior', '0019_juniorarticlepoints'), + ] + + operations = [ + migrations.AddField( + model_name='junior', + name='guardian_code_status', + field=models.CharField(blank=True, choices=[('1', 'no guardian code'), ('2', 'exist guardian code'), ('3', 'request for guardian code')], default='1', max_length=31, null=True), + ), + ] diff --git a/junior/models.py b/junior/models.py index 908822c..3adf459 100644 --- a/junior/models.py +++ b/junior/models.py @@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model """Import ArrayField""" from django.contrib.postgres.fields import ArrayField """Import django app""" -from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP +from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP, GUARDIAN_CODE_STATUS # Import guardian's model from guardian.models import Guardian # Import web admin's model @@ -74,6 +74,9 @@ class Junior(models.Model): is_verified = models.BooleanField(default=False) """guardian code is approved or not""" guardian_code_approved = models.BooleanField(default=False) + # guardian code status""" + guardian_code_status = models.CharField(max_length=31, choices=GUARDIAN_CODE_STATUS, default='1', + null=True, blank=True) # Profile created and updated time""" created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/junior/serializers.py b/junior/serializers.py index 1dc04da..178ba14 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -12,7 +12,8 @@ from account.utils import send_otp_email, generate_code from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from guardian.tasks import generate_otp from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED +from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED, + GUARDIAN_CODE_STATUS) from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail @@ -280,7 +281,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): junior_code=generate_code(JUN, user_data.id), referral_code=generate_code(ZOD, user_data.id), referral_code_used=guardian_data.referral_code, - is_password_set=False, is_verified=True) + is_password_set=False, is_verified=True, + guardian_code_status=GUARDIAN_CODE_STATUS['2']) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) """Generate otp""" @@ -399,13 +401,15 @@ class AddGuardianSerializer(serializers.ModelSerializer): relationship = self.context['relationship'] full_name = self.context['first_name'] + ' ' + self.context['last_name'] junior_data = Junior.objects.filter(auth__username=junior).last() + junior_data.guardian_code_status = GUARDIAN_CODE_STATUS['3'] + junior_data.save() instance = User.objects.filter(username=email).last() if instance: guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True, - referral_code=generate_code(ZOD, + referral_code=generate_code(ZOD, instance.id), referral_code_used=junior_data.referral_code, - is_verified=True) + is_verified=True) UserNotification.objects.get_or_create(user=instance) return guardian_data else: diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 5b42686..c338d04 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -221,15 +221,20 @@ class ArticleListSerializer(serializers.ModelSerializer): """ article_cards = ArticleCardSerializer(many=True) total_points = serializers.SerializerMethodField('get_total_points') + is_completed = serializers.SerializerMethodField('get_is_completed') class Meta: """ meta class """ model = Article - fields = ('id', 'title', 'description', 'article_cards', 'total_points') + fields = ('id', 'title', 'description', 'article_cards', 'total_points', 'is_completed') def get_total_points(self, obj): """total points of article""" total_question = ArticleSurvey.objects.filter(article=obj).count() return total_question * 5 + + def get_is_completed(self, obj): + """complete all question""" + return False \ No newline at end of file From f57b111555aaca2e4c259c5bc53b45de2b534424 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 11:48:53 +0530 Subject: [PATCH 16/17] guardian list api changes --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index c338d04..695401e 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -233,7 +233,7 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_total_points(self, obj): """total points of article""" total_question = ArticleSurvey.objects.filter(article=obj).count() - return total_question * 5 + return total_question * NUMBER['five'] def get_is_completed(self, obj): """complete all question""" From 85e4ae876135775e1ac48424384cba335e40c0d0 Mon Sep 17 00:00:00 2001 From: jain Date: Tue, 8 Aug 2023 13:54:04 +0530 Subject: [PATCH 17/17] junior position changes and decrease api response time --- account/views.py | 9 ---- guardian/serializers.py | 6 ++- guardian/views.py | 3 -- junior/admin.py | 6 ++- junior/urls.py | 4 +- junior/views.py | 49 +++++++++++++++++---- web_admin/models.py | 4 +- web_admin/serializers/article_serializer.py | 2 +- 8 files changed, 55 insertions(+), 28 deletions(-) diff --git a/account/views.py b/account/views.py index 61c4e0e..b36d75d 100644 --- a/account/views.py +++ b/account/views.py @@ -245,7 +245,6 @@ class ForgotPasswordAPIView(views.APIView): class SendPhoneOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def create(self, request, *args, **kwargs): otp = generate_otp() @@ -262,7 +261,6 @@ class SendPhoneOtp(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserPhoneOtp.objects.all() serializer_class = UserPhoneOtpSerializer def list(self, request, *args, **kwargs): try: @@ -376,7 +374,6 @@ class AdminLoginViewSet(viewsets.GenericViewSet): class UserEmailVerification(viewsets.ModelViewSet): """User Email verification""" serializer_class = EmailVerificationSerializer - queryset = UserEmailOtp.objects.all() def list(self, request, *args, **kwargs): try: @@ -419,7 +416,6 @@ class UserEmailVerification(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet): """Send otp on phone""" - queryset = UserEmailOtp.objects.all() serializer_class = EmailVerificationSerializer permission_classes = [IsAuthenticated] @@ -442,7 +438,6 @@ class ReSendEmailOtp(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = User.objects.all() serializer_class = JuniorProfileSerializer permission_classes = [IsAuthenticated] @@ -461,7 +456,6 @@ class ProfileAPIViewSet(viewsets.ModelViewSet): class UploadImageAPIViewSet(viewsets.ModelViewSet): """upload task image""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesSerializer def create(self, request, *args, **kwargs): """upload images""" @@ -480,7 +474,6 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet): class DefaultImageAPIViewSet(viewsets.ModelViewSet): """Profile viewset""" - queryset = DefaultTaskImages.objects.all() serializer_class = DefaultTaskImagesDetailsSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -511,7 +504,6 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): class UserNotificationAPIViewSet(viewsets.ModelViewSet): """notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UserNotificationSerializer permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -523,7 +515,6 @@ class UserNotificationAPIViewSet(viewsets.ModelViewSet): class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): """Update notification viewset""" - queryset = UserNotification.objects.all() serializer_class = UpdateUserNotificationSerializer permission_classes = [IsAuthenticated] diff --git a/guardian/serializers.py b/guardian/serializers.py index 1fa385b..65dd1ea 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -67,8 +67,10 @@ class UserSerializer(serializers.ModelSerializer): UserNotification.objects.get_or_create(user=user) if user_type == str(NUMBER['one']): # create junior profile - Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), - referral_code=generate_code(ZOD, user.id)) + junior = Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), + referral_code=generate_code(ZOD, user.id)) + position = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior, position=position) if user_type == str(NUMBER['two']): # create guardian profile Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id), diff --git a/guardian/views.py b/guardian/views.py index 775932a..9b55cf6 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -83,7 +83,6 @@ class SignupViewset(viewsets.ModelViewSet): class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" - queryset = Guardian.objects.all() serializer_class = CreateGuardianSerializer permission_classes = [IsAuthenticated] @@ -117,7 +116,6 @@ class UpdateGuardianProfile(viewsets.ViewSet): class AllTaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" serializer_class = TaskDetailsSerializer - queryset = JuniorTask.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -229,7 +227,6 @@ class TopJuniorListAPIView(viewsets.ModelViewSet): """Top juniors list""" serializer_class = TopJuniorSerializer permission_classes = [IsAuthenticated] - queryset = JuniorPoints.objects.all() def get_serializer_context(self): # context list diff --git a/junior/admin.py b/junior/admin.py index acd8734..5fdfcce 100644 --- a/junior/admin.py +++ b/junior/admin.py @@ -2,7 +2,7 @@ """Third party Django app""" from django.contrib import admin """Import Django app""" -from .models import Junior, JuniorPoints, JuniorGuardianRelationship +from .models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints # Register your models here. @admin.register(Junior) class JuniorAdmin(admin.ModelAdmin): @@ -27,3 +27,7 @@ class JuniorGuardianRelationshipAdmin(admin.ModelAdmin): """Junior Admin""" list_display = ['guardian', 'junior', 'relationship'] +@admin.register(JuniorArticlePoints) +class JuniorArticlePointsAdmin(admin.ModelAdmin): + """Junior Admin""" + list_display = ['junior', 'article', 'question', 'submitted_answer'] diff --git a/junior/urls.py b/junior/urls.py index f66fa30..267faef 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,7 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView) """Third party import""" from rest_framework import routers @@ -41,6 +41,8 @@ router.register('junior-points', JuniorPointsListAPIView, basename='junior-point router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') # invite guardian API""" router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') +# start article""" +router.register('start-article', StartArticleAPIView, basename='start-article') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/views.py b/junior/views.py index 15cd387..449ac5f 100644 --- a/junior/views.py +++ b/junior/views.py @@ -27,8 +27,8 @@ import requests # Import upload_image_to_alibaba # Import custom_response, custom_error_response # Import constants -from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship -from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ +from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints +from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) from guardian.models import Guardian, JuniorTask @@ -40,7 +40,7 @@ from guardian.utils import upload_image_to_alibaba from .utils import update_positions_based_on_points from notifications.utils import send_notification from notifications.constants import REMOVE_JUNIOR - +from web_admin.models import Article, ArticleSurvey, SurveyOption """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -59,7 +59,6 @@ from notifications.constants import REMOVE_JUNIOR # Create your views here. class UpdateJuniorProfile(viewsets.ViewSet): """Update junior profile""" - queryset = Junior.objects.all() serializer_class = CreateJuniorSerializer permission_classes = [IsAuthenticated] @@ -93,7 +92,6 @@ class UpdateJuniorProfile(viewsets.ViewSet): class ValidateGuardianCode(viewsets.ViewSet): """Check guardian code exist or not""" - queryset = Guardian.objects.all() permission_classes = [IsAuthenticated] def list(self, request, *args, **kwargs): @@ -115,7 +113,6 @@ class JuniorListAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) search_fields = ['auth__first_name', 'auth__last_name'] @@ -180,7 +177,6 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet): """Junior list of assosicated guardian""" serializer_class = JuniorDetailListSerializer - queryset = Junior.objects.all() permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination http_method_names = ('get',) @@ -210,7 +206,6 @@ class FilterJuniorAPIView(viewsets.ModelViewSet): serializer_class = JuniorDetailListSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = Junior.objects.all() http_method_names = ('get',) def get_queryset(self): @@ -268,7 +263,6 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): serializer_class = TaskDetailsjuniorSerializer permission_classes = [IsAuthenticated] pagination_class = PageNumberPagination - queryset = JuniorTask.objects.all() http_method_names = ('get',) def list(self, request, *args, **kwargs): @@ -458,3 +452,40 @@ class ReAssignJuniorTaskAPIView(views.APIView): return custom_error_response(ERROR_CODE['2066'], response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class StartArticleAPIView(viewsets.ModelViewSet): + """Start article""" + serializer_class = AddGuardianSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('post',) + + def create(self, request, *args, **kwargs): + """ junior list""" + try: + junior_instance = Junior.objects.filter(auth=self.request.user).last() + article_id = request.data.get('article_id') + article_data = Article.objects.filter(id=article_id).last() + if article_data: + print("article_data====>", article_data,'===>',type(article_data)) + question_query = ArticleSurvey.objects.filter(article=article_id) + print("question_query====>",question_query,'===>',type(question_query)) + for question in question_query: + print("question====>", question, '===>', type(question)) + answer = SurveyOption.objects.filter(survey=question) + print("answer===>",answer,'====>',type(answer)) + for ans in answer: + JuniorArticlePoints.objects.create(junior=junior_instance, + article=article_data, + question=question, + submitted_answer=ans) + print("CREATED CREATED") + return custom_response(SUCCESS_CODE['3034'], response_status=status.HTTP_200_OK) + # use AddJuniorSerializer serializer + # serializer = AddGuardianSerializer(data=request.data, context=info) + # if serializer.is_valid(): + # # save serializer + # serializer.save() + # return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK) + # return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/models.py b/web_admin/models.py index 8a3bb16..5dbef97 100644 --- a/web_admin/models.py +++ b/web_admin/models.py @@ -49,7 +49,7 @@ class ArticleSurvey(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.article}' + return f'{self.id} | {self.question}' class SurveyOption(models.Model): @@ -64,7 +64,7 @@ class SurveyOption(models.Model): def __str__(self): """Return title""" - return f'{self.id} | {self.survey}' + return f'{self.id} | {self.option}' class DefaultArticleCardImage(models.Model): diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 695401e..b39d20d 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -237,4 +237,4 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" - return False \ No newline at end of file + return False