Forked from
IGWN Computing and Software / GraceDB / GraceDB Server
2411 commits behind the upstream repository.
-
Branson Craig Stephens authoredBranson Craig Stephens authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
api.py 63.43 KiB
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
from django.conf import settings
from django.utils.http import urlquote
from django.utils import dateformat
import json
from gracedb.models import Event, Group, EventLog, Tag
from gracedb.views import create_label, get_performance_info
from gracedb.views import create_eel
from translator import handle_uploaded_data
from alert import issueAlertForUpdate
from buildVOEvent import buildVOEvent
import os
import urllib
import errno
import shutil
import exceptions
import pytz
import logging
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 rest_framework.renderers import BaseRenderer, JSONRenderer
from rest_framework.renderers import BrowsableAPIRenderer
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 django.contrib.auth.models import User as DjangoUser
MAX_FAILED_OPEN_ATTEMPTS = 5
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
# lsctables MUST be loaded before utils.
from glue.ligolw import lsctables
from glue.ligolw import utils
from glue.ligolw.utils import ligolw_add
import StringIO
# Note about reverse() in this file -- there are THREE versions of it here.
#
# SOURCE LOCAL NAME
# django.core.urlresolvers.reverse ==> django_reverse
# rest_framework.reverse.reverse ==> rest_framework_reverse
# reverse defined below ==> reverse
#
# The Django reverse returns relative paths.
#
# The rest framework reverse is basically the same as the Django version
# but will return full paths if the request is passed in using the request kw arg.
#
# The reverse defined below is basically the rest framework reverse, but
# will attempt to deal with multiply-include()-ed url.py-type files with
# different namespaces. (see the comments in the function)
def reverse(name, *args, **kw):
"""Find a URL. Respect where that URL was defined in urls.py
Allow for a set of URLs to have been include()-ed on multiple URL paths.
eg urlpatterns = (
(r'^api1/', include('someapp.urls', app_name="api", namespace="x509")),
(r'^api2/', include('someapp.urls', app_name="api", namespace="shib")),
...)
then reverse("api:root", request=self.request) will give the obviously
correct full URL for the URL named "root" in someapp/urls.py. Django's
reverse will pick one URL path and use it no matter what path the
URL resolver flows through and it will do so whether you specify an app_name
or not.
This function solves that issue. app_name and namespace are required.
The request must be the value at kw['request']
Assembled with hints from http://stackoverflow.com/a/13249060
"""
# XXX rashly assuming app is "api:" brutal.
if type(name) == str and not name.startswith("api:"):
name = "api:"+name
# Idea is to put 'current_app' into the kw args of reverse
# where current_app is the namespace of the urlpattern we got here from.
# Given that, reverse will find the right patterns in your urlpatterns.
# I do know know why Django does not do this by default.
# This probably only works if you give app_names which are the same
# and namespaces that are different.
if 'request' in kw and 'current_app' not in kw:
request = kw['request']
# For some reason, resolve() does not seem to like the script_prefix.
# So, remove it.
prefix = get_script_prefix()
path = request.path.replace(prefix, '/')
current_app = resolve(path).namespace
kw['current_app'] = current_app
return rest_framework_reverse(name, *args, **kw)
class LigoAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
# XXX This makes little sense. https://bugs.ligo.org/redmine/issues/920
raise exceptions.AuthenticationFailed("Bad user")
#class EventSerializer(serializers.ModelSerializer):
# # Overloaded fields.
# group = serializers.CharField(source="group.name")
# submitter = serializers.CharField(source="submitter.name")
# graceid = serializers.Field(source="graceid")
# analysisType = serializers.Field(source="get_analysisType_display")
#
# # New fields.
# labels = serializers.SerializerMethodField('get_labels')
# links = serializers.SerializerMethodField('get_links')
#
# class Meta:
# model = Event
# fields = ('submitter', 'created', 'group', 'graceid',
# 'analysisType', 'gpstime', 'instruments',
# 'nevents', 'far', 'likelihood', 'labels',
# 'links',)
#
# def get_labels(self,obj):
# request = self.context['request']
# graceid = obj.graceid()
# return dict([
# (labelling.label.name,
# reverse("labels",
# args=[graceid, labelling.label.name],
# request=request))
# for labelling in obj.labelling_set.all()])
#
# def get_links(self,obj):
# request = self.context['request']
# graceid = obj.graceid()
# return {
# "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),
# "tags" : reverse("eventtag-list", args=[graceid], request=request),
# }
class EventLogSerializer(serializers.ModelSerializer):
"""docstring for EventLogSerializer"""
comment = serializers.CharField(required=True, max_length=200)
class Meta:
model = EventLog
fields = ('comment', 'issuer', 'created')
#==================================================================
# 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 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)
return xmldoc
class LigoLwRenderer(BaseRenderer):
media_type = 'application/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, effectively ignoring the accepts header.
# Somewhat irregular?
if 'error' in data.keys():
return data['error']
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'
def render(self, data, media_type=None, renderer_context=None):
if 'error' in data.keys():
return data['error']
accessFun = {
"labels" : lambda e: \
",".join(e['labels'].keys()),
"dataurl" : lambda e: e['links']['files'],
}
def defaultAccess(e,a):
if a.find('.') < 0:
return str(e.get(a,""))
rv = e
attrs = a.split('.')
while attrs and rv:
rv = rv.get(attrs[0],"")
attrs = attrs[1:]
return str(rv)
defaultColumns = "graceid,labels,group,analysisType,far,gpstime,created,dataurl"
# XXX Que monstroso!
columns = renderer_context.get('kwargs', None).get('columns', None)
if not columns:
columns = defaultColumns
else:
columns = columns.replace('DEFAULTS',defaultColumns)
columns = columns.split(',')
header = "#" + "\t".join(columns)
outTable = [header]
for e in data['events']:
row = [ accessFun.get(column, lambda e: defaultAccess(e,column))(e) for column in columns ]
outTable.append("\t".join(row))
outTable = "\n".join(outTable)
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
SERVER_TZ = pytz.timezone(settings.TIME_ZONE)
def timeToUTC(dt):
if not dt.tzinfo:
dt = SERVER_TZ.localize(dt)
return dateformat.format(dt.astimezone(pytz.utc), settings.GRACE_DATETIME_FORMAT)
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.username
rv['created'] = timeToUTC(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()])
# XXX Try to produce a dictionary of analysis specific attributes. Duck typing.
rv['extra_attributes'] = {}
try:
# GrbEvent
rv['extra_attributes']['GRB'] = {
"ivorn" : event.ivorn,
"author_ivorn" : event.author_ivorn,
"author_shortname" : event.author_shortname,
"observatory_location_id" : event.observatory_location_id,
"coord_system" : event.coord_system,
"ra" : event.ra,
"dec" : event.dec,
"error_radius" : event.error_radius,
"how_description" : event.how_description,
"how_reference_url" : event.how_reference_url,
}
except:
pass
try:
# CoincInspiralEvent
rv['extra_attributes']['CoincInspiral'] = {
"ifos" : event.ifos,
"end_time" : event.end_time,
"end_time_ns" : event.end_time_ns,
"mass" : event.mass,
"mchirp" : event.mchirp,
"minimum_duration" : event.minimum_duration,
"snr" : event.snr,
"false_alarm_rate" : event.false_alarm_rate,
"combined_far" : event.combined_far,
}
except:
pass
try:
# MultiBurstEvent
rv['extra_attributes']['MultiBurst'] = {
"ifos" : event.ifos,
"start_time" : event.start_time,
"start_time_ns" : event.start_time_ns,
"duration" : event.duration,
"peak_time" : event.peak_time,
"peak_time_ns" : event.peak_time_ns,
"central_freq" : event.central_freq,
"bandwidth" : event.bandwidth,
"amplitude" : event.amplitude,
"snr" : event.snr,
"confidence" : event.confidence,
"false_alarm_rate" : event.false_alarm_rate,
"ligo_axis_ra" : event.ligo_axis_ra,
"ligo_axis_dec" : event.ligo_axis_dec,
"ligo_angle" : event.ligo_angle,
"ligo_angle_sig" : event.ligo_angle_sig,
}
except:
pass
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),
"tags" : reverse("eventtag-list", 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
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
parser_classes = (parsers.MultiPartParser,)
renderer_classes = (JSONRenderer, BrowsableAPIRenderer, LigoLwRenderer, TSVRenderer,)
def get(self, request, *args, **kwargs):
"""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")
columns = request.QUERY_PARAMS.get("columns", "")
events = Event.objects
if query:
form = SimpleSearchForm(request.GET)
if form.is_valid():
events = form.cleaned_data['query']
else:
d = {'error': 'Invalid query' }
return Response(d,status=status.HTTP_400_BAD_REQUEST)
events = events.order_by(sort).select_subclasses()
start = int(start)
count = int(count)
numRows = events.count()
# Fail if the output format is ligolw, and there are more than 1000 events
if request.accepted_renderer.format == 'xml' and numRows > 1000:
d = {'error': 'Too many events.' }
return Response(d, status=status.HTTP_400_BAD_REQUEST)
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()
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 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:
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)
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,)
renderer_classes = (JSONRenderer, BrowsableAPIRenderer, LigoLwRenderer,)
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, context={'request': request}).data)
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
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.user != event.submitter:
msg = "You (%s) Them (%s)" % (request.user, 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.user
except:
# XXX Bad news. If the log file fails to save because of
# race conditions, then this will also be the the message
# returned. Somehow, I think there are other things that
# could go wrong inside handle_uploaded_data besides just
# bad data. We should probably check for different types
# of exceptions here.
return Response("Bad Data",
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=status.HTTP_500_INTERNAL_SERVER_ERROR)
response = Response(voevent)
response["Cache-Control"] = "no-cache"
return response
#==================================================================
# 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.
"""
# XXX Since this returns an event list, we could add the LigoLW
# and TSV renderers.
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.username,
"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):
try:
rv = create_label(graceid, label, request.user)
except Event.DoesNotExist:
msg = "No such Event '%s'" % graceid
return Response(msg,status=status.HTTP_404_NOT_FOUND)
except ValueError, e:
return Response(e.message,
status=status.HTTP_400_BAD_REQUEST)
return Response(rv, 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, request=None):
uri = None
taglist_uri = None
file_uri = None
if request:
uri = reverse("eventlog-detail",
args=[log.event.graceid(), log.N],
request=request)
taglist_uri = reverse("eventlogtag-list",
args=[log.event.graceid(), log.N],
request=request)
if log.filename:
actual_filename = log.filename
if log.file_version:
actual_filename += ',%d' % log.file_version
filename = urlquote(actual_filename)
file_uri = reverse("files",
args=[log.event.graceid(), filename],
request=request)
return {
"comment" : log.comment,
"created" : log.created,
"issuer" : log.issuer.username,
"self" : uri,
"tags" : taglist_uri,
"file" : file_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","N")
count = logset.count()
log = [ eventLogToDict(log, request)
for log in 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')
tagname = request.DATA.get('tagname')
try:
uploadedFile = request.FILES['upload']
except:
uploadedFile = None
filename = None
file_version = None
if uploadedFile:
filename = uploadedFile.name
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')
for chunk in uploadedFile.chunks():
fdest.write(chunk)
fdest.close()
# Ascertain the version assigned to this particular file.
file_version = fdest.version
except Exception, e:
# XXX This needs some thought.
response = Response(str(e), status=status.HTTP_400_BAD_REQUEST)
logentry = EventLog(
event=event,
issuer=request.user,
comment=message,
filename=filename,
file_version=file_version)
logset = event.eventlog_set.order_by("created","N")
try:
logentry.save()
except Exception as e:
# Since this is likely due to race conditions, we will return 503
return Response("Failed to save log entry: %s" % str(e),
status=status.HTTP_503_SERVICE_UNAVAILABLE)
rv = eventLogToDict(logentry, request=request)
response = Response(rv, status=status.HTTP_201_CREATED)
response['Location'] = rv['self']
if tagname:
n = logentry.N
tmp = EventLogTagDetail()
retval = tmp.put(request, graceid, n, tagname)
# XXX This seems like a bizarre way of getting an error message out.
if retval.status_code != 201:
response['tagWarning'] = 'Error creating tag.'
# Issue alert.
description = "LOG: "
if uploadedFile:
description = "UPLOAD: '%s' " % uploadedFile.name
issueAlertForUpdate(event, description+message, doxmpp=True, filename=uploadedFile.name)
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:
return Response("Event Not Found",
status=status.HTTP_404_NOT_FOUND)
try:
rv = event.eventlog_set.filter(N=n)[0]
except:
return Response("Log Message Not Found",
status=status.HTTP_404_NOT_FOUND)
return Response(eventLogToDict(rv, request=request))
#==================================================================
# EMBBEventLog
# FIXME
# Janky serialization
def embbEventLogToDict(eel, request=None):
uri = None
file_uri = None
if request:
uri = reverse("embbeventlog-detail",
args=[eel.event.graceid(), eel.N],
request=request)
return {
"comment" : eel.comment,
"created" : eel.created,
"issuer" : eel.issuer.username,
"self" : uri,
"file" : file_uri,
}
class EMBBEventLogList(APIView):
"""EMBB 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)
eel_set = event.embbeventlog_set.order_by("created","N")
count = eel_set.count()
eel = [ embbEventLogToDict(eel, request)
for eel in eel_set.iterator() ]
rv = {
'start': 0,
'numRows' : count,
'links' : {
'self' : request.build_absolute_uri(),
'first' : request.build_absolute_uri(),
'last' : request.build_absolute_uri(),
},
'embblog' : eel,
}
return Response(rv)
def post(self, request, graceid):
try:
event = Event.getByGraceid(graceid)
except Event.DoesNotExist:
return Response("Event Not Found",
status=status.HTTP_404_NOT_FOUND)
# Now create the EEL
try:
eel = create_eel(request.DATA, event, request.user)
# XXX Need to handle multiple exception types here.
except Exception, e:
return Response("Problem creating EEL: %s" % str(e),
status=status.HTTP_400_BAD_REQUEST)
# Since this is likely due to race conditions, we will return 503
# return Response("Failed to save log entry: %s" % str(e),
# status=status.HTTP_503_SERVICE_UNAVAILABLE)
rv = embbEventLogToDict(eel, request=request)
response = Response(rv, status=status.HTTP_201_CREATED)
response['Location'] = rv['self']
# Issue alert.
description = "New EMBB log entry."
issueAlertForUpdate(event, description, doxmpp=True)
return response
class EMBBEventLogDetail(APIView):
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, graceid, n):
try:
event = Event.getByGraceid(graceid)
except Event.DoesNotExist:
return Response("Event Not Found",
status=status.HTTP_404_NOT_FOUND)
try:
rv = event.embbeventlog_set.filter(N=n)[0]
except:
return Response("Log Message Not Found",
status=status.HTTP_404_NOT_FOUND)
return Response(embbEventLogToDict(rv, request=request))
#==================================================================
# Tags
def tagToDict(tag, columns=None, request=None, event=None, n=None):
"""Convert a tag to a dictionary.
Output depends on the level of specificity.
"""
rv = {}
rv['name'] = tag.name
rv['displayName'] = tag.displayName
if event:
if n:
# We want a link to the self only. End of the line.
rv['links'] = {
"self" : reverse("eventlogtag-detail",
args=[event.graceid(),n,tag.name],
request=request)
}
else:
# Links to all log messages of the event with this tag.
rv['links'] = {
"logs" : [reverse("eventlog-detail",
args=[event.graceid(),log.N],
request=request)
for log in event.getLogsForTag(tag.name)],
"self" : reverse("eventtag-detail",
args=[event.graceid(),tag.name],
request=request)
}
else:
# XXX Unclear what the tag detail resource should be at this level.
# For now, return an empty list.
pass
# rv['links'] = {
# "events" : [reverse("event-detail",
# args=[event.graceid()],
# request=request)
# for event in tag.getEvents()],
# "self" : reverse("tag-detail",
# args=[tag.name],
# request=request)
# }
return rv
class TagList(APIView):
"""Tag List Resource
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
# Return a list of links to all tag objects.
rv = {
# 'tags' : [ reverse("tag-detail", args=[tag.name],
# request=request)
# for tag in Tag.objects.all() ]
# For now, we just output the tag names, since we don't know what
# tag-detail should look like.
'tags' : [ tag.name for tag in Tag.objects.all() ]
}
return Response(rv)
# XXX Unclear what the tag detail resource should be.
# class TagDetail(APIView):
# """Tag Detail Resource
# """
# authentication_classes = (LigoAuthentication,)
# permission_classes = (IsAuthenticated,)
#
# def get(self, request, tagname):
# try:
# tag = Tag.objects.filter(name=tagname)[0]
# except Tag.DoesNotExist:
# return Response("Tag not found.",
# status=status.HTTP_404_NOT_FOUND)
# return Response(tagToDict(tag,request=request))
class EventTagList(APIView):
"""Event Tag List Resource
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, graceid):
# Return a list of links to all tags for this event.
try:
event = Event.getByGraceid(graceid)
except Event.DoesNotExist:
# XXX Real error message.
return Response("Event does not exist.",
status=status.HTTP_404_NOT_FOUND)
rv = {
'tags' : [ reverse("eventtag-detail",args=[graceid,
tag.name],
request=request)
for tag in event.getAvailableTags()]
}
return Response(rv)
class EventTagDetail(APIView):
"""Event Tag List Resource
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, graceid, tagname):
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:
tag = Tag.objects.filter(name=tagname)[0]
rv = tagToDict(tag,event=event,request=request)
return Response(rv)
except Tag.DoesNotExist:
return Response("No such tag for event.",
status=status.HTTP_404_NOT_FOUND)
class EventLogTagList(APIView):
"""Event Log Tag List Resource
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, graceid, n):
# Return a list of links to tags associated with a given log message
try:
event = Event.getByGraceid(graceid)
eventlog = event.eventlog_set.filter(N=n)[0]
except Event.DoesNotExist:
return Response("Event does not exist.",
status=status.HTTP_404_NOT_FOUND)
except:
return Response("Log does not exist.",
status=status.HTTP_404_NOT_FOUND)
rv = {
'tags' : [ reverse("eventlogtag-detail",
args=[graceid,
n, tag.name],
request=request)
for tag in eventlog.tag_set.all()]
}
return Response(rv)
class EventLogTagDetail(APIView):
"""Event Log Tag Detail Resource
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, graceid, n, tagname):
try:
event = Event.getByGraceid(graceid)
eventlog = event.eventlog_set.filter(N=n)[0]
except Event.DoesNotExist:
return Response("Event does not exist.",
status=status.HTTP_404_NOT_FOUND)
except:
return Response("Log does not exist.",
status=status.HTTP_404_NOT_FOUND)
try:
tag = eventlog.tag_set.filter(name=tagname)[0]
# Serialize
return Response(tagToDict(tag,event=event,n=n,request=request))
except:
return Response("Tag not found.",status=status.HTTP_404_NOT_FOUND)
def put(self, request, graceid, n, tagname):
try:
event = Event.getByGraceid(graceid)
eventlog = event.eventlog_set.filter(N=n)[0]
except Event.DoesNotExist:
return Response("Event does not exist.",
status=status.HTTP_404_NOT_FOUND)
except:
return Response("Log does not exist.",
status=status.HTTP_404_NOT_FOUND)
try:
# Has this tag-eventlog relationship already been created? If so, kick out.
# Actually, adding the eventlog to the tag would not hurt anything--no
# duplicate entry would be made in the database. However, we don't want
# an extra log entry, or a deceptive HTTP response (i.e., one telling the
# client that the creation was sucessful when, in fact, the database
# was unchanged.
tag = eventlog.tag_set.filter(name=tagname)[0]
msg = "Log already has tag %s" % unicode(tag)
return Response(msg,status=status.HTTP_409_CONFLICT)
except:
# Look for the tag. If it doesn't already exist, create it.
try:
tag = Tag.objects.filter(name=tagname)[0]
except:
displayName = request.DATA.get('displayName')
tag = Tag(name=tagname, displayName=displayName)
tag.save()
# Now add the log message to this tag.
tag.eventlogs.add(eventlog)
# Create a log entry to document the tag creation.
msg = "Tagged message %s: %s " % (n, tagname)
logentry = EventLog(event=event,
issuer=request.user,
comment=msg)
try:
logentry.save()
except Exception as e:
# Since the tag creation was successful, we'll return 201.
return Response("Tag succesful, but failed to create log entry: %s" % str(e),
status=status.HTTP_201_CREATED)
return Response("Tag created.",status=status.HTTP_201_CREATED)
def delete(self, request, graceid, n, tagname):
try:
event = Event.getByGraceid(graceid)
eventlog = event.eventlog_set.filter(N=n)[0]
except Event.DoesNotExist:
# XXX Real error message.
return Response("Event does not exist.",
status=status.HTTP_404_NOT_FOUND)
except:
# XXX Real error message.
return Response("Log does not exist.",
status=status.HTTP_404_NOT_FOUND)
try:
tag = eventlog.tag_set.filter(name=tagname)[0]
tag.eventlogs.remove(eventlog)
# Is the tag empty now? If so we can delete it.
if not tag.eventlogs.all():
tag.delete()
# Create a log entry to document the tag creation.
msg = "Removed tag %s for message %s. " % (tagname, n)
logentry = EventLog(event=event,
issuer=request.user,
comment=msg)
try:
logentry.save()
except Exception as e:
# Since the tag creation was successful, we'll return 200.
return Response("Tag removed, but failed to create log entry: %s" % str(e),
status=status.HTTP_200_OK)
return Response("Tag deleted.",status=status.HTTP_200_OK)
except:
return Response("Tag not found.",status=status.HTTP_404_NOT_FOUND)
#==================================================================
# 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}")
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}")
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}")
taglist = reverse("eventlogtag-list", args=["G1200", "0"], request=request)
taglist = taglist.replace("G1200", "{graceid}")
taglist = taglist.replace("0", "{n}")
tag = reverse("eventlogtag-detail", args=["G1200", "0", "tagname"], request=request)
tag = tag.replace("G1200", "{graceid}")
tag = tag.replace("0", "{n}")
tag = tag.replace("tagname", "{tagname}")
# XXX Need a template for the tag list?
templates = {
"event-detail-template" : detail,
"event-vo-detail-template" : vo_detail,
"event-log-template" : log,
"event-label-template" : labels,
"files-template" : files,
"filemeta-template" : filemeta,
"tag-template" : tag,
"taglist-template" : taglist,
}
return Response({
"links" : {
"events" : reverse("event-list", request=request),
"self" : reverse("api-root", request=request),
"performance" : reverse("performance-info", 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.
content_type, encoding = VersionedFile.guess_mimetype(filepath)
content_type = content_type or "application/octet-stream"
# XXX encoding should probably not be ignored.
# Get a pretty filename. Just strip off version info.
# XXX This will break if you change the file-version naming convention
# (by, for instance, using a different delimiter than a comma).
try:
ind = filename.index(',')
pretty_filename = filename[:ind]
except ValueError:
pretty_filename = filename
response = HttpResponse(open(filepath, "r"), content_type=content_type)
if content_type == "application/octet-stream":
#response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
response['Content-Disposition'] = 'attachment; filename=%s' % pretty_filename
else:
response['Content-Disposition'] = 'inline; filename=%s' % pretty_filename
if encoding is not None:
response['Content-Encoding'] = encoding
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.
content_type, encoding = VersionedFile.guess_mimetype(filepath)
content_type = content_type or "application/octet-stream"
# XXX encoding should probably not be ignored.
response = HttpResponse(open(filepath, "r"), content_type=content_type)
if content_type == "application/octet-stream":
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
if encoding is not None:
response['Content-Encoding'] = encoding
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()
file_version = fdest.version
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)
# XXX Uhm, we don't to try creating a log message for this, right?
return response
# Create a log entry to document the file upload.
logentry = EventLog(event=event,
issuer=request.user,
comment='',
filename=filename,
file_version=file_version)
try:
logentry.save()
except:
# XXX something should be done here.
pass
try:
description = "UPLOAD: {0}".format(filename)
issueAlertForUpdate(event, description, doxmpp=True, filename=filename)
except:
# XXX something should be done here.
pass
return response
class FileMeta(APIView):
"""File Metadata Resource"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
pass
class PerformanceInfo(APIView):
"""
Serialized performance information
"""
authentication_classes = (LigoAuthentication,)
permission_classes = (IsAuthenticated,)
parser_classes = (parsers.MultiPartParser,)
def get(self, request, *args, **kwargs):
try:
performance_info = get_performance_info()
except Exception, e:
response = Response(str(e), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(performance_info,status=status.HTTP_200_OK)