Skip to content
Snippets Groups Projects
Select Git revision
  • b468c7d73e3a0af9ac1568ce3244be7263f3607a
  • master default protected
  • remove_cal_plots
  • network-raise-error
  • fix_multinest
  • hyper_hack_correlated
  • correlated-priors-mine
  • fix-intrinsic
  • 257-check-for-duplicate-parameters
  • 297-bilby-0-3-6-doesn-t-declare-dependency-on-astropy
  • add-min-and-max-frequency
  • reconstruct_marginalised_parameters
  • add_bbh_wfg
  • 0.4.0
  • 0.3.6
  • 0.3.5
  • 0.3.4
  • 0.3.3
  • 0.3.2
  • 0.3.1
  • 0.3
  • 0.2.3
  • 0.2.2
  • 0.2.1
  • 0.2
  • 0.1
26 results

.gitlab-ci.yml

Blame
  • Forked from lscsoft / bilby
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    api.py 33.18 KiB
    
    from django.http import HttpResponse, HttpResponseNotFound
    from django.http import HttpResponseForbidden, HttpResponseServerError
    from django.http import HttpResponseBadRequest, HttpResponseRedirect
    from django.core.urlresolvers import reverse as django_reverse
    
    from django.conf import settings
    
    import json
    
    from gracedb.models import Event, Group, EventLog, Slot
    from gracedb.views import create_label
    from translator import handle_uploaded_data
    
    from alert import issueAlertForUpdate
    
    import os
    import urllib
    import errno
    import logging
    import shutil
    
    from utils.vfile import VersionedFile
    
    ##################################################################
    
    REST_FRAMEWORK_SETTINGS = getattr(settings, 'REST_FRAMEWORK', {})
    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
    from views import _createEventFromForm
    from rest_framework import parsers      # YAMLParser, MultiPartParser
    from rest_framework.parsers import DataAndFiles
    
    from rest_framework.permissions import IsAuthenticated
    #from rest_framework.permissions import AllowAny
    from rest_framework import authentication
    from rest_framework.views import APIView
    from rest_framework.reverse import reverse
    
    from django.contrib.auth.models import User as DjangoUser
    
    MAX_FAILED_OPEN_ATTEMPTS = 5
    
    from forms import SimpleSearchForm
    
    class LigoAuthentication(authentication.BaseAuthentication):
        def authenticate(self, request):
            # LIGOAuth middleware finds you from X509 cert, but
            # Shib middleware clobbers (?) the Django user in request
            # and identifies you as anonymous.  Need to recover the
            # Django user.
            try:
                user = DjangoUser.objects.get(username=request.ligouser.unixid)
            except DjangoUser.DoesNotExist:
                # XXX Probably need to create a user.
                user = None
            return (user, None)
    
    
    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  Need to understand serializers.
    
        rv = {}
    
        graceid = event.graceid()
        rv['submitter'] = event.submitter.name
        rv['created'] = event.created
        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'] = dict([
                (labelling.label.name,
                    reverse("labels",
                        args=[graceid, labelling.label.name],
                        request=request))
                for labelling in event.labelling_set.all()])
        rv['links'] = {
                "neighbors" : reverse("neighbors", args=[graceid], request=request),
                "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
    
    
    class EventList(APIView):
        """
        This resource represents the collection of  all candidate events in GraceDB.
    
        ### GET
        Retrieve events. You may use the following parameters:
    
        * `query=Q`    : use any query string as one might use on the query page.
        * `count=N`    : the maximum number of events in a response. (default: 10)
        * `start=N`    : events starting with the Nth event. (default: 0)
        * `sort=Order` : how to order events.  (default: -created)
    
        Example:
        `curl -X GET --insecure --cert $X509_USER_PROXY https://gracedb.ligo.org/api/events/?query=LowMass%20EM_READY&orderby=-far`
    
        Add header `Accept: application/ligolw` for ligolw formatted response.
    
        ### POST
        To create an event.  Expects `multipart/form-data` mime-type with
        parameters, `group`, `type` and a file part, `eventFile` containing
        the analysis data.
    
        Allowable groups and analysis types are listed in the root resource.
    
        Example:
        `curl -X POST -F "group=Test" -F "type=LM" -F "eventFile=@coinc.xml" --insecure --cert $X509_USER_PROXY https://gracedb.ligo.org/api/events/`
    
        """
        #model = Event
        #serializer_class = EventSerializer
        ##renderer_classes = (JSONRenderer, JSONPRenderer, YAMLRenderer, XMLRenderer)
        ##permission_classes = (AllowAny,)
        ##authentication_classes = (authentication.SessionAuthentication,)
        authentication_classes = (LigoAuthentication,)
        permission_classes = (IsAuthenticated,)
        parser_classes = (parsers.MultiPartParser,)
    
    # XXX Need a LIGOLW renderer
    #   def cli_search(request):
    #      assert request.ligouser
    #      from views import assembleLigoLw
    #      form = SimpleSearchForm(request.POST)
    #      if form.is_valid():
    #          query = form.cleaned_data['query']
    #          objects = Event.objects.filter(query).distinct()
    
    #          if 'ligolw' in request.POST or 'ligolw' in request.GET:
    #              from glue.ligolw import utils
    #              if objects.count() > 1000:
    #                  return Response("Too many events.",
    #                          status=status.HTTP_400_BAD_REQUEST)
    #              xmldoc = assembleLigoLw(objects)
    #              response = HttpResponse(mimetype='application/xml')
    #              response['Content-Disposition'] = 'attachment; filename=gracedb-query.xml'
    #              utils.write_fileobj(xmldoc, response)
    #              return response
    
    
        def get(self, request):
            """I am the GET docstring for EventList"""
    
            query = request.QUERY_PARAMS.get("query")
            count = request.QUERY_PARAMS.get("count", PAGINATE_BY)
            start = request.QUERY_PARAMS.get("start", 0)
            sort = request.QUERY_PARAMS.get("sort", "-created")
    
            events = Event.objects
            if query:
                form = SimpleSearchForm(request.GET)
                if form.is_valid():
                    cooked_query = form.cleaned_data['query']
                    events = events.filter(cooked_query).distinct()
                else:
                    return Response("Invalid query",
                            status=status.HTTP_400_BAD_REQUEST)
            events = events.order_by(sort)
    
            start = int(start)
            count = int(count)
            numRows = events.count()
            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
            links['first'] = baseuri + "?" + urllib.urlencode(d)
    
            d['start'] = last
            links['last'] = baseuri + "?" + urllib.urlencode(d)
    
            if start != last:
                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):
            form = CreateEventForm(request.POST, request.FILES)
            if form.is_valid():
                event, warnings = _createEventFromForm(request, form)
                if event:
                    response = Response(
                            eventToDict(event, request=request),
                            status=status.HTTP_201_CREATED)
                    response["Location"] = reverse(
                            'event-detail',
                            args=[event.graceid()],
                            request=request)
                    return response
                else: # no event created
                    return Response({'warnings':warnings},
                            status=status.HTTP_400_BAD_REQUEST)
            else: # form not valid
                rv = {}
                rv['errors'] = ["%s: %s" % (key, form.errors[key].as_text())
                        for key in form.errors]
                return Response(rv, status=status.HTTP_400_BAD_REQUEST)
    
    
    class RawdataParser(parsers.BaseParser):
        media_type = 'application/octet-stream'
    
        def parse(self, stream, media_type=None, parser_context=None):
            class FakeFile():
                def __init__(self, name, read):
                    self.name = name
                    self.read = read
            files = { 'upload' : FakeFile("initial.data", stream.read) }
            data = {}
            return DataAndFiles(data, files)
    
    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 = (LigoLwParser, RawdataParser)
        parser_classes = (parsers.MultiPartParser,)
        serializer_class = EventSerializer
        permission_classes = (IsAuthenticated,)
    
        form = CreateEventForm
    
        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)
    
            #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? """
            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 = VersionedFile(uploadDestination, 'w')
            # Save uploaded file into user private area.
            #for chunk in f.chunks():
            #    fdest.write(chunk)
            #fdest.close()
            shutil.copyfileobj(f, fdest)
            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):
        """The neighbors of an event.
        ### GET
        The `neighborhood` parameter lets you select a GPS time
        range for what it means to be a neighbor.  Can be of the
        form `neighborhood=N` or `neighborhood=N,M` to select
        neighbors in the (inclusive) GPS time range [x-N,x+N] or [x-N, x+M],
        where x is the GPS time of the event in question.
        """
        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('neighborhood'):
                delta = request.QUERY_PARAMS['neighborhood']
                try:
                    if delta.find(',') < 0:
                        neighborhood = (int(delta), int(delta))
                    else:
                        neighborhood = map(int, delta.split(','))
                except ValueError:
                    pass
            else:
                neighborhood = event.DEFAULT_EVENT_NEIGHBORHOOD
    
            neighbors = event.neighbors(neighborhood=neighborhood)
    
            neighbors = [eventToDict(neighbor, request=request)
                        for neighbor in neighbors]
            return Response({
                    'neighbors' : neighbors,
                    'neighborhood' : neighborhood,
                    'numRows' : len(neighbors),
                    'links' : {
                        'self': request.build_absolute_uri(),
                        'event': reverse("event-detail", args=[graceid], request=request),
                        }
                    })
    
    #==================================================================
    # 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 = [ labelToDict(x,request=request)
                        for x in 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)
            try:
                create_label(graceid, label, request.ligouser)
            except ValueError, e:
                return Response(e.message,
                            status=status.HTTP_400_BAD_REQUEST)
            return Response("Created", status=status.HTTP_201_CREATED)
    
    
        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:
            uri = request.build_absolute_uri()
        elif n is not None and request:
            uri = reverse("eventlog-detail",
                    args=[log.event.graceid(), n],
                    request=request)
        else:
            uri = None
        return {
                    "comment" : log.comment,
                    "created" : log.created,
                    "issuer"  : log.issuer.name,
                    "self"    : uri,
               }
    
    class EventLogList(APIView):
        """Event Log List Resource
    
        POST param 'message'
        """
        authentication_classes = (LigoAuthentication,)
        permission_classes = (IsAuthenticated,)
    
        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)
            logset = event.eventlog_set.order_by("created")
            count = logset.count()
    
            log = [ eventLogToDict(log, n, request)
                    for (n, log) in zip(range(0,count+2), logset.iterator()) ]
    
            rv = {
                    'start': 0,
                    'numRows' : count,
                    'links' : {
                        'self' : request.build_absolute_uri(),
                        'first' : request.build_absolute_uri(),
                        'last' : request.build_absolute_uri(),
                        },
                    'log' : log,
                 }
            return Response(rv)
    
        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,)
        permission_classes = (IsAuthenticated,)
    
        def get(self, request, graceid, n):
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                # XXX Real error message.
                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
        """
        authentication_classes = (LigoAuthentication,)
        permission_classes = (IsAuthenticated,)
        parser_classes = ()
        def get(self, request):
            # XXX This seems like a scummy way to get a URI template.
            # Is there better?
            detail = reverse("event-detail", args=["G1200"], request=request)
            detail = detail.replace("G1200", "{graceid}")
            log = reverse("eventlog-list", args=["G1200"], request=request)
            log = log.replace("G1200", "{graceid}")
    
            files = reverse("files", args=["G1200", "filename"], request=request)
            files = files.replace("G1200", "{graceid}")
            files = files.replace("filename", "{filename}")
    
            filemeta = reverse('filemeta', args=["G1200", "filename"], request=request)
            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}")
    
            slot = reverse("slot", args=["G1200", "slotname"], request=request)
            slot = slot.replace("G1200", "{graceid}")
            slot = slot.replace("slotname", "{slotname}")
    
            templates = {
                    "event-detail-template" : detail,
                    "event-log-template" : log,
                    "event-label-template" : labels,
                    "files-template" : files,
                    "filemeta-template" : filemeta,
                    "slot-template" : slot,
                    }
    
            return Response({
                "links" : {
                    "events" : reverse("event-list", request=request),
                    },
                "templates" : templates,
                "groups" : [group.name for group in Group.objects.all()],
                "analysis-types" : dict(Event.ANALYSIS_TYPE_CHOICES),
               })
    
    ##################################################################
    # Old.  Must support this.
    def download(request, graceid, filename=""):
        # Do not filename to be None.  That messes up later os.path.join
        filename = filename or ""
    
        try:
            event = Event.getByGraceid(graceid)
        except Event.DoesNotExist:
            return HttpResponseNotFound("Event not found")
    
        # The plan to deal with that wretched 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.
    
        # UGLY hack to deal with /private vs /general dirs
        general = False
        if filename.startswith("general/"):
            filename = filename[len("general/"):]
            general = True
    
        filepath = os.path.join(event.datadir(general), filename)
    
        if not os.path.exists(filepath):
            response = HttpResponseNotFound("File does not exist")
        elif not os.access(filepath, os.R_OK):
            response = HttpResponseNotFound("File not readable")
        elif os.path.isfile(filepath):
            # get an actual file.
            response = HttpResponse(open(filepath, "r"), content_type="application/octet-stream")
            response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
        elif not filename:
            # Get list of files w/urls.
            rv = {}
            filepath = event.datadir()
            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] = django_reverse(download, args=[graceid, filename])
    
            # XXX UGH...  that awful general/ dir
            filepath = event.datadir(general=True)
            for dirname, dirnames, filenames in os.walk(filepath):
                # XXX HORRIBLE
                dirname = dirname[len(filepath)-len("general"):]  # 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] = django_reverse(download, args=[graceid, filename])
    
            response = HttpResponse(json.dumps(rv), content_type="application/json")
        elif os.path.isdir(filepath):
            response = HttpResponseForbidden("%s is a directory" % filename)
        else:
            response = HttpResponseServerError("Should not happen.")
    
        return response
    
    class Files(APIView):
        """Files Resource"""
    
        authentication_classes = (LigoAuthentication,)
        permission_classes = (IsAuthenticated,)
        #parser_classes = (RawdataParser,)
        parser_classes = (parsers.MultiPartParser,)
    
        def get(self, request, graceid, filename=""):
            # Do not filename to be None.  That messes up later os.path.join
            filename = filename or ""
    
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                return HttpResponseNotFound("Event not found")
    
            # 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.
    
            # UGLY hack to deal with /private vs /general dirs
            general = False
            if filename.startswith("general/"):
                filename = filename[len("general/"):]
                general = True
    
            filepath = os.path.join(event.datadir(general), filename)
    
            if not os.path.exists(filepath):
                response = HttpResponseNotFound("File does not exist")
            elif not os.access(filepath, os.R_OK):
                response = HttpResponseNotFound("File not readable")
            elif os.path.isfile(filepath):
                # get an actual file.
                response = HttpResponse(open(filepath, "r"), content_type="application/octet-stream")
                response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
            elif not filename:
                # 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.
                # Their parent is basically empty.
                filepath = event.datadir(general=True)
                for dirname, dirnames, filenames in os.walk(filepath):
                    # XXX HORRIBLE
                    dirname = dirname[len(filepath)-len("general"):]  # 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),
                                })
    
                #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.")
    
            return response
    
        def put(self, request, graceid, filename=""):
            """ File uploader.  Implements file versioning. """
            filename = filename or ""
    
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                return HttpResponseNotFound("Event not found")
    
            if filename.startswith("general/"):
                # No writing to general/
                return HttpResponseForbidden("cannot write to general directory")
    
            filepath = os.path.join(event.datadir(), filename)
    
            try:
                # Open / Write the file.
                fdest = VersionedFile(filepath, 'w')
                f = request.FILES['upload']
                for chunk in f.chunks(): 
                    fdest.write(chunk)
                fdest.close()
    
                rv = {}
                # XXX this seems wobbly.
                longname = fdest.name
                shortname = longname[longname.rfind(filename):]
                rv['permalink'] = reverse(
                        "files", args=[graceid, shortname], request=request)
                response = Response(rv, status=status.HTTP_201_CREATED)
            except Exception, e:
                # XXX This needs some thought.
                response = Response(str(e), status=status.HTTP_400_BAD_REQUEST)
    
            try:
                description = "UPLOAD: {0}".format(filename)
                issueAlertForUpdate(event, description, doxmpp=True)
            except:
                # XXX something should be done here.
                pass
    
            return response
    
    class FileMeta(APIView):
        """File Metadata Resource"""
        authentication_classes = (LigoAuthentication,)
        permission_classes = (IsAuthenticated,)
        pass
    
    #==================================================================
    # Slots
    
    class EventSlot(APIView):
        """A slot associated with an event.  
        """
    
        # Get the value of a slot.  This will be a filename. 
        def get(self, request, graceid, slotname):
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                # XXX Real error message.
                return Response("Event does not exist.",
                        status=status.HTTP_404_NOT_FOUND)
    
            try:
                slot = Slot.objects.filter(event=event).filter(name=slotname)[0]
            except:
                # Okay, no slot yet.  Probably want an error message.
                # Try looking for files that contain the slot name.
                return Response("No slot.  Search based on slotname not implemented yet.",
                        status=status.HTTP_404_NOT_FOUND)
            filename = slot.value
            dirPrefix = settings.GRACEDB_DATA_DIR
            eventDir = os.path.join(dirPrefix, event.graceid())
            filename = os.path.join(eventDir, "private", filename)
            rv = {}
            rv['value'] = filename
            return Response(rv)
    
        # Create a slot.  The slot's value will be a filename.
        # This file is assumed to have already been uploaded.
        def put(self, request, graceid, slotname):
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                # XXX Real error message.
                return Response("Event does not exist.",
                        status=status.HTTP_404_NOT_FOUND)
            dirPrefix = settings.GRACEDB_DATA_DIR
            eventDir = os.path.join(dirPrefix, event.graceid())
            filename = request.DATA.get('filename')
            # Interestingly, the None object seems to be converted to a string
            # when encoded in the HTTP request body.  Hence the 'None' string 
            # below.  If somebody intentionally named a file 'None', then 
            # they deserve to get this error message.
            if filename=='' or filename=='None' or filename==None:
                return Response("Please submit a filename or upload a file.",
                        status=status.HTTP_400_BAD_REQUEST)
            # Check for existence of the file.
            filePath = os.path.join(eventDir, "private", filename)
            if not os.path.exists(filePath):
               return Response("No slot created because file does not exist",
                        status=status.HTTP_404_NOT_FOUND)
            # Create the slot.
            slot = Slot(event=event,name=slotname,value=filename)
            slot.save()
            return Response("Slot created.",status=status.HTTP_201_CREATED)
    
        # Delete a slot.
        def delete(self, request, graceid, slotname):
            try:
                event = Event.getByGraceid(graceid)
            except Event.DoesNotExist:
                # XXX Real error message.
                return Response("Event does not exist.",
                        status=status.HTTP_404_NOT_FOUND)
    
            # Gotta find the poor devil before we can delete him.
            try:
                slot = Slot.objects.filter(event=event).filter(name=slotname)[0]
            except:
                # Okay, no slot yet.  Probably want an error message.
                # Try looking for files that contain the slot name.
                return Response("No such slot.",
                        status=status.HTTP_404_NOT_FOUND)
    
            slot.delete()
            return Response("Slot deleted.",status=status.HTTP_200_OK)