Forked from
IGWN Computing and Software / GraceDB / GraceDB Server
1512 commits behind the upstream repository.
-
Tanner Prestegard authored
Added utility functions for hiding and exposing logs from LV-EM and the public. They are triggered by log messages being tagged or untagged. Also added utilities for hiding/exposing superevents.
Tanner Prestegard authoredAdded utility functions for hiding and exposing logs from LV-EM and the public. They are triggered by log messages being tagged or untagged. Also added utilities for hiding/exposing superevents.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
utils.py 25.88 KiB
from django.shortcuts import get_object_or_404
from django.http import Http404
from .buildVOEvent import construct_voevent_file
from .models import Superevent, Log, Labelling, EMObservation, EMFootprint, \
VOEvent, Signoff
from .shortcuts import is_superevent
from events.models import Event, EventLog, Tag, Label
from events.permission_utils import assign_default_perms
from events.shortcuts import is_event
from core.vfile import create_versioned_file
from alerts.superevent_utils import issue_alert_for_superevent_creation, \
issue_alert_for_superevent_log, \
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_signoff
from alerts.event_utils import issue_alert_for_event_log
import os
from core.permission_utils import expose_log_to_lvem, expose_log_to_public, \
hide_log_from_lvem, hide_log_from_public
import logging
logger = logging.getLogger(__name__)
# NOTE: everything in here assumes that permissions have already been checked
# and handled properly
# TODO:
# Add decorator to check access permissions (??) not sure if we should do it here or in the viewset itself
def create_superevent(submitter, t_start, t_0, t_end, preferred_event,
events=[], labels=[], category='P', add_log_message=True,
issue_alert=True):
"""
Utility method for creating superevents.
Arguments:
submitter: User object (required)
t_start, t_0, t_end: floats (required)
preferred_event: Event object (required)
events: list or QuerySet of Event objects
labels: list of QuerySet of Label objects
Usage:
create_superevent(
UserModel.objects.get(username='albert.einstein@ligo.org'),
t_start=1, t_0=2, t_end=3, preferred_event=
Event.objects.get(id='123400')
)
"""
# Create superevent
s = Superevent.objects.create(submitter=submitter, t_start=t_start,
t_0=t_0, t_end=t_end, preferred_event=preferred_event,
category=category)
# Create a log message to record initial superevent parameters
creation_comment = ("Superevent created with t_start={t_start}, t_0={t_0},"
" t_end={t_end}, preferred_event={preferred_event}").format(
t_start=t_start, t_0=t_0, t_end=t_end, preferred_event=preferred_event)
if events:
creation_comment += ", events={events}".format(events=", ".join(
[ev.graceid() for ev in events]))
# This is autogenerated, but people probably don't want to hide it
# in the web interface
superevent_creation_log = create_log(submitter, creation_comment, s,
issue_alert=False, autogenerated=False)
# Write a log message for preferred event
pref_event_msg = "Set as preferred event for superevent: {superevent_id}" \
.format(superevent_id=s.superevent_id)
pref_event_log = create_log(submitter, pref_event_msg, preferred_event,
issue_alert=False, autogenerated=False)
# Add events to superevent
# Superevent log message and alerts are taken care of elsewhere, but we
# want to record logs for the individual events
# NOTE: we don't have to worry about a repeat here for the preferred event
# since events comes directly from the serializer and hasn't been updated
# to include the preferred event (like it would be if we accessed
# s.events.all())
# Alerts aren't issued here since we want to do that *after* the superevent
# creation alert is issued below.
event_log_list = []
for event in events:
_, el = add_event_to_superevent(s, event, submitter,
add_superevent_log=False, add_event_log=True,
issue_superevent_alert=False, issue_event_alert=False)
if el is not None:
event_log_list.append(el)
# Issue all relevant alerts
if issue_alert:
# Send "new" alert about superevent creation
issue_alert_for_superevent_creation(s)
# "Manually" issue alerts for preferred_event and events
issue_alert_for_event_log(pref_event_log)
for el in event_log_list:
issue_alert_for_event_log(el)
# Add labels
for label in labels:
l = add_label_to_superevent(s, label, submitter,
add_log_message=True, issue_alert=issue_alert)
# Create default GroupObjectPermissions - LVC group and executives group
# can view/change superevents
assign_default_perms(s)
# Look at event creation functions to see if there is anything else we should add here.
# CREATE DIRECTORY
os.makedirs(s.datadir)
return s
def update_superevent(superevent, updater, issue_alert=True, **kwargs):
"""
kwargs which are used as superevent parameters:
t_start, t_0, t_end, preferred_event
"""
# Extract "updatable" superevent params from kwargs
param_names = ['t_start', 't_0', 't_end', 'preferred_event']
new_params = {k: v for k,v in kwargs.iteritems() if k in param_names}
# Get old parameters
old_params = {k: getattr(superevent, k) for k in new_params.keys()}
# Update superevent object
for k,v in new_params.iteritems():
setattr(superevent, k, v)
superevent.save()
# Write a log message
# 'if' statement to handle patch requests which don't change entire object
updates = ["{name}: {old} -> {new}".format(name=k, old=old_params[k],
new=new_params[k]) for k in new_params.keys()
if old_params[k] != new_params[k]]
update_comment = "Updated superevent parameters: {0}".format(
", ".join(updates))
update_log = create_log(updater, update_comment, superevent,
issue_alert=issue_alert)
# Write event log messages if preferred event changed
if new_params.has_key('preferred_event') and \
(old_params['preferred_event'] != new_params['preferred_event']):
# Old preferred event
old_msg = ("Removed as preferred event for superevent: "
"{superevent_id}").format(superevent_id=superevent.superevent_id)
old_log = create_log(updater, old_msg, old_params['preferred_event'],
issue_alert=issue_alert)
# New preferred event
new_msg = "Set as preferred event for superevent: {superevent_id}" \
.format(superevent_id=superevent.superevent_id)
new_log = create_log(updater, new_msg, new_params['preferred_event'],
issue_alert=issue_alert)
return superevent
def create_log(issuer, comment, event_or_superevent, filename="",
data_file=None, tags=[], file_version=None, issue_alert=False,
autogenerated=False):
"""
Assume data have already been validated by a form or serializer.
tags should be a list of Tag objects (??)
data_file should be an in-memory file type
"""
# Initial dictionary for log creation
log_dict = {
'issuer': issuer,
'comment': comment,
'filename': filename,
}
if file_version is not None:
log_dict['file_version'] = file_version
if is_superevent(event_or_superevent):
log_dict['superevent'] = event_or_superevent
LogModel = Log
alert_func = issue_alert_for_superevent_log
elif is_event(event_or_superevent):
log_dict['event'] = event_or_superevent
LogModel = EventLog
alert_func = issue_alert_for_event_log
else:
# TODO: raise error
logger.error(type(event_or_superevent))
pass
# Create log object
log = LogModel.objects.create(**log_dict)
# Create versioned file
if data_file:
version = create_versioned_file(filename, event_or_superevent.datadir,
data_file)
# Update file_version
log.file_version = version
log.save(update_fields=['file_version'])
# Add tags to log messages
for t in tags:
add_tag_to_log(log, t, issuer, issue_alert=False)
if issue_alert:
alert_func(log)
# TODO:
# If user is external, add LV-EM tagname to this log message
return log
def get_log_parent(log):
# Determine if this is an event or superevent log
if isinstance(log, Log):
return log.superevent
elif isinstance(log, EventLog):
return log.event
else:
# TODO: raise exception
pass
def add_tag_to_log(log, tag, user, add_log_message=True, issue_alert=False):
# Add tag to log
log.tags.add(tag)
# If this tag controls whether the log is exposed or not, we need to create
# a corresponding GroupObjectPermission. If we get to this point,
# permissions should have already been checked.
if (tag.name == settings.EXTERNAL_ACCESS_TAGNAME):
expose_log_to_lvem(log)
elif (tag.name == settings.PUBLIC_ACCESS_TAGNAME):
expose_log_to_public(log)
# Publicly exposed tags should also be exposed to LV-EM, if they
# aren't already
lvem_tag_applied = log.tags.filter(
name=settings.EXTERNAL_ACCESS_TAGNAME).exists()
if not lvem_tag_applied:
lvem_tag = Tag.objects.get(name=settings.EXTERNAL_ACCESS_TAGNAME)
add_tag_to_log(log, lvem_tag, user)
# Create log message to record tag addition?
log_for_tag_addition = None
if add_log_message:
comment = 'Tagged message {N}: {tag_name}.'.format(N=log.N,
tag_name=tag.name)
event_or_superevent = get_log_parent(log)
log_for_tag_addition = create_log(user, comment, event_or_superevent,
issue_alert=issue_alert, autogenerated=True)
return log_for_tag_addition
def remove_tag_from_log(log, tag, user, add_log_message=True,
issue_alert=False):
# Remove tag from log
log.tags.remove(tag)
# If this tag controls whether the log is exposed or not, we need to create
# a corresponding GroupObjectPermission. If we get to this point,
# permissions should have already been checked.
if (tag.name == settings.EXTERNAL_ACCESS_TAGNAME):
hide_log_from_lvem(log)
# If the log is hidden from LV-EM, it should also be hidden from the
# public
public_tag_applied = log.tags.filter(
name=settings.PUBLIC_ACCESS_TAGNAME).exists()
if not public_tag_applied:
public_tag = Tag.objects.get(name=settings.PUBLIC_ACCESS_TAGNAME)
remove_tag_from_log(log, public_tag, user)
elif (tag.name == settings.PUBLIC_ACCESS_TAGNAME):
hide_log_from_public(log)
# Create log message to record tag removal?
log_for_tag_removal = None
if add_log_message:
comment = 'Removed tag {tag_name} from message {N}.'.format(
N=log.N, tag_name=tag.name)
event_or_superevent = get_log_parent(log)
log_for_tag_removal = create_log(user, comment, event_or_superevent,
issue_alert=issue_alert, autogenerated=True)
return log_for_tag_removal
def add_event_to_superevent(superevent, event, user, add_event_log=True,
add_superevent_log=True, issue_event_alert=True,
issue_superevent_alert=True):
"""
We return log objects in case they are needed elsewhere
"""
# Check that the event is of the correct type to be added
# to a superevent
if not superevent.event_compatible(event):
raise Superevent.EventCategoryMismatchError(
_(('Event {graceid} is of type \'{e_category}\', and '
'cannot be assigned to a superevent of type '
'\'{s_category}\'').format(graceid=event.graceid(),
e_category=event.get_event_category(),
s_category=superevent.get_category_display())))
# Add event to superevent
superevent.events.add(event)
# Create superevent log message to record event addtion?
superevent_log_for_event_addition = None
if add_superevent_log:
# Record event addition in superevent logs
superevent_comment = 'Added event: {graceid}'.format(
graceid=event.graceid())
superevent_log_for_event_addition = create_log(user,
superevent_comment, superevent, issue_alert=issue_superevent_alert,
autogenerated=True)
# Create event log message to record addition to superevent?
event_log_for_addition_to_superevent = None
if add_event_log:
# Record addition to superevent in event logs
event_comment = 'Added to superevent: {superevent_id}'.format(
superevent_id=superevent.superevent_id)
event_log_for_addition_to_superevent = create_log(user, event_comment,
event, issue_alert=issue_event_alert, autogenerated=True)
return superevent_log_for_event_addition, \
event_log_for_addition_to_superevent
def remove_event_from_superevent(superevent, event, user, add_event_log=True,
add_superevent_log=True, issue_event_alert=True,
issue_superevent_alert=True):
"""
This function should be within a try-except block to catch exceptions and
convert them to the appropriate response.
"""
# Throw error if this is the preferred event
if event == superevent.preferred_event:
raise Superevent.PreferredEventRemovalError("Can't remove a "
"superevent's preferred event without setting a new one.")
# Remove event from superevent
superevent.events.remove(event)
# Create superevent log message to record event removal?
superevent_log_for_event_removal = None
if add_superevent_log:
# Record event removal in superevent logs
superevent_comment = 'Removed event: {graceid}'.format(
graceid=event.graceid())
superevent_log_for_event_removal = create_log(user, superevent_comment,
superevent, issue_alert=issue_superevent_alert, autogenerated=True)
# Create event log message to record removal from superevent?
event_log_for_removal_from_superevent = None
if add_event_log:
event_comment ='Removed from superevent: {superevent_id}'.format(
superevent_id=superevent.superevent_id)
event_log_for_removal_from_superevent = create_log(user, event_comment,
event, issue_alert=issue_event_alert, autogenerated=True)
return superevent_log_for_event_removal, \
event_log_for_removal_from_superevent
def add_label_to_superevent(superevent, label, user, add_log_message=True,
issue_alert=True):
# Create Labelling object
labelling = Labelling.objects.create(label=label, creator=user,
superevent=superevent)
log_for_label_addition = None
if add_log_message:
# Record label addition in superevent logs
comment = 'Added label: {label_name}'.format(label_name=label.name)
log_for_label_addition = create_log(user, comment, superevent,
issue_alert=False, autogenerated=True)
if issue_alert:
issue_alert_for_superevent_label_creation(labelling)
return labelling, log_for_label_addition
def remove_label_from_superevent(labelling, user, add_log_message=True,
issue_alert=True):
# Delete Labelling object
labelling.delete()
log_for_label_removal = None
if add_log_message:
# Record label addition in superevent logs
comment = 'Removed label: {label_name}'.format(
label_name=labelling.label.name)
log_for_label_removal = create_log(user, comment, labelling.superevent,
issue_alert=False, autogenerated=True)
# labelling object still exists in memory, even though it has been
# removed from the database and does not have an ID anymore
if issue_alert:
issue_alert_for_superevent_label_removal(labelling)
return log_for_label_removal
def get_or_create_tag(tag_name, display_name=None):
tag, created = Tag.objects.get_or_create(name=tag_name)
if created and display_name is not None:
tag.displayName = display_name
tag.save()
return tag
def get_or_create_tags(tag_name_list, display_name_list=[]):
# TODO: make this a useful error
if display_name_list and (len(display_name_list) != len(tag_name_list)):
raise ValueError('')
tag_list = []
for i, tag_name in enumerate(tag_name_list):
display_name = None
if display_name_list:
display_name = display_name_list[i]
tag = get_or_create_tag(tag_name, display_name)
tag_list.append(tag)
return tag_list
# TODO: add permissions checking?
# TODO: move this somewhere else?
def get_superevent_by_date_id_or_404(superevent_id, queryset=None):
try:
filter_kwargs = Superevent.get_filter_kwargs_for_date_id_lookup(
superevent_id)
except Superevent.DateIdError as e:
# The user passed an invalid date string (i.e., month=13
# or something). Probably should return 400 when this happens,
# since it is technically a client error, but it's a lot simpler to
# just raise a 404 here than to wrap every usage of this function in a
# try-except block (or more than one, if it's in a CBV) and return a
# 400. But raising a 404 is not technically wrong.
raise Http404(e)
# TODO: filter queryset for user here
if queryset is None:
queryset = Superevent.objects.all()
return get_object_or_404(queryset, **filter_kwargs)
def confirm_superevent_as_gw(superevent, user, add_log_message=True,
issue_alert=True):
# Update superevent (mark as a GW, construct new ID, etc.)
superevent.confirm_as_gw()
# Create log message
gw_log = None
if add_log_message:
message = "Confirmed as a gravitational wave."
gw_log = create_log(user, message, superevent, issue_alert=issue_alert,
autogenerated=False)
# TODO: add label?
return gw_log
def create_emobservation_for_superevent(superevent, submitter, ra_list,
dec_list, ra_width_list, dec_width_list, start_time_list, duration_list,
emgroup, comment="", add_log_message=True, issue_alert=True):
# Create EMObservation
emo = EMObservation.objects.create(submitter=submitter,
superevent=superevent, group=emgroup, comment=comment)
# Create EMFootprint objects
for ra, dec, ra_width, dec_width, start_time, exposure_time in \
zip(ra_list, dec_list, ra_width_list, dec_width_list, start_time_list, duration_list):
emf = EMFootprint.objects.create(observation=emo, ra=ra, dec=dec,
raWidth=ra_width, decWidth=dec_width, start_time=start_time,
exposure_time=exposure_time)
# Calculation covering region for observation
emo.calculateCoveringRegion()
emo.save()
# Create a log message
if add_log_message:
message = "New EMBB observation record for {0}".format(emgroup.name)
gw_log = create_log(submitter, message, superevent, issue_alert=False,
autogenerated=False)
# Issue alert
if issue_alert:
issue_alert_for_superevent_emobservation(emo)
return emo
def create_voevent_for_superevent(superevent, issuer, voevent_type,
skymap_type=None, skymap_filename=None, skymap_image_filename=None,
internal=True, vetted=False, open_alert=False, hardware_inj=False,
CoincComment=False, ProbHasNS=None, ProbHasRemnant=None,
add_log_message=True, issue_alert=True):
# Instantiate VOEvent object
voevent = VOEvent.objects.create(superevent=superevent, issuer=issuer,
voevent_type=voevent_type)
# Construct VOEvent file text
voevent_text, ivorn = construct_voevent_file(superevent, voevent,
skymap_type=skymap_type, skymap_filename=skymap_filename,
skymap_image_filename=skymap_image_filename, internal=internal,
vetted=vetted, open_alert=open_alert, hardware_inj=hardware_inj,
CoincComment=CoincComment, ProbHasNS=ProbHasNS,
ProbHasRemnant=ProbHasRemnant)
# Save versioned VOEvent file
voevent_display_type = dict(VOEvent.VOEVENT_TYPE_CHOICES) \
[voevent.voevent_type].capitalize()
voevent_filename = "{superevent}-{N}-{voevent_type}.xml".format(
superevent=superevent.superevent_id, N=voevent.N,
voevent_type=voevent_display_type)
version = create_versioned_file(voevent_filename, superevent.datadir,
voevent_text)
# Update VOEvent object
voevent.filename = voevent_filename
voevent.file_version = version
voevent.ivorn = ivorn
voevent.save(update_fields=['filename', 'file_version', 'ivorn'])
# Create a log entry to document the new VOEvent (tag it as em_follow)
if add_log_message:
comment = "New VOEvent"
em_follow = Tag.objects.get(name='em_follow')
voevent_log = create_log(issuer, comment, superevent,
filename=voevent.filename, file_version=voevent.file_version,
tags=[em_follow], issue_alert=False)
# Issue an alert
if issue_alert:
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)