diff --git a/account/utils.py b/account/utils.py index 04c6791..c24c833 100644 --- a/account/utils.py +++ b/account/utils.py @@ -93,7 +93,7 @@ def junior_account_update(user_tb): # Update junior account junior_data.is_active = False junior_data.is_verified = False - junior_data.guardian_code = '{}' + junior_data.guardian_code = None junior_data.guardian_code_status = str(NUMBER['one']) junior_data.is_deleted = True junior_data.save() @@ -282,8 +282,9 @@ def generate_code(value, user_id): OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1) + def get_user_full_name(user_obj): """ to get user's full name """ - return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.last_name else user_obj.first_name + return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.first_name or user_obj.last_name else "User" diff --git a/account/views.py b/account/views.py index e68732e..8dbb2f7 100644 --- a/account/views.py +++ b/account/views.py @@ -51,7 +51,8 @@ class GoogleLoginMixin(object): def google_login(request): """google login function""" access_token = request.data.get('access_token') - user_type = request.data.get('user_type') + user_type = request.META.get('HTTP_USER_TYPE') + device_id = request.META.get('HTTP_DEVICE_ID') if not access_token: return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) @@ -84,14 +85,29 @@ class GoogleLoginMixin(object): if user_data.exists(): if str(user_type) == '1': junior_query = Junior.objects.filter(auth=user_data.last()).last() + if not junior_query: + return custom_error_response( + ERROR_CODE["2071"], + response_status=status.HTTP_400_BAD_REQUEST + ) serializer = JuniorSerializer(junior_query) - if str(user_type) == '2': + elif str(user_type) == '2': guardian_query = Guardian.objects.filter(user=user_data.last()).last() + if not guardian_query: + return custom_error_response( + ERROR_CODE["2070"], + response_status=status.HTTP_400_BAD_REQUEST + ) serializer = GuardianSerializer(guardian_query) + else: + return custom_error_response( + ERROR_CODE["2069"], + response_status=status.HTTP_400_BAD_REQUEST + ) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) - if not User.objects.filter(email__iexact=email).exists(): + else: user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name) if str(user_type) == '1': junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, @@ -102,13 +118,23 @@ class GoogleLoginMixin(object): serializer = JuniorSerializer(junior_query) position = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_query, position=position) - if str(user_type) == '2': + elif str(user_type) == '2': guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True, image=profile_picture,signup_method='2', guardian_code=generate_code(GRD, user_obj.id), referral_code=generate_code(ZOD, user_obj.id) ) serializer = GuardianSerializer(guardian_query) + else: + user_obj.delete() + return custom_error_response( + ERROR_CODE["2069"], + response_status=status.HTTP_400_BAD_REQUEST + ) + device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_obj) + if device_detail: + device_detail.device_id = device_id + device_detail.save() # Return a JSON response with the user's email and name return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -137,7 +163,8 @@ class SigninWithApple(views.APIView): }""" def post(self, request): token = request.data.get("access_token") - user_type = request.data.get("user_type") + user_type = request.META.get('HTTP_USER_TYPE') + device_id = request.META.get('HTTP_DEVICE_ID') try: decoded_data = jwt.decode(token, options={"verify_signature": False}) user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True} @@ -145,11 +172,26 @@ class SigninWithApple(views.APIView): try: user = User.objects.get(email=decoded_data.get("email")) if str(user_type) == '1': - junior_query = Junior.objects.filter(auth=user).last() - serializer = JuniorSerializer(junior_query) - if str(user_type) == '2': - guardian_query = Guardian.objects.filter(user=user).last() - serializer = GuardianSerializer(guardian_query) + junior_data = Junior.objects.filter(auth=user).last() + if not junior_data: + return custom_error_response( + ERROR_CODE["2071"], + response_status=status.HTTP_400_BAD_REQUEST + ) + serializer = JuniorSerializer(junior_data) + elif str(user_type) == '2': + guardian_data = Guardian.objects.filter(user=user).last() + if not guardian_data: + return custom_error_response( + ERROR_CODE["2070"], + response_status=status.HTTP_400_BAD_REQUEST + ) + serializer = GuardianSerializer(guardian_data) + else: + return custom_error_response( + ERROR_CODE["2069"], + response_status=status.HTTP_400_BAD_REQUEST + ) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) @@ -163,12 +205,22 @@ class SigninWithApple(views.APIView): serializer = JuniorSerializer(junior_query) position = Junior.objects.all().count() JuniorPoints.objects.create(junior=junior_query, position=position) - if str(user_type) == '2': + elif str(user_type) == '2': guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True, signup_method='3', guardian_code=generate_code(GRD, user.id), referral_code=generate_code(ZOD, user.id)) serializer = GuardianSerializer(guardian_query) + else: + user.delete() + return custom_error_response( + ERROR_CODE["2069"], + response_status=status.HTTP_400_BAD_REQUEST + ) + device_detail, created = UserDeviceDetails.objects.get_or_create(user=user) + if device_detail: + device_detail.device_id = device_id + device_detail.save() return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) except Exception as e: diff --git a/base/messages.py b/base/messages.py index 8930bdc..9afbe4f 100644 --- a/base/messages.py +++ b/base/messages.py @@ -106,7 +106,8 @@ ERROR_CODE = { "2077": "You can not add guardian", "2078": "This junior is not associate with you", "2079": "Please update your app version for enjoying uninterrupted services", - "2080": "Can not add App version" + "2080": "Can not add App version", + "2081": "You can not add more than 3 guardian" } """Success message code""" diff --git a/base/tasks.py b/base/tasks.py index 791be81..bd1892b 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -1,6 +1,8 @@ """ web_admin tasks file """ +import datetime + # third party imports from celery import shared_task from templated_email import send_templated_mail @@ -8,6 +10,12 @@ from templated_email import send_templated_mail # django imports from django.conf import settings +# local imports +from base.constants import PENDING, IN_PROGRESS, JUNIOR +from guardian.models import JuniorTask +from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING +from notifications.utils import send_notification + @shared_task def send_email_otp(email, verification_code): @@ -27,3 +35,45 @@ def send_email_otp(email, verification_code): } ) return True + + +@shared_task +def send_mail(recipient_list, template, context: dict = None): + """ + used to send otp on email + :param context: + :param recipient_list: e-mail list + :param template: email template + """ + if context is None: + context = {} + from_email = settings.EMAIL_FROM_ADDRESS + recipient_list = recipient_list + send_templated_mail( + template_name=template, + from_email=from_email, + recipient_list=recipient_list, + context=context + ) + return True + + +@shared_task() +def notify_task_expiry(): + """ + task to send notification for those task which expiring soon + :return: + """ + all_pending_tasks = JuniorTask.objects.filter( + task_status__in=[PENDING, IN_PROGRESS], + due_date__range=[datetime.datetime.now().date(), + (datetime.datetime.now().date() + datetime.timedelta(days=1))]) + if pending_tasks := all_pending_tasks.filter(task_status=PENDING): + for task in pending_tasks: + send_notification(PENDING_TASK_EXPIRING, None, None, task.junior.auth.id, + {'task_id': task.id}) + if in_progress_tasks := all_pending_tasks.filter(task_status=IN_PROGRESS): + for task in in_progress_tasks: + send_notification(IN_PROGRESS_TASK_EXPIRING, task.junior.auth.id, JUNIOR, task.guardian.user.id, + {'task_id': task.id}) + return True diff --git a/celerybeat-schedule b/celerybeat-schedule index 68297a7..fef85ad 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ diff --git a/guardian/serializers.py b/guardian/serializers.py index af11acc..3e24265 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -24,13 +24,13 @@ 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, Already_register_user +from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIAN 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_APPROVED, TASK_REJECTED # send notification function -from notifications.utils import send_notification, send_notification_to_junior +from notifications.utils import send_notification from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ @@ -420,8 +420,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update complete time of task # instance.completed_on = real_time() instance.completed_on = timezone.now().astimezone(pytz.utc) - send_notification_to_junior.delay(TASK_APPROVED, instance.guardian.user.id, junior_details.auth.id, - {'task_id': instance.id}) + send_notification.delay(TASK_APPROVED, instance.guardian.user.id, GUARDIAN, + junior_details.auth.id, {'task_id': instance.id}) else: # reject the task instance.task_status = str(NUMBER['three']) @@ -429,8 +429,8 @@ class ApproveTaskSerializer(serializers.ModelSerializer): # update reject time of task # instance.rejected_on = real_time() instance.rejected_on = timezone.now().astimezone(pytz.utc) - send_notification_to_junior.delay(TASK_REJECTED, instance.guardian.user.id, junior_details.auth.id, - {'task_id': instance.id}) + send_notification.delay(TASK_REJECTED, instance.guardian.user.id, GUARDIAN, + junior_details.auth.id, {'task_id': instance.id}) instance.save() junior_data.save() return junior_details diff --git a/guardian/utils.py b/guardian/utils.py index 14bd36a..80cbc56 100644 --- a/guardian/utils.py +++ b/guardian/utils.py @@ -21,7 +21,7 @@ from zod_bank.celery import app # notification's constant from notifications.constants import REFERRAL_POINTS # send notification function -from notifications.utils import send_notification, send_notification_to_junior +from notifications.utils import send_notification # Define upload image on @@ -117,7 +117,7 @@ def update_referral_points(referral_code, referral_code_used): junior_query.total_points = junior_query.total_points + NUMBER['five'] junior_query.referral_points = junior_query.referral_points + NUMBER['five'] junior_query.save() - send_notification_to_junior.delay(REFERRAL_POINTS, None, junior_queryset.auth.id, {}) + send_notification.delay(REFERRAL_POINTS, None, None, junior_queryset.auth.id, {}) diff --git a/guardian/views.py b/guardian/views.py index e49beaf..2cda5c1 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -36,10 +36,10 @@ from account.models import UserEmailOtp, UserNotification, UserDeviceDetails 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, GUARDIAN_CODE_STATUS +from base.constants import NUMBER, GUARDIAN_CODE_STATUS, GUARDIAN from .utils import upload_image_to_alibaba from notifications.constants import REGISTRATION, TASK_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED -from notifications.utils import send_notification_to_junior +from notifications.utils import send_notification """ Define APIs """ # Define Signup API, @@ -202,8 +202,8 @@ class CreateTaskAPIView(viewsets.ModelViewSet): # save serializer task = serializer.save() - send_notification_to_junior.delay(TASK_ASSIGNED, request.auth.payload['user_id'], - junior_id.auth.id, {'task_id': task.id}) + send_notification.delay(TASK_ASSIGNED, request.auth.payload['user_id'], GUARDIAN, + junior_id.auth.id, {'task_id': task.id}) return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: @@ -292,13 +292,14 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet): if serializer.is_valid(): # save serializer serializer.save() - send_notification_to_junior.delay(ASSOCIATE_APPROVED, guardian.user.id, junior_queryset.auth.id) + send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN, + junior_queryset.auth.id) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK) else: junior_queryset.guardian_code = None junior_queryset.guardian_code_status = str(NUMBER['one']) junior_queryset.save() - send_notification_to_junior.delay(ASSOCIATE_REJECTED, guardian.user.id, junior_queryset.auth.id) + send_notification.delay(ASSOCIATE_REJECTED, guardian.user.id, GUARDIAN, junior_queryset.auth.id) return custom_response(SUCCESS_CODE['3024'], 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 07a38cb..13735b3 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -16,12 +16,12 @@ from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, Juni 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, - GUARDIAN_CODE_STATUS) + GUARDIAN_CODE_STATUS, JUNIOR) from guardian.models import Guardian, JuniorTask from account.models import UserEmailOtp, UserNotification from junior.utils import junior_notification_email, junior_approval_mail from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime -from notifications.utils import send_notification, send_notification_to_junior, send_notification_to_guardian +from notifications.utils import send_notification from notifications.constants import (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION, ) from web_admin.models import ArticleCard @@ -88,17 +88,22 @@ class CreateJuniorSerializer(serializers.ModelSerializer): if junior: """update details according to the data get from request""" junior.gender = validated_data.get('gender',junior.gender) - """Update guardian code""" - junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) - """condition for guardian code""" + # Update guardian code""" + # condition for guardian code if guardian_code: - junior.guardian_code = guardian_code + if not junior.guardian_code: + junior.guardian_code = [] + junior.guardian_code.extend(guardian_code) + elif len(junior.guardian_code) < 3 and len(guardian_code) < 3: + junior.guardian_code.extend(guardian_code) + else: + raise serializers.ValidationError({"error":ERROR_CODE['2081'],"code":"400", "status":"failed"}) 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.guardian_code_status = str(NUMBER['three']) junior_approval_mail.delay(user.email, user.first_name) - send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior.auth.id, guardian_data.user.id, {}) + send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {}) junior.dob = validated_data.get('dob', junior.dob) junior.passcode = validated_data.get('passcode', junior.passcode) @@ -307,7 +312,7 @@ class AddJuniorSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email.delay(email, full_name, email, password) # push notification - send_notification_to_junior.delay(JUNIOR_ADDED, None, junior_data.auth.id, {}) + send_notification.delay(JUNIOR_ADDED, None, None, junior_data.auth.id, {}) return junior_data @@ -339,8 +344,8 @@ class CompleteTaskSerializer(serializers.ModelSerializer): instance.task_status = str(NUMBER['four']) instance.is_approved = False instance.save() - send_notification_to_guardian.delay(TASK_ACTION, instance.junior.auth.id, - instance.guardian.user.id, {'task_id': instance.id}) + send_notification.delay(TASK_ACTION, instance.junior.auth.id, JUNIOR, + instance.guardian.user.id, {'task_id': instance.id}) return instance class JuniorPointsSerializer(serializers.ModelSerializer): @@ -450,7 +455,7 @@ class AddGuardianSerializer(serializers.ModelSerializer): """Notification email""" junior_notification_email(email, full_name, email, password) junior_approval_mail.delay(email, full_name) - send_notification_to_guardian.delay(ASSOCIATE_REQUEST, junior_data.auth.id, guardian_data.user.id, {}) + send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth.id, JUNIOR, guardian_data.user.id, {}) return guardian_data class StartTaskSerializer(serializers.ModelSerializer): @@ -494,8 +499,6 @@ class ReAssignTaskSerializer(serializers.ModelSerializer): instance.save() return instance - - class RemoveGuardianCodeSerializer(serializers.ModelSerializer): """User task Serializer""" class Meta(object): diff --git a/junior/urls.py b/junior/urls.py index 08fe33a..3c597c3 100644 --- a/junior/urls.py +++ b/junior/urls.py @@ -62,5 +62,5 @@ urlpatterns = [ path('api/v1/reassign-task/', ReAssignJuniorTaskAPIView.as_view()), path('api/v1/complete-article/', CompleteArticleAPIView.as_view()), path('api/v1/read-article-card/', ReadArticleCardAPIView.as_view()), - path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view()), + path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view()) ] diff --git a/junior/views.py b/junior/views.py index 03f8ae7..69b7dbe 100644 --- a/junior/views.py +++ b/junior/views.py @@ -46,7 +46,7 @@ from base.constants import NUMBER, ARTICLE_STATUS, none from account.utils import custom_response, custom_error_response from guardian.utils import upload_image_to_alibaba from .utils import update_positions_based_on_points -from notifications.utils import send_notification, send_notification_to_junior +from notifications.utils import send_notification from notifications.constants import REMOVE_JUNIOR from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer, @@ -99,7 +99,8 @@ class UpdateJuniorProfile(viewsets.ModelViewSet): return custom_response(None, serializer.data, 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) + error_detail = e.detail.get('error', None) + return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST) class ValidateGuardianCode(viewsets.ModelViewSet): """Check guardian code exist or not""" @@ -312,7 +313,7 @@ class RemoveJuniorAPIView(views.APIView): if serializer.is_valid(): # save serializer serializer.save() - send_notification_to_junior.delay(REMOVE_JUNIOR, None, junior_queryset.auth.id, {}) + send_notification.delay(REMOVE_JUNIOR, None, None, junior_queryset.auth.id, {}) return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) else: diff --git a/notifications/constants.py b/notifications/constants.py index 0cc5a0f..f94e4aa 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -12,8 +12,9 @@ TASK_ASSIGNED = 8 TASK_ACTION = 9 TASK_REJECTED = 10 TASK_APPROVED = 11 - -REMOVE_JUNIOR = 13 +PENDING_TASK_EXPIRING = 12 +IN_PROGRESS_TASK_EXPIRING = 13 +REMOVE_JUNIOR = 15 TEST_NOTIFICATION = 99 @@ -22,16 +23,18 @@ NOTIFICATION_DICT = { "title": "Successfully registered!", "body": "You have registered successfully. Now login and complete your profile." }, + # user will receive notification as soon junior sign up application using their guardian code + # for association ASSOCIATE_REQUEST: { "title": "Associate request!", "body": "You have request from {from_user} to associate with you." }, - + # Juniors will receive notification when custodians reject their request for associate ASSOCIATE_REJECTED: { "title": "Associate request rejected!", "body": "Your request to associate has been rejected by {from_user}." }, - + # Juniors will receive notification when custodians approve their request for associate ASSOCIATE_APPROVED: { "title": "Associate request approved!", "body": "Your request to associate has been approved by {from_user}." @@ -54,7 +57,7 @@ NOTIFICATION_DICT = { # Guardian will receive notification as soon as junior send task for approval TASK_ACTION: { "title": "Task completion approval!", - "body": "You have request from {from_user} for task completion." + "body": "{from_user} completed her task {task_name}." }, # Juniors will receive notification as soon as their task is rejected by custodians TASK_REJECTED: { @@ -68,11 +71,24 @@ NOTIFICATION_DICT = { "body": "Your task completion request has been approved by {from_user}. " "Also you earned 5 points for successful completion." }, + # Juniors will receive notification when their task end date about to end + PENDING_TASK_EXPIRING: { + "title": "Task expiring soon!", + "body": "Your task {task_name} is expiring soon. Please complete it." + }, + # User will receive notification when their assigned task is about to end + # and juniors have not performed any action + IN_PROGRESS_TASK_EXPIRING: { + "title": "Task expiring soon!", + "body": "{from_user} didn't take any action on assigned task {task_name} and it's expiring soon. " + "Please assist to complete it." + }, # Juniors will receive notification as soon as their custodians remove them from account REMOVE_JUNIOR: { "title": "Disassociate by guardian!", "body": "Your guardian has disassociated you." }, + # Test notification TEST_NOTIFICATION: { "title": "Test Notification", "body": "This notification is for testing purpose from {from_user}." diff --git a/notifications/serializers.py b/notifications/serializers.py index 14f1b20..2f0222f 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -40,6 +40,8 @@ class NotificationListSerializer(serializers.ModelSerializer): class ReadNotificationSerializer(serializers.ModelSerializer): """User task Serializer""" + id = serializers.ListSerializer(child=serializers.IntegerField()) + class Meta(object): """Meta class""" model = Notification diff --git a/notifications/utils.py b/notifications/utils.py index 0d0b25b..7623673 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -9,15 +9,15 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati # django imports from django.contrib.auth import get_user_model +# local imports from account.models import UserNotification from account.utils import get_user_full_name -from guardian.models import Guardian +from base.constants import GUARDIAN, JUNIOR +from guardian.models import Guardian, JuniorTask from junior.models import Junior from notifications.constants import NOTIFICATION_DICT from notifications.models import Notification -# local imports - User = get_user_model() @@ -42,49 +42,69 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None: print(e) -def get_basic_detail(from_user_id, to_user_id): +def get_from_user_details(from_user_id, from_user_type): """ - used to get the basic details + used to get from user details """ - from_user = User.objects.get(id=from_user_id) if from_user_id else None - to_user = User.objects.get(id=to_user_id) - return from_user, to_user + from_user = None + from_user_name = None + from_user_image = None + if from_user_id: + if from_user_type == GUARDIAN: + guardian = Guardian.objects.filter(user_id=from_user_id).select_related('user').first() + from_user = guardian.user + from_user_name = get_user_full_name(from_user) + from_user_image = guardian.image + elif from_user_type == JUNIOR: + junior = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first() + from_user = junior.auth + from_user_name = get_user_full_name(from_user) + from_user_image = junior.image + return from_user_name, from_user_image, from_user -def get_notification_data(notification_type, from_user, to_user, extra_data): +def get_notification_data(notification_type, from_user_id, from_user_type, to_user_id, extra_data): """ get notification and push data + :param from_user_type: GUARDIAN or JUNIOR :param notification_type: notification_type - :param from_user: from_user obj - :param to_user: to_user obj + :param from_user_id: from_user obj + :param to_user_id: to_user obj :param extra_data: any extra data provided :return: notification and push data """ push_data = NOTIFICATION_DICT[notification_type].copy() notification_data = push_data.copy() - notification_data['to_user'] = get_user_full_name(to_user) - if from_user: - from_user_name = get_user_full_name(from_user) - push_data['body'] = push_data['body'].format(from_user=from_user_name) - notification_data['body'] = notification_data['body'].format(from_user=from_user_name) - notification_data['from_user'] = from_user_name + task_name = None + if 'task_id' in extra_data: + task = JuniorTask.objects.filter(id=extra_data.get('task_id')).first() + task_name = task.task_name + extra_data['task_image'] = task.image if task.image else task.default_image + + from_user_name, from_user_image, from_user = get_from_user_details(from_user_id, from_user_type) + + push_data['body'] = push_data['body'].format(from_user=from_user_name, task_name=task_name) + notification_data['body'] = notification_data['body'].format(from_user=from_user_name, task_name=task_name) + notification_data['from_user'] = from_user_name + notification_data['from_user_image'] = from_user_image notification_data.update(extra_data) - - return notification_data, push_data + to_user = User.objects.get(id=to_user_id) + return notification_data, push_data, from_user, to_user -def send_notification(notification_type, from_user, to_user, extra_data): +@shared_task() +def send_notification(notification_type, from_user_id, from_user_type, to_user_id, extra_data): """ used to send the push for the given notification type """ - # (from_user, to_user) = get_basic_detail(from_user_id, to_user_id) - notification_data, push_data = get_notification_data(notification_type, from_user, to_user, extra_data) + notification_data, push_data, from_user, to_user = get_notification_data(notification_type, from_user_id, + from_user_type, to_user_id, extra_data) user_notification_type = UserNotification.objects.filter(user=to_user).first() notification_data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()}) Notification.objects.create(notification_type=notification_type, notification_from=from_user, notification_to=to_user, data=notification_data) - if user_notification_type.push_notification: + if user_notification_type and user_notification_type.push_notification: send_push(to_user, push_data) @@ -104,14 +124,10 @@ def send_notification_to_guardian(notification_type, from_user_id, to_user_id, e :param extra_data: :return: """ - from_user = None if from_user_id: - from_user = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first() + from_user = Junior.objects.filter(auth_id=from_user_id).first() extra_data['from_user_image'] = from_user.image - from_user = from_user.auth - to_user = Guardian.objects.filter(user_id=to_user_id).select_related('user').first() - extra_data['to_user_image'] = to_user.image - send_notification(notification_type, from_user, to_user.user, extra_data) + send_notification(notification_type, from_user_id, to_user_id, extra_data) @shared_task() @@ -123,13 +139,9 @@ def send_notification_to_junior(notification_type, from_user_id, to_user_id, ext :param extra_data: :return: """ - from_user = None if from_user_id: - from_user = Guardian.objects.filter(user_id=from_user_id).select_related('user').first() + from_user = Guardian.objects.filter(user_id=from_user_id).first() extra_data['from_user_image'] = from_user.image - from_user = from_user.user - to_user = Junior.objects.filter(auth_id=to_user_id).select_related('auth').first() - extra_data['to_user_image'] = to_user.image - send_notification(notification_type, from_user, to_user.auth, extra_data) + send_notification(notification_type, from_user_id, to_user_id, extra_data) diff --git a/notifications/views.py b/notifications/views.py index 8674e85..4546317 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -4,27 +4,33 @@ notifications views file # django imports from django.db.models import Q from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response from rest_framework import viewsets, status, views + # local imports from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE +from base.tasks import notify_task_expiry from notifications.constants import TEST_NOTIFICATION -# Import serializer from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer -from notifications.utils import send_notification, send_notification_to_guardian, send_notification_to_junior -# Import model +from notifications.utils import send_notification from notifications.models import Notification class NotificationViewSet(viewsets.GenericViewSet): - """ used to do the notification actions """ + """ + used to do the notification actions + """ serializer_class = NotificationListSerializer permission_classes = [IsAuthenticated, ] def list(self, request, *args, **kwargs) -> Response: - """ list the notifications """ + """ + to list user's notifications + :param request: + :return: + """ queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id'] ).select_related('notification_to').order_by('-id') paginator = self.pagination_class() @@ -49,16 +55,24 @@ class NotificationViewSet(viewsets.GenericViewSet): to send test notification :return: """ - send_notification_to_guardian.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) - send_notification_to_junior.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], - {'task_id': None}) + send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'], + {}) return custom_response(SUCCESS_CODE["3000"]) - @action(methods=['get'], url_path='mark-as-read', url_name='mark-as-read', detail=True, ) + @action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False, + serializer_class=ReadNotificationSerializer) def mark_as_read(self, request, *args, **kwargs): """ notification list """ - Notification.objects.filter(id=kwargs['pk']).update(is_read=True) + Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True) + return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) + + @action(methods=['get'], url_path='task', url_name='task', detail=False, + permission_classes=[AllowAny]) + def task(self, request, *args, **kwargs): + """ + notification list + """ + notify_task_expiry() return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK) diff --git a/web_admin/serializers/analytics_serializer.py b/web_admin/serializers/analytics_serializer.py index 0e3418a..b0177d7 100644 --- a/web_admin/serializers/analytics_serializer.py +++ b/web_admin/serializers/analytics_serializer.py @@ -7,6 +7,7 @@ from rest_framework import serializers # django imports from django.contrib.auth import get_user_model +from account.utils import get_user_full_name # local imports from base.constants import USER_TYPE @@ -74,6 +75,7 @@ class UserCSVReportSerializer(serializers.ModelSerializer): """ user csv/xls report serializer """ + name = serializers.SerializerMethodField() phone_number = serializers.SerializerMethodField() user_type = serializers.SerializerMethodField() is_active = serializers.SerializerMethodField() @@ -84,7 +86,15 @@ class UserCSVReportSerializer(serializers.ModelSerializer): meta class """ model = USER - fields = ('first_name', 'last_name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined') + fields = ('name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined') + + @staticmethod + def get_name(obj): + """ + :param obj: user object + :return: full name + """ + return get_user_full_name(obj) @staticmethod def get_phone_number(obj): diff --git a/web_admin/serializers/auth_serializer.py b/web_admin/serializers/auth_serializer.py index 712e284..585b2c9 100644 --- a/web_admin/serializers/auth_serializer.py +++ b/web_admin/serializers/auth_serializer.py @@ -14,7 +14,7 @@ from account.models import UserEmailOtp from base.constants import USER_TYPE from base.messages import ERROR_CODE from guardian.tasks import generate_otp -from base.tasks import send_email_otp +from base.tasks import send_mail USER = get_user_model() @@ -48,11 +48,13 @@ class AdminOTPSerializer(serializers.ModelSerializer): :return: user_data """ email = validated_data['email'] - verification_code = generate_otp() - + template = 'email_reset_verification.email' # Send the verification code to the user's email - send_email_otp.delay(email, verification_code) + data = { + "verification_code": verification_code + } + send_mail.delay([email], template, data) expiry = timezone.now() + timezone.timedelta(days=1) user_data, created = UserEmailOtp.objects.update_or_create(email=email, diff --git a/web_admin/serializers/user_management_serializer.py b/web_admin/serializers/user_management_serializer.py index 21a0a3b..c8d0b1f 100644 --- a/web_admin/serializers/user_management_serializer.py +++ b/web_admin/serializers/user_management_serializer.py @@ -5,6 +5,7 @@ web_admin user_management serializers file from rest_framework import serializers from django.contrib.auth import get_user_model +from account.utils import get_user_full_name from base.constants import USER_TYPE, GUARDIAN, JUNIOR # local imports from base.messages import ERROR_CODE, SUCCESS_CODE @@ -37,7 +38,7 @@ class UserManagementListSerializer(serializers.ModelSerializer): :param obj: user object :return: full name """ - return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name + return get_user_full_name(obj) @staticmethod def get_country_code(obj): @@ -144,7 +145,7 @@ class GuardianSerializer(serializers.ModelSerializer): :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 + return get_user_full_name(obj.user) @staticmethod def get_first_name(obj): @@ -222,7 +223,7 @@ class JuniorSerializer(serializers.ModelSerializer): :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 + return get_user_full_name(obj.auth) @staticmethod def get_first_name(obj): diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index face900..4a010a3 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -22,7 +22,7 @@ from django.db.models.functions.window import Rank from django.http import HttpResponse # local imports -from account.utils import custom_response +from account.utils import custom_response, get_user_full_name from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, DATE_FORMAT, TASK_STATUS from guardian.models import JuniorTask from guardian.utils import upload_excel_file_to_alibaba @@ -107,13 +107,15 @@ class AnalyticsViewSet(GenericViewSet): assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] - ).exclude(task_status__in=[PENDING, EXPIRED]) + ) data = { 'task_completed': assign_tasks.filter(task_status=COMPLETED).count(), + 'task_pending': assign_tasks.filter(task_status=PENDING).count(), 'task_in_progress': assign_tasks.filter(task_status=IN_PROGRESS).count(), 'task_requested': assign_tasks.filter(task_status=REQUESTED).count(), 'task_rejected': assign_tasks.filter(task_status=REJECTED).count(), + 'task_expired': assign_tasks.filter(task_status=EXPIRED).count(), } return custom_response(None, data) @@ -169,10 +171,9 @@ class AnalyticsViewSet(GenericViewSet): serializer = UserCSVReportSerializer(queryset, many=True) df_users = pd.DataFrame([ - {'Name': f"{user['first_name']} {user['last_name']}", - 'Email': user['email'], 'Phone Number': user['phone_number'], - 'User Type': user['user_type'], 'Status': user['is_active'], - 'Date Joined': user['date_joined']} + {'Name': user['name'], 'Email': user['email'], + 'Phone Number': user['phone_number'], 'User Type': user['user_type'], + 'Status': user['is_active'], 'Date Joined': user['date_joined']} for user in serializer.data ]) write_excel_worksheet(worksheet, df_users) @@ -181,10 +182,16 @@ class AnalyticsViewSet(GenericViewSet): elif sheet_name == 'Assign Tasks': assign_tasks = JuniorTask.objects.filter( created_at__range=[start_date, (end_date + datetime.timedelta(days=1))] - ).exclude(task_status__in=[PENDING, EXPIRED]) + ).select_related('junior__auth', 'guardian__user').only('task_name', 'task_status', + 'junior__auth__first_name', + 'junior__auth__last_name', + 'guardian__user__first_name', + 'guardian__user__last_name',) df_tasks = pd.DataFrame([ - {'Task Name': task.task_name, 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()} + {'Task Name': task.task_name, 'Assign To': get_user_full_name(task.junior.auth), + 'Assign By': get_user_full_name(task.guardian.user), + 'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()} for task in assign_tasks ]) @@ -198,8 +205,7 @@ class AnalyticsViewSet(GenericViewSet): )).order_by('-total_points', 'junior__created_at')[:15] df_leaderboard = pd.DataFrame([ { - 'Junior Name': f"{junior.junior.auth.first_name} {junior.junior.auth.last_name}" - if junior.junior.auth.last_name else junior.junior.auth.first_name, + 'Name': get_user_full_name(junior.junior.auth), 'Points': junior.total_points, 'Rank': junior.rank } diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 6570f6f..f362f8b 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -229,7 +229,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin): http_method_names = ['get',] def get_queryset(self): - article = self.queryset.objects.filter(is_deleted=False).prefetch_related( + article = self.queryset.objects.filter(is_deleted=False, is_published=True).prefetch_related( 'article_cards', 'article_survey', 'article_survey__options' ).order_by('-created_at') queryset = self.filter_queryset(article) diff --git a/zod_bank/celery.py b/zod_bank/celery.py index 5cc2829..93cfb8f 100644 --- a/zod_bank/celery.py +++ b/zod_bank/celery.py @@ -27,6 +27,13 @@ app.config_from_object('django.conf:settings') # Load task modules from all registered Django apps. app.autodiscover_tasks() +app.conf.beat_schedule = { + 'notify_task_expiry': { + 'task': 'base.tasks.notify_task_expiry', + 'schedule': crontab(minute='0', hour='*/1'), + }, +} + @app.task(bind=True) def debug_task(self):