aboutsummaryrefslogtreecommitdiffstats
path: root/src/notifier
diff options
context:
space:
mode:
Diffstat (limited to 'src/notifier')
-rw-r--r--src/notifier/__init__.py8
-rw-r--r--src/notifier/admin.py14
-rw-r--r--src/notifier/apps.py15
-rw-r--r--src/notifier/manager.py133
-rw-r--r--src/notifier/migrations/0001_initial.py47
-rw-r--r--src/notifier/migrations/0002_auto_20181102_1631.py44
-rw-r--r--src/notifier/migrations/0003_auto_20190123_1741.py23
-rw-r--r--src/notifier/migrations/0004_auto_20190124_2115.py23
-rw-r--r--src/notifier/migrations/0005_auto_20190306_1616.py18
-rw-r--r--src/notifier/migrations/__init__.py0
-rw-r--r--src/notifier/models.py25
-rw-r--r--src/notifier/tests/test_dispatcher.py15
-rw-r--r--src/notifier/tests/test_models.py30
-rw-r--r--src/notifier/urls.py19
-rw-r--r--src/notifier/views.py58
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})