From af8ea39200ac498481731b57e02322a7ea552070 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 13:35:12 +0530 Subject: [PATCH 1/5] leaderboard ranking --- celerybeat-schedule | Bin 16384 -> 16384 bytes web_admin/serializers/analytics_serializer.py | 50 ++++++++++++++++++ web_admin/views/analytics.py | 36 +++++++++++-- web_admin/views/user_management.py | 2 +- zod_bank/settings.py | 25 +++++++++ 5 files changed, 109 insertions(+), 4 deletions(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 4a51b891af30ec5d0a7f9b91777d2f6b8b1a45d1..f2510fc7dfb09da6514820cff292233776337c68 100644 GIT binary patch delta 33 pcmZo@U~Fh$+_2GzgM*t@UXD?{c=Bz7Kt|Tdx`vvYO^na*005(w36B5( delta 33 pcmZo@U~Fh$+_2GzgI$KjRG8~~^yJ$Hfs9O(bqzH)n;4(r0RXa_3TFTS diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 5b653a6..34316b7 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -1,3 +1,53 @@ """ web_admin analytics serializer file """ +from rest_framework import serializers + +from junior.models import JuniorPoints, Junior +from web_admin.serializers.user_management_serializer import JuniorSerializer + + +class JuniorLeaderboardSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = Junior + fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image') + + @staticmethod + def get_name(obj): + """ + :param obj: junior object + :return: full name + """ + return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name + + @staticmethod + def get_first_name(obj): + """ + :param obj: junior object + :return: first name + """ + return obj.auth.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: junior object + :return: last name + """ + return obj.auth.last_name + + +class LeaderboardSerializer(serializers.ModelSerializer): + junior = JuniorLeaderboardSerializer() + rank = serializers.IntegerField() + + class Meta: + model = JuniorPoints + fields = ('total_points', 'rank', 'junior') diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index f769c18..688e445 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -7,12 +7,16 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from django.contrib.auth import get_user_model from django.db.models import Q -from django.db.models import Count +from django.db.models import Count, OuterRef, Subquery, Sum from django.db.models.functions import TruncDate +from django.db.models import F, Window +from django.db.models.functions.window import Rank from account.utils import custom_response from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED from guardian.models import JuniorTask +from junior.models import JuniorPoints +from web_admin.serializers.analytics_serializer import LeaderboardSerializer USER = get_user_model() @@ -59,7 +63,11 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='new-signups', url_path='new-signups', detail=False) def new_signups(self, request, *args, **kwargs): - + """ + api method to get new signups + :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :return: + """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) @@ -76,7 +84,11 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False) def assign_tasks_report(self, request, *args, **kwargs): - + """ + api method to get assign tasks + :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :return: + """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) @@ -96,3 +108,21 @@ class AnalyticsViewSet(GenericViewSet): } return custom_response(None, data) + + @action(methods=['get'], url_name='junior-leaderboard', url_path='junior-leaderboard', detail=False, + serializer_class=LeaderboardSerializer) + def junior_leaderboard(self, request): + """ + + :param request: + :return: + """ + # queryset = JuniorPoints.objects.all().order_by('-total_points', 'junior__created_at') + queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + expression=Rank(), + order_by=[F('total_points').desc(), 'junior__created_at'] + )).order_by('-total_points', 'junior__created_at') + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index c16b62a..8f53a73 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -34,7 +34,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, is_superuser=False).prefetch_related('guardian_profile', 'junior_profile' ).exclude(junior_profile__isnull=True, - guardian_profile__isnull=True).order_by('date_joined') + guardian_profile__isnull=True).order_by('-date_joined') filter_backends = (SearchFilter,) search_fields = ['first_name', 'last_name'] http_method_names = ['get', 'post', 'patch'] diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 3781f89..2d003c1 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -177,6 +177,31 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +LOGGING = { + "version": 1, + "filters": { + "require_debug_true": { + "()": "django.utils.log.RequireDebugTrue" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "filters": [ + "require_debug_true" + ], + "class": "logging.StreamHandler" + } + }, + "loggers": { + "django.db.backends": { + "level": "DEBUG", + "handlers": [ + "console" + ] + } + } +} # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ From 69c19cf09714846ff70232106ab6173b2002ce6a Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 14:40:49 +0530 Subject: [PATCH 2/5] sonar issues --- account/serializers.py | 3 +- account/views.py | 3 +- base/constants.py | 2 ++ guardian/views.py | 32 ----------------- junior/serializers.py | 1 - web_admin/serializers/analytics_serializer.py | 1 - web_admin/views/analytics.py | 34 +++++++++++-------- 7 files changed, 26 insertions(+), 50 deletions(-) diff --git a/account/serializers.py b/account/serializers.py index 4065b5c..edf4de9 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -152,7 +152,8 @@ class AdminLoginSerializer(serializers.ModelSerializer): def validate(self, attrs): user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True - ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + ).only('id', 'first_name', 'last_name', 'email', + 'username', 'is_active', 'is_superuser').first() if not user or not user.check_password(attrs['password']): raise serializers.ValidationError({'details': ERROR_CODE['2002']}) diff --git a/account/views.py b/account/views.py index fca583b..8c2932d 100644 --- a/account/views.py +++ b/account/views.py @@ -334,7 +334,8 @@ class UserLogin(viewsets.ViewSet): email = request.data.get('email') password = request.data.get('password') user = User.objects.filter(email__iexact=email, is_superuser=True - ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() + ).only('id', 'first_name', 'last_name', 'email', + 'username', 'is_active', 'is_superuser').first() if not user or not user.check_password(password): return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) diff --git a/base/constants.py b/base/constants.py index 36079b9..05fa8b8 100644 --- a/base/constants.py +++ b/base/constants.py @@ -122,3 +122,5 @@ MAX_ARTICLE_SURVEY = 10 Already_register_user = "duplicate key value violates unique constraint" ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' + +DATE_FORMAT = '%Y-%m-%d' diff --git a/guardian/views.py b/guardian/views.py index 2194f4b..3948e6f 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -124,38 +124,6 @@ class AllTaskListAPIView(viewsets.ModelViewSet): serializer = TaskDetailsSerializer(queryset, many=True) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) -# class TaskListAPIView(viewsets.ModelViewSet): -# """Update guardian profile""" -# serializer_class = TaskDetailsSerializer -# permission_classes = [IsAuthenticated] -# pagination_class = PageNumberPagination -# http_method_names = ('get',) -# -# def list(self, request, *args, **kwargs): -# """Create guardian profile""" -# try: -# status_value = self.request.GET.get('status') -# search = self.request.GET.get('search') -# if search and str(status_value) == '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user, -# task_name__icontains=search).order_by('due_date', 'created_at') -# elif search and str(status_value) != '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search, -# task_status=status_value).order_by('due_date', 'created_at') -# if search is None and str(status_value) == '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at') -# elif search is None and str(status_value) != '0': -# queryset = JuniorTask.objects.filter(guardian__user=request.user, -# task_status=status_value).order_by('due_date','created_at') -# paginator = self.pagination_class() -# # use Pagination -# paginated_queryset = paginator.paginate_queryset(queryset, request) -# # use TaskDetailsSerializer serializer -# serializer = TaskDetailsSerializer(paginated_queryset, many=True) -# return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) -# except Exception as e: -# return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) - class TaskListAPIView(viewsets.ModelViewSet): """Update guardian profile""" diff --git a/junior/serializers.py b/junior/serializers.py index 57a821b..59fbe07 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -333,7 +333,6 @@ class CompleteTaskSerializer(serializers.ModelSerializer): fields = ('id', 'image') def update(self, instance, validated_data): instance.image = validated_data.get('image', instance.image) - # instance.requested_on = real_time() instance.requested_on = timezone.now().astimezone(pytz.utc) instance.task_status = str(NUMBER['four']) instance.is_approved = False diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 34316b7..fd85118 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -4,7 +4,6 @@ web_admin analytics serializer file from rest_framework import serializers from junior.models import JuniorPoints, Junior -from web_admin.serializers.user_management_serializer import JuniorSerializer class JuniorLeaderboardSerializer(serializers.ModelSerializer): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 688e445..0133339 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -7,13 +7,13 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from django.contrib.auth import get_user_model from django.db.models import Q -from django.db.models import Count, OuterRef, Subquery, Sum +from django.db.models import Count from django.db.models.functions import TruncDate from django.db.models import F, Window from django.db.models.functions.window import Rank from account.utils import custom_response -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT from guardian.models import JuniorTask from junior.models import JuniorPoints from web_admin.serializers.analytics_serializer import LeaderboardSerializer @@ -24,6 +24,10 @@ USER = get_user_model() class AnalyticsViewSet(GenericViewSet): """ analytics api view + to get user report (active users, guardians and juniors counts) + to get new user sign up report + to get task report (completed, in-progress, requested and rejected tasks count) + to get junior leaderboard and ranking """ serializer_class = None @@ -41,7 +45,8 @@ class AnalyticsViewSet(GenericViewSet): def total_users_count(self, request, *args, **kwargs): """ api method to get total users, guardians and juniors - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :param request: start_date: date format (yyyy-mm-dd) + :param request: end_date: date format (yyyy-mm-dd) :return: """ @@ -49,8 +54,8 @@ class AnalyticsViewSet(GenericViewSet): start_date = end_date - datetime.timedelta(days=6) if request.query_params.get('start_date') and request.query_params.get('end_date'): - start_date = datetime.datetime.strptime(request.query_params.get('start_date'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT) + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT) user_qs = self.get_queryset() queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1)))) @@ -65,15 +70,16 @@ class AnalyticsViewSet(GenericViewSet): def new_signups(self, request, *args, **kwargs): """ api method to get new signups - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + :param request: start_date: date format (yyyy-mm-dd) + :param request: end_date: date format (yyyy-mm-dd) :return: """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) if request.query_params.get('start_date') and request.query_params.get('end_date'): - start_date = datetime.datetime.strptime(request.query_params.get('start_date'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT) + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT) user_qs = self.get_queryset() signup_data = user_qs.filter(date_joined__range=[start_date, (end_date + datetime.timedelta(days=1))] @@ -85,16 +91,17 @@ class AnalyticsViewSet(GenericViewSet): @action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False) def assign_tasks_report(self, request, *args, **kwargs): """ - api method to get assign tasks - :param request: query params {start_date and end_date}, date format (yyyy-mm-dd) + api method to get assign tasks count for (completed, in-progress, requested and rejected) task + :param request: start_date: date format (yyyy-mm-dd) + :param request: end_date: date format (yyyy-mm-dd) :return: """ end_date = datetime.date.today() start_date = end_date - datetime.timedelta(days=6) if request.query_params.get('start_date') and request.query_params.get('end_date'): - start_date = datetime.datetime.strptime(request.query_params.get('start_date'), '%Y-%m-%d') - end_date = datetime.datetime.strptime(request.query_params.get('end_date'), '%Y-%m-%d') + start_date = datetime.datetime.strptime(request.query_params.get('start_date'), DATE_FORMAT) + end_date = datetime.datetime.strptime(request.query_params.get('end_date'), DATE_FORMAT) assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] @@ -113,11 +120,10 @@ class AnalyticsViewSet(GenericViewSet): serializer_class=LeaderboardSerializer) def junior_leaderboard(self, request): """ - + to get junior leaderboard and rank :param request: :return: """ - # queryset = JuniorPoints.objects.all().order_by('-total_points', 'junior__created_at') queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] From 54200dba5276990c9771e05d8200a7de28b38f8b Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 15:39:25 +0530 Subject: [PATCH 3/5] is complete article key --- web_admin/serializers/article_serializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index c58f97a..f8c7f67 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -240,7 +240,7 @@ class ArticleListSerializer(serializers.ModelSerializer): """complete all question""" junior_article = JuniorArticle.objects.filter(article=obj).last() if junior_article: - junior_article.is_completed + return junior_article.is_completed return False class ArticleQuestionSerializer(serializers.ModelSerializer): From d5c30f2029d6ed4e6529c1e81f1fd8104c9ccac3 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Fri, 11 Aug 2023 15:54:54 +0530 Subject: [PATCH 4/5] added pagination in leaderboard admin --- web_admin/pagination.py | 13 +++++++++++++ web_admin/views/analytics.py | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 web_admin/pagination.py diff --git a/web_admin/pagination.py b/web_admin/pagination.py new file mode 100644 index 0000000..6a6aff4 --- /dev/null +++ b/web_admin/pagination.py @@ -0,0 +1,13 @@ +""" +web_admin pagination file +""" +from rest_framework.pagination import PageNumberPagination + + +class CustomPageNumberPagination(PageNumberPagination): + """ + custom paginator class + """ + page_size = 10 # Set the desired page size + page_size_query_param = 'page_size' + max_page_size = 100 # Set a maximum page size if needed diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 688e445..e8a8f5c 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -1,10 +1,15 @@ """ web_admin analytics view file """ +# python imports import datetime +# third party imports from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +# django imports from django.contrib.auth import get_user_model from django.db.models import Q from django.db.models import Count, OuterRef, Subquery, Sum @@ -12,10 +17,13 @@ from django.db.models.functions import TruncDate from django.db.models import F, Window from django.db.models.functions.window import Rank +# local imports from account.utils import custom_response from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED from guardian.models import JuniorTask from junior.models import JuniorPoints +from web_admin.pagination import CustomPageNumberPagination +from web_admin.permission import AdminPermission from web_admin.serializers.analytics_serializer import LeaderboardSerializer USER = get_user_model() @@ -26,6 +34,7 @@ class AnalyticsViewSet(GenericViewSet): analytics api view """ serializer_class = None + permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -122,7 +131,7 @@ class AnalyticsViewSet(GenericViewSet): expression=Rank(), order_by=[F('total_points').desc(), 'junior__created_at'] )).order_by('-total_points', 'junior__created_at') - paginator = self.pagination_class() + paginator = CustomPageNumberPagination() paginated_queryset = paginator.paginate_queryset(queryset, request) serializer = self.serializer_class(paginated_queryset, many=True) return custom_response(None, serializer.data) From 082f93ff9d135d39d0f868d202cb231699c168e0 Mon Sep 17 00:00:00 2001 From: jain Date: Fri, 11 Aug 2023 16:54:27 +0530 Subject: [PATCH 5/5] guardian code chnages --- junior/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/junior/serializers.py b/junior/serializers.py index 57a821b..3101886 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -503,6 +503,7 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer): model = Junior fields = ('id', ) def update(self, instance, validated_data): + instance.guardian_code = None instance.guardian_code_status = str(NUMBER['one']) instance.save() return instance