From 5b13aa6f954682e70ba1c76b088b31077f7c4cc8 Mon Sep 17 00:00:00 2001
From: Branson Stephens <stephenb@uwm.edu>
Date: Thu, 20 Jun 2013 14:40:30 -0500
Subject: [PATCH] got new cli search working with REST api

---
 gracedb/api.py | 129 +++++++++++++++++++++++++++++++++++++------------
 1 file changed, 99 insertions(+), 30 deletions(-)

diff --git a/gracedb/api.py b/gracedb/api.py
index 842fb708f..a5b2abcfe 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -1,5 +1,5 @@
 
-from django.http import HttpResponse, HttpResponseNotFound
+from django.http import HttpResponse, HttpResponseNotFound, Http404
 from django.http import HttpResponseForbidden, HttpResponseServerError
 from django.http import HttpResponseBadRequest, HttpResponseRedirect
 from django.core.urlresolvers import reverse as django_reverse
@@ -59,6 +59,8 @@ from forms import SimpleSearchForm
 from rest_framework.reverse import reverse as rest_framework_reverse
 from django.core.urlresolvers import resolve, get_script_prefix
 
+from rest_framework.exceptions import APIException, PermissionDenied
+
 ##################################################################
 # Stuff for the LigoLwRenderer
 from glue.ligolw import ligolw
@@ -182,14 +184,38 @@ class EventLogSerializer(serializers.ModelSerializer):
         model = EventLog
         fields = ('comment', 'issuer', 'created')
 #==================================================================
-# Custom renderers
-
-def assembleLigoLw(eventDict):
+# Custom renderers and various accoutrements
+
+class MissingCoinc(Exception):
+    status_code = status.HTTP_400_BAD_REQUEST
+    default_detail = "Event list contained event(s) for which no coinc exists."  
+    def __init__(self, detail=None):
+        self.detail = detail or self.default_detail 
+    def __str__(self):
+        return repr(self.detail)
+
+class CoincAccess(Exception):
+    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
+    default_detail = "Problem reading coinc file."
+    def __init__(self, detail=None):
+        self.detail = detail or self.default_detail 
+    def __str__(self):
+        return repr(self.detail)
+
+def assembleLigoLw(data):
+    if 'events' in data.keys():
+        eventDictList = data['events']
+    else:
+        # There is only one event.
+        eventDictList = [data,]
     xmldoc = ligolw.Document()
-    for e in eventDict:
+    for e in eventDictList:
         fname = os.path.join(settings.GRACEDB_DATA_DIR, e['graceid'], "private", "coinc.xml")
+        if not os.path.exists(fname):
+            raise MissingCoinc
+        elif not os.access(fname, os.R_OK):
+            raise CoincAccess
         utils.load_filename(fname, xmldoc=xmldoc)
-
     ligolw_add.reassign_ids(xmldoc)
     ligolw_add.merge_ligolws(xmldoc)
     ligolw_add.merge_compatible_tables(xmldoc)
@@ -197,27 +223,26 @@ def assembleLigoLw(eventDict):
 
 class LigoLwRenderer(BaseRenderer):
     media_type = 'application/xml'
-    format = '.xml'
+    format = 'xml'
 
     def render(self, data, media_type=None, renderer_context=None):
         # XXX If there was an error, we will return the error message
-        # in plain text. Somewhat irregular?
+        # in plain text, effectively ignoring the accepts header. 
+        # Somewhat irregular?
         if 'error' in data.keys():
             return data['error']
         
-        xmldoc = assembleLigoLw(data['events'])
-        # XXX Aaargh! Just give me the contents of the xml doc.
+        xmldoc = assembleLigoLw(data)
+        # XXX Aaargh! Just give me the contents of the xml doc. Annoying.
         output = StringIO.StringIO()
         xmldoc.write(output)
         return output.getvalue()
 
 class TSVRenderer(BaseRenderer):
     media_type = 'text/tab-separated-values'
-    format = '.tsv'
+    format = 'tsv'
 
     def render(self, data, media_type=None, renderer_context=None):
-        # XXX If there was an error, we will return the error message
-        # in plain text.
         if 'error' in data.keys():
             return data['error']
 
@@ -323,12 +348,13 @@ class EventList(APIView):
     parser_classes = (parsers.MultiPartParser,)
     renderer_classes = (JSONRenderer, BrowsableAPIRenderer, LigoLwRenderer, TSVRenderer,)
 
+    # XXX Branson, remember to get rid of this.
     def __init__(self, **kwargs):
         # Try to define the logger in here.
         self.logger = logging.getLogger(__name__)
         super(EventList, self).__init__(**kwargs)
 
-    def get(self, request):
+    def get(self, request, *args, **kwargs):
 
         """I am the GET docstring for EventList"""
         query = request.QUERY_PARAMS.get("query")
@@ -357,7 +383,7 @@ class EventList(APIView):
 
         # XXX Let's check.  If the output format is ligolw, and 
         # there are more than 1000 events, we error out.  
-        if request.accepted_renderer.format == '.xml' and numRows > 1000:
+        if request.accepted_renderer.format == 'xml' and numRows > 1000:
             # XXX Here again, I don't think this is going to render correctly.
             d = {'error': 'Too many events.' }
             return Response(d, status=status.HTTP_400_BAD_REQUEST)
@@ -383,23 +409,36 @@ class EventList(APIView):
             d['start'] = start+count
             links['next'] = baseuri + "?" + urllib.urlencode(d)
         rv['numRows'] = events.count()
-        # XXX Branson: I believe the following line is unnecessary.
-        # d['links'] = links
-        # XXX Get the columns into renderer_context. Bizarre? Why, yes.
-        setattr(self, 'kwargs', {'columns': columns})
-        self.logger.debug("accepted_renderer = %s" % request.accepted_renderer)
+
+        response = Response(rv)
+
+        # XXX Next, we try finalizing and rendering the response. According to
+        # the django rest framework docs (see .render() in
+        # http://django-rest-framework.org/api-guide/responses.html), this is
+        # unusual. But we want to handle the exceptions raised during rendering 
+        # ourselves.  And that is not easy to do if the exceptions are raised 
+        # somewhere deep inside the entrails of django.
+        # NOTE: This will not result in two calls to render().  Django will check
+        # the _is_rendered property of the response before attempteding to render.
+        # I have tested this by putting logging commands inside the custom renderers.
         try:
-            # If the rendering process fails, this will throw an exception.
-            resp = Response(rv)
-            # XXX Not sure if this will actually work.
-            if request.accepted_renderer.format == '.xml':
-                resp['Content-Disposition'] = 'attachment; filename=gracedb-query.xml'
+            if request.accepted_renderer.format == 'xml':
+                response['Content-Disposition'] = 'attachment; filename=gracedb-query.xml'
+            # XXX Get the columns into renderer_context. Bizarre? Why, yes.
+            setattr(self, 'kwargs', {'columns': columns})
+            # NOTE When finalize_response is calld in its natural habitat, the 
+            # args and kwargs are the same as those passed to the 'handler', i.e., 
+            # the function we are presently inside.
+            response = self.finalize_response(request, response, *args, **kwargs)
+            response.render()
         except Exception, e:
-            d = {'error': str(e) }
-            # XXX Okay, we probably want this to return JSON.  Even if if the
-            # Accept header said something else.  Maybe?
-            return Response(d, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-        return resp
+            try:
+                status_code = e.status_code
+            except:
+                status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
+            return Response({'error': str(e)}, status=status_code)
+
+        return response
 
     def post(self, request, format=None):
         form = CreateEventForm(request.POST, request.FILES)
@@ -472,6 +511,7 @@ class EventDetail(APIView):
     parser_classes = (parsers.MultiPartParser,)
     #serializer_class = EventSerializer
     permission_classes = (IsAuthenticated,)
+    renderer_classes = (JSONRenderer, BrowsableAPIRenderer, LigoLwRenderer,)
 
     form = CreateEventForm
 
@@ -487,6 +527,35 @@ class EventDetail(APIView):
         response = Response(eventToDict(event, request=request))
 
         response["Cache-Control"] = "no-cache"
+
+        # XXX Next, we try finalizing and rendering the response. According to
+        # the django rest framework docs (see .render() in
+        # http://django-rest-framework.org/api-guide/responses.html), this is
+        # unusual. But we want to handle the exceptions raised during rendering 
+        # ourselves.  And that is not easy to do if the exceptions are raised 
+        # somewhere deep inside the entrails of django.
+        # NOTE: This will not result in two calls to render().  Django will check
+        # the _is_rendered property of the response before attempteding to render.
+        # I have tested this by putting logging commands inside the custom renderers.
+        try:
+            if request.accepted_renderer.format == 'xml':
+                response['Content-Disposition'] = 'attachment; filename=gracedb-event.xml'
+            # NOTE When finalize_response is calld in its natural habitat, the 
+            # args and kwargs are the same as those passed to the 'handler', i.e., 
+            # the function we are presently inside.
+            response = self.finalize_response(request, response)
+            response.render()
+        except Exception, e:
+            try:
+                status_code = e.status_code
+            except:
+                status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
+            return Response({'error': str(e)}, status=status_code)
+
+        return response
+
+
+
         return response
 
     def put(self, request, graceid):
-- 
GitLab