diff --git a/extra/settings_prod.py.example b/extra/settings_prod.py.example index 8ef2d9c..e13ab7c 100644 --- a/extra/settings_prod.py.example +++ b/extra/settings_prod.py.example @@ -27,6 +27,10 @@ SLACK_SETTINGS = { 'apikey': "YOUR_SLACK_API_KEY" } +PROWL_SETTINGS = { + 'priority': 0 + 'application': 'openduty' +} DATABASES = { 'default': { diff --git a/notification/models.py b/notification/models.py index b035b07..7806377 100644 --- a/notification/models.py +++ b/notification/models.py @@ -19,8 +19,10 @@ class UserNotificationMethod(models.Model): METHOD_PUSHOVER = 'pushover' METHOD_XMPP = 'xmpp' METHOD_SLACK = 'slack' + METHOD_PROWL = 'prowl' - methods = [METHOD_XMPP, METHOD_PUSHOVER, METHOD_EMAIL, METHOD_TWILIO_SMS, METHOD_TWILIO_CALL, METHOD_SLACK] + + methods = [METHOD_XMPP, METHOD_PUSHOVER, METHOD_EMAIL, METHOD_TWILIO_SMS, METHOD_TWILIO_CALL, METHOD_SLACK, METHOD_PROWL] user = models.ForeignKey(User, related_name='notification_methods') position = models.IntegerField() diff --git a/notification/notifier/prowl.py b/notification/notifier/prowl.py new file mode 100644 index 0000000..012cda2 --- /dev/null +++ b/notification/notifier/prowl.py @@ -0,0 +1,26 @@ +import httplib +import urllib + +__author__ = 'gabo' + +class ProwlNotifier: + def __init__(self, config): + self.__config = config + + def notify(self, notification): + conn = httplib.HTTPSConnection("api.prowlapp.com", 443, timeout=10) + conn.request("POST", "/publicapi/add", + urllib.urlencode({ + "apikey": notification.user_to_notify.profile.prowl_api_key, + "application" : notification.user_to_notify.profile.prowl_application or self.__config.get('application', 'openduty'), + "url" : notification.user_to_notify.profile.prowl_url or "", + "priority" : self.__config.get('priority', 0), + "event" : notification.incident.description, + "description": notification.incident.details, + }), { "Content-type": "application/x-www-form-urlencoded" }) + status = conn.getresponse().status + if status >= 200 and status < 300: + print("Done") + else: + # todo: error handling + print("Unable to connect.") \ No newline at end of file diff --git a/notification/tasks.py b/notification/tasks.py index 686462c..4496d6e 100644 --- a/notification/tasks.py +++ b/notification/tasks.py @@ -7,6 +7,8 @@ from notification.notifier.twilio_sms import TwilioSmsNotifier from notification.notifier.twilio_call import TwilioCallNotifier from notification.notifier.slack import SlackNotifier +from notification.notifier.prowl import ProwlNotifier + from notification.models import ScheduledNotification, UserNotificationMethod from django.conf import settings @@ -26,6 +28,8 @@ def send_notifications(notification_id): notifier = SlackNotifier(settings.SLACK_SETTINGS) elif notification.notifier == UserNotificationMethod.METHOD_PUSHOVER: notifier = PushoverNotifier() + elif notification.notifier == UserNotificationMethod.METHOD_PROWL: + notifier = ProwlNotifier(settings.PROWL_SETTINGS) notifier.notify(notification) notification.delete() except ScheduledNotification.DoesNotExist: diff --git a/openduty/migrations/0005_auto__add_field_userprofile_prowl_api_key__add_field_userprofile_prowl.py b/openduty/migrations/0005_auto__add_field_userprofile_prowl_api_key__add_field_userprofile_prowl.py new file mode 100644 index 0000000..0b446e9 --- /dev/null +++ b/openduty/migrations/0005_auto__add_field_userprofile_prowl_api_key__add_field_userprofile_prowl.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'UserProfile.prowl_api_key' + db.add_column(u'openduty_userprofile', 'prowl_api_key', + self.gf('django.db.models.fields.CharField')(default='', max_length=50, blank=True), + keep_default=False) + + # Adding field 'UserProfile.prowl_application' + db.add_column(u'openduty_userprofile', 'prowl_application', + self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True), + keep_default=False) + + # Adding field 'UserProfile.prowl_url' + db.add_column(u'openduty_userprofile', 'prowl_url', + self.gf('django.db.models.fields.CharField')(default='', max_length=512, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'UserProfile.prowl_api_key' + db.delete_column(u'openduty_userprofile', 'prowl_api_key') + + # Deleting field 'UserProfile.prowl_application' + db.delete_column(u'openduty_userprofile', 'prowl_application') + + # Deleting field 'UserProfile.prowl_url' + db.delete_column(u'openduty_userprofile', 'prowl_url') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'openduty.eventlog': { + 'Meta': {'object_name': 'EventLog'}, + 'data': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'occurred_at': ('django.db.models.fields.DateTimeField', [], {}), + 'service_key': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['openduty.Service']"}) + }, + u'openduty.incident': { + 'Meta': {'unique_together': "(('service_key', 'incident_key'),)", 'object_name': 'Incident'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'details': ('django.db.models.fields.TextField', [], {}), + 'event_type': ('django.db.models.fields.CharField', [], {'max_length': '15'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'incident_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'occurred_at': ('django.db.models.fields.DateTimeField', [], {}), + 'service_key': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['openduty.Service']"}) + }, + u'openduty.schedulepolicy': { + 'Meta': {'object_name': 'SchedulePolicy'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'repeat_times': ('django.db.models.fields.IntegerField', [], {}) + }, + u'openduty.schedulepolicyrule': { + 'Meta': {'object_name': 'SchedulePolicyRule'}, + 'escalate_after': ('django.db.models.fields.IntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'position': ('django.db.models.fields.IntegerField', [], {}), + 'schedule': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Calendar']", 'null': 'True', 'blank': 'True'}), + 'schedule_policy': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rules'", 'to': u"orm['openduty.SchedulePolicy']"}), + 'user_id': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + u'openduty.service': { + 'Meta': {'object_name': 'Service'}, + 'escalate_after': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('uuidfield.fields.UUIDField', [], {'unique': 'True', 'max_length': '32', 'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'policy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['openduty.SchedulePolicy']", 'null': 'True', 'blank': 'True'}), + 'retry': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + u'openduty.servicetokens': { + 'Meta': {'object_name': 'ServiceTokens'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'service_id': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['openduty.Service']"}), + 'token_id': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['openduty.Token']"}) + }, + u'openduty.token': { + 'Meta': {'object_name': 'Token'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}) + }, + u'openduty.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'phone_number': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'prowl_api_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'prowl_application': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'prowl_url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}), + 'pushover_app_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'pushover_user_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'slack_room_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + 'schedule.calendar': { + 'Meta': {'object_name': 'Calendar'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200'}) + } + } + + complete_apps = ['openduty'] \ No newline at end of file diff --git a/openduty/models.py b/openduty/models.py index 268ed14..2517f95 100644 --- a/openduty/models.py +++ b/openduty/models.py @@ -179,6 +179,9 @@ class UserProfile(models.Model): pushover_user_key = models.CharField(max_length=50) pushover_app_key = models.CharField(max_length=50) slack_room_name = models.CharField(max_length=50) + prowl_api_key = models.CharField(max_length=50, blank=True) + prowl_application = models.CharField(max_length=256, blank=True) + prowl_url= models.CharField(max_length=512, blank=True) def create_user_profile(sender, instance, created, **kwargs): diff --git a/openduty/settings.py b/openduty/settings.py index 2748f7b..845ca33 100644 --- a/openduty/settings.py +++ b/openduty/settings.py @@ -112,6 +112,9 @@ SLACK_SETTINGS = { } +PROWL_SETTINGS = { +} + # Database # https://docs.djangoproject.com/en/1.6/ref/settings/#databases diff --git a/openduty/templates/users/edit.html b/openduty/templates/users/edit.html index ebb2cae..a212921 100644 --- a/openduty/templates/users/edit.html +++ b/openduty/templates/users/edit.html @@ -84,6 +84,27 @@ +
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
diff --git a/openduty/users.py b/openduty/users.py index cd569ef..e96d89a 100644 --- a/openduty/users.py +++ b/openduty/users.py @@ -69,15 +69,20 @@ def save(request): method.user = user method.position = idx +1 method.save() - try: - profile = user.get_profile() - except UserProfile.DoesNotExist: + + if user.profile is None: profile = UserProfile() profile.user = user + else: + profile = user.profile + profile.phone_number = request.POST['phone_number'] profile.pushover_user_key = request.POST['pushover_user_key'] profile.pushover_app_key = request.POST['pushover_app_key'] profile.slack_room_name = request.POST['slack_room_name'] + profile.prowl_api_key = request.POST['prowl_api_key'] + profile.prowl_application = request.POST['prowl_application'] + profile.prowl_url = request.POST['prowl_url'] profile.save() return HttpResponseRedirect(reverse('openduty.users.list'))