From 51038a819ba7c203207466bf263dd17b6a9b8b6b Mon Sep 17 00:00:00 2001
From: Brian Moe <lars@moe.phys.uwm.edu>
Date: Wed, 25 Nov 2009 14:41:08 -0600
Subject: [PATCH] Added user profile facility

---
 gracedb/alert.py                          |  53 ++++++++-
 gracedb/templatetags/flash.py             | 139 ++++++++++++++++++++++
 gracedb/views.py                          |  12 +-
 settings.py                               |   2 +
 settings_dev.py                           |   2 +
 static/css/admin-nav.css                  |   2 +
 static/css/style.css                      |   2 +
 templates/base.html                       |   7 ++
 templates/profile/createNotification.html |  15 +++
 templates/profile/notifications.html      |  34 ++++++
 urls.py                                   |   1 +
 userprofile/__init__.py                   |   0
 userprofile/admin.py                      |  21 ++++
 userprofile/forms.py                      |  17 +++
 userprofile/models.py                     |  61 ++++++++++
 userprofile/tests.py                      |  23 ++++
 userprofile/urls.py                       |  22 ++++
 userprofile/views.py                      | 116 ++++++++++++++++++
 18 files changed, 521 insertions(+), 8 deletions(-)
 create mode 100644 gracedb/templatetags/flash.py
 create mode 100644 templates/profile/createNotification.html
 create mode 100644 templates/profile/notifications.html
 create mode 100644 userprofile/__init__.py
 create mode 100644 userprofile/admin.py
 create mode 100644 userprofile/forms.py
 create mode 100644 userprofile/models.py
 create mode 100644 userprofile/tests.py
 create mode 100644 userprofile/urls.py
 create mode 100644 userprofile/views.py

diff --git a/gracedb/alert.py b/gracedb/alert.py
index 96c7a1678..dad9aee1b 100644
--- a/gracedb/alert.py
+++ b/gracedb/alert.py
@@ -4,11 +4,13 @@ import time
 from subprocess import Popen, PIPE, STDOUT
 import StringIO
 
-from django.core.mail import send_mail
+from django.core.mail import send_mail, EmailMessage
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse, get_script_prefix
 
+from gracedb.userprofile.models import Trigger, AnalysisType
+
 import glue.ligolw.utils
 import glue.lvalert.utils
 
@@ -32,13 +34,49 @@ def prepareSummary(event):
     # XXX TBD what exactly this summary is.
     return "GPS Time: %s" % event.gpstime
 
+
+def issueEmailAlertForLabel(event, label):
+    profileRecips = []
+    atype = AnalysisType.objects.filter(code=event.analysisType)[0]
+    triggers = label.trigger_set.filter(atypes=atype)
+    for trigger in triggers:
+        for recip in trigger.contacts.all():
+            profileRecips.append(recip.email)
+
+    subject = "[gracedb] %s / %s / %s" % (label.name, event.get_analysisType_display(), event.graceid())
+
+    message = "A %s event with graceid %s was labelled with %s" % \
+              (event.get_analysisType_display(), event.graceid(), label.name)
+
+    if event.group.name == "Test":
+        fromaddress = settings.ALERT_TEST_EMAIL_FROM
+        toaddresses = settings.ALERT_TEST_EMAIL_TO
+        message += "\n\nWould have send email to: %s" % str(profileRecips)
+    else:
+        fromaddress = settings.ALERT_EMAIL_FROM
+        toaddresses = profileRecips
+
+    if toaddresses:
+        email = EmailMessage(subject, message, fromaddress, [], toaddresses)
+        email.send()
+
+
 def issueEmailAlert(event, location):
+
+    # Gather Recipients
     if event.group.name == 'Test':
         fromaddress = settings.ALERT_TEST_EMAIL_FROM
-        toaddress = settings.ALERT_TEST_EMAIL_TO
+        toaddresses = settings.ALERT_TEST_EMAIL_TO
     else:
         fromaddress = settings.ALERT_EMAIL_FROM
-        toaddress = settings.ALERT_EMAIL_TO
+        toaddresses = settings.ALERT_EMAIL_TO
+
+        atype = AnalysisType.objects.filter(code=event.analysisType)[0]
+        triggers = atype.trigger_set.filter(labels=None)
+        for trigger in triggers:
+            for recip in trigger.contacts.all():
+                toaddresses.append(recip.email)
+
     subject = "[gracedb] %s event. ID: %s" % (event.get_analysisType_display(), event.graceid())
     message = """
 New Event
@@ -58,8 +96,13 @@ Event Summary:
                 event.weburl(),
                 event.wikiurl(),
                 event.submitter.name,
-                indent(3, prepareSummary(event)))
-    send_mail(subject, message, fromaddress, toaddress)
+                indent(3, prepareSummary(event))
+               )
+
+    email = EmailMessage(subject, message, fromaddress, [], toaddresses)
+    email.send()
+
+    #send_mail(subject, message, fromaddress, toaddresses)
 
 def issueXMPPAlert(event, location, temp_data_loc):
     nodename = "%s_%s"% (event.group.name, event.get_analysisType_display())
diff --git a/gracedb/templatetags/flash.py b/gracedb/templatetags/flash.py
new file mode 100644
index 000000000..4093b2db2
--- /dev/null
+++ b/gracedb/templatetags/flash.py
@@ -0,0 +1,139 @@
+"""
+To function this requires the following to be installed:
+
+TEMPLATE_CONTEXT_PROCESSORS
+django.core.context_processors.request
+
+MIDDLEWARE_CLASSES
+django.contrib.sessions.middleware.SessionMiddleware
+
+@author: Robert Conner (rtconner)
+"""
+
+# It's pretty simple. Do something like this in your view ..
+
+# >>>request.session['flash_msg'] = 'Your changes have been save'
+# >>>request.session['flash_params'] = {'type': 'success'}
+
+# And maybe put something like this in your template
+#
+# {% load flash %}
+# {% flash %}
+#   <h2>{{ params.type }}</h2>
+#   {{ msg }}
+# {% endflash %}
+
+# It also support a flash template, you can specify a file FLASH_TEMPLATE in
+# your settings file and then that file will be rendered with msg and params as
+# available variable. Usage for this would simply be {% flash_template %} and
+# then you gotta make a template file that does whatever you like.
+
+# Outside of that just be aware you need the Django session middleware and
+# request context installed in your app to use this. 
+
+
+from django import template
+from django.template import resolve_variable, Context
+import datetime
+from django.template.loader import render_to_string
+from django.contrib.sessions.models import Session
+from django.conf import settings
+
+register = template.Library()
+
+
+def session_clear(session):
+    """
+    Private function, clear flash msgsfrom the session
+    """
+    try:
+        del session['flash_msg']
+    except KeyError:
+        pass
+    
+    try:
+        del session['flash_params']
+    except KeyError:
+        pass
+    
+    # Save changes to session
+    if(session.session_key):
+        Session.objects.save(session.session_key, session._session,
+            datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+
+
+class RunFlashBlockNode(template.Node):
+    def __init__(self, nodelist):
+        self.nodelist = nodelist
+        
+    def render(self, context):
+        
+        session = context['request'].session
+        ret = None
+        if session.get('flash_msg', False):
+            ret = {'msg': session['flash_msg']}
+            if 'flash_params' in session:
+                ret['params'] = session.get('flash_params', False)
+            session_clear(session);
+
+        if ret is not None:
+            context.update(ret)
+            return self.nodelist.render(context)
+        return ''
+
+
+class RunFlashTemplateNode(template.Node):
+    def __init__(self):
+        pass
+    
+    def render(self, context):
+        session = context['request'].session
+        if session.get('flash_msg', False):
+            ret = {'msg': session['flash_msg']}
+            if 'flash_params' in session:
+                ret['params'] = session.get('flash_params', False)
+                
+            session_clear(session);
+            try:
+                template = settings.FLASH_TEMPLATE
+            except AttributeError:
+                template = 'elements/flash.html'
+            return render_to_string(template, dictionary=ret) 
+        return ''
+
+@register.tag(name="flash_template")
+def do_flash_template(parser, token):
+    """
+    Call template if there is flash message in session
+        
+    Runs a check if there is a flash message in the session.
+    If the flash message exists it calls settings.FLASH_TEMPLATE
+    and passes the template the variables 'msg' and 'params'.
+    Calling this clears the flash from the session automatically
+    
+    To set a flash msg, in a view call:
+    request.session['flash_msg'] = 'sometihng'
+    request.session[flash_'params'] = {'note': 'remember me'}
+    
+    In the template {{ msg }} and {{ params.note }} are available
+    """
+    return RunFlashTemplateNode()
+
+@register.tag(name="flash")
+def do_flash_block(parser, token):
+    """
+    A block section where msg and params are both available.
+    Calling this clears the flash from the session automatically
+    
+    If there is no flash msg, then nothing inside this block
+    gets rendered
+    
+    Example:
+    {% flash %}
+        {{msg}}<br />
+        {{params.somekey}}
+    {% endflash %}
+    """
+    nodelist = parser.parse(('endflash',))
+    parser.delete_first_token()
+    return RunFlashBlockNode(nodelist)
diff --git a/gracedb/views.py b/gracedb/views.py
index 91c9173ba..ed906098b 100644
--- a/gracedb/views.py
+++ b/gracedb/views.py
@@ -10,7 +10,7 @@ from django.views.generic.list_detail import object_detail, object_list
 
 from models import Event, Group, EventLog, Labelling, Label
 from forms import CreateEventForm, EventSearchForm
-from alert import issueAlert
+from alert import issueAlert, issueEmailAlertForLabel
 from translator import handle_uploaded_data
 
 import os
@@ -140,7 +140,7 @@ def _createEventFromForm(request, form):
                        os.path.join(event.clusterurl(), "private", f.name),
                        temp_data_loc)
         except Exception, e:
-            warnings += ["Problem handling event creation (%s)" % e]
+            warnings += ["Problem issuing an alert (%s)" % e]
         #return HttpResponseRedirect(reverse(view, args=[event.graceid()]))
     except Exception, e:
         # something went wrong.
@@ -262,6 +262,7 @@ def cli_label(request):
     graceid = request.POST.get('graceid')
     labelName = request.POST.get('label')
 
+    d = {}
     event = graceid and Event.getByGraceid(graceid)
     
     try:
@@ -281,7 +282,12 @@ def cli_label(request):
         log = EventLog(event=event, issuer=request.ligouser, comment=message)
         log.save()
 
-    msg = str({})
+    try:
+        issueEmailAlertForLabel(event, label)
+    except Exception, e:
+        d['warning'] = "Problem issuing email alert (%s)" % str(e)
+
+    msg = str(d)
     response = HttpResponse(mimetype='application/json')
     response.write(msg)
     response['Content-length'] = len(msg)
diff --git a/settings.py b/settings.py
index 50f7bcd01..29f250679 100644
--- a/settings.py
+++ b/settings.py
@@ -83,6 +83,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
     "django.core.context_processors.media",
+    "django.core.context_processors.request",
     "gracedb.middleware.auth.LigoAuthContext",
 )
 
@@ -113,4 +114,5 @@ INSTALLED_APPS = (
     'django.contrib.sessions',
     'django.contrib.sites',
     'gracedb.gracedb',
+    'gracedb.userprofile',
 )
diff --git a/settings_dev.py b/settings_dev.py
index 3c03f0fe5..67ef59ec6 100644
--- a/settings_dev.py
+++ b/settings_dev.py
@@ -75,6 +75,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     "django.core.context_processors.debug",
     "django.core.context_processors.i18n",
     "django.core.context_processors.media",
+    "django.core.context_processors.request",
     "gracedb.middleware.auth.LigoAuthContext",
 )
 
@@ -105,4 +106,5 @@ INSTALLED_APPS = (
     'django.contrib.sessions',
     'django.contrib.sites',
     'gracedb.gracedb',
+    'gracedb.userprofile',
 )
diff --git a/static/css/admin-nav.css b/static/css/admin-nav.css
index 3a309536f..d19d736a5 100644
--- a/static/css/admin-nav.css
+++ b/static/css/admin-nav.css
@@ -61,6 +61,7 @@
 #archive #nav-archive a,
 #lab #nav-lab a,
 #reviews #nav-reviews a,
+#userprofile #nav-userprofile a,
 #contact #nav-contact a {
     background: #a9b0ba;  /* Nav selected color */
     /* color:#fff;  / * Use if bg is dark */
@@ -74,6 +75,7 @@
 #archive #nav-archive a:hover,
 #lab #nav-lab a:hover,
 #reviews #nav-reviews a:hover,
+#userprofile #nav-userprofile a:hover,
 #contact #nav-contact a:hover {
     /* background:#e35a00; */
     background: #a9b0ba;  /* Nav selected color */
diff --git a/static/css/style.css b/static/css/style.css
index c0d737efd..0d82bfe5f 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -125,6 +125,7 @@ a.link, a, a.active {
 #archive #nav-archive a,
 #lab #nav-lab a,
 #reviews #nav-reviews a,
+#userprofile #nav-userprofile a,
 #contact #nav-contact a {
     background: #a9b0ba;  /* Nav selected color */
     /* color:#fff;  / * Use if bg is dark */
@@ -138,6 +139,7 @@ a.link, a, a.active {
 #archive #nav-archive a:hover,
 #lab #nav-lab a:hover,
 #reviews #nav-reviews a:hover,
+#userprofile #nav-userprofile a:hover,
 #contact #nav-contact a:hover {
     /* background:#e35a00; */
     background: #a9b0ba;  /* Nav selected color */
diff --git a/templates/base.html b/templates/base.html
index 327c19629..893af0f37 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -61,12 +61,19 @@ function changeTime(obj, label) {
     <li id="nav-search"><a href="{% url search %}">Search</a></li>
     <li id="nav-create"><a href="{% url create %}">Create</a></li>
     <li id="nav-feeds"><a href="{% url feeds %}">RSS</a></li>
+    <li id="nav-userprofile"><a href="{% url userprofile-home %}">Options</a></li>
     {% if ligouser %}<li id="nav-user">Authenticated as: {{ ligouser.name }}</li>{% endif %}
 </ul>
 {% endblock %}
 
         <p>&nbsp;</p> <!-- bad way to create vertical space -->
 
+{% load flash %}
+{% flash %}
+    <div id="status_block" class="{{ params.class }}">{{ msg }}</div>
+{% endflash %}
+
+
         <h2>{% block heading %}Title{% endblock %}</h2>
 
 
diff --git a/templates/profile/createNotification.html b/templates/profile/createNotification.html
new file mode 100644
index 000000000..a5a81e1d9
--- /dev/null
+++ b/templates/profile/createNotification.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block title %}Options | Create {{ creating }}{% endblock %}
+{% block heading %}Create {{ creating }}{% endblock %}
+{% block pageid %}userprofile{% endblock %}
+
+{% block content %}
+{{ explanation }}
+<form method="POST">
+    <table>
+        {{ form.as_table }}
+    </table>
+    <input type="submit" value="Submit"/>
+</form>
+{% endblock %}
diff --git a/templates/profile/notifications.html b/templates/profile/notifications.html
new file mode 100644
index 000000000..ea4105918
--- /dev/null
+++ b/templates/profile/notifications.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+
+{% block title %}Options | Notifications{% endblock %}
+{% block heading %}Notifications{% endblock %}
+{% block pageid %}userprofile{% endblock %}
+
+{% block content %}
+
+{% for trigger in triggers %}
+   <ul>
+     <li> 
+        <!-- <a href="{% url userprofile-edit trigger.id  %}">Edit</a> -->
+        <a href="{% url userprofile-delete trigger.id %}">Delete</a>
+        {{ trigger.userlessDisplay }}
+     </li>
+   </ul>
+{% endfor %}
+
+<a href="{% url userprofile-create %}">Create New Notification</a>
+
+<h2>Contacts</h2>
+{% for contact in contacts %}
+    <ul>
+        <li>
+            <!-- <a href="{% url userprofile-edit-contact contact.id %}">Edit</a> -->
+            <a href="{% url userprofile-delete-contact contact.id %}">Delete</a>
+            {{ contact.desc }} / {{ contact.email }}
+        </li>
+    </ul>
+{% endfor %}
+
+<a href="{% url userprofile-create-contact %}">Create New Contact</a>
+
+{% endblock %}
diff --git a/urls.py b/urls.py
index 2cdb02cbd..1f3f70127 100644
--- a/urls.py
+++ b/urls.py
@@ -14,6 +14,7 @@ urlpatterns = patterns('',
 
     url (r'^$', 'gracedb.gracedb.views.index', name="home"),
     (r'^events/', include('gracedb.gracedb.urls')),
+    (r'^options/', include('gracedb.userprofile.urls')),
     (r'^cli/create', 'gracedb.gracedb.views.create'),
     (r'^cli/ping', 'gracedb.gracedb.views.ping'),
     (r'^cli/log', 'gracedb.gracedb.views.log'),
diff --git a/userprofile/__init__.py b/userprofile/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/userprofile/admin.py b/userprofile/admin.py
new file mode 100644
index 000000000..95ff41db4
--- /dev/null
+++ b/userprofile/admin.py
@@ -0,0 +1,21 @@
+
+from models import AnalysisType, Contact, Trigger
+
+from django.contrib import admin
+
+class AnalysisTypeManager(admin.ModelAdmin):
+    list_display = [ 'display' ]
+
+class ContactManager(admin.ModelAdmin):
+    pass
+#   list_display = [ 'user', 'desc' ]
+
+class TriggerManager(admin.ModelAdmin):
+    pass
+#   exclude = [ 'labels' ]
+#   list_display = [ 'user', ]
+
+admin.site.register(AnalysisType, AnalysisTypeManager)
+admin.site.register(Contact, ContactManager)
+admin.site.register(Trigger, TriggerManager)
+
diff --git a/userprofile/forms.py b/userprofile/forms.py
new file mode 100644
index 000000000..e26ddf67b
--- /dev/null
+++ b/userprofile/forms.py
@@ -0,0 +1,17 @@
+from django import forms
+from django.db import models
+from models import Trigger, Contact
+
+from django.forms.models import modelformset_factory
+
+class TriggerForm(forms.ModelForm):
+    class Meta:
+        model = Trigger
+        exclude = ['user', 'triggerType']
+
+
+class ContactForm(forms.ModelForm):
+    class Meta:
+        model = Contact
+        exclude = ['user']
+
diff --git a/userprofile/models.py b/userprofile/models.py
new file mode 100644
index 000000000..ba3ad70dc
--- /dev/null
+++ b/userprofile/models.py
@@ -0,0 +1,61 @@
+
+from django.db import models
+
+from gracedb.gracedb.models import User, Label, Event
+
+
+#class Notification(models.Model):
+#    user = models.ForeignKey(User, null=False)
+#    onLabel = models.ManyToManyField(Label, blank=True)
+#    onTypeCreate = models.CharField(max_length=20, choices=TYPES, blank=True)
+#    onTypeChange = models.CharField(max_length=20, choices=TYPES, blank=True)
+#    email = models.EmailField()
+
+class AnalysisType(models.Model):
+    # XXX Event.analysisType should probably point to this.
+    #  The choice list thing is obnoxious for notifications to track
+    code = models.CharField(max_length=20, unique=True)
+    display = models.CharField(max_length=20, unique=True)
+
+    def __unicode__(self):
+        return self.display
+
+def populateAnalysisType():
+    lastError = None
+    for code, display in Event.ANALYSIS_TYPE_CHOICES:
+        try:
+            atype = AnalysisType(code=code, display=display)
+            atype.save()
+        except Exception, e:
+            lastError = e
+    if lastError is not None:
+        raise lastError
+
+class Contact(models.Model):
+    user = models.ForeignKey(User, null=False)
+    desc = models.CharField(max_length=20)
+    email = models.EmailField()
+
+    def __unicode__(self):
+        return "%s: %s" % (self.user.name, self.desc)
+
+class Trigger(models.Model):
+    TYPES = ( ("create", "create"), ("change","change"), ("label","label") )
+    user = models.ForeignKey(User, null=False)
+    triggerType = models.CharField(max_length=20, choices=TYPES, blank=True)
+    labels = models.ManyToManyField(Label, blank=True)
+    atypes = models.ManyToManyField(AnalysisType, blank=True, verbose_name="Analysis Types")
+    contacts = models.ManyToManyField(Contact, blank=True)
+
+    def __unicode__(self):
+        return ("%s: %s") % (
+            self.user.name,
+            self.userlessDisplay()
+        )
+
+    def userlessDisplay(self):
+        return ("(%s) & (%s) -> %s") % (
+            "|".join([a.display for a in self.atypes.all()]) or "any type",
+            "|".join([a.name for a in self.labels.all()]) or "creating",
+            ",".join([x.desc for x in self.contacts.all()])
+        )
diff --git a/userprofile/tests.py b/userprofile/tests.py
new file mode 100644
index 000000000..2247054b3
--- /dev/null
+++ b/userprofile/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/userprofile/urls.py b/userprofile/urls.py
new file mode 100644
index 000000000..00f08d595
--- /dev/null
+++ b/userprofile/urls.py
@@ -0,0 +1,22 @@
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('gracedb.userprofile.views',
+    url (r'^$', 'index', name="userprofile-home"),
+    url (r'^contact/create$', 'createContact', name="userprofile-create-contact"),
+    url (r'^contact/delete/(?P<id>[\d]+)$', 'deleteContact', name="userprofile-delete-contact"),
+    url (r'^contact/edit/(?P<id>[\d]+)$', 'editContact', name="userprofile-edit-contact"),
+
+    url (r'^trigger/create$', 'create', name="userprofile-create"),
+    url (r'^trigger/delete/(?P<id>[\d]+)$', 'delete', name="userprofile-delete"),
+    url (r'^trigger/edit/(?P<id>[\d]+)$', 'edit', name="userprofile-edit"),
+
+#   (r'^view/(?P<uid>[\w\d]+)', 'view'),
+#   (r'^edit/(?P<uid>[\w\d]+)', 'edit'),
+#   (r'^request_archive/(?P<uid>[\w\d]+)(?P<rescind>/rescind)?', 'request_archive'),
+#   (r'^approve_archive/(?P<uid>[\w\d]+)(?P<rescind>/rescind)?', 'approve_archive'),
+#   url (r'^query', 'query', name="search"),
+#   url (r'^mine/$', 'mine', name="mine"),
+#   url (r'^myapprovals/$', 'myapprovals', name="myapprovals"),
+)
diff --git a/userprofile/views.py b/userprofile/views.py
new file mode 100644
index 000000000..244ecb81e
--- /dev/null
+++ b/userprofile/views.py
@@ -0,0 +1,116 @@
+
+from django.http import HttpResponse
+from django.http import HttpResponseRedirect, HttpResponseNotFound
+from django.http import Http404, HttpResponseForbidden
+
+from django.core.urlresolvers import reverse
+
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+
+from gracedb.userprofile.models import Trigger, Contact
+
+from forms import TriggerForm, ContactForm
+
+def index(request):
+    triggers = Trigger.objects.filter(user=request.ligouser)
+    contacts = Contact.objects.filter(user=request.ligouser)
+    d = { 'triggers' : triggers, 'contacts': contacts }
+    return render_to_response('profile/notifications.html',
+                              d,
+                              context_instance=RequestContext(request))
+
+def create(request):
+    explanation = ""
+    message = ""
+    if request.method == "POST":
+        form = TriggerForm(request.POST)
+        if form.is_valid():
+            # Create the Trigger
+            t = Trigger(user=request.ligouser)
+            labels = form.cleaned_data['labels']
+            atypes = form.cleaned_data['atypes']
+            contacts = form.cleaned_data['contacts']
+
+            if contacts and (labels or atypes):
+                t.save() # Need an id before relations can be set.
+                try:
+                    t.labels = labels
+                    t.atypes = atypes
+                    t.contacts = contacts
+                except:
+                    t.delete()
+                t.save()
+                request.session['flash_msg'] = "Created: %s" % t.userlessDisplay()
+                return HttpResponseRedirect(reverse(index))
+
+        # Data was bad
+        if not contacts:
+            message += "You must specify at least one contact. "
+        if not (labels or atypes):
+            message += "You need to indicate label(s) and/or analysis type(s)."
+    else:
+        form = TriggerForm()
+    if message:
+        request.session['flash_msg'] = message
+    return render_to_response('profile/createNotification.html',
+                              { "form" : form,
+                                "creating":"Notification",
+                                "explanation": explanation,
+                              },
+                              context_instance=RequestContext(request))
+
+def edit(request, id):
+    raise Http404
+
+def delete(request, id):
+    try:
+        t = Trigger.objects.get(id=id)
+    except Trigger.DoesNotExist:
+        raise Http404
+    if request.ligouser != t.user:
+        return HttpResponseForbidden("NO!")
+    request.session['flash_msg'] = "Notification Deleted: %s" % t.userlessDisplay()
+    t.delete()
+    return index(request)
+
+#--------------
+#-- Contacts --
+#--------------
+
+def createContact(request):
+    if request.method == "POST":
+        form = ContactForm(request.POST)
+        if form.is_valid():
+            # Create the Contact
+            c = Contact(
+                    user=request.ligouser,
+                    desc = form.cleaned_data['desc'],
+                    email = form.cleaned_data['email']
+                )
+            c.save()
+            request.session['flash_msg'] = "Created: %s" % c
+            return HttpResponseRedirect(reverse(index))
+    else:
+        form = ContactForm()
+    return render_to_response('profile/createNotification.html',
+                              { "form" : form,
+                                "creating":"Contact",
+                               },
+                              context_instance=RequestContext(request))
+
+
+def editContact(request, id):
+    raise Http404
+
+def deleteContact(request, id):
+    try:
+        c = Contact.objects.get(id=id)
+    except Contact.DoesNotExist:
+        raise Http404
+    if request.ligouser != c.user:
+        return HttpResponseForbidden("NO!")
+    request.session['flash_msg'] = "Notification Deleted: %s" % c
+    c.delete()
+    return index(request)
+
-- 
GitLab