diff options
Diffstat (limited to 'src/notifier')
-rw-r--r-- | src/notifier/__init__.py | 8 | ||||
-rw-r--r-- | src/notifier/admin.py | 14 | ||||
-rw-r--r-- | src/notifier/apps.py | 15 | ||||
-rw-r--r-- | src/notifier/manager.py | 133 | ||||
-rw-r--r-- | src/notifier/migrations/0001_initial.py | 47 | ||||
-rw-r--r-- | src/notifier/migrations/0002_auto_20181102_1631.py | 44 | ||||
-rw-r--r-- | src/notifier/migrations/0003_auto_20190123_1741.py | 23 | ||||
-rw-r--r-- | src/notifier/migrations/0004_auto_20190124_2115.py | 23 | ||||
-rw-r--r-- | src/notifier/migrations/0005_auto_20190306_1616.py | 18 | ||||
-rw-r--r-- | src/notifier/migrations/__init__.py | 0 | ||||
-rw-r--r-- | src/notifier/models.py | 25 | ||||
-rw-r--r-- | src/notifier/tests/test_dispatcher.py | 15 | ||||
-rw-r--r-- | src/notifier/tests/test_models.py | 30 | ||||
-rw-r--r-- | src/notifier/urls.py | 19 | ||||
-rw-r--r-- | src/notifier/views.py | 58 |
15 files changed, 472 insertions, 0 deletions
diff --git a/src/notifier/__init__.py b/src/notifier/__init__.py new file mode 100644 index 0000000..d65b13a --- /dev/null +++ b/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/src/notifier/admin.py b/src/notifier/admin.py new file mode 100644 index 0000000..4a2984c --- /dev/null +++ b/src/notifier/admin.py @@ -0,0 +1,14 @@ +############################################################################## +# 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.contrib import admin + +from notifier.models import Notification + +admin.site.register(Notification) diff --git a/src/notifier/apps.py b/src/notifier/apps.py new file mode 100644 index 0000000..52902da --- /dev/null +++ b/src/notifier/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# 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.apps import AppConfig + + +class NotifierConfig(AppConfig): + name = 'notifier' diff --git a/src/notifier/manager.py b/src/notifier/manager.py new file mode 100644 index 0000000..240cf85 --- /dev/null +++ b/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/src/notifier/migrations/0001_initial.py b/src/notifier/migrations/0001_initial.py new file mode 100644 index 0000000..e5d0009 --- /dev/null +++ b/src/notifier/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 2.1 on 2018-09-14 14:48 + +from django.db import migrations, models +import django.db.models.deletion +import fernet_fields.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('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)), + ('title', models.CharField(max_length=240)), + ('content', fernet_fields.fields.EncryptedTextField()), + ('sender', models.CharField(default='unknown', max_length=240)), + ('message_type', models.CharField(choices=[('email', 'Email'), ('webnotification', 'Web Notification')], default='email', max_length=240)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='account.UserProfile')), + ], + ), + ] diff --git a/src/notifier/migrations/0002_auto_20181102_1631.py b/src/notifier/migrations/0002_auto_20181102_1631.py new file mode 100644 index 0000000..e5fef89 --- /dev/null +++ b/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/src/notifier/migrations/0003_auto_20190123_1741.py b/src/notifier/migrations/0003_auto_20190123_1741.py new file mode 100644 index 0000000..f491993 --- /dev/null +++ b/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/src/notifier/migrations/0004_auto_20190124_2115.py b/src/notifier/migrations/0004_auto_20190124_2115.py new file mode 100644 index 0000000..306ec7b --- /dev/null +++ b/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/src/notifier/migrations/0005_auto_20190306_1616.py b/src/notifier/migrations/0005_auto_20190306_1616.py new file mode 100644 index 0000000..d92c988 --- /dev/null +++ b/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/src/notifier/migrations/__init__.py b/src/notifier/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/notifier/migrations/__init__.py diff --git a/src/notifier/models.py b/src/notifier/models.py new file mode 100644 index 0000000..49189e8 --- /dev/null +++ b/src/notifier/models.py @@ -0,0 +1,25 @@ +############################################################################## +# 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 import models +from account.models import UserProfile + + +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 to_preview_html(self): + return "<h3>" + self.title + "</h3>" # TODO - template? diff --git a/src/notifier/tests/test_dispatcher.py b/src/notifier/tests/test_dispatcher.py new file mode 100644 index 0000000..086f621 --- /dev/null +++ b/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/src/notifier/tests/test_models.py b/src/notifier/tests/test_models.py new file mode 100644 index 0000000..d332254 --- /dev/null +++ b/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/src/notifier/urls.py b/src/notifier/urls.py new file mode 100644 index 0000000..fedb9e8 --- /dev/null +++ b/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/src/notifier/views.py b/src/notifier/views.py new file mode 100644 index 0000000..3a85eda --- /dev/null +++ b/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}) |