aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/rest/emitters.py37
-rw-r--r--src/rest/parsers.py23
-rw-r--r--src/rest/resource.py115
3 files changed, 93 insertions, 82 deletions
diff --git a/src/rest/emitters.py b/src/rest/emitters.py
index 87faa559..dcbaf7b7 100644
--- a/src/rest/emitters.py
+++ b/src/rest/emitters.py
@@ -4,12 +4,8 @@ import json
from utils import dict2xml
class BaseEmitter(object):
- def __init__(self, resource, request, status, headers, form):
- self.request = request
+ def __init__(self, resource):
self.resource = resource
- self.status = status
- self.headers = headers
- self.form = form
def emit(self, output):
return output
@@ -18,28 +14,43 @@ class TemplatedEmitter(BaseEmitter):
template = None
def emit(self, output):
- content = json.dumps(output, indent=4, sort_keys=True)
+ if output is None:
+ content = ''
+ else:
+ content = json.dumps(output, indent=4, sort_keys=True)
+
template = loader.get_template(self.template)
- context = RequestContext(self.request, {
+ context = RequestContext(self.resource.request, {
'content': content,
- 'status': self.status,
- 'reason': STATUS_CODE_TEXT.get(self.status, ''),
- 'headers': self.headers,
+ 'status': self.resource.resp_status,
+ 'reason': STATUS_CODE_TEXT.get(self.resource.resp_status, ''),
+ 'headers': self.resource.resp_headers,
'resource_name': self.resource.__class__.__name__,
'resource_doc': self.resource.__doc__,
- 'create_form': self.form,
- 'update_form': self.form,
- 'request': self.request,
+ 'create_form': self.resource.form,
+ 'update_form': self.resource.form,
+ 'request': self.resource.request,
'resource': self.resource,
})
+
+ # Munge DELETE Response code to allow us to return content
+ if self.resource.resp_status == 204:
+ self.resource.resp_status = 200
+
return template.render(context)
class JSONEmitter(BaseEmitter):
def emit(self, output):
+ if output is None:
+ # Treat None as no message body, rather than serializing
+ return ''
return json.dumps(output)
class XMLEmitter(BaseEmitter):
def emit(self, output):
+ if output is None:
+ # Treat None as no message body, rather than serializing
+ return ''
return dict2xml(output)
class HTMLEmitter(TemplatedEmitter):
diff --git a/src/rest/parsers.py b/src/rest/parsers.py
index 85b0e51f..73073243 100644
--- a/src/rest/parsers.py
+++ b/src/rest/parsers.py
@@ -1,9 +1,8 @@
import json
class BaseParser(object):
- def __init__(self, resource, request):
+ def __init__(self, resource):
self.resource = resource
- self.request = request
def parse(self, input):
return {}
@@ -20,9 +19,9 @@ class FormParser(BaseParser):
"""The default parser for form data.
Return a dict containing a single value for each non-reserved parameter
"""
- def __init__(self, resource, request):
+ def __init__(self, resource):
- if request.method == 'PUT':
+ if resource.request.method == 'PUT':
# Fix from piston to force Django to give PUT requests the same
# form processing that POST requests get...
#
@@ -36,22 +35,22 @@ class FormParser(BaseParser):
# the first time _load_post_and_files is called (both by wsgi.py and
# modpython.py). If it's set, the request has to be 'reset' to redo
# the query value parsing in POST mode.
- if hasattr(request, '_post'):
+ if hasattr(resource.request, '_post'):
del request._post
del request._files
try:
- request.method = "POST"
- request._load_post_and_files()
- request.method = "PUT"
+ resource.request.method = "POST"
+ resource.request._load_post_and_files()
+ resource.request.method = "PUT"
except AttributeError:
- request.META['REQUEST_METHOD'] = 'POST'
- request._load_post_and_files()
- request.META['REQUEST_METHOD'] = 'PUT'
+ resource.request.META['REQUEST_METHOD'] = 'POST'
+ resource.request._load_post_and_files()
+ resource.request.META['REQUEST_METHOD'] = 'PUT'
#
self.data = {}
- for (key, val) in request.POST.items():
+ for (key, val) in resource.request.POST.items():
if key not in resource.RESERVED_PARAMS:
self.data[key] = val
diff --git a/src/rest/resource.py b/src/rest/resource.py
index c57ebfad..15ccce48 100644
--- a/src/rest/resource.py
+++ b/src/rest/resource.py
@@ -57,7 +57,7 @@ class Resource(object):
"""Make the class callable so it can be used as a Django view."""
self = object.__new__(cls)
self.__init__()
- self._request = request
+ self.request = request
try:
return self._handle_request(request, *args, **kwargs)
except:
@@ -76,7 +76,7 @@ class Resource(object):
TODO: Add SITEMAP option.
Provided for convienience."""
- return self._request.build_absolute_uri(reverse(view, *args, **kwargs))
+ return self.request.build_absolute_uri(reverse(view, *args, **kwargs))
def make_absolute(self, uri):
@@ -84,7 +84,7 @@ class Resource(object):
TODO: Add SITEMAP option.
Provided for convienience."""
- return self._request.build_absolute_uri(uri)
+ return self.request.build_absolute_uri(uri)
def read(self, headers={}, *args, **kwargs):
@@ -137,36 +137,41 @@ class Resource(object):
- def determine_form(self, input_data=None, return_data=None):
+ def determine_form(self, data=None, is_response=False):
"""Optionally return a Django Form instance, which may be used for validation
and/or rendered by an HTML/XHTML emitter.
- The input_data or return_data arguments can be used to bind the form either to the deserialized input,
- or to a return object.
+ If data is not None the form will be bound to data. is_response indicates if data should be
+ treated as the input data (bind to client input) or the response data (bind to an existing object).
"""
if self.form:
- if input_data:
- return self.form(input_data)
- elif return_data:
- return self.form(return_data)
+ if data:
+ return self.form(data)
else:
return self.form()
return None
- def cleanup_request(self, data, form=None):
+ def cleanup_request(self, data):
"""Perform any resource-specific data deserialization and/or validation
after the initial HTTP content-type deserialization has taken place.
- Optionally this may use a Django Form which will have been bound to the data,
- rather than using the data directly.
- """
- return data
+ By default this uses form validation to filter the basic input into the required types."""
+ if self.form is None:
+ return data
+
+ if not self.form.is_valid():
+ details = dict((key, map(unicode, val)) for (key, val) in self.form.errors.iteritems())
+ raise ResourceException(STATUS_400_BAD_REQUEST, {'detail': details})
+
+ return self.form.cleaned_data
def cleanup_response(self, data):
"""Perform any resource-specific data filtering prior to the standard HTTP
- content-type serialization."""
+ content-type serialization.
+
+ Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can."""
return data
@@ -247,53 +252,57 @@ class Resource(object):
4. cleanup the response data
5. serialize response data into response content, using standard HTTP content negotiation
"""
- method = self.determine_method(request)
emitter = None
- form = None
+
+ # We make these attributes to allow for a certain amount of munging,
+ # eg The HTML emitter needs to render this information
+ self.method = self.determine_method(request)
+ self.form = None
+ self.resp_status = None
+ self.resp_content = None
+ self.resp_headers = {}
+
try:
# Before we attempt anything else determine what format to emit our response data with.
mimetype, emitter = self.determine_emitter(request)
# Ensure the requested operation is permitted on this resource
- self.check_method_allowed(method)
+ self.check_method_allowed(self.method)
# Get the appropriate create/read/update/delete function
- func = getattr(self, self.CALLMAP.get(method, ''))
+ func = getattr(self, self.CALLMAP.get(self.method, ''))
# Either generate the response data, deserializing and validating any request data
- if method in ('PUT', 'POST'):
+ if self.method in ('PUT', 'POST'):
parser = self.determine_parser(request)
- data = parser(self, request).parse(request.raw_post_data)
- form = self.determine_form(input_data=data)
- data = self.cleanup_request(data, form)
- (status, ret, headers) = func(data, request.META, *args, **kwargs)
+ data = parser(self).parse(request.raw_post_data)
+ self.form = self.determine_form(data)
+ data = self.cleanup_request(data)
+ (self.resp_status, ret, self.resp_headers) = func(data, request.META, *args, **kwargs)
else:
- (status, ret, headers) = func(request.META, *args, **kwargs)
- form = self.determine_form(return_data=ret)
+ (self.resp_status, ret, self.resp_headers) = func(request.META, *args, **kwargs)
+ self.form = self.determine_form(ret, is_response=True)
except ResourceException, exc:
- (status, ret, headers) = (exc.status, exc.content, exc.headers)
+ (self.resp_status, ret, self.resp_headers) = (exc.status, exc.content, exc.headers)
+ if emitter is None:
+ mimetype, emitter = self.emitters[0]
+ if self.form is None:
+ self.form = self.determine_form()
- # Use a default emitter if request failed without being able to determine an acceptable emitter
- if emitter is None:
- mimetype, emitter = self.emitters[0]
-
- # Create an unbound form if one has not yet been created
- if form is None:
- form = self.determine_form()
# Always add the allow header
- headers['Allow'] = ', '.join([self.REVERSE_CALLMAP[operation] for operation in self.allowed_operations])
+ self.resp_headers['Allow'] = ', '.join([self.REVERSE_CALLMAP[operation] for operation in self.allowed_operations])
# Serialize the response content
ret = self.cleanup_response(ret)
- content = emitter(self, request, status, headers, form).emit(ret)
+ content = emitter(self).emit(ret)
# Build the HTTP Response
- resp = HttpResponse(content, mimetype=mimetype, status=status)
- for (key, val) in headers.items():
+ resp = HttpResponse(content, mimetype=mimetype, status=self.resp_status)
+ for (key, val) in self.resp_headers.items():
resp[key] = val
return resp
@@ -313,10 +322,10 @@ class ModelResource(Resource):
fields = None
form_fields = None
- def determine_form(self, input_data=None, return_data=None):
+ def determine_form(self, data=None, is_response=False):
"""Return a form that may be used in validation and/or rendering an html emitter"""
if self.form:
- return self.form
+ return super(self.__class__, self).determine_form(data, is_response=is_response)
elif self.model:
class NewModelForm(ModelForm):
@@ -324,26 +333,17 @@ class ModelResource(Resource):
model = self.model
fields = self.form_fields if self.form_fields else None #self.fields
- if input_data:
- return NewModelForm(input_data)
- elif return_data:
- return NewModelForm(instance=return_data)
+ if data and not is_response:
+ return NewModelForm(data)
+ elif data and is_response:
+ return NewModelForm(instance=data)
else:
return NewModelForm()
else:
return None
- def cleanup_request(self, data, form=None):
- """Filter data into form-cleaned data, performing validation and type coercion."""
- if form is None:
- return data
- if not form.is_valid():
- details = dict((key, map(unicode, val)) for (key, val) in form.errors.iteritems())
- raise ResourceException(STATUS_400_BAD_REQUEST, {'detail': details})
-
- return form.cleaned_data
def cleanup_response(self, data):
"""
@@ -614,7 +614,7 @@ class ModelResource(Resource):
try:
instance = self.model.objects.get(**kwargs)
except self.model.DoesNotExist:
- return (404, '', {})
+ return (404, None, {})
return (200, instance, {})
@@ -633,13 +633,14 @@ class ModelResource(Resource):
def delete(self, headers={}, *args, **kwargs):
instance = self.model.objects.get(**kwargs)
instance.delete()
- return (204, '', {})
+ return (204, None, {})
+
class QueryModelResource(ModelResource):
allowed_methods = ('read',)
- def determine_form(self, input_data=None, return_data=None):
+ def determine_form(self, data=None, is_response=False):
return None
def read(self, headers={}, *args, **kwargs):