diff --git a/gracedb/alerts/main.py b/gracedb/alerts/main.py index c7ae382bc0ebbaebe5e6d63a4d1f64feadd5aafd..bcf3b62354617dcbec6a28408743df8614ba26bf 100644 --- a/gracedb/alerts/main.py +++ b/gracedb/alerts/main.py @@ -106,9 +106,9 @@ def issue_alerts(event_or_superevent, alert_type, url=None, file_name="", description="", label=None, serialized_object=None): # Check alert_type - if alert_type not in ["new", "label", "update"]: + if alert_type not in ["new", "label", "update", "signoff"]: raise ValueError(("alert_type is {0}, should be 'new', 'label', " - "or 'update'").format(alert_type)) + "'update', or 'signoff'").format(alert_type)) # Send XMPP alert if settings.SEND_XMPP_ALERTS: @@ -121,8 +121,8 @@ def issue_alerts(event_or_superevent, alert_type, url=None, file_name="", if is_superevent(event_or_superevent): return - # We currently don't send phone or email alerts for updates - if alert_type == "update": + # We currently don't send phone or email alerts for updates or signoffs + if alert_type == "update" or alert_type == "signoff": return # Don't send phone or email alerts for MDC events or Test events diff --git a/gracedb/alerts/superevent_utils.py b/gracedb/alerts/superevent_utils.py index aa23f3c362ec1b09f8b339edd312d00a346fd76c..da8b9daf379cab41f2bf0293845694b7552aa809 100644 --- a/gracedb/alerts/superevent_utils.py +++ b/gracedb/alerts/superevent_utils.py @@ -5,7 +5,8 @@ from .main import issue_alerts from core.urls import build_absolute_uri from superevents.api.serializers import SupereventSerializer, \ SupereventLogSerializer, SupereventLabelSerializer, \ - SupereventEMObservationSerializer, SupereventVOEventSerializer + SupereventEMObservationSerializer, SupereventVOEventSerializer, \ + SupereventSignoffSerializer from superevents.shortcuts import is_superevent import logging @@ -140,3 +141,16 @@ def issue_alert_for_superevent_emobservation(emobservation, request=None): # Send alerts issue_alerts(emobservation.superevent, alert_type="update", url=url, description=description, serialized_object=serialized_object) + + +def issue_alert_for_superevent_signoff(signoff, request=None): + # Get URL for superevent webview and serialized label + url, serialized_object = superevent_alert_helper(signoff, + SupereventSignoffSerializer, request=request) + + # Description + description = signoff.status + + # Send alerts + issue_alerts(signoff.superevent, alert_type="signoff", url=url, + description=description, serialized_object=serialized_object) diff --git a/gracedb/events/models.py b/gracedb/events/models.py index 97341779540588259a5242468f32b4a7c83cf9c7..771750211d16d264ecc03404e5e23dbf3e43763e 100644 --- a/gracedb/events/models.py +++ b/gracedb/events/models.py @@ -458,9 +458,6 @@ class EMObservation(EMObservationBase, AutoIncrementModel): return "{event_id} | {group} | {N}".format( event_id=self.event.graceid(), group=self.group.name, N=self.N) - def __unicode__(self): - return "%s-%s-%d" % (self.event.graceid(), self.group.name, self.N) - def calculateCoveringRegion(self): footprints = self.emfootprint_set.all() super(EMObservation, self).calculateCoveringRegion(footprints) @@ -514,6 +511,11 @@ class Labelling(m2mThroughBase): event = models.ForeignKey(Event) label = models.ForeignKey(Label) + def __unicode__(self): + return "{graceid} | {label}".format(graceid=self.event.graceid(), + label=self.label.name) + + # XXX Deprecated? Is this used *anywhere*? # Appears to only be used in models.py. Here and Event class as approval_set class Approval(models.Model): @@ -923,7 +925,7 @@ class SignoffBase(models.Model): """Custom clean method for signoffs""" # Make sure instrument is non-blank if this is an operator signoff - if (signoff_type == self.SIGNOFF_TYPE_OPERATOR and + if (self.signoff_type == self.SIGNOFF_TYPE_OPERATOR and not self.instrument): raise ValidationError({'instrument': @@ -931,6 +933,32 @@ class SignoffBase(models.Model): super(SignoffBase, self).clean(*args, **kwargs) + def get_req_label_name(self): + if self.signoff_type == 'OP': + return self.instrument + 'OPS' + elif self.signoff_type == 'ADV': + return 'ADVREQ' + + def get_status_label_name(self): + if self.signoff_type == 'OP': + return self.instrument + self.status + elif self.signoff_type == 'ADV': + return 'ADV' + self.status + + @property + def opposite_status(self): + if self.status == 'OK': + return 'NO' + elif self.status == 'NO': + return 'OK' + + def get_opposite_status_label_name(self): + if self.signoff_type == 'OP': + return self.instrument + self.opposite_status + elif self.signoff_type == 'ADV': + return 'ADV' + self.opposite_status + + class Signoff(SignoffBase): """Class for Event signoffs""" diff --git a/gracedb/superevents/api/serializers.py b/gracedb/superevents/api/serializers.py index cc0f302749dc399806b4625a86d6c5a2d944f4dc..2283082c4eeafcbe7afac59c1ad177b48cba7052 100644 --- a/gracedb/superevents/api/serializers.py +++ b/gracedb/superevents/api/serializers.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from django.utils.translation import ugettext_lazy as _ from django.conf import settings from ..models import Superevent, Labelling, Log, VOEvent, EMObservation, \ - EMFootprint + EMFootprint, Signoff from .fields import ParentObjectDefault, CommaSeparatedOrListField from .settings import SUPEREVENT_LOOKUP_FIELD @@ -607,3 +607,12 @@ class SupereventEMObservationSerializer(serializers.ModelSerializer): return emo + +class SupereventSignoffSerializer(serializers.ModelSerializer): + submitter = serializers.SlugRelatedField(slug_field='username', + read_only=True) + + class Meta: + model = Signoff + fields = ['submitter', 'instrument', 'status', 'comment', + 'signoff_type'] diff --git a/gracedb/superevents/utils.py b/gracedb/superevents/utils.py index cc0f320d7acc0320f7e04242a50aeb927a271e1e..5beeaf0012a6fd1eb54c429ae076aaece177eba8 100644 --- a/gracedb/superevents/utils.py +++ b/gracedb/superevents/utils.py @@ -14,7 +14,7 @@ from alerts.superevent_utils import issue_alert_for_superevent_creation, \ issue_alert_for_superevent_label_creation, \ issue_alert_for_superevent_label_removal, \ issue_alert_for_superevent_emobservation, \ - issue_alert_for_superevent_voevent + issue_alert_for_superevent_voevent, issue_alert_for_superevent_signoff from alerts.event_utils import issue_alert_for_event_log import os @@ -505,3 +505,135 @@ def create_voevent_for_superevent(superevent, issuer, voevent_type, issue_alert_for_superevent_voevent(voevent) return voevent + + +# TODO: wrap this function in a try-except block in the form +def create_signoff_for_superevent(superevent, user, signoff_type, + signoff_instrument, signoff_status, signoff_comment, add_log_message=True, + issue_alert=True): + + # Create signoff + signoff = Signoff.objects.create(superevent=superevent, submitter=user, + instrument=signoff_instrument, signoff_type=signoff_type, + status=signoff_status, comment=signoff_comment) + + # Create log message to document the signoff + if add_log_message: + signoff_type_full = dict(Signoff.SIGNOFF_TYPE_CHOICES)[signoff_type] + comment = "{signoff_type} signoff certified status as {status}".format( + signoff_type=signoff_type_full.capitalize(), status=signoff_status) + if signoff_instrument: + comment += " for {inst}".format(inst=signoff_instrument) + em_follow = Tag.objects.get(name='em_follow') + signoff_log = create_log(user, comment, superevent, issue_alert=False, + tags=[em_follow]) + + # Remove label which requested signoff + labelling_to_remove = superevent.labelling_set.filter(label__name= + signoff.get_req_label_name()).first() + if labelling_to_remove is not None: + remove_label_from_superevent(labelling_to_remove, user) + + # Add new label depending on signoff status + label_to_add = Label.objects.get(name=signoff.get_status_label_name()) + add_label_to_superevent(superevent, label_to_add, user) + + # Issue alert + if issue_alert: + issue_alert_for_superevent_signoff(signoff) + + return signoff + +def update_signoff_for_superevent(signoff, user, changed_data, + add_log_message=True, issue_alert=True): + # changed_data is a dict which contains the fields of the signoff + # which have changed + + # Get superevent + superevent = signoff.superevent + + # Save signoff + signoff.save() + + if 'status' in changed_data: + # If label for opposite status exists, remove it (i.e., the status has + # changed in this update, so we need to update the labels) + labelling_to_remove = superevent.labelling_set.filter(label__name= + signoff.get_opposite_status_label_name()).first() + if labelling_to_remove is not None: + remove_label_from_superevent(labelling_to_remove, user, + add_log_message=False, issue_alert=True) + + # If label for current status doesn't exist, add it + label_to_add = Label.objects.get(name=signoff.get_status_label_name()) + if label_to_add not in superevent.labels.all(): + add_label_to_superevent(superevent, label_to_add, user, + add_log_message=False, issue_alert=True) + + # Create log message to document the update + if add_log_message: + # Construct message + signoff_type_full = dict(Signoff.SIGNOFF_TYPE_CHOICES) \ + [signoff.signoff_type] + comment = "{signoff_type} signoff updated".format( + signoff_type=signoff_type_full.capitalize()) + if signoff.instrument: + comment += " for {inst}".format(inst=signoff.instrument) + if 'status' in changed_data: + comment += (": status {old_status} -> {new_status}, label " + "{r_label} removed, and label {a_label} applied").format( + old_status=signoff.opposite_status, new_status=signoff.status, + r_label=labelling_to_remove.label.name, + a_label=label_to_add.name) + em_follow = Tag.objects.get(name='em_follow') + signoff_log = create_log(user, comment, superevent, issue_alert=False, + tags=[em_follow]) + + # Issue alert + if issue_alert: + issue_alert_for_superevent_signoff(signoff) + + return signoff + + +def delete_signoff_for_superevent(signoff, user, add_log_message=True, + issue_alert=True): + + # Get superevent + superevent = signoff.superevent + + # Delete signoff + signoff.delete() + + # Remove current OK or NO label: we don't add a log message here since + # we'll document this in the full log message about the signoff + labelling_to_remove = superevent.labelling_set.filter(label__name= + signoff.get_status_label_name()).first() + remove_label_from_superevent(labelling_to_remove, user, + add_log_message=False, issue_alert=True) + + # Reapply initial "req" labe: we don't add a log message here since we'll + # document this in the full log message about the signoff + label_to_add = Label.objects.get(name=signoff.get_req_label_name()) + add_label_to_superevent(superevent, label_to_add, user, + add_log_message=False, issue_alert=True) + + # Log message + if add_log_message: + # Construct log message + signoff_type_full = dict(Signoff.SIGNOFF_TYPE_CHOICES) \ + [signoff.signoff_type] + comment = "{signoff_type} signoff ".format(signoff_type= + signoff_type_full.capitalize()) + if signoff.instrument: + comment += "for {inst} ".format(inst=signoff.instrument) + comment += "deleted: {r_label} removed and {a_label} reapplied".format( + r_label=labelling_to_remove.label.name, a_label=label_to_add.name) + em_follow = Tag.objects.get(name='em_follow') + signoff_log = create_log(user, comment, superevent, tags=[em_follow], + issue_alert=False) + + # Alert + if issue_alert: + issue_alert_for_superevent_log(signoff_log) +