From 81cfb043f06ab71da7c021a063f80f6df58305cc Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Wed, 24 Oct 2018 15:12:32 -0400 Subject: Rewrite Notification subsystem In this commit: - delete a lot of really bad and / or unused code - redesign a much simpler Notification model - create and send notifications to the user's inbox on booking start & end - migrations - emails user when booking is ready and when it ends Not in this commit: - Creating notifications from lab messages - warning messages when a booking is about to end - creating "summary" notifications when e.g. a booking has been fulfilled by a lab Change-Id: I69b4dc36c3f2bce76d810106baadeef5a562cc7d Signed-off-by: Parker Berberian --- dashboard/src/notifier/admin.py | 6 +- dashboard/src/notifier/dispatchers.py | 33 ---- dashboard/src/notifier/manager.py | 181 ++++++++++++--------- .../notifier/migrations/0002_auto_20181102_1631.py | 44 +++++ dashboard/src/notifier/models.py | 40 +---- dashboard/src/notifier/views.py | 11 +- 6 files changed, 161 insertions(+), 154 deletions(-) delete mode 100644 dashboard/src/notifier/dispatchers.py create mode 100644 dashboard/src/notifier/migrations/0002_auto_20181102_1631.py (limited to 'dashboard/src/notifier') diff --git a/dashboard/src/notifier/admin.py b/dashboard/src/notifier/admin.py index d3e8be5..4a2984c 100644 --- a/dashboard/src/notifier/admin.py +++ b/dashboard/src/notifier/admin.py @@ -9,8 +9,6 @@ from django.contrib import admin -from notifier.models import * +from notifier.models import Notification -admin.site.register(Notifier) -admin.site.register(MetaBooking) -admin.site.register(LabMessage) +admin.site.register(Notification) diff --git a/dashboard/src/notifier/dispatchers.py b/dashboard/src/notifier/dispatchers.py deleted file mode 100644 index 1b66b37..0000000 --- a/dashboard/src/notifier/dispatchers.py +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from django.db.models.signals import pre_save -from django.dispatch import receiver -from django.contrib import messages -from django.core.mail import send_mail - -class DispatchHandler(): - - @receiver(pre_save, sender='notifier.Notifier') - def dispatch(sender, instance, *args, **kwargs): - try: - msg_type = getattr(DispatchHandler, instance.message_type) - msg_type(instance) - except AttributeError: - instance.msg_sent = 'no dispatcher by given name exists: sending by email' - email(instance) - - def email(instance): - if instance.msg_sent != 'no dispatcher by given name exists: sending by email': - instance.msg_sent = 'by email' - send_mail(instance.title,instance.content, - instance.sender,[instance.user.email_addr], fail_silently=False) - - def webnotification(instance): - instance.msg_sent='by web notification' diff --git a/dashboard/src/notifier/manager.py b/dashboard/src/notifier/manager.py index a705d00..cc1aa16 100644 --- a/dashboard/src/notifier/manager.py +++ b/dashboard/src/notifier/manager.py @@ -1,98 +1,125 @@ ############################################################################## -# Copyright (c) 2018 Sawyer Bergeron and others. +# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## +import os +from notifier.models import Notification -from booking.models import * -from notifier.models import Notifier, MetaBooking, LabMessage -from django.utils import timezone -from datetime import timedelta -from django.template import Template, Context -from account.models import UserProfile +from django.core.mail import send_mail +from django.template.loader import render_to_string -from django.db import models -class NotifyPeriodic(object): - def task(): - bookings_new = Booking.objects.filter(metabooking__isnull=True) - bookings_old = Booking.objects.filter(end__lte=timezone.now() + timedelta(hours=24)).filter(metabooking__ended_notified=False) +class NotificationHandler(object): - for booking in bookings_old: - metabooking = booking.metabooking - if booking.end <= timezone.now() + timedelta(hours=24): - if not metabooking.ending_notified: - Notify().notify(Notify.TOCLEAN, booking) - metabooking.ending_notified = True - metabooking.save() - if booking.end <= timezone.now(): - metabooking = booking.metabooking - if not metabooking.ended_notified: - Notify().notify(Notify.CLEANED, booking) - metabooking.ended_notified = True - metabooking.save() + @classmethod + def notify_new_booking(cls, booking): + template = "notifier/new_booking.html" + titles = ["You have a new Booking", "You have been added to a Booking"] + cls.booking_notify(booking, template, titles) - for booking in bookings_new: - metabooking = MetaBooking() - metabooking.booking = booking - metabooking.created_notified = True - metabooking.save() + @classmethod + def notify_booking_end(cls, booking): + template = "notifier/end_booking.html" + titles = ["Your booking has ended", "A booking you collaborate on has ended"] + cls.booking_notify(booking, template, titles) - Notify().notify(Notify.CREATED, booking) + @classmethod + def booking_notify(cls, booking, template, titles): + """ + Creates a notification for a booking owner and collaborators + using the template. + titles is a list - the first is the title for the owner's notification, + the last is the title for the collaborators' + """ + owner_notif = Notification.objects.create( + title=titles[0], + content=render_to_string(template, context={ + "booking": booking, + "owner": True + }) + ) + owner_notif.recipients.add(booking.owner) + if not booking.collaborators.all().exists(): + return # no collaborators - were done + collab_notif = Notification.objects.create( + title=titles[-1], + content=render_to_string(template, context={ + "booking": booking, + "owner": False + }) + ) + for c in booking.collaborators.all(): + collab_notif.recipients.add(c) -class Notify(object): + @classmethod + def email_job_fulfilled(cls, job): + template_name = "notifier/email_fulfilled.txt" + all_tasks = job.get_tasklist() + users = list(job.booking.collaborators.all()) + users.append(job.booking.owner) + for user in users: + user_tasklist = [] + # gather up all the relevant messages from the lab + for task in all_tasks: + if (not hasattr(task, "user")) or task.user == user: + user_tasklist.append({ + "title": task.type_str + " Message: ", + "content": task.message + }) + # gather up all the other needed info + context = { + "user_name": user.userprofile.full_name, + "messages": user_tasklist, + "booking_url": os.environ.get("DASHBOARD_URL", "") + "/booking/detail/" + str(job.booking.id) + "/" + } - CREATED = "created" - TOCLEAN = "toclean" - CLEANED = "cleaned" + # render email template + message = render_to_string(template_name, context) - TITLES = {} - TITLES["created"] = "Your booking has been confirmed" - TITLES["toclean"] = "Your booking is ending soon" - TITLES["cleaned"] = "Your booking has ended" + # finally, send the email + send_mail( + "Your Booking is Ready", + message, + os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"), + user.userprofile.email_addr, + fail_silently=False + ) - """ - Lab message is provided with the following context elements: - * if is for owner or for collaborator (if owner) - * recipient username (.username) - * recipient full name (.userprofile.full_name) - * booking it pertains to (booking) - * status message should convey (currently "created", "toclean" and "cleaned" as strings) - It should be a django template that can be rendered with these context elements - and should generally use all of them in one way or another. - It should be applicable to email, the web based general view, and should be scalable for - all device formats across those mediums. - """ - def notify(self, notifier_type, booking): - template = Template(LabMessage.objects.filter(lab=booking.lab).first().msg) + @classmethod + def email_booking_over(cls, booking): + template_name = "notifier/email_ended.txt" + hostnames = [host.template.resource.name for host in booking.resource.hosts.all()] + users = list(booking.collaborators.all()) + users.append(booking.owner) + for user in users: + context = { + "user_name": user.userprofile.full_name, + "booking": booking, + "hosts": hostnames, + "booking_url": os.environ.get("DASHBOARD_URL", "") + "/booking/detail/" + str(booking.id) + "/" + } - context = {} - context["owner"] = booking.owner - context["notify_type"] = notifier_type - context["booking"] = booking - message = template.render(Context(context)) - notifier = Notifier() - notifier.title = self.TITLES[notifier_type] - notifier.content = message - notifier.user = booking.owner.userprofile - notifier.sender = str(booking.lab) - notifier.save() - notifier.send() + message = render_to_string(template_name, context) + send_mail( + "Your Booking has Expired", + message, + os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"), + user.userprofile.email_addr, + fail_silently=False + ) - context["owner"] = False - - for user in booking.collaborators.all(): - context["collaborator"] = user - message = template.render(Context(context)) - notifier = Notifier() - notifier.title = self.TITLES[notifier_type] - notifier.content = message - notifier.user = UserProfile.objects.get(user=user) - notifier.sender = str(booking.lab) - notifier.save() - notifier.send() + @classmethod + def task_updated(cls, task): + """ + called every time a lab updated info about a task. + currently only checks if the job is now done so I can send an email, + may add more functionality later + """ + if task.job.is_fulfilled(): + cls.email_job_fulfilled(task.job) diff --git a/dashboard/src/notifier/migrations/0002_auto_20181102_1631.py b/dashboard/src/notifier/migrations/0002_auto_20181102_1631.py new file mode 100644 index 0000000..e5fef89 --- /dev/null +++ b/dashboard/src/notifier/migrations/0002_auto_20181102_1631.py @@ -0,0 +1,44 @@ +# Generated by Django 2.1 on 2018-11-02 16:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_publicnetwork'), + ('notifier', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=150)), + ('content', models.TextField()), + ('recipients', models.ManyToManyField(to='account.UserProfile')), + ], + ), + migrations.RemoveField( + model_name='labmessage', + name='lab', + ), + migrations.RemoveField( + model_name='metabooking', + name='booking', + ), + migrations.RemoveField( + model_name='notifier', + name='user', + ), + migrations.DeleteModel( + name='LabMessage', + ), + migrations.DeleteModel( + name='MetaBooking', + ), + migrations.DeleteModel( + name='Notifier', + ), + ] diff --git a/dashboard/src/notifier/models.py b/dashboard/src/notifier/models.py index ed0edeb..5e7c60e 100644 --- a/dashboard/src/notifier/models.py +++ b/dashboard/src/notifier/models.py @@ -8,44 +8,16 @@ ############################################################################## from django.db import models -from booking.models import Booking from account.models import UserProfile -from fernet_fields import EncryptedTextField -from account.models import Lab -class MetaBooking(models.Model): - id = models.AutoField(primary_key=True) - booking = models.OneToOneField(Booking, on_delete=models.CASCADE, related_name="metabooking") - ending_notified = models.BooleanField(default=False) - ended_notified = models.BooleanField(default=False) - created_notified = models.BooleanField(default=False) - - -class LabMessage(models.Model): - lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - msg = models.TextField() # django template should be put here - - -class Notifier(models.Model): - id = models.AutoField(primary_key=True) - title = models.CharField(max_length=240) - content = EncryptedTextField() - user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True) - sender = models.CharField(max_length=240, default='unknown') - message_type = models.CharField(max_length=240, default='email', choices=( - ('email', 'Email'), - ('webnotification', 'Web Notification'))) - msg_sent = '' +class Notification(models.Model): + title = models.CharField(max_length=150) + content = models.TextField() + recipients = models.ManyToManyField(UserProfile) def __str__(self): return self.title - """ - Implement for next PR: send Notifier by media agreed to by user - """ - def send(self): - pass - - def getEmail(self): - return self.user.email_addr + def to_preview_html(self): + return "

" + self.title + "

" # TODO - template? diff --git a/dashboard/src/notifier/views.py b/dashboard/src/notifier/views.py index 026894a..c1a2f7e 100644 --- a/dashboard/src/notifier/views.py +++ b/dashboard/src/notifier/views.py @@ -7,28 +7,27 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from notifier.models import * +from notifier.models import Notification from django.shortcuts import render + def InboxView(request): if request.user.is_authenticated: user = request.user else: return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - return render(request, "notifier/inbox.html", {'notifier_messages': Notifier.objects.filter(user=user.userprofile)}) + return render(request, "notifier/inbox.html", {'notifications': Notification.objects.filter(recipient=user.userprofile)}) def NotificationView(request, notification_id): - if notification_id == 0: - pass if request.user.is_authenticated: user = request.user else: return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - notification = Notifier.objects.get(id=notification_id) - if not notification.user.user.username == user.username: + notification = Notification.objects.get(id=notification_id) + if user not in notification.recipients: return render(request, "dashboard/login.html", {'title': 'Access Denied'}) return render(request, "notifier/notification.html", {'notification': notification}) -- cgit 1.2.3-korg