From c22db72628df5450fb89848d5d35b62a8d38506b Mon Sep 17 00:00:00 2001 From: Akash Kumar Sah Date: Wed, 6 Nov 2024 22:14:21 +0530 Subject: [PATCH 1/6] Setup Email Notifications and architected new announcement system --- FusionIIIT/Fusion/settings/common.py | 5 +- FusionIIIT/applications/department/views.py | 224 +++++++++--------- FusionIIIT/applications/globals/views.py | 3 + FusionIIIT/applications/leave/tasks.py | 6 +- .../programme_curriculum/views.py | 1 + FusionIIIT/notification/admin.py | 19 ++ FusionIIIT/notification/forms.py | 33 +++ .../notification/migrations/0001_initial.py | 39 +++ .../notification/migrations/__init__.py | 0 FusionIIIT/notification/models.py | 25 ++ FusionIIIT/notification/views.py | 97 +++++++- FusionIIIT/templates/dashboard/alerts1.html | 29 +-- .../templates/department/dep_request.html | 8 +- .../notifications/create_announcement.html | 76 ++++++ .../notifications/email_notification.html | 70 ++++++ 15 files changed, 488 insertions(+), 147 deletions(-) create mode 100644 FusionIIIT/notification/admin.py create mode 100644 FusionIIIT/notification/forms.py create mode 100644 FusionIIIT/notification/migrations/0001_initial.py create mode 100644 FusionIIIT/notification/migrations/__init__.py create mode 100644 FusionIIIT/templates/notifications/create_announcement.html create mode 100644 FusionIIIT/templates/notifications/email_notification.html diff --git a/FusionIIIT/Fusion/settings/common.py b/FusionIIIT/Fusion/settings/common.py index d9017dd40..9e3d325e4 100644 --- a/FusionIIIT/Fusion/settings/common.py +++ b/FusionIIIT/Fusion/settings/common.py @@ -51,6 +51,7 @@ # email of sender EMAIL_HOST_USER = 'fusionmailservice@iiitdmj.ac.in' +# EMAIL_HOST_PASSWORD = 'password' EMAIL_PORT = 587 ACCOUNT_EMAIL_REQUIRED = True @@ -79,8 +80,8 @@ # CELERY STUFF -# CELERY_BROKER_URL = 'redis://localhost:6379' -# CELERY_RESULT_BACKEND = 'redis://localhost:6379' +CELERY_BROKER_URL = 'redis://localhost:6379' +CELERY_RESULT_BACKEND = 'redis://localhost:6379' CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' diff --git a/FusionIIIT/applications/department/views.py b/FusionIIIT/applications/department/views.py index 6e5a0d936..f25a96218 100644 --- a/FusionIIIT/applications/department/views.py +++ b/FusionIIIT/applications/department/views.py @@ -20,6 +20,7 @@ from .models import SpecialRequest, Announcements , Information from jsonschema import validate from jsonschema.exceptions import ValidationError +from notification.views import create_announcement def department_information(request): @@ -261,127 +262,128 @@ def staff_view(request): department_context = department_information(request) requests_received = get_to_request(usrnm) - if request.method == 'POST': - form_type = request.POST.get('form_type', '') - if form_type == 'form1' : + # if request.method == 'POST': + # form_type = request.POST.get('form_type', '') + # if form_type == 'form1' : - batch = request.POST.get('batch', '') - programme = request.POST.get('programme', '') - message = request.POST.get('announcement', '') - upload_announcement = request.FILES.get('upload_announcement') - department = request.POST.get('department') - ann_date = date.today() - user_info = ExtraInfo.objects.all().select_related('user','department').get(id=ann_maker_id) - getstudents = ExtraInfo.objects.select_related('user') - recipients = User.objects.filter(extrainfo__in=getstudents) - - obj1, created = Announcements.objects.get_or_create(maker_id=user_info, - batch=batch, - programme=programme, - message=message, - upload_announcement=upload_announcement, - department = department, - ann_date=ann_date) - department_notif(usrnm, recipients , message) + # batch = request.POST.get('batch', '') + # programme = request.POST.get('programme', '') + # message = request.POST.get('announcement', '') + # upload_announcement = request.FILES.get('upload_announcement') + # department = request.POST.get('department') + # ann_date = date.today() + # user_info = ExtraInfo.objects.all().select_related('user','department').get(id=ann_maker_id) + # getstudents = ExtraInfo.objects.select_related('user') + # recipients = User.objects.filter(extrainfo__in=getstudents) + + # obj1, created = Announcements.objects.get_or_create(maker_id=user_info, + # batch=batch, + # programme=programme, + # message=message, + # upload_announcement=upload_announcement, + # department = department, + # ann_date=ann_date) + # department_notif(usrnm, recipients , message) - elif form_type == 'form2' : + # elif form_type == 'form2' : - email = request.POST.get('email', '') - phone_number = request.POST.get('contact_number', '') - facilites = request.POST.get('facilities', '') - labs = request.POST.get('labs', '') - department_id = user_departmentid - - # Check if a row with the specified department_id already exists - try: - department_info = Information.objects.get(department_id=department_id) - # If row exists, update the values - department_info.email = email - department_info.phone_number_number = phone_number - department_info.facilites = facilites - department_info.labs = labs - department_info.save() - except Information.DoesNotExist: - # If row does not exist, create a new one - department_info = Information.objects.create( - department_id=department_id, - email=email, - phone_number=phone_number, - facilites=facilites, - labs=labs - ) + # email = request.POST.get('email', '') + # phone_number = request.POST.get('contact_number', '') + # facilites = request.POST.get('facilities', '') + # labs = request.POST.get('labs', '') + # department_id = user_departmentid + + # # Check if a row with the specified department_id already exists + # try: + # department_info = Information.objects.get(department_id=department_id) + # # If row exists, update the values + # department_info.email = email + # department_info.phone_number_number = phone_number + # department_info.facilites = facilites + # department_info.labs = labs + # department_info.save() + # except Information.DoesNotExist: + # # If row does not exist, create a new one + # department_info = Information.objects.create( + # department_id=department_id, + # email=email, + # phone_number=phone_number, + # facilites=facilites, + # labs=labs + # ) - context = browse_announcements() + # context = browse_announcements() - department_templates = { - 51: 'department/csedep_request.html', - 30: 'department/ecedep_request.html', - 37: 'department/medep_request.html', - 53: 'department/smdep_request.html', - - } - default_template = 'department/dep_request.html' + # department_templates = { + # 51: 'department/csedep_request.html', + # 30: 'department/ecedep_request.html', + # 37: 'department/medep_request.html', + # 53: 'department/smdep_request.html', + + # } + # default_template = 'department/dep_request.html' - desig=request.session.get('currentDesignationSelected', 'default_value') - if desig=='deptadmin_cse': - template_name = 'department/admin_cse.html' + # desig=request.session.get('currentDesignationSelected', 'default_value') + # if desig=='deptadmin_cse': + # template_name = 'department/admin_cse.html' - return render(request, template_name, { - "user_designation": user_info.user_type, - "announcements": context, - "request_to": requests_received, - "fac_list": context_f, - "department_info": department_context - }) - elif desig=='deptadmin_ece': - template_name = 'department/admin_ece.html' - return render(request, template_name, { - "user_designation": user_info.user_type, - "announcements": context, - "request_to": requests_received, - "fac_list": context_f, - "department_info": department_context - }) - elif desig=='deptadmin_me': - template_name = 'department/admin_me.html' - return render(request, template_name, { - "user_designation": user_info.user_type, - "announcements": context, - "request_to": requests_received, - "fac_list": context_f, - "department_info": department_context - }) - elif desig=='deptadmin_sm': - template_name = 'department/admin_sm.html' - return render(request, template_name, { - "user_designation": user_info.user_type, - "announcements": context, - "request_to": requests_received, - "fac_list": context_f, - "department_info": department_context - }) + # return render(request, template_name, { + # "user_designation": user_info.user_type, + # "announcements": context, + # "request_to": requests_received, + # "fac_list": context_f, + # "department_info": department_context + # }) + # elif desig=='deptadmin_ece': + # template_name = 'department/admin_ece.html' + # return render(request, template_name, { + # "user_designation": user_info.user_type, + # "announcements": context, + # "request_to": requests_received, + # "fac_list": context_f, + # "department_info": department_context + # }) + # elif desig=='deptadmin_me': + # template_name = 'department/admin_me.html' + # return render(request, template_name, { + # "user_designation": user_info.user_type, + # "announcements": context, + # "request_to": requests_received, + # "fac_list": context_f, + # "department_info": department_context + # }) + # elif desig=='deptadmin_sm': + # template_name = 'department/admin_sm.html' + # return render(request, template_name, { + # "user_designation": user_info.user_type, + # "announcements": context, + # "request_to": requests_received, + # "fac_list": context_f, + # "department_info": department_context + # }) - # if desig == 'deptadmin_cse': - # return render(request, 'admin_cse.html') - # elif desig == 'deptadmin_ece': - # return render(request, 'admin_ece.html') - # elif desig == 'deptadmin_sm': - # return render(request, 'admin_sm.html') - # elif desig == 'deptadmin_me': - # return render(request, 'admin_me.html') - # else: - # return render(request, 'default.html') - - template_name = department_templates.get(user_departmentid, default_template) - return render(request, template_name, { - "user_designation": user_info.user_type, - "announcements": context, - "request_to": requests_received, - "fac_list": context_f, - "department_info": department_context - }) + # # if desig == 'deptadmin_cse': + # # return render(request, 'admin_cse.html') + # # elif desig == 'deptadmin_ece': + # # return render(request, 'admin_ece.html') + # # elif desig == 'deptadmin_sm': + # # return render(request, 'admin_sm.html') + # # elif desig == 'deptadmin_me': + # # return render(request, 'admin_me.html') + # # else: + # # return render(request, 'default.html') + + # template_name = department_templates.get(user_departmentid, default_template) + # return render(request, template_name, { + # "user_designation": user_info.user_type, + # "announcements": context, + # "request_to": requests_received, + # "fac_list": context_f, + # "department_info": department_context + # }) + return create_announcement(request, 'department/dep_request.html', {"user_designation": user_info.user_type}) diff --git a/FusionIIIT/applications/globals/views.py b/FusionIIIT/applications/globals/views.py index dedad3d8d..f982ba075 100644 --- a/FusionIIIT/applications/globals/views.py +++ b/FusionIIIT/applications/globals/views.py @@ -30,6 +30,7 @@ from notifications.models import Notification from .models import * from applications.hostel_management.models import (HallCaretaker,HallWarden) +from notification.views import announcement_list def index(request): context = {} @@ -754,11 +755,13 @@ def dashboard(request): 'designation' : designation, 'hall_caretaker': hall_caretaker_user, 'hall_warden': hall_warden_user, + 'announcements': announcement_list(request)['announcements'] } # a=HoldsDesignation.objects.select_related('user','working','designation').filter(designation = user) print(context) print(type(user.extrainfo.user_type)) + print(announcement_list(request)) if(request.user.get_username() == 'director'): return render(request, "dashboard/director_dashboard2.html", {}) elif( "dean_rspc" in designation): diff --git a/FusionIIIT/applications/leave/tasks.py b/FusionIIIT/applications/leave/tasks.py index d29285a92..8460273bd 100644 --- a/FusionIIIT/applications/leave/tasks.py +++ b/FusionIIIT/applications/leave/tasks.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -import celery +from Fusion.celery import app from django.db import transaction from django.db.models import Q from django.utils import timezone @@ -8,7 +8,7 @@ from .models import LeaveMigration -@celery.task() +@app.task() @transaction.atomic def execute_leave_migrations(): today = timezone.now().date() @@ -27,6 +27,6 @@ def execute_leave_migrations(): migrations.delete() -@celery.task() +@app.task() def testing(a, b): return a+b \ No newline at end of file diff --git a/FusionIIIT/applications/programme_curriculum/views.py b/FusionIIIT/applications/programme_curriculum/views.py index 4ea51e609..e0fdb5a50 100644 --- a/FusionIIIT/applications/programme_curriculum/views.py +++ b/FusionIIIT/applications/programme_curriculum/views.py @@ -225,6 +225,7 @@ def view_a_courseslot(request, courseslot_id): elif 'hod' in request.session['currentDesignationSelected'].lower(): url+='faculty/' course_slot = get_object_or_404(CourseSlot, Q(id=courseslot_id)) + notifs = request.user.notifications.all() return render(request, url+'view_a_courseslot.html', {'course_slot': course_slot,'notifications': notifs,}) diff --git a/FusionIIIT/notification/admin.py b/FusionIIIT/notification/admin.py new file mode 100644 index 000000000..c8a0866df --- /dev/null +++ b/FusionIIIT/notification/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +from .models import Announcements, AnnouncementRecipients + +# Customize the admin interface for Announcements +@admin.register(Announcements) +class AnnouncementsAdmin(admin.ModelAdmin): + list_display = ('created_by', 'created_at', 'message', 'target_group', 'department', 'batch') + search_fields = ('message', 'created_by__username', 'department') + list_filter = ('target_group', 'department', 'created_at') + date_hierarchy = 'created_at' + ordering = ('-created_at',) + +# Customize the admin interface for AnnouncementRecipients +@admin.register(AnnouncementRecipients) +class AnnouncementRecipientsAdmin(admin.ModelAdmin): + list_display = ('announcement', 'user') + search_fields = ('announcement__message', 'user__user__username') + list_filter = ('announcement__target_group',) + diff --git a/FusionIIIT/notification/forms.py b/FusionIIIT/notification/forms.py new file mode 100644 index 000000000..b34f490a2 --- /dev/null +++ b/FusionIIIT/notification/forms.py @@ -0,0 +1,33 @@ +from django import forms +from .models import Announcements, AnnouncementRecipients +from applications.globals.models import ExtraInfo +from django.contrib.auth.models import User + + +class AnnouncementForm(forms.ModelForm): + specific_users = forms.ModelMultipleChoiceField( + queryset=ExtraInfo.objects.all(), + required=False, + widget=forms.CheckboxSelectMultiple + ) + + class Meta: + model = Announcements + fields = ['message', 'target_group', 'department', 'batch', 'specific_users'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super().clean() + target_group = cleaned_data.get('target_group') + + # Validation based on target group + if target_group == 'faculty' and not cleaned_data.get('department'): + self.add_error('department', 'Department is required for faculty announcements.') + elif target_group == 'students': + if not cleaned_data.get('department') or not cleaned_data.get('batch'): + self.add_error('department', 'Department is required for student announcements.') + self.add_error('batch', 'Batch is required for student announcements.') + + return cleaned_data \ No newline at end of file diff --git a/FusionIIIT/notification/migrations/0001_initial.py b/FusionIIIT/notification/migrations/0001_initial.py new file mode 100644 index 000000000..6b4bf8941 --- /dev/null +++ b/FusionIIIT/notification/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 3.1.5 on 2024-11-06 22:10 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('globals', '0002_auto_20241007_2302'), + ] + + operations = [ + migrations.CreateModel( + name='Announcements', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('message', models.CharField(max_length=200)), + ('target_group', models.CharField(choices=[('faculty', 'Faculty'), ('students', 'Students'), ('all', 'All Staff and Students'), ('specific_users', 'Specific Users')], max_length=20)), + ('batch', models.IntegerField(blank=True, null=True)), + ('upload_announcement', models.FileField(default=' ', null=True, upload_to='notifications/upload_announcement')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='globals.departmentinfo')), + ], + ), + migrations.CreateModel( + name='AnnouncementRecipients', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('announcement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recipients', to='notification.announcements')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='globals.extrainfo')), + ], + ), + ] diff --git a/FusionIIIT/notification/migrations/__init__.py b/FusionIIIT/notification/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/FusionIIIT/notification/models.py b/FusionIIIT/notification/models.py index 71a836239..c28d17a89 100644 --- a/FusionIIIT/notification/models.py +++ b/FusionIIIT/notification/models.py @@ -1,3 +1,28 @@ from django.db import models +from django.contrib.auth.models import User +from applications.globals.models import ExtraInfo, DepartmentInfo # Create your models here. +class Announcements(models.Model): + TARGET_GROUPS = [ + ('faculty', 'Faculty'), + ('students', 'Students'), + ('all', 'All Staff and Students'), + ('specific_users', 'Specific Users'), + ] + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + created_at = models.DateTimeField(auto_now_add=True) + message = models.CharField(max_length=200) + target_group = models.CharField(max_length=20, choices=TARGET_GROUPS) + department = models.ForeignKey(DepartmentInfo, on_delete=models.CASCADE, null=True, blank=True) + batch = models.IntegerField(null=True, blank=True) + upload_announcement = models.FileField(upload_to='notifications/upload_announcement', null=True, default=" ") + def __str__(self): + return str(self.created_by.username) + +class AnnouncementRecipients(models.Model): + announcement = models.ForeignKey(Announcements, on_delete=models.CASCADE, related_name='recipients') + user = models.ForeignKey(ExtraInfo, on_delete=models.CASCADE) + + def __str__(self): + return f"{self.user.id} - {self.announcement.message}" \ No newline at end of file diff --git a/FusionIIIT/notification/views.py b/FusionIIIT/notification/views.py index ca732a7b7..3619f06a5 100644 --- a/FusionIIIT/notification/views.py +++ b/FusionIIIT/notification/views.py @@ -1,9 +1,98 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from requests import Response from notifications.signals import notify +from django.core.mail import EmailMessage +from django.template.loader import render_to_string +from Fusion.celery import app +from .forms import AnnouncementForm +from .models import Announcements, AnnouncementRecipients +from applications.globals.models import ExtraInfo +from applications.academic_information.models import Student +from django.contrib import messages +from django.db.models import Q # Create your views here. +def create_announcement(request, template_name='notifications/create_announcement.html', extra_context=None): + if request.method == 'POST': + form = AnnouncementForm(request.POST) + if form.is_valid(): + announcement = form.save(commit=False) + announcement.created_by = request.user + announcement.save() + + # If specific users are selected, create entries in AnnouncementRecipients + if form.cleaned_data['target_group'] == 'specific_users': + specific_users = form.cleaned_data['specific_users'] + for user in specific_users: + AnnouncementRecipients.objects.create( + announcement=announcement, + user=user + ) + + messages.success(request, 'Announcement created successfully.') + return redirect('/') + else: + form = AnnouncementForm() + # print(form) + context = {'form': form} + rendered_form = render_to_string('notifications/create_announcement.html', context, request=request) + context = {'rendered_form': rendered_form} + if extra_context: + context.update(extra_context) + + return render(request, template_name, context) + + +def announcement_list(request): + user_extrainfo = ExtraInfo.objects.filter(user=request.user).first() + if user_extrainfo.user_type == 'faculty': + announcements = Announcements.objects.filter( + Q(target_group='all') | + (Q(target_group='faculty') & (Q(department=user_extrainfo.department) | Q(department__isnull=True))) + ) + elif user_extrainfo.user_type == 'student': + student = Student.objects.filter(id=user_extrainfo).first() + announcements = Announcements.objects.filter( + Q(target_group='all') | + (Q(target_group='students') & + (Q(department=user_extrainfo.department) | Q(department__isnull=True)) & + (Q(batch=student.batch) | Q(batch__isnull=True)) + ) + ) + else: + announcements = Announcements.objects.filter(target_group='all') + + # Include specific user announcements + specific_announcements = Announcements.objects.filter(recipients__user=user_extrainfo) + + context = { + 'announcements': announcements | specific_announcements + } + return context + +@app.task +def send_notification_email(recipient_username, recipient_email, verb, module): + print("Trying to send notif.") + + # Make sure the recipient has an email address + if recipient_email: + subject = f"New Notification from {module}" + html_content = render_to_string('notifications/email_notification.html', { + 'recipient_username': recipient_username, + 'module': module, + 'verb': verb + }) + + email = EmailMessage( + subject, + html_content, + 'akashsah2003@gmail.com', # Replace with your email + [recipient_email] + ) + email.content_subtype = 'html' + email.send() + def leave_module_notif(sender, recipient, type, date=None): url = 'leave:leave' module = 'Leave Module' @@ -35,6 +124,7 @@ def leave_module_notif(sender, recipient, type, date=None): notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) + # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def placement_cell_notif(sender, recipient, type): @@ -46,6 +136,7 @@ def placement_cell_notif(sender, recipient, type): notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) + # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def academics_module_notif(sender, recipient, type): @@ -57,6 +148,7 @@ def academics_module_notif(sender, recipient, type): notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) + # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def office_module_notif(sender, recipient): @@ -68,6 +160,7 @@ def office_module_notif(sender, recipient): notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) + # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def central_mess_notif(sender, recipient, type, message=None): @@ -93,6 +186,7 @@ def central_mess_notif(sender, recipient, type, message=None): verb = "You have been added to the mess committee. " notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) + # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def placement_cellNotif(sender, recipient, type): url = 'placement:placement' @@ -155,6 +249,7 @@ def healthcare_center_notif(sender, recipient, type, message): elif type == 'rel_approved': verb = 'Your medical relief request has been approved' notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb, flag=flag) + # send_notification_email.delay(recipient.username, recipient.email, verb, module) def file_tracking_notif(sender, recipient, title): url = 'filetracking:inward' diff --git a/FusionIIIT/templates/dashboard/alerts1.html b/FusionIIIT/templates/dashboard/alerts1.html index ae78bdebd..f7513102a 100644 --- a/FusionIIIT/templates/dashboard/alerts1.html +++ b/FusionIIIT/templates/dashboard/alerts1.html @@ -3,47 +3,30 @@ {% block alerts1 %} {% load notifications_tags %}
- {% for notice in notifications %} + {% for notice in announcements %} {% comment %}A single notification starts here!{% endcomment %} - {% if notice.data.flag %} - {% if notice.unread %}
- {% else %} -
- {% endif %}
- {{ notice.timesince }} ago + At {{ notice.created_at }}
- {{ notice.verb }} - {% ifnotequal notice.actor notice.recipient %} - -by {{ notice.actor.first_name }} {{ notice.actor.last_name }} - {% endifnotequal %} + {{ notice.message }} + -by {{ notice.created_by.first_name }} {{ notice.created_by.last_name }}
- {% if notice.unread %} -
-
Mark as Read
-
- {% else %} -
-
Mark as Unread
-
- {% endif %}
- {% endif %} {% comment %}A single notification ends here!{% endcomment %} {% endfor %} diff --git a/FusionIIIT/templates/department/dep_request.html b/FusionIIIT/templates/department/dep_request.html index 37d375246..89de8b732 100644 --- a/FusionIIIT/templates/department/dep_request.html +++ b/FusionIIIT/templates/department/dep_request.html @@ -89,13 +89,7 @@ {% comment %}Make announcement{% endcomment %}
- {% block make_announcement %} - {% if user_designation == "faculty" %} - {% include "department/make_announcements_fac.html" %} - {% elif user_designation == "staff" %} - {% include "department/make_announcements_staff.html" %} - {% endif %} - {% endblock %} + {{ rendered_form|safe}}
{% comment %}Make announcement{% endcomment %} diff --git a/FusionIIIT/templates/notifications/create_announcement.html b/FusionIIIT/templates/notifications/create_announcement.html new file mode 100644 index 000000000..b1dea15a7 --- /dev/null +++ b/FusionIIIT/templates/notifications/create_announcement.html @@ -0,0 +1,76 @@ +{% load static %} +{% block make_announcements %} + +
+
+ {% csrf_token %} +
+ Make a new Announcement: +
+
+ + {{ form.non_field_errors }} + +
+ {{ form.message.label_tag }} + {{ form.message }} +
+ +
+ {{ form.target_group.label_tag }} + {{ form.target_group }} +
+ +
+ {{ form.department.label_tag }} + {{ form.department }} +
+ +
+ {{ form.batch.label_tag }} + {{ form.batch }} +
+ + +
+ {{ form.specific_users.label_tag }} + +
+ +
+ + +
+ +
+ + + +
+
+ +
+ +
+
+
+ +{% endblock %} + +{% block javascript %} + + + + + +{% endblock %} diff --git a/FusionIIIT/templates/notifications/email_notification.html b/FusionIIIT/templates/notifications/email_notification.html new file mode 100644 index 000000000..4b0afd755 --- /dev/null +++ b/FusionIIIT/templates/notifications/email_notification.html @@ -0,0 +1,70 @@ + + + + + + Notification Email + + + +
+
+

Notification

+
+
+

Dear {{ recipient_username }},

+

This is a notification email from Fusion.

+

{{ verb }}

+

Best regards,
Fusion Team, {{ module }}

+
+ +
+ + \ No newline at end of file From 08f7723e9ce52eb458c9745ec44da2c6c225fe9d Mon Sep 17 00:00:00 2001 From: Akash Kumar Sah Date: Thu, 7 Nov 2024 13:46:31 +0530 Subject: [PATCH 2/6] Added module to announcement schema and lazy loading to specific_users --- FusionIIIT/applications/department/views.py | 2 +- FusionIIIT/applications/globals/api/urls.py | 3 +- FusionIIIT/applications/globals/api/views.py | 16 ++++- FusionIIIT/notification/forms.py | 5 +- .../migrations/0002_announcements_module.py | 18 +++++ FusionIIIT/notification/models.py | 1 + FusionIIIT/notification/views.py | 26 +++++++- FusionIIIT/templates/dashboard/alerts1.html | 2 +- .../notifications/create_announcement.html | 66 +++++++++++++++++-- 9 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 FusionIIIT/notification/migrations/0002_announcements_module.py diff --git a/FusionIIIT/applications/department/views.py b/FusionIIIT/applications/department/views.py index f25a96218..249f4875c 100644 --- a/FusionIIIT/applications/department/views.py +++ b/FusionIIIT/applications/department/views.py @@ -383,7 +383,7 @@ def staff_view(request): # "fac_list": context_f, # "department_info": department_context # }) - return create_announcement(request, 'department/dep_request.html', {"user_designation": user_info.user_type}) + return create_announcement(request, 'department/dep_request.html', 'Department', {"user_designation": user_info.user_type}) diff --git a/FusionIIIT/applications/globals/api/urls.py b/FusionIIIT/applications/globals/api/urls.py index 53dd23f95..25deaf527 100644 --- a/FusionIIIT/applications/globals/api/urls.py +++ b/FusionIIIT/applications/globals/api/urls.py @@ -19,5 +19,6 @@ url(r'^notification/',views.notification,name='notification'), url(r'^notificationread',views.NotificationRead,name='notifications-read'), url(r'^notificationdelete',views.delete_notification,name='notifications-delete'), - url(r'^notificationunread',views.NotificationUnread,name='notifications-unread') + url(r'^notificationunread',views.NotificationUnread,name='notifications-unread'), + url(r'^search-users/', views.search_users, name='search_users'), ] diff --git a/FusionIIIT/applications/globals/api/views.py b/FusionIIIT/applications/globals/api/views.py index 0aff2180d..2f28cb84f 100644 --- a/FusionIIIT/applications/globals/api/views.py +++ b/FusionIIIT/applications/globals/api/views.py @@ -14,6 +14,7 @@ from rest_framework.decorators import api_view, permission_classes,authentication_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response +from django.http import JsonResponse from . import serializers @@ -376,4 +377,17 @@ def delete_notification(request): 'error': 'Failed to mark the notification as deleted.', 'details': str(e) } - return Response(response, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(response, status=status.HTTP_400_BAD_REQUEST) + + +def search_users(request): + query = request.GET.get('q', '') + if query: + users = ExtraInfo.objects.filter(user__username__icontains=query).values('id', 'user__username') + else: + users = ExtraInfo.objects.none() + + results = [ + {'id': user['id'], 'text': user['user__username']} for user in users + ] + return JsonResponse({'results': results}) \ No newline at end of file diff --git a/FusionIIIT/notification/forms.py b/FusionIIIT/notification/forms.py index b34f490a2..fe18ec6a9 100644 --- a/FusionIIIT/notification/forms.py +++ b/FusionIIIT/notification/forms.py @@ -5,10 +5,9 @@ class AnnouncementForm(forms.ModelForm): - specific_users = forms.ModelMultipleChoiceField( - queryset=ExtraInfo.objects.all(), + specific_users = forms.CharField( required=False, - widget=forms.CheckboxSelectMultiple + widget=forms.SelectMultiple(attrs={'class': 'ui fluid multiple search selection dropdown'}) ) class Meta: diff --git a/FusionIIIT/notification/migrations/0002_announcements_module.py b/FusionIIIT/notification/migrations/0002_announcements_module.py new file mode 100644 index 000000000..43537cf35 --- /dev/null +++ b/FusionIIIT/notification/migrations/0002_announcements_module.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.5 on 2024-11-07 13:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='announcements', + name='module', + field=models.CharField(default='Fusion', max_length=200), + ), + ] diff --git a/FusionIIIT/notification/models.py b/FusionIIIT/notification/models.py index c28d17a89..a6f1e697a 100644 --- a/FusionIIIT/notification/models.py +++ b/FusionIIIT/notification/models.py @@ -17,6 +17,7 @@ class Announcements(models.Model): department = models.ForeignKey(DepartmentInfo, on_delete=models.CASCADE, null=True, blank=True) batch = models.IntegerField(null=True, blank=True) upload_announcement = models.FileField(upload_to='notifications/upload_announcement', null=True, default=" ") + module = models.CharField(max_length=200, default='Fusion') def __str__(self): return str(self.created_by.username) diff --git a/FusionIIIT/notification/views.py b/FusionIIIT/notification/views.py index 3619f06a5..1906ad1f6 100644 --- a/FusionIIIT/notification/views.py +++ b/FusionIIIT/notification/views.py @@ -10,28 +10,48 @@ from applications.academic_information.models import Student from django.contrib import messages from django.db.models import Q +import ast # Create your views here. -def create_announcement(request, template_name='notifications/create_announcement.html', extra_context=None): +def create_announcement(request, template_name='notifications/create_announcement.html', module='Module', extra_context=None): if request.method == 'POST': form = AnnouncementForm(request.POST) + print(form) if form.is_valid(): announcement = form.save(commit=False) announcement.created_by = request.user + announcement.module = module announcement.save() # If specific users are selected, create entries in AnnouncementRecipients if form.cleaned_data['target_group'] == 'specific_users': specific_users = form.cleaned_data['specific_users'] - for user in specific_users: + print(specific_users, type(specific_users)) + # Split the input into individual user IDs and clean them + specific_user_ids = ast.literal_eval(specific_users) + print(specific_user_ids) + + # Fetch corresponding ExtraInfo objects for these user IDs + extra_info_users = ExtraInfo.objects.filter(id__in=specific_user_ids) + print(extra_info_users) + + # Create entries in AnnouncementRecipients for each valid user + for extra_info in extra_info_users: AnnouncementRecipients.objects.create( announcement=announcement, - user=user + user=extra_info # This links the ExtraInfo object, not User ) messages.success(request, 'Announcement created successfully.') return redirect('/') + else: + # Handle invalid form and return the errors to the template + messages.error(request, 'There were errors in the form. Please correct them and try again.') + context = {'form': form} # Pass form with errors back to the template + if extra_context: + context.update(extra_context) + return render(request, template_name, context) else: form = AnnouncementForm() # print(form) diff --git a/FusionIIIT/templates/dashboard/alerts1.html b/FusionIIIT/templates/dashboard/alerts1.html index f7513102a..47e5d2d95 100644 --- a/FusionIIIT/templates/dashboard/alerts1.html +++ b/FusionIIIT/templates/dashboard/alerts1.html @@ -8,7 +8,7 @@
- Module Name + {{ notice.module }} diff --git a/FusionIIIT/templates/notifications/create_announcement.html b/FusionIIIT/templates/notifications/create_announcement.html index b1dea15a7..aabf27fec 100644 --- a/FusionIIIT/templates/notifications/create_announcement.html +++ b/FusionIIIT/templates/notifications/create_announcement.html @@ -1,5 +1,20 @@ {% load static %} {% block make_announcements %} +{% if form.errors %} +
+
    + {% for field in form %} + {% if field.errors %} +
  • {{ field.label }}: + {% for error in field.errors %} +

    {{ error }}

    + {% endfor %} +
  • + {% endif %} + {% endfor %} +
+
+{% endif %}
@@ -34,9 +49,9 @@
{{ form.specific_users.label_tag }} - +
@@ -62,15 +77,52 @@ {% endblock %} {% block javascript %} + + {% endblock %} From aab8ae468a165c8f06bbf1dbe7c5c8572077f276 Mon Sep 17 00:00:00 2001 From: Akash Kumar Sah Date: Fri, 8 Nov 2024 19:36:56 +0530 Subject: [PATCH 3/6] Added create announcement and get announcement apis and added redis as dependency --- .../api/serializers.py | 47 ++++++++++++++++++- .../notifications_extension/api/urls.py | 4 ++ .../notifications_extension/api/views.py | 42 +++++++++++++++-- 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/FusionIIIT/applications/notifications_extension/api/serializers.py b/FusionIIIT/applications/notifications_extension/api/serializers.py index 899c50e57..08a7f7948 100644 --- a/FusionIIIT/applications/notifications_extension/api/serializers.py +++ b/FusionIIIT/applications/notifications_extension/api/serializers.py @@ -1,6 +1,51 @@ from rest_framework import serializers from notifications.models import Notification +from notification.models import Announcements, AnnouncementRecipients +from applications.globals.models import ExtraInfo, DepartmentInfo class NotificationSerializer(serializers.ModelSerializer): class Meta: model = Notification - fields = '__all__' \ No newline at end of file + fields = '__all__' + +class AnnouncementListSerializer(serializers.ModelSerializer): + class Meta: + model = Announcements + fields = '__all__' # Specify the fields you need + + +class AnnouncementSerializer(serializers.ModelSerializer): + specific_users = serializers.ListField( + child=serializers.IntegerField(), write_only=True, required=False + ) # List of specific user IDs for specific_users target group + + class Meta: + model = Announcements + fields = ['message', 'target_group', 'department', 'batch', 'module', 'specific_users'] + + def validate(self, data): + target_group = data.get('target_group') + + # Validate 'faculty' target group: department must be provided + if target_group == 'faculty' and not data.get('department'): + raise serializers.ValidationError("Department is required for faculty announcements.") + + # Validate 'students' target group: department and batch must be provided + if target_group == 'students': + if not data.get('department'): + raise serializers.ValidationError("Department is required for student announcements.") + if not data.get('batch'): + raise serializers.ValidationError("Batch is required for student announcements.") + + return data + + def create(self, validated_data): + specific_users = validated_data.pop('specific_users', []) + announcement = Announcements.objects.create(**validated_data) + + # Handle specific_users for AnnouncementRecipients + if validated_data['target_group'] == 'specific_users': + extra_info_users = ExtraInfo.objects.filter(id__in=specific_users) + for extra_info in extra_info_users: + AnnouncementRecipients.objects.create(announcement=announcement, user=extra_info) + + return announcement \ No newline at end of file diff --git a/FusionIIIT/applications/notifications_extension/api/urls.py b/FusionIIIT/applications/notifications_extension/api/urls.py index 62238c007..78862e821 100644 --- a/FusionIIIT/applications/notifications_extension/api/urls.py +++ b/FusionIIIT/applications/notifications_extension/api/urls.py @@ -24,6 +24,8 @@ MarkAsRead, Delete, NotificationsList, + AnnouncementCreateView, + AnnouncementListView, ) urlpatterns = [ @@ -50,4 +52,6 @@ path('office_dean_RSPC_notification/', OfficeDeanRSPCNotificationAPIView.as_view(), name='office_dean_RSPC_notification'), path('research_procedures_notification/', ResearchProceduresNotificationAPIView.as_view(), name='research_procedures_notification'), path('hostel_notifications/', HostelModuleNotificationAPIView.as_view(), name='hostel_notifications'), + path('announcements/create', AnnouncementCreateView.as_view(), name='announcement_create'), + path('announcements/', AnnouncementListView.as_view(), name='announcement_list') ] diff --git a/FusionIIIT/applications/notifications_extension/api/views.py b/FusionIIIT/applications/notifications_extension/api/views.py index c5017f79f..d45525682 100644 --- a/FusionIIIT/applications/notifications_extension/api/views.py +++ b/FusionIIIT/applications/notifications_extension/api/views.py @@ -7,7 +7,7 @@ from rest_framework.generics import ListAPIView from notifications.models import Notification from rest_framework import status -from .serializers import NotificationSerializer +from .serializers import NotificationSerializer, AnnouncementSerializer, AnnouncementListSerializer from notification.views import (leave_module_notif, placement_cell_notif, academics_module_notif, @@ -30,7 +30,8 @@ department_notif, office_module_DeanRSPC_notif, research_procedures_notif, - hostel_notifications) + hostel_notifications, + announcement_list) @@ -368,4 +369,39 @@ class NotificationsList(ListAPIView): # queryset = Notification.objects.all(actor_object_id=) serializer_class = NotificationSerializer def get_queryset(self): - return Notification.objects.all().filter(recipient_id=self.request.user.id) \ No newline at end of file + return Notification.objects.all().filter(recipient_id=self.request.user.id) + +class AnnouncementCreateView(APIView): + def post(self, request): + # Extract module from request or default to 'Fusion' + module = request.data.get('module', 'Fusion') + request.data['module'] = module + + # Initialize serializer with the request data + serializer = AnnouncementSerializer(data=request.data) + if serializer.is_valid(): + # Save the announcement with the current user as 'created_by' + serializer.save(created_by=request.user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + # Return error response if data is invalid + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class AnnouncementListView(APIView): + """ + API View that reuses the announcement_list function to fetch announcements for the user + and return them via an API response. + """ + + def get(self, request): + # Call the existing announcement_list function + announcement_context = announcement_list(request) + + # Get the queryset of announcements from the context + announcements = announcement_context['announcements'] + + # Serialize the queryset + serializer = AnnouncementListSerializer(announcements, many=True) + + # Return the serialized data as JSON response + return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file From 6ee8db85dfb131d3d7aebb3ba34b1de2b1353631 Mon Sep 17 00:00:00 2001 From: Akash Kumar Sah Date: Fri, 8 Nov 2024 14:19:33 +0530 Subject: [PATCH 4/6] Sorted GET Announcement result order --- FusionIIIT/notification/views.py | 10 +++++----- requirements.txt | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/FusionIIIT/notification/views.py b/FusionIIIT/notification/views.py index 1906ad1f6..722bbfb9b 100644 --- a/FusionIIIT/notification/views.py +++ b/FusionIIIT/notification/views.py @@ -70,7 +70,7 @@ def announcement_list(request): announcements = Announcements.objects.filter( Q(target_group='all') | (Q(target_group='faculty') & (Q(department=user_extrainfo.department) | Q(department__isnull=True))) - ) + ).order_by('-created_at') elif user_extrainfo.user_type == 'student': student = Student.objects.filter(id=user_extrainfo).first() announcements = Announcements.objects.filter( @@ -79,15 +79,15 @@ def announcement_list(request): (Q(department=user_extrainfo.department) | Q(department__isnull=True)) & (Q(batch=student.batch) | Q(batch__isnull=True)) ) - ) + ).order_by('-created_at') else: announcements = Announcements.objects.filter(target_group='all') # Include specific user announcements - specific_announcements = Announcements.objects.filter(recipients__user=user_extrainfo) + specific_announcements = Announcements.objects.filter(recipients__user=user_extrainfo).order_by('-created_at') context = { - 'announcements': announcements | specific_announcements + 'announcements': (announcements | specific_announcements).distinct().order_by('-created_at') } return context @@ -192,6 +192,7 @@ def central_mess_notif(sender, recipient, type, message=None): if type == 'feedback_submitted': verb = 'Your feedback has been successfully submitted.' + send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) elif type == 'menu_change_accepted': verb = 'Menu request has been approved' elif type == 'leave_request': @@ -206,7 +207,6 @@ def central_mess_notif(sender, recipient, type, message=None): verb = "You have been added to the mess committee. " notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) - # send_notification_email(sender=sender, recipient=recipient, url=url, module=module, verb=verb) def placement_cellNotif(sender, recipient, type): url = 'placement:placement' diff --git a/requirements.txt b/requirements.txt index 4b97f144f..f6a4b0aa8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -55,6 +55,7 @@ python-bidi==0.4.2 python-dateutil==2.9.0 python3-openid==3.2.0 pytz==2020.5 +redis==5.1.1 reportlab==3.5.59 requests==2.25.1 requests-oauthlib==1.3.0 From 7bde1384e1ce64d9c698d1ae824ef79efcb0978b Mon Sep 17 00:00:00 2001 From: Aarav Jain <128894052+aarav0012@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:44:26 +0530 Subject: [PATCH 5/6] department info api (#1) Co-authored-by: aarav0012 --- FusionIIIT/applications/globals/api/urls.py | 5 +++++ FusionIIIT/applications/globals/api/views.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/FusionIIIT/applications/globals/api/urls.py b/FusionIIIT/applications/globals/api/urls.py index 25deaf527..f25b901b9 100644 --- a/FusionIIIT/applications/globals/api/urls.py +++ b/FusionIIIT/applications/globals/api/urls.py @@ -1,4 +1,6 @@ from django.conf.urls import url +from django.urls import path +from .views import department_info from . import views @@ -21,4 +23,7 @@ url(r'^notificationdelete',views.delete_notification,name='notifications-delete'), url(r'^notificationunread',views.NotificationUnread,name='notifications-unread'), url(r'^search-users/', views.search_users, name='search_users'), + # url(r'^department-info/', views.department_info, name='department_info'), + path('department-info/', department_info, name='department-info'), + ] diff --git a/FusionIIIT/applications/globals/api/views.py b/FusionIIIT/applications/globals/api/views.py index 2f28cb84f..953fdc268 100644 --- a/FusionIIIT/applications/globals/api/views.py +++ b/FusionIIIT/applications/globals/api/views.py @@ -390,4 +390,18 @@ def search_users(request): results = [ {'id': user['id'], 'text': user['user__username']} for user in users ] - return JsonResponse({'results': results}) \ No newline at end of file + return JsonResponse({'results': results}) + +@api_view(['GET']) # Declare that this view handles GET requests +@permission_classes([]) # No permissions required +@authentication_classes([]) # No authentication required +def department_info(request): + """ + Retrieve department information and return as JSON. + """ + try: + departments = DepartmentInfo.objects.all() # Fetch all department objects + serializer = serializers.DepartmentInfoSerializer(departments, many=True) # Serialize the data + return Response(serializer.data, status=status.HTTP_200_OK) # Return serialized data as JSON + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) From 144f26eb812f090c3d3678dfc949d74d67f38a7a Mon Sep 17 00:00:00 2001 From: aarav0012 Date: Mon, 27 Jan 2025 23:46:35 +0530 Subject: [PATCH 6/6] rspc notification api added --- .../notifications_extension/api/urls.py | 4 +++- .../notifications_extension/api/views.py | 18 +++++++++++++++-- FusionIIIT/notification/views.py | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/FusionIIIT/applications/notifications_extension/api/urls.py b/FusionIIIT/applications/notifications_extension/api/urls.py index 78862e821..c3f42cb17 100644 --- a/FusionIIIT/applications/notifications_extension/api/urls.py +++ b/FusionIIIT/applications/notifications_extension/api/urls.py @@ -26,6 +26,7 @@ NotificationsList, AnnouncementCreateView, AnnouncementListView, + RSPCNotificationAPIView ) urlpatterns = [ @@ -53,5 +54,6 @@ path('research_procedures_notification/', ResearchProceduresNotificationAPIView.as_view(), name='research_procedures_notification'), path('hostel_notifications/', HostelModuleNotificationAPIView.as_view(), name='hostel_notifications'), path('announcements/create', AnnouncementCreateView.as_view(), name='announcement_create'), - path('announcements/', AnnouncementListView.as_view(), name='announcement_list') + path('announcements/', AnnouncementListView.as_view(), name='announcement_list'), + path('RSPC_notif', RSPCNotificationAPIView.as_view(), name='RSPC_notification') ] diff --git a/FusionIIIT/applications/notifications_extension/api/views.py b/FusionIIIT/applications/notifications_extension/api/views.py index d45525682..e4c125786 100644 --- a/FusionIIIT/applications/notifications_extension/api/views.py +++ b/FusionIIIT/applications/notifications_extension/api/views.py @@ -31,7 +31,8 @@ office_module_DeanRSPC_notif, research_procedures_notif, hostel_notifications, - announcement_list) + announcement_list, + RSPC_notif) @@ -404,4 +405,17 @@ def get(self, request): serializer = AnnouncementListSerializer(announcements, many=True) # Return the serialized data as JSON response - return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file + return Response(serializer.data, status=status.HTTP_200_OK) + +class RSPCNotificationAPIView(APIView): + def post(self, request, *args, **kwargs): + + sender = request.user + recipient_id = request.data.get('recipient') + type = request.data.get('type') + User = get_user_model() + recipient = User.objects.get(pk=recipient_id) + + RSPC_notif(sender,recipient, type) + + return Response({'message' : 'Notification sent successfully'}, status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/FusionIIIT/notification/views.py b/FusionIIIT/notification/views.py index 722bbfb9b..5f134a9a7 100644 --- a/FusionIIIT/notification/views.py +++ b/FusionIIIT/notification/views.py @@ -552,6 +552,26 @@ def office_module_DeanRSPC_notif(sender, recipient, type): notify.send(sender=sender, recipient=recipient, url=url, module=module, verb=verb) +def RSPC_notif(sender, recipient, type): + url = 'rspc' + module = 'RSPC' + sender = sender + recipient = recipient + verb = "" + + if type == "Approved": + verb = "Your request has been approved." + elif type == "Rejected": + verb = "Your request has been rejected." + elif type == "Processing": + verb = "You have a new request to process." + elif type == "Created": + verb = "Your project has been added to RSPC." + elif type == "Forwording": + verb = f"Your request has been forworded to {sender.username}. Kindly wait for decision" + + notify.send(sender=sender, recipient=recipient, + url=url, module=module, verb=verb) def research_procedures_notif(sender, recipient, type): url = 'research_procedures:patent_registration'