From c9d564245554ba85098e638e2b01afd6ce3b2777 Mon Sep 17 00:00:00 2001 From: Brian Moe <brian.moe@ligo.org> Date: Fri, 30 Nov 2012 14:19:52 -0600 Subject: [PATCH] Misc --- gracedb/api.py | 320 ++++++++++++++++++++++++++++++++++++++----- gracedb/models.py | 6 +- gracedb/urls_rest.py | 29 ++-- 3 files changed, 308 insertions(+), 47 deletions(-) diff --git a/gracedb/api.py b/gracedb/api.py index e7e3f988e..624f5687c 100644 --- a/gracedb/api.py +++ b/gracedb/api.py @@ -8,7 +8,8 @@ from django.conf import settings import json -from gracedb.models import Event, Group +from gracedb.models import Event, Group, EventLog, Label +from translator import handle_uploaded_data import os import urllib @@ -24,6 +25,8 @@ PAGINATE_BY = REST_FRAMEWORK_SETTINGS.get('PAGINATE_BY', 10) # rest_framework from rest_framework import serializers, status from rest_framework.response import Response +from rest_framework.parsers import BaseParser +#from rest_framework import generics #from rest_framework.renderers import JSONRenderer, JSONPRenderer #from rest_framework.renderers import YAMLRenderer, XMLRenderer from forms import CreateEventForm @@ -51,15 +54,28 @@ class LigoAuthentication(authentication.BaseAuthentication): return (user, None) -class EventSerializer(serializers.Serializer): - group = serializers.CharField(required=True, max_length=100) - analysisType = serializers.CharField(required=True, max_length=100) +class EventSerializer(serializers.ModelSerializer): + #group = serializers.CharField(required=True, max_length=100) + #analysisType = serializers.CharField(required=True, max_length=100) + group = serializers.CharField(source="group.name") + class Meta: + model = Event + fields = ('far', 'instruments', 'group') +class EventLogSerializer(serializers.ModelSerializer): + """docstring for EventLogSerializer""" + comment = serializers.CharField(required=True, max_length=200) + class Meta: + model = EventLog + fields = ('comment', 'issuer', 'created') + +#================================================================== +# Events def eventToDict(event, columns=None, request=None): """Convert an Event to a dictionary so it can be serialized. (ugh)""" - # XXX Seems wrong. Need to understand serializers. + # XXX Need to understand serializers. rv = {} @@ -69,20 +85,27 @@ def eventToDict(event, columns=None, request=None): rv['group'] = event.group.name rv['graceid'] = graceid rv['analysisType'] = event.get_analysisType_display() + rv['gpstime'] = event.gpstime rv['instruments'] = event.instruments rv['nevents'] = event.nevents rv['far'] = event.far rv['likelihood'] = event.likelihood - rv['labels'] = [labelling.label.name - for labelling in event.labelling_set.all()] + rv['labels'] = dict([ + (labelling.label.name, + reverse("labels", + args=[graceid, labelling.label.name], + request=request)) + for labelling in event.labelling_set.all()]) rv['links'] = { - "neighbors" : dict( - [(e.gpstime, reverse("event-detail", args=[e.graceid()])) - for e in event.neighbors()]), +# "neighbors" : dict( +# [(e.gpstime, reverse("event-detail", args=[e.graceid()], request=request)) +# for e in event.neighbors()]), + "neighbors" : reverse("neighbors", args=[graceid], request=request), "data" : event.weburl(), "log" : reverse("eventlog-list", args=[graceid], request=request), "files" : reverse("files", args=[graceid], request=request), "filemeta" : reverse("filemeta", args=[graceid], request=request), + "labels" : reverse("labels", args=[graceid], request=request), "self" : reverse("event-detail", args=[graceid], request=request), } return rv @@ -165,21 +188,28 @@ class EventList(APIView): start = int(start) count = int(count) numRows = events.count() - last = max(0, (count / numRows) - 1) + last = max(0, (numRows / count)) * count rv = {} + links = {} + rv['links'] = links rv['events'] = [eventToDict(e, request=request) for e in events[start:start+count]] baseuri = reverse('event-list', request=request) + + links['self'] = request.build_absolute_uri() + d = { 'start' : 0, "count": count, "sort": sort } if query: d['query'] = query - rv['first'] = baseuri + "?" + urllib.urlencode(d) + links['first'] = baseuri + "?" + urllib.urlencode(d) + d['start'] = last - rv['last'] = baseuri + "?" + urllib.urlencode(d) - rv['self'] = request.build_absolute_uri() + links['last'] = baseuri + "?" + urllib.urlencode(d) + if start != last: - d['start'] = start+1 - rv['next'] = baseuri + "?" + urllib.urlencode(d) + d['start'] = start+count + links['next'] = baseuri + "?" + urllib.urlencode(d) rv['numRows'] = events.count() + d['links'] = links return Response(rv) def post(self, request, format=None): @@ -204,9 +234,42 @@ class EventList(APIView): for key in form.errors] return Response(rv, status=status.HTTP_400_BAD_REQUEST) + +class LigoLwParser(parsers.MultiPartParser): + # XXX Revisit this. + # Doing it right involves refactoring translator.py + media_type = "multipart/form-data" + def parse(self, *args, **kwargs): + data = parsers.MultiPartParser.parse(self, *args, **kwargs) + return data + +# eventData = data.files['eventFile'].read() + +# dirPrefix = settings.GRACEDB_DATA_DIR +# eventDir = os.path.join(dirPrefix, event.graceid()) +# # XXX handle duplicate file names. +# f = request.FILES['eventFile'] +# uploadDestination = os.path.join(eventDir, "private", f.name) +# fdest = open(uploadDestination, 'w') +# # Save uploaded file into user private area. +# for chunk in f.chunks(): +# fdest.write(chunk) +# fdest.close() + +# # Extract Info from uploaded data +# try: +# handle_uploaded_data(event, uploadDestination) +# except: +# return Response("Bad Data", +# status=status.HTTP_400_BAD_REQUEST) +# return Response(status=status.HTTP_202_ACCEPTED) + + class EventDetail(APIView): authentication_classes = (LigoAuthentication,) - parser_classes = (parsers.MultiPartParser,) + parser_classes = (LigoLwParser,) + #parser_classes = (parsers.MultiPartParser,) + serializer_class = EventSerializer form = CreateEventForm def get(self, request, graceid): @@ -214,21 +277,151 @@ class EventDetail(APIView): event = Event.getByGraceid(graceid) except Event.DoesNotExist: # XXX Real error message. - return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND) - return Response(eventToDict(event, request=request)) + return Response("Event Not Found", + status=status.HTTP_404_NOT_FOUND) + + #response = Response(self.serializer_class(event).data) + response = Response(eventToDict(event, request=request)) + + response["Cache-Control"] = "no-cache" + return response def put(self, request, graceid): """ I am a doc. Do I not get put anywhere? """ - raise NotImplementedError() + try: + event = Event.getByGraceid(graceid) + except Event.DoesNotExist: + return Response("Event Not Found", + status=status.HTTP_404_NOT_FOUND) + + try: + if request.ligouser != event.submitter: + msg = "You (%s) Them (%s)" % (request.ligouser, event.submitter) + return HttpResponseForbidden("You did not create this event. %s" %msg) + except Exception, e: + return Response(str(e)) + +# messages = [] +# if event.group.name != request.DATA['group']: +# messages += [ +# "Existing event group ({0}) does not match " +# "replacement event group ({1})".format( +# event.group.name, request.DATA['group'])] +# if event.analysisType != request.DATA['type']: +# messages += [ +# "Existing event type ({0}) does not match " +# "replacement event type ({1})".format( +# event.analysisType, request.DATA['type'])] +# if messages: +# return Response("\n".join(messages), +# status=status.HTTP_400_BAD_REQUEST) + + dirPrefix = settings.GRACEDB_DATA_DIR + eventDir = os.path.join(dirPrefix, event.graceid()) + # XXX handle duplicate file names. + f = request.FILES['eventFile'] + uploadDestination = os.path.join(eventDir, "private", f.name) + fdest = open(uploadDestination, 'w') + # Save uploaded file into user private area. + for chunk in f.chunks(): + fdest.write(chunk) + fdest.close() + + # Extract Info from uploaded data + try: + handle_uploaded_data(event, uploadDestination) + event.submitter = request.ligouser + except: + return Response("Bad Data", + status=status.HTTP_400_BAD_REQUEST) + return Response(status=status.HTTP_202_ACCEPTED) +#================================================================== +# Neighbors + +class EventNeighbors(APIView): + def get(self, request, graceid): + try: + event = Event.getByGraceid(graceid) + except Event.DoesNotExist: + # XXX Real error message. + return Response("Event does not exist.", + status=status.HTTP_404_NOT_FOUND) + if request.QUERY_PARAMS.has_key('delta'): + delta = request.QUERY_PARAMS['delta'] + if delta.find(',') < 0: + delta = delta2 = int(delta) + else: + delta , delta2 = map(int, delta.split(',')) + neighbors = event.neighbors(delta=delta, delta2=delta2) + else: + neighbors = event.neighbors() + return Response( + [eventToDict(neighbor, request=request) + for neighbor in neighbors]) + +#================================================================== +# Labels + +# XXX NOT FINISHED + +def labelToDict(label, request=None): + return { + "name" : label.label.name, + "creator" : label.creator.name, + "created" : label.created, + "self" : reverse("labels", + args=[label.event.graceid(), label.label.name], + request=request), + } + +class EventLabel(APIView): + """Event Label""" + authentication_classes = (LigoAuthentication,) + + def get(self, request, graceid, label): + event = Event.getByGraceid(graceid) + if label is not None: + theLabel = event.labelling_set.filter(label__name=label).all() + if len(theLabel) < 1: + return Response("Label Not %s Found" % label, + status=status.HTTP_404_NOT_FOUND) + theLabel = theLabel[0] + return Response(labelToDict(theLabel, request=request)) + else: + labels = [map( + lambda x: labelToDict(x,request=request), + event.labelling_set.all())] + return Response({ + 'links' : [{ + 'self': request.build_absolute_uri(), + 'event': reverse("event-detail", + args=[event.graceid()], + request=request), + }], + 'labels': labels + }) + + def put(self, request, graceid, label): + return Response("Not Implemented", status=status.HTTP_501_NOT_IMPLEMENTED) + + def delete(self, request, graceid, label): + return Response("Not Implemented", status=status.HTTP_501_NOT_IMPLEMENTED) + +#================================================================== +# EventLog + +# Janky serialization def eventLogToDict(log, n=None, request=None): # XXX Messy. n should not be here but in the model. - if n is None and request: + if (n is None) and request: uri = request.build_absolute_uri() elif n is not None and request: - uri = reverse("eventlog-detail", args=[log.event.graceid(), n], request=request) + uri = reverse("eventlog-detail", + args=[log.event.graceid(), n], + request=request) else: - uri = "" + uri = None return { "comment" : log.comment, "created" : log.created, @@ -237,6 +430,10 @@ def eventLogToDict(log, n=None, request=None): } class EventLogList(APIView): + """Event Log List Resource + + POST param 'message' + """ authentication_classes = (LigoAuthentication,) def get(self, request, graceid): @@ -244,16 +441,30 @@ class EventLogList(APIView): event = Event.getByGraceid(graceid) except Event.DoesNotExist: # XXX Real error message. - return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND) - logset = event.eventlog_set + return Response("Event does not exist.", + status=status.HTTP_404_NOT_FOUND) + logset = event.eventlog_set.order_by("created") count = logset.count() rv = [ eventLogToDict(log, n, request) for (n, log) in zip(range(0,count+2), logset.iterator()) ] return Response(rv) -class EventLogDetail(APIView): - """docstring for EventLogDetail""" + def post(self, request, graceid): + event = Event.getByGraceid(graceid) + message = request.DATA.get('message') + logentry = EventLog( + event=event, + issuer=request.ligouser, + comment=message) + logset = event.eventlog_set.order_by("created") + n = len(logset) + logentry.save() + rv = eventLogToDict(logentry, n, request=request) + response = Response(rv, status=status.HTTP_201_CREATED) + response['Location'] = rv['self'] + return response +class EventLogDetail(APIView): authentication_classes = (LigoAuthentication,) def get(self, request, graceid, n): @@ -261,10 +472,14 @@ class EventLogDetail(APIView): event = Event.getByGraceid(graceid) except Event.DoesNotExist: # XXX Real error message. - return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND) - rv = event.eventlog_set.all()[int(n)] + return Response("Log Entry Not Found", + status=status.HTTP_404_NOT_FOUND) + rv = event.eventlog_set.order_by("created").all()[int(n)] return Response(eventLogToDict(rv, request=request)) +#================================================================== +# Root Resource + class GracedbRoot(APIView): """ Root of the Gracedb REST API @@ -286,14 +501,24 @@ class GracedbRoot(APIView): filemeta = filemeta.replace("G1200", "{graceid}") filemeta = filemeta.replace("filename", "{filename}") + labels = reverse('labels', args=["G1200", "thelabel"], request=request) + labels = labels.replace("G1200", "{graceid}") + labels = labels.replace("thelabel", "{label}") + return Response({ - "event-list" : reverse("event-list", request=request), - "event-detail-template" : detail, + "resources" : { + "events" : reverse("event-list", request=request), + }, + "resource-templates" : { + "event-template" : detail, "event-log-template" : log, - "files-template" : files, - "filemeta-template" : filemeta, + "event-files-template" : files, + "event-filemeta-template" : filemeta, + "event-label-template" : labels, + }, "groups" : [group.name for group in Group.objects.all()], "analysis-types" : dict(Event.ANALYSIS_TYPE_CHOICES), + "labels" : [label.name for label in Label.objects.all()], }) ################################################################## @@ -363,8 +588,8 @@ class Files(APIView): authentication_classes = (LigoAuthentication,) parser_classes = (parsers.MultiPartParser,) - def get(self, request, graceid, filename=""): - # Do not filename to be None. That messes up later os.path.join + def get(self, request, graceid, filename=None): + # Do not let filename be None. That messes up later os.path.join filename = filename or "" try: @@ -372,7 +597,7 @@ class Files(APIView): except Event.DoesNotExist: return HttpResponseNotFound("Event not found") - # The plan to deal with that wretched general/ directory maybe + # The plan to deal with that general/ directory maybe # should be to move it INTO private. Then externally, things # would look like they do now, but the code here would be MUCH # more sane and much shorter. @@ -397,12 +622,19 @@ class Files(APIView): # Get list of files w/urls. rv = {} filepath = event.datadir() + files = [] for dirname, dirnames, filenames in os.walk(filepath): dirname = dirname[len(filepath):] # cut off base event dir path for filename in filenames: # relative path from root of event data dir filename = os.path.join(dirname, filename) rv[filename] = reverse("files", args=[graceid, filename], request=request) + files.append({ + 'name' : filename, + 'link' : reverse("files", + args=[graceid, filename], + request=request), + }) # XXX UGH... that awful general/ dir # Actually not terrible, but do not like private/general as siblings. @@ -415,10 +647,26 @@ class Files(APIView): # relative path from root of event data dir filename = os.path.join(dirname, filename) rv[filename] = reverse("files", args=[graceid, filename], request=request) + files.append({ + 'name' : filename, + 'link' : reverse("files", + args=[graceid, filename], + request=request), + }) #response = HttpResponse(simplejson.dumps(rv), content_type="application/json") +# response = Response({ +# 'links' : { +# 'self' : request.build_absolute_uri(), +# 'event' : reverse("event-detail", +# args=[graceid], +# request=request), +# }, +# 'files' : files, +# }) response = Response(rv) elif os.path.isdir(filepath): + # XXX Really? response = HttpResponseForbidden("%s is a directory" % filename) else: response = HttpResponseServerError("Should not happen.") diff --git a/gracedb/models.py b/gracedb/models.py index bf1f1eb7d..ea901390e 100644 --- a/gracedb/models.py +++ b/gracedb/models.py @@ -123,14 +123,16 @@ class Event(models.Model): gps_time = int(posixToGpsTime(posix_time)) return gps_time - self.gpstime - def neighbors(self, delta=5): + def neighbors(self, delta=5, delta2=None): if not self.gpstime: return [] if self.group.name == 'Test': nearby = Event.objects.filter(group__name='Test') else: nearby = Event.objects.exclude(group__name='Test') - nearby = nearby.filter(gpstime__range=(self.gpstime-delta, self.gpstime+delta)) + if delta2 is None: + delta2 = delta + nearby = nearby.filter(gpstime__range=(self.gpstime-delta, self.gpstime+delta2)) nearby = nearby.exclude(id=self.id) nearby = nearby.order_by('gpstime') return nearby diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py index 9a9ae8d61..86f9794af 100644 --- a/gracedb/urls_rest.py +++ b/gracedb/urls_rest.py @@ -6,36 +6,47 @@ from gracedb.api import GracedbRoot from gracedb.api import EventList, EventDetail from gracedb.api import EventLogList, EventLogDetail from gracedb.api import Files, FileMeta +from gracedb.api import EventNeighbors, EventLabel urlpatterns = patterns('gracedb.api', url (r'^$', GracedbRoot.as_view(), name="api-root"), # Event Resources # events/[{graceid}[/{version}]] - url (r'events/$', EventList.as_view(), name='event-list'), - url (r'events/(?P<graceid>[GEHT]\d+)$', EventDetail.as_view(), name='event-detail'), + url (r'events/$', + EventList.as_view(), name='event-list'), + url (r'events/(?P<graceid>[GEHT]\d+)$', + EventDetail.as_view(), name='event-detail'), + # Event Log Resources # events/{graceid}/logs/[{logid}] - url (r'events/(?P<graceid>[GEHT]\d+)/log/$', EventLogList.as_view(), name='eventlog-list'), - url (r'events/(?P<graceid>[GEHT]\d+)/log/(?P<n>\d+)$', EventLogDetail.as_view(), name='eventlog-detail'), + url (r'events/(?P<graceid>[GEHT]\d+)/log/$', + EventLogList.as_view(), name='eventlog-list'), + url (r'events/(?P<graceid>[GEHT]\d+)/log/(?P<n>\d+)$', + EventLogDetail.as_view(), name='eventlog-detail'), # Event File Resources # events/{graceid}/files/[{filename}[/{version}]] - # XXX change to DjangoRF. But this works now with the cli. - #url (r'^events/(?P<graceid>[\w\d]+)/files/(?P<filename>.+)?$', 'download', name="files"), - url (r'^events/(?P<graceid>\w[\d]+)/files/(?P<filename>.+)?$', Files.as_view(), name="files"), + url (r'^events/(?P<graceid>\w[\d]+)/files/(?P<filename>.+)?$', + Files.as_view(), name="files"), # events/{graceid}/filemeta/[{filename}] - url (r'^events/(?P<graceid>\w[\d]+)/filemeta/(?P<filename>.+)?$', FileMeta.as_view(), name="filemeta"), + url (r'^events/(?P<graceid>\w[\d]+)/filemeta/(?P<filename>.+)?$', + FileMeta.as_view(), name="filemeta"), # Event Labels # events/{graceid}/labels/[{label}] + url (r'^events/(?P<graceid>\w[\d]+)/labels/(?P<label>.+)?$', + EventLabel.as_view(), name="labels"), # Event Slots # events/{graceid}/slots/[{slotid}] # Event Neighbors - # events/{graceid}/neighbors/[?neighborhood=N] + # events/{graceid}/neighbors/[?delta=(N|(N,N))] + url (r'^events/(?P<graceid>\w[\d]+)/neighbors/$', + EventNeighbors.as_view(), name="neighbors"), # Legacy + #url (r'^events/(?P<graceid>\w[\d]+)/files/(?P<filename>.+)?$', 'download', name="files"), url (r'^event/(?P<graceid>\w[\d]+)/files/(?P<filename>.+)?$', 'download', name="download2"), ) -- GitLab