diff --git a/gracedb/api.py b/gracedb/api.py index 0b9bc7877a9b8e735faf5c2c46a0c0d5ae11ca53..2386e71e67aac381e10d25995cc50ec56556d307 100644 --- a/gracedb/api.py +++ b/gracedb/api.py @@ -14,12 +14,13 @@ from django.contrib.auth.models import Group as AuthGroup from django.contrib.contenttypes.models import ContentType from gracedb.models import Event, Group, Search, Pipeline, EventLog, Tag from gracedb.models import EMGroup, EMBBEventLog, EMSPECTRUM +from gracedb.models import VOEvent from view_logic import create_label, get_performance_info from view_logic import _createEventFromForm from view_logic import create_eel from view_utils import fix_old_creation_request from view_utils import eventToDict, eventLogToDict, labelToDict -from view_utils import embbEventLogToDict +from view_utils import embbEventLogToDict, voeventToDict from view_utils import reverse from translator import handle_uploaded_data @@ -343,32 +344,6 @@ class TSVRenderer(BaseRenderer): return outTable -# XXX this doesn't work because you don't have the request here. You could -# try stuffing it into the renderer context, but that's really ugly. -#class VOEventRenderer(BaseRenderer): -# media_type = 'application/xml' -# format = 'xml' -# -# def render(self, data, media_type=None, renderer_context=None): -# if 'error' in data.keys(): -# return data['error'] -# -# outDoc = '' -# for e in data['events']: -# graceid = e['graceid'] -# -# try: -# # XXX If any part of this fails, the VOEvent will be empty. -# event = Event.getByGraceid(graceid) -# if not event.far or not event.gpstime: -# raise Exception -# voevent = buildVOEvent(event, request) -# except: -# voevent = '' -# outDoc += voevent + '\n' -# -# return outDoc - #================================================================== # Events @@ -650,33 +625,6 @@ class EventDetail(APIView): status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_202_ACCEPTED) -# FIXME or something. -# This should really be a renderer and not a view. But the problem -# is that the renderer needs the request in order to build up URLs. -# There must be a better way of doing this. -class EventVODetail(APIView): - authentication_classes = (LigoAuthentication,) - #parser_classes = (LigoLwParser, RawdataParser) - parser_classes = (parsers.MultiPartParser,) - #serializer_class = EventSerializer - permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) - renderer_classes = (JSONRenderer, BrowsableAPIRenderer,) - - @event_and_auth_required - def get(self, request, event): - voevent_type = request.QUERY_PARAMS.get('voevent_type', 'preliminary') - try: - voevent = buildVOEvent(event, request, voevent_type=voevent_type) - except VOEventBuilderException, e: - return Response(str(e), status=status.HTTP_400_BAD_REQUEST) - except Exception, e: - return Response("Problem building VOEvent: %s" % str(e), - status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - response = Response(voevent) - response["Cache-Control"] = "no-cache" - return response - #================================================================== # Neighbors @@ -1336,10 +1284,10 @@ class GracedbRoot(APIView): # Is there better? detail = reverse("event-detail", args=["G1200"], request=request) detail = detail.replace("G1200", "{graceid}") - vo_detail = reverse("event-vo-detail", args=["G1200"], request=request) - vo_detail = vo_detail.replace("G1200", "{graceid}") log = reverse("eventlog-list", args=["G1200"], request=request) log = log.replace("G1200", "{graceid}") + voevent = reverse("voevent-list", args=["G1200"], request=request) + voevent = voevent.replace("G1200", "{graceid}") embb = reverse("embbeventlog-list", args=["G1200"], request=request) embb = embb.replace("G1200", "{graceid}") @@ -1368,7 +1316,7 @@ class GracedbRoot(APIView): templates = { "event-detail-template" : detail, - "event-vo-detail-template" : vo_detail, + "voevent-list-template" : voevent, "event-log-template" : log, "embb-event-log-template" : embb, "event-label-template" : labels, @@ -1408,6 +1356,7 @@ class GracedbRoot(APIView): "wavebands" : dict(EMSPECTRUM), "eel-statuses" : dict(EMBBEventLog.EEL_STATUS_CHOICES), "obs-statuses" : dict(EMBBEventLog.OBS_STATUS_CHOICES), + "voevent-types" : dict(VOEvent.VOEVENT_TYPE_CHOICES), }) ################################################################## @@ -1612,3 +1561,122 @@ class PerformanceInfo(APIView): return Response(performance_info,status=status.HTTP_200_OK) + +#================================================================== +# VOEvent Resources + +class VOEventList(APIView): + """VOEvent List Resource + """ + authentication_classes = (LigoAuthentication,) + permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) + + @event_and_auth_required + def get(self, request, event): + voeventset = event.voevent_set.order_by("created","N") + count = voeventset.count() + + voevents = [ voeventToDict(voevent, request) + for voevent in voeventset.iterator() ] + + rv = { + 'start': 0, + 'numRows' : count, + 'links' : { + 'self' : request.build_absolute_uri(), + 'first' : request.build_absolute_uri(), + 'last' : request.build_absolute_uri(), + }, + 'voevents' : voevents, + } + return Response(rv) + + @event_and_auth_required + def post(self, request, event): + voevent_type = request.DATA.get('voevent_type', None) + if not voevent_type: + msg = "You must provide a valid voevent_type." + return Response({'error': msg}, status = status.HTTP_400_BAD_REQUEST) + + skymap_type = request.DATA.get('skymap_type', None) + skymap_filename = request.DATA.get('skymap_filename', None) + skymap_image_filename = request.DATA.get('skymap_image_filename', None) + + if (skymap_filename and not skymap_type) or (skymap_type and not skymap_filename): + msg = "Both or neither of skymap_time and skymap_filename must be specified." + return Response({'error': msg}, status = status.HTTP_400_BAD_REQUEST) + + # Instantiate the voevent and save in order to get the serial number + voevent = VOEvent(voevent_type=voevent_type, event=event, issuer=request.user) + + try: + voevent.save() + except Exception as e: + return Response("Failed to create VOEvent: %s" % str(e), + status=status.HTTP_503_SERVICE_UNAVAILABLE) + + # Now, you need to actually build the VOEvent. + try: + voevent_text, ivorn = buildVOEvent(event, voevent.N, voevent_type, request, + skymap_filename = skymap_filename, skymap_type = skymap_type, + skymap_image_filename = skymap_image_filename) + except VOEventBuilderException, e: + msg = "Problem building VOEvent: %s" % str(e) + return Response({'error': msg}, status = status.HTTP_400_BAD_REQUEST) + + voevent_display_type = dict(VOEvent.VOEVENT_TYPE_CHOICES)[voevent_type].capitalize() + filename = "%s-%d-%s.xml" % (event.graceid(), voevent.N, voevent_display_type) + filepath = os.path.join(event.datadir(), filename) + fdest = VersionedFile(filepath, 'w') + fdest.write(voevent_text) + fdest.close() + file_version = fdest.version + + voevent.filename = filename + voevent.file_version = file_version + voevent.ivorn = ivorn + voevent.save() + + rv = voeventToDict(voevent, request=request) + + # Create LogEntry to document the new VOEvent. + logentry = EventLog(event=event, + issuer=request.user, + comment='', + filename=filename, + file_version=file_version) + try: + logentry.save() + except: + rv['warnings'] = 'Problem saving log entry for VOEvent %s of %s' % (voevent.N, + event.graceid()) + + # Tag log entry as 'sky_loc' + tmp = EventLogTagDetail() + retval = tmp.put(request, event.graceid(), logentry.N, 'em_follow') + # XXX This seems like a bizarre way of getting an error message out. + if retval.status_code != 201: + rv['tagWarning'] = 'Error tagging VOEvent log message as em_follow.' + + # Issue alert. + description = "VOEVENT: %s" % filename + issueAlertForUpdate(event, description, doxmpp=True, + filename=filename, serialized_object=rv) + + response = Response(rv, status=status.HTTP_201_CREATED) + response['Location'] = rv['self'] + return response + +class VOEventDetail(APIView): + authentication_classes = (LigoAuthentication,) + permission_classes = (IsAuthenticated,IsAuthorizedForEvent,) + + @event_and_auth_required + def get(self, request, event, n): + try: + voevent = event.voevent_set.filter(N=n)[0] + except: + return Response("VOEvent does not exist.", + status=status.HTTP_404_NOT_FOUND) + return Response(voeventToDict(voevent, request=request)) + diff --git a/gracedb/buildVOEvent.py b/gracedb/buildVOEvent.py index 27b363037480b4402e03846116cf0f356f3b014b..899a2d37d39d157bf470b283d08f7df819c1a2b4 100755 --- a/gracedb/buildVOEvent.py +++ b/gracedb/buildVOEvent.py @@ -8,7 +8,9 @@ See the VOEvent specification for details http://www.ivoa.net/Documents/latest/VOEvent.html """ -from VOEventLib.VOEvent import VOEvent, Who, Author, Param, How, Why, What, Group +from VOEventLib.VOEvent import VOEvent, Who, Author, Param, How, What, Group +from VOEventLib.VOEvent import Citations, EventIVORN +#from VOEventLib.VOEvent import Why from VOEventLib.Vutil import makeWhereWhen, stringVOEvent # XXX ER2.utils. utils is in project directory. ugh. @@ -17,6 +19,7 @@ from datetime import datetime from django.conf import settings from django.core.urlresolvers import reverse from models import CoincInspiralEvent, MultiBurstEvent +from models import VOEvent as GraceDBVOEvent import os @@ -30,37 +33,10 @@ def get_url(request, graceid, view_name, file_name=None): rel_url = reverse(view_name, args=args) return request.build_absolute_uri(rel_url) -# -# Types of VOEvents: -# preliminary: no skymap -# initial: BAYESTAR skymap -# update: PE skymap -# -# If the type of skymap doesn't exist, then we need to fail in such -# a way as to get the attention of the requestor. We don't want to -# forward a bad VOEvent with now skymap. -# -# For each skymap, we demand that there be at least one of: 1) an image file, -# 2) a data file. The image (data) file name should conform to the pattern: -# stem + '.png' ('.fits.gz'). -# This is obviously very fragile. A 'Skymap' data model would help this -# situation considerably, especially if additional skymap types arise. -# -SKYMAP_INFO = { - 'initial' : { - 'name' : 'BAYESTAR', - 'stem' : 'bayestar', - }, - 'update' : { - 'name' : 'LALINFERENCE_MCMC', - 'stem' : 'lalinference_nest', - } -} - -VOEVENT_TYPES = ['preliminary', 'initial', 'update',] - -def buildVOEvent(event, request=None, description=None, role=None, - voevent_type='preliminary'): +VOEVENT_TYPE_DICT = dict(GraceDBVOEvent.VOEVENT_TYPE_CHOICES) + +def buildVOEvent(event, serial_number, voevent_type, request=None, skymap_filename=None, + skymap_type=None, skymap_image_filename = None): if not event.far: raise VOEventBuilderException("Cannot build a VOEvent because event has no FAR.") @@ -68,17 +44,24 @@ def buildVOEvent(event, request=None, description=None, role=None, if not event.gpstime: raise VOEventBuilderException("Cannot build a VOEvent because event has no gpstime.") - if not voevent_type in VOEVENT_TYPES: - # Do something real here XXX - raise VOEventBuilderException("voevent_type must be preliminary, initial, or update") + if not voevent_type in VOEVENT_TYPE_DICT.keys(): + raise VOEventBuilderException("voevent_type must be preliminary, initial, update, or retraction") + + # Let's convert that voevent_type to something nicer looking + voevent_type = VOEVENT_TYPE_DICT[voevent_type] objid = event.graceid() + # Now build the IVORN. + event_id = "%s-%d-%s" % (objid, serial_number, voevent_type.capitalize()) + ivorn = settings.SKYALERT_IVORN_PATTERN % event_id + ############ VOEvent header ############################ v = VOEvent(version="2.0") - v.set_ivorn(settings.SKYALERT_IVORN_PATTERN % objid) - v.set_role(role or settings.SKYALERT_ROLE) - v.set_Description(description or settings.SKYALERT_DESCRIPTION) + v.set_ivorn(ivorn) + v.set_role(settings.SKYALERT_ROLE) + if voevent_type != 'retraction': + v.set_Description(settings.SKYALERT_DESCRIPTION) ############ Who ############################ w = Who() @@ -90,21 +73,25 @@ def buildVOEvent(event, request=None, description=None, role=None, v.set_Who(w) ############ Why ############################ - y = Why() - y.add_Description("Candidate gravitational wave event identified by low-latency analysis") - v.set_Why(y) + # Moving this information into the 'How' section. + #if voevent_type != 'retraction': + # y = Why() + # y.add_Description("Candidate gravitational wave event identified by low-latency analysis") + # v.set_Why(y) ############ How ############################ - h = How() - instruments = event.instruments.split(',') - if 'H1' in instruments: - h.add_Description("H1: LIGO Hanford 4 km gravitational wave detector") - if 'L1' in instruments: - h.add_Description("L1: LIGO Livingston 4 km gravitational wave detector") - if 'V1' in instruments: - h.add_Description("V1: Virgo 3 km gravitational wave detector") - v.set_How(h) + if voevent_type != 'retraction': + h = How() + h.add_Description("Candidate gravitational wave event identified by low-latency analysis") + instruments = event.instruments.split(',') + if 'H1' in instruments: + h.add_Description("H1: LIGO Hanford 4 km gravitational wave detector") + if 'L1' in instruments: + h.add_Description("L1: LIGO Livingston 4 km gravitational wave detector") + if 'V1' in instruments: + h.add_Description("V1: Virgo 3 km gravitational wave detector") + v.set_How(h) ############ What ############################ w = What() @@ -123,6 +110,9 @@ def buildVOEvent(event, request=None, description=None, role=None, # [25] http://vizier.u-strasbg.fr/doc/catstd-3.2.htx # # basically, a string that makes sense to humans about what units a value is. eg. "m/s" + + # The serial number + w.add_Param(Param(name="Pkt_Ser_Num", value=serial_number)) # The GraceID w.add_Param(Param(name="GraceID", @@ -139,65 +129,86 @@ def buildVOEvent(event, request=None, description=None, role=None, value = voevent_type.capitalize(), Description=["VOEvent alert type"])) - # False alarm rate - w.add_Param(Param(name="FAR", - dataType="float", - ucd="arith.rate;stat.falsealarm", - unit="Hz", - value=float(event.far), - Description=["False alarm rate for GW candidates with this strength or greater"])) - # Shib protected event page w.add_Param(Param(name="EventPage", ucd="meta.ref.url", value=get_url(request, objid, "view2"), Description=["Web page for evolving status of this candidate event"])) - # Pipeline - w.add_Param(Param(name="Pipeline", - dataType="string", - ucd="meta.code", - unit="", - value=event.pipeline.name, - Description=["Low-latency data analysis pipeline"])) + if voevent_type != 'retraction': + # Instruments + w.add_Param(Param(name="Instruments", + dataType="string", + ucd="meta.code", + value=event.instruments, + Description=["List of instruments used in analysis to identify this event"])) - # Search - if event.search: - w.add_Param(Param(name="Search", + # False alarm rate + w.add_Param(Param(name="FAR", + dataType="float", + ucd="arith.rate;stat.falsealarm", + unit="Hz", + value=float(event.far), + Description=["False alarm rate for GW candidates with this strength or greater"])) + + # Pipeline + w.add_Param(Param(name="Pipeline", + dataType="string", ucd="meta.code", unit="", - dataType="string", - value=event.search.name, - Description=["Specific low-latency search"])) + value=event.pipeline.name, + Description=["Low-latency data analysis pipeline"])) + + # Search + if event.search: + w.add_Param(Param(name="Search", + ucd="meta.code", + unit="", + dataType="string", + value=event.search.name, + Description=["Specific low-latency search"])) if voevent_type in ["initial", "update"]: - # Skymaps. Create group and set particular fits and image file names - g = Group('GW_SKYMAP', SKYMAP_INFO[voevent_type]['name']) - fits_name = SKYMAP_INFO[voevent_type]['stem'] + '.fits.gz' - img_name = SKYMAP_INFO[voevent_type]['stem'] + '.png' - # Check for the existence of the files. - for filename in [fits_name, img_name]: - filepath = os.path.join(event.datadir(), filename) - if not os.path.exists(filepath): - raise VOEventBuilderException("Skymap file %s not found" % filename) + if not skymap_filename: + raise VOEventBuilderException("Skymap filename not provided.") + + fits_name = skymap_filename + fits_path = os.path.join(event.datadir(), fits_name) + if not os.path.exists(fits_path): + raise VOEventBuilderException("Skymap file does not exist: %s" % skymap_filename) + + # Let's try to get an image. + if not skymap_image_filename: + stem = '.'.join(fits_name.split('.')[:-1]) + img_name = stem + '.png' + img_path = os.path.join(event.datadir(), img_name) + if not os.path.exists(img_path): + img_name = None + + if not skymap_type: + raise VOEventBuilderException("Skymap type must be provided.") + + # Skymaps. Create group and set particular fits and image file names + g = Group('GW_SKYMAP', skymap_type) # shib urls. shib_fits_skymap_url = get_url(request, objid, "file", fits_name) - shib_png_skymap_url = get_url(request, objid, "file", img_name) + if img_name: + shib_png_skymap_url = get_url(request, objid, "file", img_name) # x509 urls. Hafta specify the api namespace. x509_fits_skymap_url = get_url(request, objid, "x509:files", fits_name) - x509_png_skymap_url = get_url(request, objid, "x509:files", img_name) + if img_name: + x509_png_skymap_url = get_url(request, objid, "x509:files", img_name) # Add parameters to the skymap group - g.add_Param(Param(name="skymap_png_x509", + g.add_Param(Param(name="skymap_fits_shib", dataType="string", ucd="meta.ref.url", unit="", - value=x509_png_skymap_url, - Description=["Sky Map image X509 protected"])) - + value=shib_fits_skymap_url, + Description=["Sky Map FITS Shibboleth protected"])) g.add_Param(Param(name="skymap_fits_x509", dataType="string", ucd="meta.ref.url", @@ -205,140 +216,156 @@ def buildVOEvent(event, request=None, description=None, role=None, value=x509_fits_skymap_url, Description=["Sky Map FITS X509 protected"])) - g.add_Param(Param(name="skymap_png_shib", - dataType="string", - ucd="meta.ref.url", - unit="", - value=shib_png_skymap_url, - Description=["Sky Map image Shibboleth protected"])) - - g.add_Param(Param(name="skymap_fits_shib", - dataType="string", - ucd="meta.ref.url", - unit="", - value=shib_fits_skymap_url, - Description=["Sky Map FITS Shibboleth protected"])) + if img_name: + g.add_Param(Param(name="skymap_png_shib", + dataType="string", + ucd="meta.ref.url", + unit="", + value=shib_png_skymap_url, + Description=["Sky Map image Shibboleth protected"])) + g.add_Param(Param(name="skymap_png_x509", + dataType="string", + ucd="meta.ref.url", + unit="", + value=x509_png_skymap_url, + Description=["Sky Map image X509 protected"])) w.add_Group(g) # Analysis specific attributes - if isinstance(event,CoincInspiralEvent): - # get mchirp and mass - mchirp = float(event.mchirp) - mass = float(event.mass) - # calculate eta = (mchirp/total_mass)**(5/3) - eta = pow((mchirp/mass),5.0/3.0) - w.add_Param(Param(name="ChirpMass", - dataType="float", - ucd="phys.mass", - unit="solar mass", - value=mchirp, - Description=["Estimated CBC chirp mass"])) + if voevent_type != 'retraction': + if isinstance(event,CoincInspiralEvent) and voevent_type != 'retraction': + # get mchirp and mass + mchirp = float(event.mchirp) + mass = float(event.mass) + # calculate eta = (mchirp/total_mass)**(5/3) + eta = pow((mchirp/mass),5.0/3.0) + w.add_Param(Param(name="ChirpMass", + dataType="float", + ucd="phys.mass", + unit="solar mass", + value=mchirp, + Description=["Estimated CBC chirp mass"])) - w.add_Param(Param(name="Eta", - dataType="float", - ucd="phys.mass;arith.factor", - unit="", - value=eta, - Description=["Estimated ratio of reduced mass to total mass"])) - - # build up MaxDistance. event.singleinspiral_set.all()? - # Each detector calculates an effective distance assuming the inspiral is - # optimally oriented. It is the maximum distance at which a source of the - # given parameters would've been seen by that particular detector. To get - # an effective 'maximum distance', we just find the minumum over detectors - max_distance = float('inf') - for obj in event.singleinspiral_set.all(): - if obj.eff_distance < max_distance: - max_distance = obj.eff_distance - if max_distance < float('inf'): - w.add_Param(Param(name="MaxDistance", + w.add_Param(Param(name="Eta", dataType="float", - ucd="pos.distance", - unit="Mpc", - value=max_distance, - Description=["Estimated maximum distance for CBC event"])) - - elif isinstance(event,MultiBurstEvent): - w.add_Param(Param(name="CentralFreq", - dataType="float", - ucd="gw.frequency", - unit="Hz", - value=float(event.central_freq), - Description=["Central frequency of GW burst signal"])) - w.add_Param(Param(name="Duration", - dataType="float", - ucd="time.duration", - unit="s", - value=float(event.duration), - Description=["Measured duration of GW burst signal"])) - - # XXX Calculate the fluence. Unfortunately, this requires parsing the trigger.txt - # file for hrss values. These should probably be pulled into the database. - # But there is no consensus on whether hrss or fluence is meaningful. So I will - # put off changing the schema for now. - try: - # Go find the data file. - log = event.eventlog_set.filter(comment__startswith="Original Data").all()[0] - filename = log.filename - filepath = os.path.join(event.datadir(),filename) - if os.path.isfile(filepath): - datafile = open(filepath,"r") - else: - raise Exception("No file found.") - # Now parse the datafile. - # The line we want looks like: - # hrss: 1.752741e-23 2.101590e-23 6.418900e-23 - for line in datafile: - if line.startswith('hrss:'): - hrss_values = [float(hrss) for hrss in line.split()[1:]] - max_hrss = max(hrss_values) - # From Min-A Cho: fluence = pi*(c**3)*(freq**2)*(hrss_max**2)*(10**3)/(4*G) - # Note that hrss here actually has units of s^(-1/2) - pi = 3.14152 - c = 2.99792E10 - G = 6.674E-8 - fluence = pi * pow(c,3) * pow(event.central_freq,2) * 1000.0 - fluence = fluence * pow(max_hrss,2) - fluence = fluence / (4.0*G) - - w.add_Param(Param(name="Fluence", + ucd="phys.mass;arith.factor", + unit="", + value=eta, + Description=["Estimated ratio of reduced mass to total mass"])) + + # build up MaxDistance. event.singleinspiral_set.all()? + # Each detector calculates an effective distance assuming the inspiral is + # optimally oriented. It is the maximum distance at which a source of the + # given parameters would've been seen by that particular detector. To get + # an effective 'maximum distance', we just find the minumum over detectors + max_distance = float('inf') + for obj in event.singleinspiral_set.all(): + if obj.eff_distance < max_distance: + max_distance = obj.eff_distance + if max_distance < float('inf'): + w.add_Param(Param(name="MaxDistance", + dataType="float", + ucd="pos.distance", + unit="Mpc", + value=max_distance, + Description=["Estimated maximum distance for CBC event"])) + + elif isinstance(event,MultiBurstEvent): + w.add_Param(Param(name="CentralFreq", + dataType="float", + ucd="gw.frequency", + unit="Hz", + value=float(event.central_freq), + Description=["Central frequency of GW burst signal"])) + w.add_Param(Param(name="Duration", dataType="float", - ucd="gw.fluence", - unit="erg/cm^2", - value=fluence, - Description=["Estimated fluence of GW burst signal"])) - except Exception: + ucd="time.duration", + unit="s", + value=float(event.duration), + Description=["Measured duration of GW burst signal"])) + + # XXX Calculate the fluence. Unfortunately, this requires parsing the trigger.txt + # file for hrss values. These should probably be pulled into the database. + # But there is no consensus on whether hrss or fluence is meaningful. So I will + # put off changing the schema for now. + try: + # Go find the data file. + log = event.eventlog_set.filter(comment__startswith="Original Data").all()[0] + filename = log.filename + filepath = os.path.join(event.datadir(),filename) + if os.path.isfile(filepath): + datafile = open(filepath,"r") + else: + raise Exception("No file found.") + # Now parse the datafile. + # The line we want looks like: + # hrss: 1.752741e-23 2.101590e-23 6.418900e-23 + for line in datafile: + if line.startswith('hrss:'): + hrss_values = [float(hrss) for hrss in line.split()[1:]] + max_hrss = max(hrss_values) + # From Min-A Cho: fluence = pi*(c**3)*(freq**2)*(hrss_max**2)*(10**3)/(4*G) + # Note that hrss here actually has units of s^(-1/2) + pi = 3.14152 + c = 2.99792E10 + G = 6.674E-8 + fluence = pi * pow(c,3) * pow(event.central_freq,2) * 1000.0 + fluence = fluence * pow(max_hrss,2) + fluence = fluence / (4.0*G) + + w.add_Param(Param(name="Fluence", + dataType="float", + ucd="gw.fluence", + unit="erg/cm^2", + value=fluence, + Description=["Estimated fluence of GW burst signal"])) + except Exception: + pass + else: pass - else: - pass v.set_What(w) ############ Wherewhen ############################ - wwd = {'observatory': 'LIGO Virgo', - 'coord_system': 'UTC-FK5-GEO', - # XXX time format - 'time': str(gpsToUtc(event.gpstime).isoformat())[:-6], #'1918-11-11T11:11:11', - #'timeError': 1.0, - 'longitude': 0.0, - 'latitude': 0.0, - 'positionalError': 180.0, - } - - ww = makeWhereWhen(wwd) - if ww: v.set_WhereWhen(ww) + if voevent_type != 'retraction': + wwd = {'observatory': 'LIGO Virgo', + 'coord_system': 'UTC-FK5-GEO', + # XXX time format + 'time': str(gpsToUtc(event.gpstime).isoformat())[:-6], #'1918-11-11T11:11:11', + #'timeError': 1.0, + 'longitude': 0.0, + 'latitude': 0.0, + 'positionalError': 180.0, + } + + ww = makeWhereWhen(wwd) + if ww: v.set_WhereWhen(ww) ############ Citation ############################ - #c = Citations() - #c.add_EventIVORN(EventIVORN(cite="followup", valueOf_="ivo:silly/billy#89474")) - #c.add_EventIVORN(EventIVORN(cite="followup", valueOf_="ivo:silly/billy#89475")) - #v.set_Citations(c) + if voevent_type != 'preliminary': + c = Citations() + for ve in event.voevent_set.all(): + # Oh, actually we need to exclude *this* voevent. + if serial_number == ve.N: + continue + if voevent_type == 'initial': + ei = EventIVORN('supersedes', ve.ivorn) + c.set_Description('Initial localization is now available') + elif voevent_type == 'update': + ei = EventIVORN('supersedes', ve.ivorn) + c.set_Description('Updated localization is now available') + elif voevent_type == 'retraction': + ei = EventIVORN('retraction', ve.ivorn) + c.set_Description('Determined to not be a viable GW event candidate') + c.add_EventIVORN(ei) + + v.set_Citations(c) ############ output the event ############################ xml = stringVOEvent(v) #schemaURL = "http://www.ivoa.net/xml/VOEvent/VOEvent-v2.0.xsd") - return xml + return xml, ivorn def submitToSkyalert(event, validate_only=False): ## Python stub code for validating and authoring VOEvents to Skyalert diff --git a/gracedb/migrations/0038_auto__add_voevent__add_unique_voevent_event_N.py b/gracedb/migrations/0038_auto__add_voevent__add_unique_voevent_event_N.py new file mode 100644 index 0000000000000000000000000000000000000000..38a7fbedd4426b71069eadcf247a30d516c0db7b --- /dev/null +++ b/gracedb/migrations/0038_auto__add_voevent__add_unique_voevent_event_N.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'VOEvent' + db.create_table(u'gracedb_voevent', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('event', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gracedb.Event'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('issuer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('ivorn', self.gf('django.db.models.fields.CharField')(default='', max_length=200)), + ('filename', self.gf('django.db.models.fields.CharField')(default='', max_length=100)), + ('file_version', self.gf('django.db.models.fields.IntegerField')(null=True)), + ('N', self.gf('django.db.models.fields.IntegerField')()), + ('voevent_type', self.gf('django.db.models.fields.CharField')(max_length=2)), + )) + db.send_create_signal(u'gracedb', ['VOEvent']) + + # Adding unique constraint on 'VOEvent', fields ['event', 'N'] + db.create_unique(u'gracedb_voevent', ['event_id', 'N']) + + + def backwards(self, orm): + # Removing unique constraint on 'VOEvent', fields ['event', 'N'] + db.delete_unique(u'gracedb_voevent', ['event_id', 'N']) + + # Deleting model 'VOEvent' + db.delete_table(u'gracedb_voevent') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gracedb.approval': { + 'Meta': {'object_name': 'Approval'}, + 'approvedEvent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + 'approver': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'approvingCollaboration': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'gracedb.coincinspiralevent': { + 'Meta': {'ordering': "['-id']", 'object_name': 'CoincInspiralEvent', '_ormbases': [u'gracedb.Event']}, + 'combined_far': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'end_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'end_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}), + 'false_alarm_rate': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ifos': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}), + 'mass': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'mchirp': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'minimum_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'}) + }, + u'gracedb.embbeventlog': { + 'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'EMBBEventLog'}, + 'N': ('django.db.models.fields.IntegerField', [], {}), + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'decList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'decWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'decWidthList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'duration': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'durationList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'eel_status': ('django.db.models.fields.CharField', [], {'max_length': '2'}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + 'extra_info_dict': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'footprintID': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'gpstime': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'gpstimeList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.EMGroup']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instrument': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'obs_status': ('django.db.models.fields.CharField', [], {'max_length': '2'}), + 'ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'raList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'raWidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'raWidthList': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'waveband': ('django.db.models.fields.CharField', [], {'max_length': '25'}) + }, + u'gracedb.emgroup': { + 'Meta': {'object_name': 'EMGroup'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}) + }, + u'gracedb.event': { + 'Meta': {'ordering': "['-id']", 'object_name': 'Event'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'far': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'gpstime': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '16', 'decimal_places': '6'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'instruments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}), + 'labels': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['gracedb.Label']", 'through': u"orm['gracedb.Labelling']", 'symmetrical': 'False'}), + 'likelihood': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'nevents': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'perms': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pipeline': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Pipeline']"}), + 'search': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Search']", 'null': 'True'}), + 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'gracedb.eventlog': { + 'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'EventLog'}, + 'N': ('django.db.models.fields.IntegerField', [], {}), + 'comment': ('django.db.models.fields.TextField', [], {}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + 'file_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'filename': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) + }, + u'gracedb.grbevent': { + 'Meta': {'ordering': "['-id']", 'object_name': 'GrbEvent', '_ormbases': [u'gracedb.Event']}, + 'author_ivorn': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'author_shortname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'coord_system': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'error_radius': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}), + 'how_description': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'how_reference_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'ivorn': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'observatory_location_id': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}) + }, + u'gracedb.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '20'}) + }, + u'gracedb.label': { + 'Meta': {'object_name': 'Label'}, + 'defaultColor': ('django.db.models.fields.CharField', [], {'default': "'black'", 'max_length': '20'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}) + }, + u'gracedb.labelling': { + 'Meta': {'object_name': 'Labelling'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Label']"}) + }, + u'gracedb.multiburstevent': { + 'Meta': {'ordering': "['-id']", 'object_name': 'MultiBurstEvent', '_ormbases': [u'gracedb.Event']}, + 'amplitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'bandwidth': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'central_freq': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'confidence': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + u'event_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['gracedb.Event']", 'unique': 'True', 'primary_key': 'True'}), + 'false_alarm_rate': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ifos': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20'}), + 'ligo_angle': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ligo_angle_sig': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ligo_axis_dec': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ligo_axis_ra': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'peak_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'peak_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'start_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'start_time_ns': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}) + }, + u'gracedb.pipeline': { + 'Meta': {'object_name': 'Pipeline'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gracedb.search': { + 'Meta': {'object_name': 'Search'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gracedb.singleinspiral': { + 'Gamma0': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma1': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma2': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma3': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma4': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma5': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma6': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma7': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma8': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Gamma9': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'Meta': {'object_name': 'SingleInspiral'}, + 'alpha': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha1': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha2': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha3': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha4': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha5': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'alpha6': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'amplitude': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'bank_chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'bank_chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'beta': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'chi': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'coa_phase': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'cont_chisq': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'cont_chisq_dof': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'eff_distance': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'end_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'end_time_gmst': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'end_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'eta': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + 'event_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'f_final': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ifo': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}), + 'impulse_time': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'impulse_time_ns': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'kappa': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'mass1': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'mass2': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'mchirp': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'mtotal': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'psi0': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'psi3': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'rsqveto_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'search': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}), + 'sigmasq': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'snr': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin1x': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin1y': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin1z': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin2x': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin2y': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'spin2z': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'tau0': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'tau2': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'tau3': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'tau4': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'tau5': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'template_duration': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'ttotal': ('django.db.models.fields.FloatField', [], {'null': 'True'}) + }, + u'gracedb.tag': { + 'Meta': {'object_name': 'Tag'}, + 'displayName': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}), + 'eventlogs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['gracedb.EventLog']", 'symmetrical': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'gracedb.voevent': { + 'Meta': {'ordering': "['-created', '-N']", 'unique_together': "(('event', 'N'),)", 'object_name': 'VOEvent'}, + 'N': ('django.db.models.fields.IntegerField', [], {}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gracedb.Event']"}), + 'file_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'filename': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'ivorn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200'}), + 'voevent_type': ('django.db.models.fields.CharField', [], {'max_length': '2'}) + } + } + + complete_apps = ['gracedb'] \ No newline at end of file diff --git a/gracedb/models.py b/gracedb/models.py index 16770c922f26a2c038ac1bc19529d8cc4bbd96c8..210caea14783dfe60c9013bcff999d893470d506 100644 --- a/gracedb/models.py +++ b/gracedb/models.py @@ -822,3 +822,54 @@ class Tag(models.Model): # # messages in the tag. # eventlist = [log.event for log in self.eventlogs.all()] +class VOEvent(models.Model): + class Meta: + ordering = ['-created','-N'] + unique_together = ("event","N") + # Now N will be the serial number. + event = models.ForeignKey(Event, null=False) + created = models.DateTimeField(auto_now_add=True) + issuer = models.ForeignKey(DjangoUser) + ivorn = models.CharField(max_length=200, default="") + filename = models.CharField(max_length=100, default="") + file_version = models.IntegerField(null=True) + N = models.IntegerField(null=False) + VOEVENT_TYPE_CHOICES = (('PR','preliminary'), ('IN','initial'), ('UP','update'), ('RE', 'retraction'),) + voevent_type = models.CharField(max_length=2, choices=VOEVENT_TYPE_CHOICES) + + def fileurl(self): + if self.filename: + actual_filename = self.filename + if self.file_version >= 0: + actual_filename += ',%d' % self.file_version + return reverse('file', args=[self.event.graceid(), actual_filename]) + else: + return None + + def save(self, *args, **kwargs): + success = False + # XXX filename must not be 'None' because null=False for the filename + # field above. + self.filename = self.filename or "" + attempts = 0 + while (not success and attempts < 5): + attempts = attempts + 1 + if not self.N: + if self.event.voevent_set.count(): + self.N = int(self.event.voevent_set.aggregate(models.Max('N'))['N__max']) + 1 + else: + self.N = 1 + try: + super(VOEvent, self).save(*args, **kwargs) + success = True + except IntegrityError: + # IntegrityError means an attempt to insert a duplicate + # key or to violate a foreignkey constraint. + # We are under race conditions. Let's try again. + pass + + if not success: + # XXX Should this be a custom exception? That way we could catch it + # in the views that use it and give an informative error message. + raise Exception("Too many attempts to save log message. Something is wrong.") + diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py index 65b32c934d5488b0bae997b005b9fb11e9236ef8..1f12d8f5da1e6b2b3624cd9bde148caee45cff0c 100644 --- a/gracedb/urls_rest.py +++ b/gracedb/urls_rest.py @@ -5,7 +5,7 @@ from django.conf.urls import patterns, url # rest_framework from gracedb.api import GracedbRoot -from gracedb.api import EventList, EventDetail, EventVODetail +from gracedb.api import EventList, EventDetail from gracedb.api import EventLogList, EventLogDetail from gracedb.api import EMBBEventLogList, EMBBEventLogDetail from gracedb.api import TagList @@ -18,6 +18,7 @@ from gracedb.api import PerformanceInfo from gracedb.api import EventPermissionList from gracedb.api import GroupEventPermissionList from gracedb.api import GroupEventPermissionDetail +from gracedb.api import VOEventList, VOEventDetail urlpatterns = patterns('gracedb.api', @@ -28,8 +29,6 @@ urlpatterns = patterns('gracedb.api', # events/[{graceid}[/{version}]] url (r'events/$', EventList.as_view(), name='event-list'), - url (r'events/voevent/(?P<graceid>[GEHMT]\d+)$', - EventVODetail.as_view(), name='event-vo-detail'), url (r'events/(?P<graceid>[GEHMT]\d+)$', EventDetail.as_view(), name='event-detail'), @@ -40,6 +39,13 @@ urlpatterns = patterns('gracedb.api', url (r'events/(?P<graceid>[GEHMT]\d+)/log/(?P<n>\d+)$', EventLogDetail.as_view(), name='eventlog-detail'), + # VOEvent Resources + # events/{graceid}/voevent/[{serial_number}] + url (r'events/(?P<graceid>[GEHMT]\d+)/voevent/$', + VOEventList.as_view(), name='voevent-list'), + url (r'events/(?P<graceid>[GEHMT]\d+)/voevent/(?P<n>\d+)$', + VOEventDetail.as_view(), name='voevent-detail'), + # EMBB Event Log Resources # events/{graceid}/logs/[{logid}] url (r'events/(?P<graceid>[GEHMT]\d+)/embb/$', diff --git a/gracedb/view_utils.py b/gracedb/view_utils.py index f286a86aed3cd3191900f80aaa703973d99f2930..e56ede5f77efbc679aaebd16304f4936eeff1ed6 100644 --- a/gracedb/view_utils.py +++ b/gracedb/view_utils.py @@ -284,6 +284,45 @@ def embbEventLogToDict(eel, request=None): "extra_info_dict" : eel.extra_info_dict, } +# VOEvent serializer +def voeventToDict(voevent, request=None): + filename = urlquote('%s,%d' % (voevent.filename, voevent.file_version)) + + uri = None + file_uri = None + if request: + uri = reverse("voevent-detail", + args=[voevent.event.graceid(), voevent.N], + request=request) + file_uri = reverse("files", + args=[voevent.event.graceid(), filename], + request=request) + + issuer_info = { + "username": voevent.issuer.username, + "display_name": "%s %s" % (voevent.issuer.first_name, voevent.issuer.last_name), + } + + # Read in the filecontents + filepath = os.path.join(voevent.event.datadir(), voevent.filename) + text = None + try: + text = open(filepath, 'r').read() + except: + pass + + return { + "self" : uri, + "text" : text, + "file" : file_uri, + "N" : voevent.N, + "issuer" : issuer_info, + "ivorn" : voevent.ivorn, + "filename" : voevent.filename, + "file_version" : voevent.file_version, + "voevent_type" : voevent.voevent_type, + "created" : voevent.created.isoformat(), + } #---------------------------------------------------------------------------------------