diff --git a/gracedb/api.py b/gracedb/api.py index 30d71fc601aa1952f98e5546b19b2c45ed5b63f0..b44a50c2189c7b8db2339a731e0436de12fdadd0 100644 --- a/gracedb/api.py +++ b/gracedb/api.py @@ -15,6 +15,7 @@ from gracedb.views import create_label from translator import handle_uploaded_data from alert import issueAlertForUpdate +from buildVOEvent import buildVOEvent import os import urllib @@ -282,6 +283,34 @@ 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): +# logger = logging.getLogger(__name__) +# logger.debug("inside voevent renderer") +# 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 @@ -669,6 +698,36 @@ 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,) + renderer_classes = (JSONRenderer, BrowsableAPIRenderer, ) + + def get(self, request, graceid): + try: + event = Event.getByGraceid(graceid) + except Event.DoesNotExist: + # XXX Real error message. + return Response("Event Not Found", + status=status.HTTP_404_NOT_FOUND) + + try: + voevent = buildVOEvent(event,request) + except Exception, e: + return Response("Problem building VOEvent: %s" % str(e), + status=HTTP_500_INTERNAL_SERVER_ERROR) + + response = Response(voevent) + response["Cache-Control"] = "no-cache" + return response + #================================================================== # Neighbors @@ -1188,6 +1247,8 @@ 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}") @@ -1216,6 +1277,7 @@ class GracedbRoot(APIView): templates = { "event-detail-template" : detail, + "event-vo-detail-template" : vo_detail, "event-log-template" : log, "event-label-template" : labels, "files-template" : files, diff --git a/gracedb/buildVOEvent.py b/gracedb/buildVOEvent.py index f04e83d055e710ab46a9dacf4a86653d9e922dab..ac35bf6b04e549e0e634351c293618c61e107688 100755 --- a/gracedb/buildVOEvent.py +++ b/gracedb/buildVOEvent.py @@ -8,7 +8,7 @@ See the VOEvent specification for details http://www.ivoa.net/Documents/latest/VOEvent.html """ -from VOEventLib.VOEvent import VOEvent, Who, What, Author, Param +from VOEventLib.VOEvent import VOEvent, Who, What, Author, Param, How, Why from VOEventLib.Vutil import makeWhereWhen, stringVOEvent # XXX ER2.utils. utils is in project directory. ugh. @@ -29,11 +29,28 @@ def buildVOEvent(gevent, request=None, description=None, role=None): ############ Who ############################ w = Who() a = Author() - a.add_contactName("LIGO Scientific Consortium") - a.add_contactEmail("postmaster@ligo.org") + a.add_contactName("LIGO Scientific Collaboration and Virgo Collaboration") + #a.add_contactEmail("postmaster@ligo.org") w.set_Author(a) v.set_Who(w) + ############ Why ############################ + y = Why() + y.add_Description("Candidate gravitational wave event identified by low-latency analysis") + v.set_Why(y) + + ############ How ############################ + + h = How() + instruments = gevent.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() @@ -53,16 +70,30 @@ def buildVOEvent(gevent, request=None, description=None, role=None): # basically, a string that makes sense to humans about what units a value is. eg. "m/s" # params related to the event. None are in Groups. - p = Param(name="gracedbid", ucd="meta.id", value="%s"% objid) - p.set_Description(["Identifier assigned by gracedb"]) + p = Param(name="GraceID", ucd="meta.id", value="%s"% objid) + p.set_Description(["Identifier in GraceDB"]) w.add_Param(p) - p = Param(name="gpstime", ucd="time.epoch", dataType="int", value=str(gevent.gpstime)) - p.set_Description(["GPS time of the trigger"]) - w.add_Param(p) + try: + p = Param(name="FAR", dataType="float", ucd="arith.rate;stat.falsealarm", unit="Hz", + value=float(gevent.far)) + p.set_Description(["False alarm rate for GW candidates with this strength or greater"]) + w.add_Param(p) + except: + pass - p = Param(name="likelihood", ucd="stat.likelihood", dataType="float", value=str(gevent.likelihood)) - p.set_Description(["Likelihood"]) + #p = Param(name="gpstime", ucd="time.epoch", dataType="int", value=str(gevent.gpstime)) + #p.set_Description(["GPS time of the trigger"]) + #w.add_Param(p) + + #p = Param(name="likelihood", ucd="stat.likelihood", dataType="float", value=str(gevent.likelihood)) + #p.set_Description(["Likelihood"]) + #w.add_Param(p) + + shib_event_page_url = reverse("view2", args=[gevent.graceid()]) + shib_event_page_url = request.build_absolute_uri(shib_event_page_url) + p = Param(name="EventPage", ucd="meta.ref.url", value=shib_event_page_url) + p.set_Description(["Web page for evolving status of this candidate event"]) w.add_Param(p) # For GCN / SkyAlert. @@ -73,27 +104,21 @@ def buildVOEvent(gevent, request=None, description=None, role=None): # IFO list dataType="string" ucd= # URL to skymap Reference/URL - p = Param(name="analysistype", dataType="string", value=str(gevent.get_analysisType_display())) - p.set_Description(["LIGO analysis which produced this result"]) + p = Param(name="SearchType", dataType="string", value=str(gevent.get_analysisType_display())) + p.set_Description(["Low-latency search type"]) w.add_Param(p) - try: - p = Param(name="far", dataType="float", ucd="arith.rate", unit="Hz", value=float(gevent.far)) - p.set_Description(["False Alarm Rate"]) - w.add_Param(p) - except: - pass - p = Param(name="ifolist", dataType="string", value=str(gevent.instruments)) - p.set_Description(["Interferometers"]) - w.add_Param(p) + #p = Param(name="ifolist", dataType="string", value=str(gevent.instruments)) + #p.set_Description(["Interferometers"]) + #w.add_Param(p) # Skymaps # Four of them, per Roy Williams. (FITS and PNG) x (x509 auth and Shib auth) - # relative URLs - x509_fits_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.fits"]) - x509_png_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.png"]) + # relative URLs (huh? -- Branson) + #x509_fits_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.fits"]) + #x509_png_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.png"]) # XXX gracedb.ligo.org urls. they are a little problematic. # they do not do mime-types correctly and they do not let go of the connection for some reason. @@ -101,8 +126,15 @@ def buildVOEvent(gevent, request=None, description=None, role=None): #shib_png_skymap_url = reverse("file", args=[gevent.graceid(), "general/bayestar/skymap.png"]) # Old sad bad ldad-jobs urls - shib_fits_skymap_url = gevent.weburl() + "/general/bayestar/skymap.fits" - shib_png_skymap_url = gevent.weburl() + "/general/bayestar/skymap.png" + # Confused. These are protected by shib, but so are the others. And these are ugly. + # shib_fits_skymap_url = gevent.weburl() + "/private/skymap.fits" + # shib_png_skymap_url = gevent.weburl() + "/private/skymap.png" + shib_fits_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.fits"]) + shib_png_skymap_url = reverse("file", args=[gevent.graceid(), "skymap.png"]) + + # x509 urls. Hafta specify the api namespace. + x509_fits_skymap_url = reverse("x509:files", args=[gevent.graceid(), "skymap.fits"]) + x509_png_skymap_url = reverse("x509:files", args=[gevent.graceid(), "skymap.png"]) # Need request to build absolute URL # XXX should probably be an error if we can't give the full absolute url. @@ -145,13 +177,13 @@ def buildVOEvent(gevent, request=None, description=None, role=None): v.set_What(w) ############ Wherewhen ############################ - wwd = {'observatory': 'LIGO', + wwd = {'observatory': 'LIGO Virgo', 'coord_system': 'UTC-FK5-GEO', # XXX time format 'time': str(gpsToUtc(gevent.gpstime).isoformat())[:-6], #'1918-11-11T11:11:11', - 'timeError': 1.0, - 'longitude': 123.45, - 'latitude': 67.89, + #'timeError': 1.0, + 'longitude': 0.0, + 'latitude': 0.0, 'positionalError': 180.0, } diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py index 828bfec477bde347f8b5592539e343a79e6f9209..89e6faea18a50cf79069f7447d3cdd4aa48c1f84 100644 --- a/gracedb/urls_rest.py +++ b/gracedb/urls_rest.py @@ -3,7 +3,7 @@ from django.conf.urls.defaults import patterns, url # rest_framework from gracedb.api import GracedbRoot -from gracedb.api import EventList, EventDetail +from gracedb.api import EventList, EventDetail, EventVODetail from gracedb.api import EventLogList, EventLogDetail from gracedb.api import TagList # from gracedb.api import TagDetail @@ -20,6 +20,8 @@ urlpatterns = patterns('gracedb.api', # events/[{graceid}[/{version}]] url (r'events/$', EventList.as_view(), name='event-list'), + url (r'events/voevent/(?P<graceid>[GEHT]\d+)$', + EventVODetail.as_view(), name='event-vo-detail'), url (r'events/(?P<graceid>[GEHT]\d+)$', EventDetail.as_view(), name='event-detail'), diff --git a/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py b/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py new file mode 100644 index 0000000000000000000000000000000000000000..c9b3db36ce4cba328c1e186f0c4b456e2799bdd0 --- /dev/null +++ b/ligoauth/migrations/0006_add_gcn_sender_user_and_dn.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + + +users = [ + { + 'username' : 'gcn_sender', + 'first_name' : '', + 'last_name' : 'GCN Sender', + 'email' : 'branson@gravity.phys.uwm.edu', + 'dns' : [ + "/DC=org/DC=ligo/O=LIGO/OU=Services/CN=gcn_sender/archie.phys.uwm.edu", + "/DC=org/DC=ligo/O=LIGO/OU=Services/CN=gcn_sender/gracedb.phys.uwm.edu", + ] + }, +] + + +class Migration(DataMigration): + + def forwards(self, orm): + LocalUser = orm['ligoauth.LocalUser'] + X509Cert = orm['ligoauth.X509Cert'] + + # Local Users + for entry in users: + user, created = LocalUser.objects.get_or_create(username=entry['username']) + if created: + user.first_name = entry['first_name'] + user.last_name = entry['last_name'] + user.email = entry['email'] + user.is_active = True + user.is_staff = False + user.is_superuser = False + user.save() + current_dns = set([cert.subject for cert in user.x509cert_set.all()]) + new_dns = set(entry['dns']) + + missing_dns = new_dns - current_dns + redundant_dns = current_dns - new_dns + + for dn in missing_dns: + cert, created = X509Cert.objects.get_or_create(subject=dn) + if created: + cert.save() + cert.users.add(user) + + for dn in redundant_dns: + cert = X509Cert.objects.get(subject=dn) + cert.users.remove(user) + + def backwards(self, orm): + LocalUser = orm['ligoauth.LocalUser'] + X509Cert = orm['ligoauth.X509Cert'] + for entry in users: + for dn in entry['dns']: + cert = X509Cert.objects.get(subject=dn) + cert.delete() + + user = LocalUser.objects.get(username=entry['username']) + user.delete() + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + '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': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + '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', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + '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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + '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'}), + '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'}) + }, + 'ligoauth.ligoldapuser': { + 'Meta': {'object_name': 'LigoLdapUser', '_ormbases': ['auth.User']}, + 'ldap_dn': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'ligoauth.localuser': { + 'Meta': {'object_name': 'LocalUser', '_ormbases': ['auth.User']}, + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'ligoauth.x509cert': { + 'Meta': {'object_name': 'X509Cert'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + } + } + + complete_apps = ['ligoauth'] + symmetrical = True diff --git a/settings/branson.py b/settings/branson.py index 476191ca6ba23f337d80a9e65c9023b28c70c94e..39c4d70bdfa81c0920276d49dc19289adc38818f 100644 --- a/settings/branson.py +++ b/settings/branson.py @@ -39,9 +39,6 @@ XMPP_ALERT_CHANNELS = [ 'test_lowmass', ] -# SkyAlert -SKYALERT_IVORN_PATTERN = "ivo://ligo.org/gracedb#%s-dev" - # Latency histograms. Where they go and max latency to bin. LATENCY_REPORT_DEST_DIR = "/home/branson/data/latency" LATENCY_REPORT_WEB_PAGE_FILE_PATH = LATENCY_REPORT_DEST_DIR + "/latency.inc" diff --git a/settings/default.py b/settings/default.py index e207015ac63f76389ad4dd812af4f60f6ba716d7..af32ed0dac389f5c718b3426242793d916e5ba25 100644 --- a/settings/default.py +++ b/settings/default.py @@ -62,9 +62,9 @@ DATABASES = { # SkyAlert -SKYALERT_IVORN_PATTERN = "ivo://ligo.org/gracedb#%s" +SKYALERT_IVORN_PATTERN = "ivo://gwnet/%s" SKYALERT_ROLE = "test" -SKYALERT_DESCRIPTION = "LIGO / Virgo trigger" +SKYALERT_DESCRIPTION = "Report of a candidate gravitational wave event" SKYALERT_SUBMITTERS = ['Patrick Brady', 'Brian Moe'] diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000000000000000000000000000000000000..b297a2c814b0ef4ee9af19c46387d8319f32a90d --- /dev/null +++ b/templates/500.html @@ -0,0 +1,20 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + +<!-- lifted from http://david.feinzeig.com/blog/2012/02/18/tips-for-creating-404-page-not-found-and-500-server-error-templates-in-django-plus-configuring-email-alerts/ --> +Â +<head> + <title>Page unavailable</title> +</head> +Â +<body> + <h1>500: Page unavailable</h1> +Â + <p>Sorry, but the requested page is unavailable due to a server hiccup.</p> +Â + <p>The admins have been notified.</p> +</body> +Â +</html> +