From f54160865655df1f1a1ebccae5111697fc283985 Mon Sep 17 00:00:00 2001 From: abutalib-kiwi Date: Thu, 24 Aug 2023 12:10:18 +0530 Subject: [PATCH] changed notification method, added celery task to notify expiring task --- account/utils.py | 3 +- base/tasks.py | 29 ++++++++++++++ celerybeat-schedule | Bin 16384 -> 16384 bytes guardian/serializers.py | 12 +++--- guardian/utils.py | 4 +- guardian/views.py | 13 ++++--- junior/serializers.py | 14 +++---- junior/views.py | 4 +- notifications/constants.py | 26 ++++++++++--- notifications/utils.py | 77 ++++++++++++++++++++++--------------- notifications/views.py | 35 ++++++++++++----- zod_bank/celery.py | 7 ++++ 12 files changed, 153 insertions(+), 71 deletions(-) diff --git a/account/utils.py b/account/utils.py index 04c6791..59693c7 100644 --- a/account/utils.py +++ b/account/utils.py @@ -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/base/tasks.py b/base/tasks.py index 791be81..a737380 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,24 @@ def send_email_otp(email, verification_code): } ) 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 68297a7db074145de61828e2a94f36b892493ec5..fef85ada273c6b1f27fa57f6340942760415d802 100644 GIT binary patch delta 28 kcmZo@U~Fh$+;GK!U4l)Po26~j;j7*z#4KMQn0EVLpBme*a delta 28 kcmZo@U~Fh$+;GK!U6f0fmE~~H;jEtLg4KMQn0EeOpH2?qr 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..8c00bfb 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 @@ -98,7 +98,7 @@ class CreateJuniorSerializer(serializers.ModelSerializer): 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 +307,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 +339,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 +450,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): diff --git a/junior/views.py b/junior/views.py index 03f8ae7..15aa7f9 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, @@ -312,7 +312,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/utils.py b/notifications/utils.py index 0d0b25b..bcb9749 100644 --- a/notifications/utils.py +++ b/notifications/utils.py @@ -11,7 +11,8 @@ from django.contrib.auth import get_user_model 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 @@ -42,44 +43,64 @@ 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, @@ -104,14 +125,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 +140,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 d60c429..8c0e07c 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -4,27 +4,35 @@ 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.constants import JUNIOR, GUARDIAN from base.messages import SUCCESS_CODE, ERROR_CODE +from base.tasks import notify_task_expiry +from guardian.models import Guardian 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,10 +57,8 @@ 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=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False, @@ -63,3 +69,12 @@ class NotificationViewSet(viewsets.GenericViewSet): """ 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/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):