Implement Notification Framework with Initial Email Support 03/49103/3
authorSawyer Bergeron <sbergeron@iol.unh.edu>
Fri, 15 Dec 2017 19:35:40 +0000 (14:35 -0500)
committerSawyer Bergeron <sbergeron@iol.unh.edu>
Tue, 2 Jan 2018 22:29:22 +0000 (22:29 +0000)
JIRA: None

Notification/notifier objects are now created with title,
content, sender, associated resource, and recipient and there is
now support for emails at the very least to be sent as notifications.

Change-Id: I456cf0e901d9a1e2a1e7d187dcc03d28fca003fb
Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.edu>
22 files changed:
dashboard/config.env.sample
dashboard/src/account/migrations/0002_userprofile_email_addr.py [new file with mode: 0644]
dashboard/src/account/models.py
dashboard/src/api/serializers.py
dashboard/src/api/urls.py
dashboard/src/api/views.py
dashboard/src/notification/admin.py [deleted file]
dashboard/src/notification/apps.py [deleted file]
dashboard/src/notification/migrations/0001_initial.py [deleted file]
dashboard/src/notification/models.py [deleted file]
dashboard/src/notification/signals.py [deleted file]
dashboard/src/notification/tasks.py [deleted file]
dashboard/src/notification/tests.py [deleted file]
dashboard/src/notifier/__init__.py [new file with mode: 0644]
dashboard/src/notifier/admin.py [moved from dashboard/src/notification/migrations/__init__.py with 82% similarity]
dashboard/src/notifier/apps.py [moved from dashboard/src/notification/__init__.py with 83% similarity]
dashboard/src/notifier/dispatchers.py [new file with mode: 0644]
dashboard/src/notifier/migrations/0001_initial.py [new file with mode: 0644]
dashboard/src/notifier/migrations/__init__.py [new file with mode: 0644]
dashboard/src/notifier/models.py [new file with mode: 0644]
dashboard/src/pharos_dashboard/settings.py
dashboard/web/requirements.txt

index b370fe2..edd2bf0 100644 (file)
@@ -25,3 +25,9 @@ RABBITMQ_PASSWORD=opnfvopnfv
 
 #Jenkins Build Server
 JENKINS_URL=https://build.opnfv.org/ci
+
+# Email Settings
+EMAIL_HOST=
+EMAIL_PORT=
+EMAIL_HOST_USER=
+EMAIL_HOST_PASSWORD=
diff --git a/dashboard/src/account/migrations/0002_userprofile_email_addr.py b/dashboard/src/account/migrations/0002_userprofile_email_addr.py
new file mode 100644 (file)
index 0000000..bfbed17
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2017-12-14 20:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='userprofile',
+            name='email_addr',
+            field=models.CharField(default='email@mail.com', max_length=300),
+        ),
+    ]
index bfc0bbe..47b012b 100644 (file)
@@ -20,7 +20,7 @@ class UserProfile(models.Model):
     timezone = models.CharField(max_length=100, blank=False, default='UTC')
     ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
     pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
-    email_addr = models.CharField(max_length=300, blank=false, default='email@mail.com')
+    email_addr = models.CharField(max_length=300, blank=False, default='email@mail.com')
     company = models.CharField(max_length=200, blank=False)
 
     oauth_token = models.CharField(max_length=1024, blank=False)
index 4478175..dc79d9d 100644 (file)
@@ -10,6 +10,7 @@
 
 from rest_framework import serializers
 
+from notifier.models import Notifier
 from booking.models import Booking
 from dashboard.models import Server, Resource, ResourceStatus
 
@@ -37,3 +38,8 @@ class ResourceStatusSerializer(serializers.ModelSerializer):
     class Meta:
         model = ResourceStatus
         fields = ('id', 'resource', 'timestamp','type', 'title', 'content')
+
+class NotifierSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Notifier
+        fields = ('id', 'title', 'content', 'user', 'sender', 'message_type', 'msg_sent')
index a4a4b2f..71cd3ef 100644 (file)
@@ -33,8 +33,9 @@ router.register(r'resources', ResourceViewSet)
 router.register(r'servers', ServerViewSet)
 router.register(r'bookings', BookingViewSet)
 router.register(r'resource_status', ResourceStatusViewSet)
+router.register(r'notifier', NotifierViewSet)
 
 urlpatterns = [
     url(r'^', include(router.urls)),
     url(r'^token$', GenerateTokenView.as_view(), name='generate_token'),
-]
\ No newline at end of file
+]
index 84fa1b5..c16d57d 100644 (file)
@@ -8,7 +8,7 @@
 ##############################################################################
 
 
-from django.contrib.auth.decorators import login_required
+from django.contrib.auth.decorators import login_required, user_passes_test
 from django.shortcuts import redirect
 from django.utils.decorators import method_decorator
 from django.views import View
@@ -41,6 +41,9 @@ class ResourceStatusViewSet(viewsets.ModelViewSet):
     queryset = ResourceStatus.objects.all()
     serializer_class = ResourceStatusSerializer
 
+class NotifierViewSet(viewsets.ModelViewSet):
+    queryset = Notifier.objects.none()
+    serializer_class = NotifierSerializer
 
 @method_decorator(login_required, name='dispatch')
 class GenerateTokenView(View):
diff --git a/dashboard/src/notification/admin.py b/dashboard/src/notification/admin.py
deleted file mode 100644 (file)
index bcaa1ab..0000000
+++ /dev/null
@@ -1,17 +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.conf import settings
-from django.contrib import admin
-
-from notification.models import BookingNotification
-
-if settings.DEBUG:
-    admin.site.register(BookingNotification)
\ No newline at end of file
diff --git a/dashboard/src/notification/apps.py b/dashboard/src/notification/apps.py
deleted file mode 100644 (file)
index 2de22c4..0000000
+++ /dev/null
@@ -1,18 +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.apps import AppConfig
-
-
-class NotificationConfig(AppConfig):
-    name = 'notification'
-
-    def ready(self):
-        import notification.signals #noqa
\ No newline at end of file
diff --git a/dashboard/src/notification/migrations/0001_initial.py b/dashboard/src/notification/migrations/0001_initial.py
deleted file mode 100644 (file)
index 8b8414e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2016-11-03 13:33
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-        ('booking', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='BookingNotification',
-            fields=[
-                ('id', models.AutoField(primary_key=True, serialize=False)),
-                ('type', models.CharField(max_length=100)),
-                ('submit_time', models.DateTimeField()),
-                ('submitted', models.BooleanField(default=False)),
-                ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')),
-            ],
-        ),
-    ]
diff --git a/dashboard/src/notification/models.py b/dashboard/src/notification/models.py
deleted file mode 100644 (file)
index 89b3023..0000000
+++ /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 import models
-
-class BookingNotification(models.Model):
-    id = models.AutoField(primary_key=True)
-    type = models.CharField(max_length=100)
-    booking = models.ForeignKey('booking.Booking', on_delete=models.CASCADE)
-    submit_time = models.DateTimeField()
-    submitted = models.BooleanField(default=False)
-
-    def get_content(self):
-        return {
-            'resource_id': self.booking.resource.id,
-            'booking_id': self.booking.id,
-            'user': self.booking.user.username,
-            'user_id': self.booking.user.id,
-        }
-
-    def save(self, *args, **kwargs):
-        notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude(
-            id=self.id)
-        #if notifications.count() > 0:
-        #    raise ValueError('Doubled Notification')
-        return super(BookingNotification, self).save(*args, **kwargs)
diff --git a/dashboard/src/notification/signals.py b/dashboard/src/notification/signals.py
deleted file mode 100644 (file)
index 936c25b..0000000
+++ /dev/null
@@ -1,25 +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 post_save
-from django.dispatch import receiver
-
-from booking.models import Booking
-from notification.models import BookingNotification
-
-
-@receiver(post_save, sender=Booking)
-def booking_notification_handler(sender, instance, **kwargs):
-    BookingNotification.objects.update_or_create(
-        booking=instance, type='booking_start', defaults={'submit_time': instance.start}
-    )
-    BookingNotification.objects.update_or_create(
-        booking=instance, type='booking_end', defaults={'submit_time': instance.end}
-    )
\ No newline at end of file
diff --git a/dashboard/src/notification/tasks.py b/dashboard/src/notification/tasks.py
deleted file mode 100644 (file)
index 7f73762..0000000
+++ /dev/null
@@ -1,49 +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
-##############################################################################
-
-
-import os
-import sys
-from datetime import timedelta
-
-from celery import shared_task
-from django.conf import settings
-from django.utils import timezone
-
-from notification.models import BookingNotification
-
-# this adds the top level directory to the python path, this is needed so that we can access the
-# notification library
-sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
-
-from dashboard_notification.notification import Notification, Message
-
-
-@shared_task
-def send_booking_notifications():
-    with Notification(dashboard_url=settings.RABBITMQ_URL, user=settings.RABBITMQ_USER, password=settings.RABBITMQ_PASSWORD) as messaging:
-        now = timezone.now()
-        notifications = BookingNotification.objects.filter(submitted=False,
-                                                           submit_time__gt=now - timedelta(minutes=1),
-                                                           submit_time__lt=now + timedelta(minutes=5))
-        for notification in notifications:
-            message = Message(type=notification.type, topic=notification.booking.resource.name,
-                              content=notification.get_content())
-            messaging.send(message)
-            notification.submitted = True
-            notification.save()
-
-@shared_task
-def notification_debug():
-    with Notification(dashboard_url=settings.RABBITMQ_URL) as messaging:
-        notifications = BookingNotification.objects.all()
-        for notification in notifications:
-            message = Message(type=notification.type, topic=notification.booking.resource.name,
-                              content=notification.get_content())
-            messaging.send(message)
diff --git a/dashboard/src/notification/tests.py b/dashboard/src/notification/tests.py
deleted file mode 100644 (file)
index 9df9aa6..0000000
+++ /dev/null
@@ -1,41 +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 datetime import timedelta
-from unittest import TestCase
-
-from django.contrib.auth.models import User
-from django.utils import timezone
-
-from booking.models import Booking
-from dashboard.models import Resource
-from jenkins.models import JenkinsSlave
-from notification.models import *
-
-
-class JenkinsModelTestCase(TestCase):
-    def setUp(self):
-        self.slave = JenkinsSlave.objects.create(name='test1', url='test')
-        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
-                                            url='x')
-        self.user1 = User.objects.create(username='user1')
-
-        start = timezone.now()
-        end = start + timedelta(days=1)
-        self.booking = Booking.objects.create(start=start, end=end, purpose='test',
-                                              resource=self.res1, user=self.user1)
-
-    def test_booking_notification(self):
-        BookingNotification.objects.create(type='test', booking=self.booking,
-                                           submit_time=timezone.now())
-
-        self.assertRaises(ValueError, BookingNotification.objects.create, type='test',
-                          booking=self.booking,
-                          submit_time=timezone.now())
diff --git a/dashboard/src/notifier/__init__.py b/dashboard/src/notifier/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
similarity index 82%
rename from dashboard/src/notification/migrations/__init__.py
rename to dashboard/src/notifier/admin.py
index b5914ce..cfbe778 100644 (file)
@@ -7,4 +7,8 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
+from django.contrib import admin
 
+from notifier.models import *
+
+admin.site.register(Notifier)
similarity index 83%
rename from dashboard/src/notification/__init__.py
rename to dashboard/src/notifier/apps.py
index 37dcbdd..da5d3b0 100644 (file)
@@ -7,5 +7,8 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
+from django.apps import AppConfig
 
-default_app_config = 'notification.apps.NotificationConfig'
\ No newline at end of file
+
+class NotifierConfig(AppConfig):
+    name = 'notifier'
diff --git a/dashboard/src/notifier/dispatchers.py b/dashboard/src/notifier/dispatchers.py
new file mode 100644 (file)
index 0000000..64be2a5
--- /dev/null
@@ -0,0 +1,34 @@
+##############################################################################
+# 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 + 
+            '\n\n This message pertains to the following resource: ' + 
+            instance.resource.name,instance.sender,[instance.user.email_addr], fail_silently=False)
+
+    def webnotification(instance):
+        instance.msg_sent='by web notification'
diff --git a/dashboard/src/notifier/migrations/0001_initial.py b/dashboard/src/notifier/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..78d2bad
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2017-12-14 21:41
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import fernet_fields.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('account', '0002_userprofile_email_addr'),
+        ('dashboard', '0002_auto_20170505_0815'),
+    ]
+
+    operations = [
+        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/dashboard/src/notifier/migrations/__init__.py b/dashboard/src/notifier/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dashboard/src/notifier/models.py b/dashboard/src/notifier/models.py
new file mode 100644 (file)
index 0000000..9ebc6fc
--- /dev/null
@@ -0,0 +1,38 @@
+##############################################################################
+# 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 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
+
+    def __str__(self):
+        return self.title
+
+    def getEmail(self):
+        return self.user.email_addr
+
index 83ad172..240f68e 100644 (file)
@@ -14,7 +14,7 @@ INSTALLED_APPS = [
     'booking',
     'account',
     'jenkins',
-    'notification',
+    'notifier',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
@@ -188,3 +188,9 @@ CI_SLAVES_URL = os.environ['JENKINS_URL'] + '/label/ci-pod/api/json?tree=nodes[n
 ALL_JOBS_URL = os.environ['JENKINS_URL'] + '/api/json?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp,result]'
 GET_SLAVE_URL = os.environ['JENKINS_URL'] + '/computer/'
 
+# Notifier Settings
+EMAIL_HOST = os.environ['EMAIL_HOST']
+EMAIL_PORT = os.environ['EMAIL_PORT']
+EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
+EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
+EMAIL_USE_TLS=True
index f80f1c0..0d864d3 100644 (file)
@@ -15,3 +15,4 @@ pika==0.10.0
 psycopg2==2.6.2
 PyJWT==1.4.2
 requests==2.11.0
+django-fernet-fields==0.5