From 1135690d05819986f0b0223e2be26312c05b9bb6 Mon Sep 17 00:00:00 2001 From: Branson Stephens <branson.stephens@ligo.org> Date: Fri, 11 Sep 2015 13:55:39 -0500 Subject: [PATCH] Advocate signoff feature. Integrated with operator signoff. --- gracedb/api.py | 18 +- gracedb/forms.py | 6 +- .../migrations/0007_auto_new_signoff_model.py | 47 +++ .../migrations/0008_add_advocate_labels.py | 19 + gracedb/models.py | 6 +- gracedb/query.py | 2 +- gracedb/urls.py | 2 +- gracedb/urls_rest.py | 2 +- gracedb/view_utils.py | 11 +- gracedb/views.py | 328 ++++++------------ migrations/auth/0008_add_advocates.py | 33 ++ settings/default.py | 2 + templates/gracedb/event_detail.html | 63 +++- templates/gracedb/event_detail_script.js | 11 +- 14 files changed, 307 insertions(+), 243 deletions(-) create mode 100644 gracedb/migrations/0007_auto_new_signoff_model.py create mode 100644 gracedb/migrations/0008_add_advocate_labels.py create mode 100644 migrations/auth/0008_add_advocates.py diff --git a/gracedb/api.py b/gracedb/api.py index b3f820c55..caab25189 100644 --- a/gracedb/api.py +++ b/gracedb/api.py @@ -24,7 +24,7 @@ from view_utils import fix_old_creation_request from view_utils import eventToDict, eventLogToDict, labelToDict from view_utils import embbEventLogToDict, voeventToDict from view_utils import emObservationToDict, skymapViewerEMObservationToDict -from view_utils import operatorSignoffToDict +from view_utils import signoffToDict from view_utils import reverse from translator import handle_uploaded_data @@ -1466,8 +1466,8 @@ class GracedbRoot(APIView): tag = tag.replace("0", "{n}") tag = tag.replace("tagname", "{tagname}") - operatorsignofflist = reverse("operatorsignoff-list", args=["G1200"], request=request) - operatorsignofflist = operatorsignofflist.replace("G1200", "{graceid}") + signofflist = reverse("signoff-list", args=["G1200"], request=request) + signofflist = signofflist.replace("G1200", "{graceid}") # XXX Need a template for the tag list? @@ -1482,7 +1482,7 @@ class GracedbRoot(APIView): "filemeta-template" : filemeta, "tag-template" : tag, "taglist-template" : taglist, - "operatorsignoff-list-template": operatorsignofflist, + "signoff-list-template": signofflist, } return Response({ @@ -1854,11 +1854,9 @@ class OperatorSignoffList(APIView): @event_and_auth_required def get(self, request, event): - operator_signoff_set = event.operatorsignoff_set.all() - count = operator_signoff_set.count() - - operator_signoff = [ operatorSignoffToDict(os) - for os in operator_signoff_set.iterator() ] + signoff_set = event.signoff_set.all() + count = signoff_set.count() + signoff = [ signoffToDict(s) for s in signoff_set.iterator() ] rv = { 'start': 0, @@ -1868,7 +1866,7 @@ class OperatorSignoffList(APIView): 'first' : request.build_absolute_uri(), 'last' : request.build_absolute_uri(), }, - 'operator_signoff' : operator_signoff, + 'signoff' : signoff, } return Response(rv) diff --git a/gracedb/forms.py b/gracedb/forms.py index 6cbe08de5..1dae6b981 100644 --- a/gracedb/forms.py +++ b/gracedb/forms.py @@ -3,7 +3,7 @@ from django import forms from django.utils.safestring import mark_safe from django.utils.html import escape from models import Event, Group, Label -from models import Pipeline, Search, OperatorSignoff +from models import Pipeline, Search, Signoff from django.contrib.auth.models import User from django.core.exceptions import FieldError from django.forms import ModelForm @@ -81,7 +81,7 @@ class EventSearchForm(forms.Form): labels = forms.MultipleChoiceField(choices=labelChoices, required=False) get_neighbors = forms.BooleanField(required=False) -class OperatorSignoffForm(ModelForm): +class SignoffForm(ModelForm): class Meta: - model = OperatorSignoff + model = Signoff fields = [ 'status', 'comment' ] diff --git a/gracedb/migrations/0007_auto_new_signoff_model.py b/gracedb/migrations/0007_auto_new_signoff_model.py new file mode 100644 index 000000000..e59715275 --- /dev/null +++ b/gracedb/migrations/0007_auto_new_signoff_model.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('gracedb', '0006_add_operator_signoff_labels'), + ] + + operations = [ + migrations.CreateModel( + name='Signoff', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('signoff_type', models.CharField(max_length=3, choices=[(b'OP', b'operator'), (b'ADV', b'advocate')])), + ('instrument', models.CharField(blank=True, max_length=2, choices=[(b'H1', b'LHO'), (b'L1', b'LLO'), (b'V1', b'Virgo')])), + ('status', models.CharField(max_length=2, choices=[(b'OK', b'OKAY'), (b'NO', b'NOT OKAY')])), + ('comment', models.TextField(blank=True)), + ('event', models.ForeignKey(to='gracedb.Event')), + ('submitter', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AlterUniqueTogether( + name='operatorsignoff', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='operatorsignoff', + name='event', + ), + migrations.RemoveField( + model_name='operatorsignoff', + name='submitter', + ), + migrations.DeleteModel( + name='OperatorSignoff', + ), + migrations.AlterUniqueTogether( + name='signoff', + unique_together=set([('event', 'instrument')]), + ), + ] diff --git a/gracedb/migrations/0008_add_advocate_labels.py b/gracedb/migrations/0008_add_advocate_labels.py new file mode 100644 index 000000000..5546b1931 --- /dev/null +++ b/gracedb/migrations/0008_add_advocate_labels.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + +def add_advocate_labels(apps, schema_editor): + Label = apps.get_model("gracedb", "Label") + for name in ['ADVREQ', 'ADVOK', 'ADVNO']: + Label.objects.create(name=name) + +class Migration(migrations.Migration): + + dependencies = [ + ('gracedb', '0007_auto_new_signoff_model'), + ] + + operations = [ + migrations.RunPython(add_advocate_labels), + ] diff --git a/gracedb/models.py b/gracedb/models.py index e9b47a6fb..21ee7987a 100644 --- a/gracedb/models.py +++ b/gracedb/models.py @@ -1098,12 +1098,14 @@ class VOEvent(models.Model): INSTRUMENTS = ( ('H1', 'LHO'), ('L1', 'LLO'), ('V1', 'Virgo') ) OPERATOR_STATUSES = ( ('OK', 'OKAY'), ('NO', 'NOT OKAY') ) -class OperatorSignoff(models.Model): +SIGNOFF_TYPE_CHOICES = ( ('OP', 'operator'), ('ADV', 'advocate') ) +class Signoff(models.Model): class Meta: unique_together = ("event","instrument") submitter = models.ForeignKey(DjangoUser) event = models.ForeignKey(Event) - instrument = models.CharField(max_length=2, choices=INSTRUMENTS) + signoff_type = models.CharField(max_length=3, choices=SIGNOFF_TYPE_CHOICES, blank=False) + instrument = models.CharField(max_length=2, choices=INSTRUMENTS, blank=True) status = models.CharField(max_length=2, choices=OPERATOR_STATUSES, blank=False) comment = models.TextField(blank=True) diff --git a/gracedb/query.py b/gracedb/query.py index 3015d13c4..66a433ef9 100644 --- a/gracedb/query.py +++ b/gracedb/query.py @@ -181,7 +181,7 @@ createdQ = createdQ.setParseAction(maybeRange("created")) # Labels # XXX should we not get these from the DB? -labelNames = ["DQV", "INJ", "LUMIN_NO", "LUMIN_GO", "SWIFT_NO", "SWIFT_GO", "EM_READY", "cWB_r","cWB_s", "H1OPS", "L1OPS", "V1OPS"] +labelNames = ["DQV", "INJ", "LUMIN_NO", "LUMIN_GO", "SWIFT_NO", "SWIFT_GO", "EM_READY", "cWB_r","cWB_s", "H1OPS", "L1OPS", "V1OPS", "H1OK", "H1NO", "L1OK", "L1NO", "V1OK", "V1NO", "ADVREQ", "ADVOK", "ADVNO"] label = Or([CaselessLiteral(n) for n in labelNames]).\ setParseAction( lambda toks: Q(labels__name=toks[0]) ) diff --git a/gracedb/urls.py b/gracedb/urls.py index 61a233b94..2c71cc045 100644 --- a/gracedb/urls.py +++ b/gracedb/urls.py @@ -19,7 +19,7 @@ urlpatterns = patterns('gracedb.views', url (r'^(?P<graceid>[GEHMT]\d+)$', 'view', name="view2"), url (r'^(?P<graceid>[GEHMT]\d+)/t90/$', 'modify_t90', name="modify_t90"), url (r'^(?P<graceid>[GEHMT]\d+)/perms/$', 'modify_permissions', name="modify_permissions"), - url (r'^(?P<graceid>[GEHMT]\d+)/signoff/$', 'modify_operator_signoff', name="modify_operator_signoff"), + url (r'^(?P<graceid>[GEHMT]\d+)/signoff/$', 'modify_signoff', name="modify_signoff"), url (r'^(?P<graceid>[GEHMT]\d+)/files/$', 'file_list', name="file_list"), url (r'^(?P<graceid>[GEHMT]\d+)/files/(?P<filename>.*)$', download, name="file"), url (r'^(?P<graceid>[GEHMT]\d+)/log/(?P<num>([\d]*|preview))$', 'logentry', name="logentry"), diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py index 048550373..6236d8317 100644 --- a/gracedb/urls_rest.py +++ b/gracedb/urls_rest.py @@ -107,7 +107,7 @@ urlpatterns = patterns('gracedb.api', # Operator Signoff Resources url (r'events/(?P<graceid>[GEHMT]\d+)/signoff/$', - OperatorSignoffList.as_view(), name='operatorsignoff-list'), + OperatorSignoffList.as_view(), name='signoff-list'), # Performance stats diff --git a/gracedb/view_utils.py b/gracedb/view_utils.py index 70b3d5167..8c62738b0 100644 --- a/gracedb/view_utils.py +++ b/gracedb/view_utils.py @@ -526,12 +526,13 @@ def singleInspiralToDict(single_inspiral): rv.update({ field_name: value }) return rv -def operatorSignoffToDict(operator_signoff): +def signoffToDict(signoff): return { - 'submitter': operator_signoff.submitter.username, - 'instrument': operator_signoff.instrument, - 'status': operator_signoff.status, - 'comment': operator_signoff.comment, + 'submitter': signoff.submitter.username, + 'instrument': signoff.instrument, + 'status': signoff.status, + 'comment': signoff.comment, + 'signoff_type': signoff.signoff_type } #--------------------------------------------------------------------------------------- diff --git a/gracedb/views.py b/gracedb/views.py index c571bff6f..21e40bbd0 100644 --- a/gracedb/views.py +++ b/gracedb/views.py @@ -7,8 +7,8 @@ from django.core.urlresolvers import reverse from django.shortcuts import render_to_response from models import Event, Group, EventLog, Label, Tag, Pipeline, Search, GrbEvent -from models import EMGroup, OperatorSignoff -from forms import CreateEventForm, EventSearchForm, SimpleSearchForm, OperatorSignoffForm +from models import EMGroup, Signoff +from forms import CreateEventForm, EventSearchForm, SimpleSearchForm, SignoffForm from django.contrib.auth.models import User, Permission from django.contrib.auth.models import Group as AuthGroup @@ -324,29 +324,51 @@ def view(request, event): if event.pipeline.name in settings.GRB_PIPELINES: context['can_modify_t90'] = request.user.has_perm('gracedb.t90_grbevent') - # Does the user have permission to sign off on the event? - signoff_authorized = False + # Does the user have permission to sign off on the event as the control room operator? + operator_signoff_authorized = False # XXX Note that this may not be the best way to perform the authorization check. # In particular, this assumes that the user can only be a member of one group # at a time. That should be the case, however, as the control room machines are # physically well separated and should have different IPs. for group in request.user.groups.all(): if '_control_room' in group.name: - signoff_authorized = True + operator_signoff_authorized = True context['signoff_instrument'] = group.name[:2].upper() - context['signoff_form'] = OperatorSignoffForm() + context['signoff_form'] = SignoffForm() instrument = group.name[:2].upper() try: - context['signoff_object'] = OperatorSignoff.objects.get(event=event, instrument=instrument) + context['operator_signoff_object'] = Signoff.objects.get(event=event, + instrument=instrument, signoff_type='OP') except: - context['signoff_object'] = None - label_name = instrument + 'OPS' - label_exists = label_name in [l.label.name for l in event.labelling_set.all()] + context['operator_signoff_object'] = None + req_label = instrument + 'OPS' + label_exists = req_label in [l.label.name for l in event.labelling_set.all()] - context['signoff_active'] = label_exists or context['signoff_object'] + context['operator_signoff_active'] = label_exists or context['operator_signoff_object'] break - context['signoff_authorized'] = signoff_authorized + context['operator_signoff_authorized'] = operator_signoff_authorized + + # XXX A lot of repetition here. Hopefully this will be fixed later. + # Does the user have permission to sign off on the event as an EM advocate? + advocate_signoff_authorized = False + for group in request.user.groups.all(): + if settings.EM_ADVOCATE_GROUP==group.name: + advocate_signoff_authorized = True + context['signoff_form'] = SignoffForm() + instrument = '' + try: + context['advocate_signoff_object'] = Signoff.objects.get(event=event, + instrument=instrument, signoff_type='ADV') + except: + context['advocate_signoff_object'] = None + req_label = 'ADVREQ' + label_exists = req_label in [l.label.name for l in event.labelling_set.all()] + + context['advocate_signoff_active'] = label_exists or context['advocate_signoff_object'] + + break + context['advocate_signoff_authorized'] = advocate_signoff_authorized # Choose your template according to the event's pipeline. templates = ['gracedb/event_detail.html',] @@ -891,41 +913,67 @@ def modify_t90(request, event): # XXX So this should probably be moved into view_logic anyway. from alert import issueXMPPAlert -from view_utils import operatorSignoffToDict +from view_utils import signoffToDict +from models import SIGNOFF_TYPE_CHOICES + +def get_signoff_type(stype): + for t in SIGNOFF_TYPE_CHOICES: + if stype in t: + return t[0] + return None @event_and_auth_required -def modify_operator_signoff(request, event): - # Get group_name and action from POST +def modify_signoff(request, event): if not request.method=='POST': msg = 'create_operator_signoff only allows POST.' return HttpResponseBadRequest(msg) + import logging + logger = logging.getLogger(__name__) + logger.debug("Got POST dict: %s" % request.POST) authorized = False - instrument = None - # XXX Note that this may not be the best way to perform the authorization check. - # In particular, this assumes that the user can only be a member of one group - # at a time. That should be the case, however, as the control room machines are - # physically well separated and should have different IPs. - for group in request.user.groups.all(): - if '_control_room' in group.name: + instrument = '' + action = request.POST.get('action', 'create') + signoff_type = request.POST.get('signoff_type', 'operator') + + if signoff_type=='operator': + # XXX Note that this may not be the best way to perform the authorization check. + # In particular, this assumes that the user can only be a member of one group + # at a time. That should be the case, however, as the control room machines are + # physically well separated and should have different IPs. + for group in request.user.groups.all(): + if '_control_room' in group.name: + authorized = True + instrument = group.name[:2].upper() + break + if not len(instrument): + msg = "Unknown instrument/control room for signoff." + return HttpResponseBadRequest(msg) + + req_label = instrument + 'OPS' + label_stem = instrument + elif signoff_type=='advocate': + user_groups = [g.name for g in request.user.groups.all()] + if settings.EM_ADVOCATE_GROUP in user_groups: authorized = True - instrument = group.name[:2].upper() - break + + req_label = 'ADVREQ' + label_stem = 'ADV' + else: + msg = 'Unknown signoff type.' + return HttpResponseBadRequest(msg) if not authorized: - msg = "You do not appear to be in one of the control rooms." - msg += " Therefore, you are not authorized to perform the requested action." + msg += "You are not authorized to perform the requested action." return HttpResponseForbidden(msg) - action = request.POST.get('action', 'create') - label_name = instrument + 'OPS' - - existing = OperatorSignoff.objects.filter(event=event, instrument=instrument) + existing = Signoff.objects.filter(event=event, instrument=instrument, + signoff_type=get_signoff_type(signoff_type)) if existing.count() and action=='create': - msg = 'Cannot create multiple signoffs for the same event/instrument.' + msg = 'Cannot create multiple signoffs for the same event.' return HttpResponseBadRequest(msg) - f = OperatorSignoffForm(request.POST) + f = SignoffForm(request.POST) status = None comment = None if f.is_valid(): @@ -938,21 +986,24 @@ def modify_operator_signoff(request, event): return HttpResponseBadRequest(msg) # Create the OperatorSignoff object. - os = OperatorSignoff.objects.create(submitter = request.user, + signoff = Signoff.objects.create(submitter = request.user, event = event, instrument = instrument, - status = status, comment = comment) + status = status, comment = comment, + signoff_type = get_signoff_type(signoff_type)) - # Remove the signoff label. + # Remove the request label. for l in event.labelling_set.all(): - if l.label.name == label_name: + if l.label.name == req_label: l.delete() # Create a new label. - os_label_name = instrument + status - create_label(event, os_label_name, request.user, doAlert=False, doXMPP=False) + label_name = label_stem + status + create_label(event, label_name, request.user, doAlert=False, doXMPP=False) # Create a log message - msg = "Operator certified %s status as %s" % (instrument, status) + msg = "%s signoff certified status as %s" % (signoff_type, status) + if len(instrument): + msg += ' for %s' % instrument if comment: msg += ': %s' % comment logentry = EventLog.objects.create(event=event, issuer=request.user, comment=msg) @@ -967,34 +1018,39 @@ def modify_operator_signoff(request, event): # Issue an alert. issueXMPPAlert(event, location='', alert_type="signoff", description=status, - serialized_object = operatorSignoffToDict(os)) + serialized_object = signoffToDict(signoff)) elif action=='edit': # get the existing object - os = None - if existing.count(): - os = existing[0] + signoff = None + if existing.count()==1: + signoff = existing[0] + elif existing.count()>1: + msg = 'Found too many existing signoffs. Something is wrong.' + return HttpResponseServerError(msg) - if not os: - msg = 'Could not find existing OperatorSignoff for this event/instrument.' + if not signoff: + msg = 'Could not find existing signoff for this event/instrument.' return HttpResponseBadRequest(msg) # remove the existing label - os_label_name = os.instrument + os.status + label_name = label_stem + signoff.status for l in event.labelling_set.all(): - if l.label.name == os_label_name: + if l.label.name == label_name: l.delete() delete = request.POST.get('delete', None) if delete: # delete the operator signoff object - os.delete() + signoff.delete() # also restore the label - create_label(event, label_name, request.user) + create_label(event, req_label, request.user) # Create a log message - msg = "%s operator deleted signoff status" % instrument + msg = "deleted %s signoff status" % signoff_type + if len(instrument): + msg += ' for %s' % instrument logentry = EventLog.objects.create(event=event, issuer=request.user, comment=msg) # XXX Ugh. Hardcoding tagname here. @@ -1009,19 +1065,21 @@ def modify_operator_signoff(request, event): msg = "Please select a valid status." return HttpResponseBadRequest(msg) # update the values - os.status = status - os.comment = comment - os.save() + signoff.status = status + signoff.comment = comment + signoff.save() # Issue an alert. issueXMPPAlert(event, location='', alert_type="signoff", description=status, - serialized_object = operatorSignoffToDict(os)) + serialized_object = signoffToDict(signoff)) # Create a new label. - os_label_name = instrument + status - create_label(event, os_label_name, request.user, doAlert=False, doXMPP=False) + label_name = instrument + status + create_label(event, label_name, request.user, doAlert=False, doXMPP=False) # Create a log message - msg = "Operator updated %s status as %s" % (instrument, status) + msg = "updated %s signoff status as %s" % (signoff_type, status) + if len(instrument): + msg += ' for %s' % instrument if comment: msg += ': %s' % comment logentry = EventLog.objects.create(event=event, issuer=request.user, comment=msg) @@ -1037,159 +1095,3 @@ def modify_operator_signoff(request, event): # Finished. Redirect back to the event. return HttpResponseRedirect(reverse("view", args=[event.graceid()])) -#------------------------------------------------------------------------------------------ -# Old Stuff -#------------------------------------------------------------------------------------------ -# -# Here is the old stuff we used for the Latest page. -# Originally, public users could see a version of this page with some -# fields stripped out. We may still want to do something like that in the -# future, but for now, we're actually limiting *which* events a public user -# can see. -# -#class LimitedEvent(): -# def __init__(self, event): -# self._event = event -# def __getattr__(self, attr): -# if attr == 'gpstime': -# return None -# elif attr == 'created': -# return self._event.created.replace(second=0) -# else: -# return getattr(self._event, attr) -# -#def latest_limited(request): -# return latest(request) -# -#def latest(request): -# context = {} -# -# if request.method == "GET": -# form = SimpleSearchForm(request.GET) -# else: -# form = SimpleSearchForm(request.POST) -# -# template = 'gracedb/latest.html' -# if not request.user or not request.user.is_authenticated(): -# limit = LimitedEvent -# template = 'gracedb/latest_public.html' -# else: -# limit = lambda x: x -# -# context['form'] = form -# context['rawquery'] = request.GET.get('query') or request.POST.get('query') or "" -# -# if form.is_valid(): -# objects = form.cleaned_data['query'] -# objects = filter_events_for_user(objects, request.user, 'view')[0:50] -# context['objects'] = map(limit, objects) -# context['error'] = False -# else: -# context['error'] = True -# -# return render_to_response( -# template, -# context, -# context_instance=RequestContext(request)) -# -# -# XXX This looks interesting. Apparently an old attempt by Brian to make a nice -# graphical timeline of events, a la SkyAlert. Or something? -#def timeline(request): -# from utils import gpsToUtc -# from django.utils import dateformat -# -# response = HttpResponse(mimetype='application/javascript') -# events = [] -# for event in Event.objects.exclude(group__name="Test").all(): -# if event.gpstime: -# t = dateformat.format(gpsToUtc(event.gpstime), "F j, Y h:i:s")+" UTC" -# -# events.append({ -# 'start': t, -# 'title': event.get_analysisType_display(), -# 'description': -# "%s<br/>%s" %(event.get_analysisType_display(),"GPS time:%s"%event.gpstime), -# 'durationEvent':False, -# }) -# d = {'events': events} -# msg = json.dumps(d) -# response['Content-length'] = len(msg) -# response.write(msg) -# return response -# -#import re -#from django.core.mail import mail_admins -#from buildVOEvent import submitToSkyalert -# -#def skyalert_authorized(request): -# try: -# return u"{0} {1}".format(request.user.first_name, request.user.last_name) in settings.SKYALERT_SUBMITTERS -# except: -# return Fals -# -#def skyalert(request, graceid): -# event = Event.getByGraceid(graceid) -# createLogEntry = True -# -# if not event.gpstime: -# request.session['flash_msg'] = "No GPS time. Event not suitable for submission to SkyAlert" -# return HttpResponseRedirect(reverse(view, args=[graceid])) -# -# if not event.far: -# request.session['flash_msg'] = "No FAR. Event not suitable for submission to SkyAlert" -# return HttpResponseRedirect(reverse(view, args=[graceid])) -# -# if not skyalert_authorized(request): -# request.session['flash_msg'] = "You are not authorized for SkyAlert submission" -# return HttpResponseRedirect(reverse(view, args=[graceid])) -# -# try: -# skyalert_response = submitToSkyalert(event) -# except Exception, e: -# message = "SkyAlert Submission Error" -# skyalert_response = "" -# # XXX umm. don't we want to know if this email fails silently? -# mail_admins("SkyAlert Submission Error", -# "Event: %s\nException: %s\n" % (graceid, e), -# fail_silently=True) -# -# flashmessage = None -# if skyalert_response.find("Success") >= 0: -# urlpat = re.compile('https?://[^ ]*') -# match = urlpat.search(skyalert_response) -# if match: -# message = "Submitted to Skyalert: %s" % match.group() -# url = match.group() -# flashmessage = 'Submitted to Skyalert: %s' % url -# message = 'Submitted to Skyalert: <a href="%s">%s</a>' % (url,url) -# else: -# message = "SkyAlert submission problem. Cannot parse SkyAlert response." -# # XXX umm. don't we want to know if this email fails silently? -# mail_admins("SkyAlert response parsing problem", -# "Event: %s\nSkyAlert Response: %s\n" % (graceid, skyalert_response), -# fail_silently=True) -# elif (skyalert_response.find('already') >= 0) or (skyalert_response.find('Duplicate') >= 0): -# message = "Event already submitted to SkyAlert" -# createLogEntry = False -# elif skyalert_response: -# message = "Skyalert Submission Failed." -# mail_admins("SkyAlert submission failed", -# "Event: %s\nSkyAlert Response: %s\n" % (graceid, skyalert_response), -# fail_silently=True) -# -# request.session['flash_msg'] = flashmessage or message -# -# if createLogEntry: -# logentry = EventLog(event=event, issuer=request.ligouser, comment=message) -# try: -# logentry.save() -# except: -# # XXX Failed to create log entry for skyalert submission. -# # Error message? -# pass -# -# return HttpResponseRedirect(reverse(view, args=[graceid])) -# - - diff --git a/migrations/auth/0008_add_advocates.py b/migrations/auth/0008_add_advocates.py new file mode 100644 index 000000000..d0e940406 --- /dev/null +++ b/migrations/auth/0008_add_advocates.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + +advocate_usernames = [ + 'branson.stephens@LIGO.ORG', +] + +def add_advocates_group_and_users(apps, schema_editor): + from django.conf import settings + + User = apps.get_model("auth", "User") + Group = apps.get_model("auth", "Group") + + advocates = Group.objects.create(name=settings.EM_ADVOCATE_GROUP) + + for username in advocate_usernames: + try: + user = User.objects.get(username=username) + advocates.user_set.add(user) + except: + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0007_auto_20150708_1134'), + ] + + operations = [ + migrations.RunPython(add_advocates_group_and_users), + ] diff --git a/settings/default.py b/settings/default.py index 73cc15a16..96175b10d 100644 --- a/settings/default.py +++ b/settings/default.py @@ -47,6 +47,8 @@ EMBB_IGNORE_ADDRESSES = ['Mailer-Daemon@gracedb.phys.uwm.edu',] # Added for django 1.7.8 TEST_RUNNER = 'django.test.runner.DiscoverRunner' +EM_ADVOCATE_GROUP = 'em_advocates' + # 11/18/14. No longer checking XMPP_ALERT_CHANNELS. This is not necessary. # If someone sends out an event, an alert should go out. Listerers have the # option to unsubscribe from nodes if so desired. diff --git a/templates/gracedb/event_detail.html b/templates/gracedb/event_detail.html index 003559c38..a6699c6fc 100644 --- a/templates/gracedb/event_detail.html +++ b/templates/gracedb/event_detail.html @@ -62,19 +62,20 @@ </div> {% endif %} -{% if signoff_authorized and signoff_active %} +<!-- Here is a section for the control room operator signoff --> +{% if operator_signoff_authorized and operator_signoff_active %} <div class="signoff-area"> <h2> {{signoff_instrument}} Operator Signoff </h2> <p> You are seeing this section because you've connected from a machine that, according to our records, is in the {{signoff_instrument}} control room. - {% if signoff_object %} + {% if operator_signoff_object %} This event has already been signed off on. Use the form below if you wish to edit or delete the record. </p> - <form action="{% url "modify_operator_signoff" object.graceid %}" method="post"> + <form action="{% url "modify_signoff" object.graceid %}" method="post"> <table> <tr><th><label for="id_status">Status:</label></th><td><select id="id_status" name="status"> <option value="">---------</option> - {% if signoff_object.status == "OK" %} + {% if operator_signoff_object.status == "OK" %} <option value="OK" selected="selected">OKAY</option> <option value="NO">NOT OKAY</option> {% else %} @@ -83,8 +84,9 @@ {% endif %} </select></td></tr> <tr><th><label for="id_comment">Comment:</label></th><td> - <textarea cols="40" id="id_comment" name="comment" rows="10"> {{signoff_object.comment}} + <textarea cols="40" id="id_comment" name="comment" rows="10"> {{operator_signoff_object.comment}} </textarea></td></tr> + <input type="hidden" name="signoff_type" value="operator"> <input type="hidden" name="action" value="edit"> <tr> <th> Delete </th> <td> <input type="checkbox" name="delete" value="true"> </td> </tr> <tr> <td></td> <td> <input type="submit" value="Submit" class="searchButtonClass"> </td> </tr> @@ -104,9 +106,10 @@ event, {% endif %} was the operating status of the detector basically okay, or not? </p> - <form action="{% url "modify_operator_signoff" object.graceid %}" method="post"> + <form action="{% url "modify_signoff" object.graceid %}" method="post"> <table> {{ signoff_form.as_table }} + <input type="hidden" name="signoff_type" value="operator"> <input type="hidden" name="action" value="create"> <tr> <td></td> <td> <input type="submit" value="Submit" class="searchButtonClass"> </td> </tr> </table> @@ -115,6 +118,54 @@ </div> {% endif %} + +<!-- Here is a section for the EM advocate signoffs. --> +<!-- XXX So much ugliness here. Is there a way to do this with the JS? --> +{% if advocate_signoff_authorized and advocate_signoff_active %} + <div class="signoff-area"> + <h2> Advocate Signoff </h2> + <p> You are seeing this section because you're a designated EM followup + advocate. + {% if advocate_signoff_object %} + This event has already been signed off on. + Use the form below if you wish to edit or delete the record. </p> + <form action="{% url "modify_signoff" object.graceid %}" method="post"> + <table> + <tr><th><label for="id_status">Status:</label></th><td><select id="id_status" name="status"> + <option value="">---------</option> + {% if advocate_signoff_object.status == "OK" %} + <option value="OK" selected="selected">OKAY</option> + <option value="NO">NOT OKAY</option> + {% else %} + <option value="OK">OKAY</option> + <option value="NO" selected="selected">NOT OKAY</option> + {% endif %} + </select></td></tr> + <tr><th><label for="id_comment">Comment:</label></th><td> + <textarea cols="40" id="id_comment" name="comment" rows="10"> {{advocate_signoff_object.comment}} + </textarea></td></tr> + <input type="hidden" name="signoff_type" value="advocate"> + <input type="hidden" name="action" value="edit"> + <tr> <th> Delete </th> <td> <input type="checkbox" name="delete" value="true"> </td> </tr> + <tr> <td></td> <td> <input type="submit" value="Submit" class="searchButtonClass"> </td> </tr> + </table> + </form> + + {% else %} + This event still requires EM Followup advocate signoff. </p> + <form action="{% url "modify_signoff" object.graceid %}" method="post"> + <table> + {{ signoff_form.as_table }} + <input type="hidden" name="signoff_type" value="advocate"> + <input type="hidden" name="action" value="create"> + <tr> <td></td> <td> <input type="submit" value="Submit" class="searchButtonClass"> </td> </tr> + </table> + </form> + {% endif %} + </div> +{% endif %} + + <div class="content-area"> {% block basic_info %} <h2> Basic Info </h2> diff --git a/templates/gracedb/event_detail_script.js b/templates/gracedb/event_detail_script.js index 7ae3b13b6..1ae0ca3cf 100644 --- a/templates/gracedb/event_detail_script.js +++ b/templates/gracedb/event_detail_script.js @@ -9,7 +9,16 @@ var label_descriptions = { LUMIN_NO: "LUMIN No", LUMIN_GO: "LUMIN Go", DQV: "Data quality veto.", - INJ: "Injection occured near this time." + INJ: "Injection occured near this time.", + ADVREQ: "EM advocate signoff requested.", + ADVNO: "EM advocate says event is not okay.", + ADVOK: "EM advocate says event is okay.", + H1OPS: "H1 operator signoff requested.", + H1OK: "H1 operator says event is okay.", + H1NO: "H1 operator says event is not okay.", + L1OPS: "L1 operator signoff requested.", + L1OK: "L1 operator says event is okay.", + L1NO: "L1 operator says event is not okay." } function tooltiptext(name, creator, time) { return ( creator + " " + time + "<br/>" + label_descriptions[name] ); -- GitLab