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/celerybeat-schedule b/celerybeat-schedule index 4a51b89..f2510fc 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ 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..f102972 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 @@ -503,6 +502,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 diff --git a/web_admin/pagination.py b/web_admin/pagination.py new file mode 100644 index 0000000..6a6aff4 --- /dev/null +++ b/web_admin/pagination.py @@ -0,0 +1,13 @@ +""" +web_admin pagination file +""" +from rest_framework.pagination import PageNumberPagination + + +class CustomPageNumberPagination(PageNumberPagination): + """ + custom paginator class + """ + page_size = 10 # Set the desired page size + page_size_query_param = 'page_size' + max_page_size = 100 # Set a maximum page size if needed diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 5b653a6..fd85118 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -1,3 +1,52 @@ """ web_admin analytics serializer file """ +from rest_framework import serializers + +from junior.models import JuniorPoints, Junior + + +class JuniorLeaderboardSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + first_name = serializers.SerializerMethodField() + last_name = serializers.SerializerMethodField() + + class Meta: + """ + meta class + """ + model = Junior + fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image') + + @staticmethod + def get_name(obj): + """ + :param obj: junior object + :return: full name + """ + return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name + + @staticmethod + def get_first_name(obj): + """ + :param obj: junior object + :return: first name + """ + return obj.auth.first_name + + @staticmethod + def get_last_name(obj): + """ + :param obj: junior object + :return: last name + """ + return obj.auth.last_name + + +class LeaderboardSerializer(serializers.ModelSerializer): + junior = JuniorLeaderboardSerializer() + rank = serializers.IntegerField() + + class Meta: + model = JuniorPoints + fields = ('total_points', 'rank', 'junior') diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 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): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index f769c18..8c21cb3 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -1,18 +1,30 @@ """ web_admin analytics view file """ +# python imports import datetime +# third party imports from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +# django imports from django.contrib.auth import get_user_model from django.db.models import Q from django.db.models import Count from django.db.models.functions import TruncDate +from django.db.models import F, Window +from django.db.models.functions.window import Rank +# local imports from account.utils import custom_response -from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED +from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT from guardian.models import JuniorTask +from junior.models import JuniorPoints +from web_admin.pagination import CustomPageNumberPagination +from web_admin.permission import AdminPermission +from web_admin.serializers.analytics_serializer import LeaderboardSerializer USER = get_user_model() @@ -20,8 +32,13 @@ USER = get_user_model() class AnalyticsViewSet(GenericViewSet): """ analytics api view + to get user report (active users, guardians and juniors counts) + to get new user sign up report + to get task report (completed, in-progress, requested and rejected tasks count) + to get junior leaderboard and ranking """ serializer_class = None + permission_classes = [IsAuthenticated, AdminPermission] def get_queryset(self): user_qs = USER.objects.filter( @@ -37,7 +54,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: """ @@ -45,8 +63,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)))) @@ -59,13 +77,18 @@ 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: 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))] @@ -76,13 +99,18 @@ 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 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))] @@ -96,3 +124,20 @@ 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): + """ + to get junior leaderboard and rank + :param request: + :return: + """ + queryset = JuniorPoints.objects.prefetch_related('junior', 'junior__auth').annotate(rank=Window( + expression=Rank(), + order_by=[F('total_points').desc(), 'junior__created_at'] + )).order_by('-total_points', 'junior__created_at') + paginator = CustomPageNumberPagination() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, many=True) + return custom_response(None, serializer.data) diff --git a/web_admin/views/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/