diff --git a/account/serializers.py b/account/serializers.py index 284ae09..5686931 100644 --- a/account/serializers.py +++ b/account/serializers.py @@ -155,9 +155,9 @@ class AdminLoginSerializer(serializers.ModelSerializer): ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() if not user: - raise serializers.ValidationError({'details': ERROR_CODE['2063']}) + raise serializers.ValidationError({'details': ERROR_CODE['2002']}) elif not user.check_password(attrs['password']): - raise serializers.ValidationError({'details': ERROR_CODE['2031']}) + raise serializers.ValidationError({'details': ERROR_CODE['2002']}) self.context.update({'user': user}) return attrs diff --git a/account/views.py b/account/views.py index 0f9747e..8b25973 100644 --- a/account/views.py +++ b/account/views.py @@ -333,9 +333,9 @@ class UserLogin(viewsets.ViewSet): ).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() if not user: - return custom_error_response(ERROR_CODE["2063"], response_status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) elif not user.check_password(password): - return custom_error_response(ERROR_CODE["2031"], response_status=status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) serializer = SuperUserSerializer(user) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) diff --git a/base/constants.py b/base/constants.py index e38cb29..7efdb8b 100644 --- a/base/constants.py +++ b/base/constants.py @@ -112,7 +112,7 @@ MAX_ARTICLE_CARD = 6 MIN_ARTICLE_SURVEY = 5 MAX_ARTICLE_SURVEY = 10 -# real time url -time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" +# already register +Already_register_user = "duplicate key value violates unique constraint" ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' diff --git a/base/messages.py b/base/messages.py index 05b4ec3..88982ba 100644 --- a/base/messages.py +++ b/base/messages.py @@ -23,15 +23,15 @@ ERROR_CODE_REQUIRED = { # Error code ERROR_CODE = { - "2000": "Email not found.", + "2000": "Invalid email address. Please enter a registered email.", "2001": "This is your existing password. Please choose other one", - "2002": "Invalid login credentials.", + "2002": "Invalid username or password.", "2003": "An account already exists with this email address.", "2004": "User not found.", "2005": "Your account has been activated.", "2006": "Your account is not activated.", "2007": "Your account already activated.", - "2008": "Invalid OTP.", + "2008": "The OTP entered is not correct.", "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.", "2010": "Invalid Link.", "2011": "Your profile has not been completed yet.", @@ -54,7 +54,7 @@ ERROR_CODE = { "2026": "New password should not same as old password", "2027": "data should contain `identityToken`", "2028": "You are not authorized person to sign up on this platform", - "2029": "Validity of otp verification is expired", + "2029": "Validity of otp verification has expired. Please request a new one.", "2030": "Use correct user type and token", # invalid password "2031": "Invalid password", @@ -111,7 +111,7 @@ SUCCESS_CODE = { # Success code for link verified "3005": "Your account is deleted successfully.", # Success code for password reset - "3006": "Your password has been reset successfully.", + "3006": "Password reset successful. You can now log in with your new password.", # Success code for password update "3007": "Your password has been changed successfully.", # Success code for valid link @@ -124,8 +124,8 @@ SUCCESS_CODE = { "3012": "Phone OTP Verified successfully", "3013": "Valid Guardian code", "3014": "Password has been updated successfully.", - "3015": "Verification code sent on your email.", - "3016": "Send otp on your Email successfully", + "3015": "Verification code has been sent on your email.", + "3016": "An OTP has been sent on your email.", "3017": "Profile image update successfully", "3018": "Task created successfully", "3019": "Support Email sent successfully", diff --git a/guardian/serializers.py b/guardian/serializers.py index 27ab351..0a8b625 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -23,7 +23,7 @@ from account.models import UserProfile, UserEmailOtp, UserNotification from account.utils import generate_code from junior.serializers import JuniorDetailSerializer from base.messages import ERROR_CODE, SUCCESS_CODE -from base.constants import NUMBER, JUN, ZOD, GRD +from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from .utils import real_time, convert_timedelta_into_datetime, update_referral_points # notification's constant @@ -84,7 +84,7 @@ class UserSerializer(serializers.ModelSerializer): otp_verified = False if otp and otp.is_verified: otp_verified = True - raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":bool(otp_verified), + raise serializers.ValidationError({"details": ERROR_CODE['2021'], "otp_verified":bool(otp_verified), "code": 400, "status":"failed", }) diff --git a/guardian/utils.py b/guardian/utils.py index 9a6ae05..57e8080 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -11,7 +11,7 @@ import tempfile # Import date time module's function from datetime import datetime, time # import Number constant -from base.constants import NUMBER, time_url +from base.constants import NUMBER # Import Junior's model from junior.models import Junior, JuniorPoints # Import guardian's model diff --git a/guardian/views.py b/guardian/views.py index 4645c7c..8d38395 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -10,8 +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 rest_framework.filters import SearchFilter from django.utils import timezone # Import guardian's model, @@ -59,29 +59,26 @@ class SignupViewset(viewsets.ModelViewSet): serializer_class = UserSerializer def create(self, request, *args, **kwargs): """Create user profile""" - try: - if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]: - serializer = UserSerializer(context=request.data['user_type'], data=request.data) - if serializer.is_valid(): - user = serializer.save() - """Generate otp""" - otp = generate_otp() - # expire otp after 1 day - expiry = OTP_EXPIRY - # create user email otp object - UserEmailOtp.objects.create(email=request.data['email'], otp=otp, - user_type=str(request.data['user_type']), expired_at=expiry) - """Send email to the register user""" - send_otp_email(request.data['email'], otp) - # send push notification for registration - send_notification.delay(REGISTRATION, None, user.id, {}) - return custom_response(SUCCESS_CODE['3001'], - response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) - else: - return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST) - except Exception as e: - return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]: + serializer = UserSerializer(context=request.data['user_type'], data=request.data) + if serializer.is_valid(): + user = serializer.save() + """Generate otp""" + otp = generate_otp() + # expire otp after 1 day + expiry = OTP_EXPIRY + # create user email otp object + UserEmailOtp.objects.create(email=request.data['email'], otp=otp, + user_type=str(request.data['user_type']), expired_at=expiry) + """Send email to the register user""" + send_otp_email(request.data['email'], otp) + # send push notification for registration + send_notification.delay(REGISTRATION, None, user.id, {}) + return custom_response(SUCCESS_CODE['3001'], + response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + else: + return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST) class UpdateGuardianProfile(viewsets.ViewSet): """Update guardian profile""" diff --git a/junior/serializers.py b/junior/serializers.py index 6ff9d89..bfd79d1 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -210,7 +210,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer): fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image', 'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task', - 'requested_task', 'rejected_task', 'position', 'is_invited'] + 'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status'] class JuniorProfileSerializer(serializers.ModelSerializer): """junior serializer""" @@ -289,6 +289,8 @@ class AddJuniorSerializer(serializers.ModelSerializer): guardian_code_status=GUARDIAN_CODE_STATUS[1][0]) JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, relationship=relationship) + total_junior = Junior.objects.all().count() + JuniorPoints.objects.create(junior=junior_data, position=total_junior) """Generate otp""" otp_value = generate_otp() expiry_time = timezone.now() + timezone.timedelta(days=1) @@ -486,3 +488,4 @@ class ReAssignTaskSerializer(serializers.ModelSerializer): instance.requested_on = None instance.save() return instance + diff --git a/junior/urls.py b/junior/urls.py index 267faef..76e5b47 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -4,7 +4,8 @@ from django.urls import path, include from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, - InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView) + InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView, + StartAssessmentAPIView, CheckAnswerAPIView) """Third party import""" from rest_framework import routers @@ -43,6 +44,10 @@ router.register('validate-referral-code', ValidateReferralCode, basename='valida router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') # start article""" router.register('start-article', StartArticleAPIView, basename='start-article') +# start assessment api""" +router.register('start-assessment', StartAssessmentAPIView, basename='start-assessment') +# check answer api""" +router.register('check-answer', CheckAnswerAPIView, basename='check-answer') # Define url pattern""" urlpatterns = [ path('api/v1/', include(router.urls)), diff --git a/junior/utils.py b/junior/utils.py index fefe950..621a6dd 100644 --- a/junior/utils.py +++ b/junior/utils.py @@ -50,7 +50,7 @@ def junior_approval_mail(guardian, full_name): def update_positions_based_on_points(): """Update position of the junior""" # First, retrieve all the JuniorPoints instances ordered by total_points in descending order. - juniors_points = JuniorPoints.objects.order_by('-total_points') + juniors_points = JuniorPoints.objects.order_by('-total_points', 'updated_at') # Now, iterate through the queryset and update the position field based on the order. position = 1 diff --git a/junior/views.py b/junior/views.py index e1cb8e0..9a7ce35 100644 --- a/junior/views.py +++ b/junior/views.py @@ -6,6 +6,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.pagination import PageNumberPagination from django.contrib.auth.models import User from rest_framework.filters import SearchFilter +from django.db.models import F import datetime import requests @@ -41,6 +42,8 @@ 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 +from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, + StartAssessmentSerializer) """ Define APIs """ # Define validate guardian code API, # update junior profile, @@ -351,6 +354,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): def get_queryset(self): """get queryset""" + update_positions_based_on_points() return JuniorTask.objects.filter(junior__auth__email=self.request.user).last() def list(self, request, *args, **kwargs): """profile view""" @@ -358,7 +362,6 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet): try: queryset = self.get_queryset() # update position of junior - update_positions_based_on_points() serializer = JuniorPointsSerializer(queryset) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) except Exception as e: @@ -488,3 +491,57 @@ class StartArticleAPIView(viewsets.ModelViewSet): return custom_response(SUCCESS_CODE['3040'], response_status=status.HTTP_200_OK) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class StartAssessmentAPIView(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = StartAssessmentSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + article_id = self.request.GET.get('article_id') + # if referral_code: + article = Article.objects.filter(id=article_id, is_deleted=False).prefetch_related( + 'article_cards', 'article_survey', 'article_survey__options' + ).order_by('-created_at') + return article + def list(self, request, *args, **kwargs): + """profile view""" + + try: + queryset = self.get_queryset() + paginator = self.pagination_class() + paginated_queryset = paginator.paginate_queryset(queryset, request) + serializer = self.serializer_class(paginated_queryset, context={"user":request.user}, many=True) + return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + +class CheckAnswerAPIView(viewsets.ModelViewSet): + """Junior Points viewset""" + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + question_id = self.request.GET.get('question_id') + article = ArticleSurvey.objects.filter(id=question_id).last() + return article + def list(self, request, *args, **kwargs): + """profile view""" + + try: + answer_id = self.request.GET.get('answer_id') + queryset = self.get_queryset() + submit_ans = SurveyOption.objects.filter(id=answer_id, is_answer=True).last() + junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user, + question=queryset) + if submit_ans: + junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True) + JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points= + F('total_points')+ queryset.points) + else: + junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, earn_points=0, + is_answer_correct=False) + return custom_response(None, response_status=status.HTTP_200_OK) + except Exception as e: + return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index b39d20d..9458190 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -12,7 +12,7 @@ from base.messages import ERROR_CODE from guardian.utils import upload_image_to_alibaba from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage from web_admin.utils import pop_id, get_image_url - +from junior.models import JuniorArticlePoints USER = get_user_model() @@ -64,7 +64,7 @@ class ArticleSurveySerializer(serializers.ModelSerializer): meta class """ model = ArticleSurvey - fields = ('id', 'question', 'options') + fields = ('id', 'question', 'options', 'points') class ArticleSerializer(serializers.ModelSerializer): @@ -220,6 +220,7 @@ class ArticleListSerializer(serializers.ModelSerializer): serializer for article API """ article_cards = ArticleCardSerializer(many=True) + article_survey = ArticleSurveySerializer(many=True) total_points = serializers.SerializerMethodField('get_total_points') is_completed = serializers.SerializerMethodField('get_is_completed') @@ -228,7 +229,7 @@ class ArticleListSerializer(serializers.ModelSerializer): meta class """ model = Article - fields = ('id', 'title', 'description', 'article_cards', 'total_points', 'is_completed') + fields = ('id', 'title', 'description', 'article_cards', 'article_survey', 'total_points', 'is_completed') def get_total_points(self, obj): """total points of article""" @@ -238,3 +239,56 @@ class ArticleListSerializer(serializers.ModelSerializer): def get_is_completed(self, obj): """complete all question""" return False + +class ArticleQuestionSerializer(serializers.ModelSerializer): + """ + article survey serializer + """ + id = serializers.IntegerField(required=False) + options = SurveyOptionSerializer(many=True) + is_attempt = serializers.SerializerMethodField('get_is_attempt') + + def get_is_attempt(self, obj): + """attempt question or not""" + context_data = self.context.get('user') + junior_article_obj = JuniorArticlePoints.objects.filter(junior__auth=context_data, question=obj).last() + if junior_article_obj: + return junior_article_obj.is_attempt + return False + + class Meta: + """ + meta class + """ + model = ArticleSurvey + fields = ('id', 'question', 'options', 'points', 'is_attempt') + +class StartAssessmentSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_survey = ArticleQuestionSerializer(many=True) + + class Meta: + """ + meta class + """ + model = Article + fields = ('article_survey',) + + +class CheckAnswerSerializer(serializers.ModelSerializer): + """ + serializer for article API + """ + article_survey = ArticleSurveySerializer(many=True) + + class Meta: + """ + meta class + """ + model = ArticleSurvey + fields = ('id', 'article_survey') + + def create(self, validated_data): + """create function""" diff --git a/web_admin/urls.py b/web_admin/urls.py index 065d30d..6bf3b5e 100644 --- a/web_admin/urls.py +++ b/web_admin/urls.py @@ -6,7 +6,8 @@ from django.urls import path, include from rest_framework import routers # local imports -from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet +from web_admin.views.article import (ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet, + ArticleCardListViewSet) from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.user_management import UserManagementViewSet @@ -17,6 +18,7 @@ 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') +router.register('article-card-list', ArticleCardListViewSet, basename='article-card-list') # forgot and reset password api for admin router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') diff --git a/web_admin/views/article.py b/web_admin/views/article.py index f334650..d4cc843 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -4,7 +4,7 @@ web_admin views file # django imports from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework import status +from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated, AllowAny from django.contrib.auth import get_user_model @@ -16,7 +16,8 @@ from base.messages import SUCCESS_CODE, ERROR_CODE from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.permission import AdminPermission from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, - DefaultArticleCardImageSerializer, ArticleListSerializer) + DefaultArticleCardImageSerializer, ArticleListSerializer + ) USER = get_user_model() @@ -224,4 +225,24 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): 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) + return custom_response(None, data=serializer.data) + +class ArticleCardListViewSet(viewsets.ModelViewSet): + """Junior Points viewset""" + serializer_class = ArticleCardSerializer + permission_classes = [IsAuthenticated] + http_method_names = ('get',) + + def get_queryset(self): + """get queryset""" + return ArticleCard.objects.filter(article=self.request.GET.get('article_id')) + def list(self, request, *args, **kwargs): + """profile view""" + + try: + queryset = self.get_queryset() + # article card list + serializer = ArticleCardSerializer(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) diff --git a/web_admin/views/auth.py b/web_admin/views/auth.py index 0273a08..fae973e 100644 --- a/web_admin/views/auth.py +++ b/web_admin/views/auth.py @@ -33,7 +33,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): if serializer.is_valid(): serializer.save() return custom_response(SUCCESS_CODE['3015']) - return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2000'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='verify-otp', url_path='verify-otp', detail=False, serializer_class=AdminVerifyOTPSerializer) @@ -45,7 +45,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): serializer = self.serializer_class(data=request.data) if serializer.is_valid(): return custom_response(SUCCESS_CODE['3011']) - return custom_error_response(ERROR_CODE['2063'], status.HTTP_400_BAD_REQUEST) + return custom_error_response(ERROR_CODE['2008'], status.HTTP_400_BAD_REQUEST) @action(methods=['post'], url_name='create-password', url_path='create-password', detail=False, serializer_class=AdminCreatePasswordSerializer) @@ -59,5 +59,5 @@ class ForgotAndResetPasswordViewSet(GenericViewSet): user = USER.objects.filter(email=serializer.validated_data.get('email')).first() user.set_password(serializer.validated_data.get('new_password')) user.save() - return custom_response(SUCCESS_CODE['3007']) + return custom_response(SUCCESS_CODE['3006']) return custom_error_response(ERROR_CODE['2064'], status.HTTP_400_BAD_REQUEST) diff --git a/web_admin/views/user_management.py b/web_admin/views/user_management.py index 20184cd..3bd8f64 100644 --- a/web_admin/views/user_management.py +++ b/web_admin/views/user_management.py @@ -55,11 +55,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :return: """ queryset = self.get_queryset() - count = queryset.count() + queryset = queryset.filter( + (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True))) 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) + return custom_response(None, data=serializer.data, count=queryset.count()) def retrieve(self, request, *args, **kwargs): """ @@ -69,7 +70,7 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin, :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) + return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) queryset = self.queryset if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): queryset = queryset.filter(guardian_profile__user__id=kwargs['pk'])