diff options
Diffstat (limited to 'dashboard/src/notifier')
-rw-r--r-- | dashboard/src/notifier/__init__.py | 8 | ||||
-rw-r--r-- | dashboard/src/notifier/admin.py | 6 | ||||
-rw-r--r-- | dashboard/src/notifier/apps.py | 3 | ||||
-rw-r--r-- | dashboard/src/notifier/dispatchers.py | 33 | ||||
-rw-r--r-- | dashboard/src/notifier/manager.py | 133 | ||||
-rw-r--r-- | dashboard/src/notifier/migrations/0001_initial.py | 25 | ||||
-rw-r--r-- | dashboard/src/notifier/migrations/0002_auto_20181102_1631.py | 44 | ||||
-rw-r--r-- | dashboard/src/notifier/migrations/0003_auto_20190123_1741.py | 23 | ||||
-rw-r--r-- | dashboard/src/notifier/migrations/0004_auto_20190124_2115.py | 23 | ||||
-rw-r--r-- | dashboard/src/notifier/migrations/0005_auto_20190306_1616.py | 18 | ||||
-rw-r--r-- | dashboard/src/notifier/models.py | 31 | ||||
-rw-r--r-- | dashboard/src/notifier/tests/test_dispatcher.py | 15 | ||||
-rw-r--r-- | dashboard/src/notifier/tests/test_models.py | 30 | ||||
-rw-r--r-- | dashboard/src/notifier/urls.py | 19 | ||||
-rw-r--r-- | dashboard/src/notifier/views.py | 58 |
15 files changed, 406 insertions, 63 deletions
diff --git a/dashboard/src/notifier/__init__.py b/dashboard/src/notifier/__init__.py index e69de29..d65b13a 100644 --- a/dashboard/src/notifier/__init__.py +++ b/dashboard/src/notifier/__init__.py @@ -0,0 +1,8 @@ +############################################################################## +# Copyright (c) 2018 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 +############################################################################## diff --git a/dashboard/src/notifier/admin.py b/dashboard/src/notifier/admin.py index cfbe778..4a2984c 100644 --- a/dashboard/src/notifier/admin.py +++ b/dashboard/src/notifier/admin.py @@ -1,5 +1,5 @@ ############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. +# 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 @@ -9,6 +9,6 @@ from django.contrib import admin -from notifier.models import * +from notifier.models import Notification -admin.site.register(Notifier) +admin.site.register(Notification) diff --git a/dashboard/src/notifier/apps.py b/dashboard/src/notifier/apps.py index da5d3b0..52902da 100644 --- a/dashboard/src/notifier/apps.py +++ b/dashboard/src/notifier/apps.py @@ -1,5 +1,5 @@ ############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. +# 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 @@ -7,6 +7,7 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## + from django.apps import AppConfig diff --git a/dashboard/src/notifier/dispatchers.py b/dashboard/src/notifier/dispatchers.py deleted file mode 100644 index c35fe2b..0000000 --- a/dashboard/src/notifier/dispatchers.py +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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'
\ No newline at end of file diff --git a/dashboard/src/notifier/manager.py b/dashboard/src/notifier/manager.py new file mode 100644 index 0000000..240cf85 --- /dev/null +++ b/dashboard/src/notifier/manager.py @@ -0,0 +1,133 @@ +############################################################################## +# 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 django.core.mail import send_mail +from django.template.loader import render_to_string + + +class NotificationHandler(object): + + @classmethod + def notify_new_booking(cls, booking): + template = "notifier/new_booking.html" + titles = ["You have a new booking (" + str(booking.id) + ")", "You have been added to a booking (" + str(booking.id) + ")"] + cls.booking_notify(booking, template, titles) + + @classmethod + def notify_booking_end(cls, booking): + template = "notifier/end_booking.html" + titles = ["Your booking (" + str(booking.id) + ") has ended", "A booking (" + str(booking.id) + ") that you collaborate on has ended"] + cls.booking_notify(booking, template, titles) + + @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.userprofile) + 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.userprofile) + + @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", "<Dashboard url>") + "/booking/detail/" + str(job.booking.id) + "/" + } + + # render email template + message = render_to_string(template_name, context) + + # 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 + ) + + @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", "<Dashboard url>") + "/booking/detail/" + str(booking.id) + "/" + } + + 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 + ) + + @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/0001_initial.py b/dashboard/src/notifier/migrations/0001_initial.py index cac4d04..e5d0009 100644 --- a/dashboard/src/notifier/migrations/0001_initial.py +++ b/dashboard/src/notifier/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2017-12-14 21:41 -from __future__ import unicode_literals +# Generated by Django 2.1 on 2018-09-14 14:48 from django.db import migrations, models import django.db.models.deletion @@ -12,11 +10,30 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('dashboard', '0002_auto_20170505_0815'), + ('account', '0001_initial'), + ('booking', '0001_initial'), ] operations = [ migrations.CreateModel( + name='LabMessage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('msg', models.TextField()), + ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), + ], + ), + migrations.CreateModel( + name='MetaBooking', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('ending_notified', models.BooleanField(default=False)), + ('ended_notified', models.BooleanField(default=False)), + ('created_notified', models.BooleanField(default=False)), + ('booking', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='metabooking', to='booking.Booking')), + ], + ), + migrations.CreateModel( name='Notifier', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), 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/migrations/0003_auto_20190123_1741.py b/dashboard/src/notifier/migrations/0003_auto_20190123_1741.py new file mode 100644 index 0000000..f491993 --- /dev/null +++ b/dashboard/src/notifier/migrations/0003_auto_20190123_1741.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1 on 2019-01-23 17:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifier', '0002_auto_20181102_1631'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='is_html', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='notification', + name='is_read', + field=models.BooleanField(default=True), + ), + ] diff --git a/dashboard/src/notifier/migrations/0004_auto_20190124_2115.py b/dashboard/src/notifier/migrations/0004_auto_20190124_2115.py new file mode 100644 index 0000000..306ec7b --- /dev/null +++ b/dashboard/src/notifier/migrations/0004_auto_20190124_2115.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1 on 2019-01-24 21:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_publicnetwork'), + ('notifier', '0003_auto_20190123_1741'), + ] + + operations = [ + migrations.RemoveField( + model_name='notification', + name='is_read', + ), + migrations.AddField( + model_name='notification', + name='read_by', + field=models.ManyToManyField(related_name='read_notifications', to='account.UserProfile'), + ), + ] diff --git a/dashboard/src/notifier/migrations/0005_auto_20190306_1616.py b/dashboard/src/notifier/migrations/0005_auto_20190306_1616.py new file mode 100644 index 0000000..d92c988 --- /dev/null +++ b/dashboard/src/notifier/migrations/0005_auto_20190306_1616.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1 on 2019-03-06 16:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifier', '0004_auto_20190124_2115'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='recipients', + field=models.ManyToManyField(related_name='notifications', to='account.UserProfile'), + ), + ] diff --git a/dashboard/src/notifier/models.py b/dashboard/src/notifier/models.py index 9ebc6fc..49189e8 100644 --- a/dashboard/src/notifier/models.py +++ b/dashboard/src/notifier/models.py @@ -1,5 +1,5 @@ ############################################################################## -# Copyright (c) 2016 Max Breitenfeldt and others. +# 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 @@ -8,31 +8,18 @@ ############################################################################## from django.db import models -from jira import JIRA, JIRAError -from dashboard.models import Resource -from booking.models import Booking -from django.contrib.auth.models import User from account.models import UserProfile -from django.contrib import messages -from django.db.models.signals import pre_save -from fernet_fields import EncryptedTextField -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 = '' - import notifier.dispatchers +class Notification(models.Model): + title = models.CharField(max_length=150) + content = models.TextField() + recipients = models.ManyToManyField(UserProfile, related_name='notifications') + is_html = models.BooleanField(default=True) + read_by = models.ManyToManyField(UserProfile, related_name='read_notifications') def __str__(self): return self.title - def getEmail(self): - return self.user.email_addr - + def to_preview_html(self): + return "<h3>" + self.title + "</h3>" # TODO - template? diff --git a/dashboard/src/notifier/tests/test_dispatcher.py b/dashboard/src/notifier/tests/test_dispatcher.py new file mode 100644 index 0000000..086f621 --- /dev/null +++ b/dashboard/src/notifier/tests/test_dispatcher.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2018 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 +############################################################################## + +from django.test import TestCase + + +class DispatchTestCase(TestCase): + # This is a stub, it will be filled out as this feature is remade with saner practices. + pass diff --git a/dashboard/src/notifier/tests/test_models.py b/dashboard/src/notifier/tests/test_models.py new file mode 100644 index 0000000..d332254 --- /dev/null +++ b/dashboard/src/notifier/tests/test_models.py @@ -0,0 +1,30 @@ +############################################################################## +# Copyright (c) 2018 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 +############################################################################## + + +from django.test import TestCase +from notifier.models import Notifier +from django.contrib.auth.models import User + + +class NotifierTestCase(TestCase): + + def test_valid_notifier_saves(self): + + sender = User.objects.create() + recipient = User.objects.create() + self.assertTrue( + Notifier.objects.create( + title='notification title', + content='notification body', + user=recipient, + sender=sender, + message_type='email' + ) + ) diff --git a/dashboard/src/notifier/urls.py b/dashboard/src/notifier/urls.py new file mode 100644 index 0000000..fedb9e8 --- /dev/null +++ b/dashboard/src/notifier/urls.py @@ -0,0 +1,19 @@ +############################################################################## +# 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.conf.urls import url + +from notifier.views import InboxView, NotificationView + +app_name = "notifier" +urlpatterns = [ + url(r'^$', InboxView, name='messages'), + url(r'^notification/(?P<notification_id>[0-9]+)/$', NotificationView, name='notifier_single') +] diff --git a/dashboard/src/notifier/views.py b/dashboard/src/notifier/views.py new file mode 100644 index 0000000..3a85eda --- /dev/null +++ b/dashboard/src/notifier/views.py @@ -0,0 +1,58 @@ +############################################################################## +# Copyright (c) 2018 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 +############################################################################## + +from django.shortcuts import render +from notifier.models import Notification +from django.db.models import Q + + +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", + {'unread_notifications': Notification.objects.filter(recipients=user.userprofile).order_by('-id').filter(~Q(read_by=user.userprofile)), + 'read_notifications': Notification.objects.filter(recipients=user.userprofile).order_by('-id').filter(read_by=user.userprofile)}) + + +def NotificationView(request, notification_id): + + if request.user.is_authenticated: + user = request.user + else: + return render(request, + "dashboard/login.html", + {'title': 'Authentication Required'}) + + notification = Notification.objects.get(id=notification_id) + if user.userprofile not in notification.recipients.all(): + return render(request, + "dashboard/login.html", {'title': 'Access Denied'}) + + notification.read_by.add(user.userprofile) + notification.save() + if request.method == 'POST': + if 'delete' in request.POST: + # handle deleting + notification.recipients.remove(user.userprofile) + if not notification.recipients.exists(): + notification.delete() + else: + notification.save() + + if 'unread' in request.POST: + notification.read_by.remove(user.userprofile) + notification.save() + + return render(request, + "notifier/notification.html", {'notification': notification}) |