mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-29 13:04:03 +03:00
Various cleanups
This commit is contained in:
parent
42825e44e1
commit
5557dfb54c
|
@ -4,12 +4,8 @@ import json
|
||||||
from utils import dict2xml
|
from utils import dict2xml
|
||||||
|
|
||||||
class BaseEmitter(object):
|
class BaseEmitter(object):
|
||||||
def __init__(self, resource, request, status, headers, form):
|
def __init__(self, resource):
|
||||||
self.request = request
|
|
||||||
self.resource = resource
|
self.resource = resource
|
||||||
self.status = status
|
|
||||||
self.headers = headers
|
|
||||||
self.form = form
|
|
||||||
|
|
||||||
def emit(self, output):
|
def emit(self, output):
|
||||||
return output
|
return output
|
||||||
|
@ -18,28 +14,43 @@ class TemplatedEmitter(BaseEmitter):
|
||||||
template = None
|
template = None
|
||||||
|
|
||||||
def emit(self, output):
|
def emit(self, output):
|
||||||
|
if output is None:
|
||||||
|
content = ''
|
||||||
|
else:
|
||||||
content = json.dumps(output, indent=4, sort_keys=True)
|
content = json.dumps(output, indent=4, sort_keys=True)
|
||||||
|
|
||||||
template = loader.get_template(self.template)
|
template = loader.get_template(self.template)
|
||||||
context = RequestContext(self.request, {
|
context = RequestContext(self.resource.request, {
|
||||||
'content': content,
|
'content': content,
|
||||||
'status': self.status,
|
'status': self.resource.resp_status,
|
||||||
'reason': STATUS_CODE_TEXT.get(self.status, ''),
|
'reason': STATUS_CODE_TEXT.get(self.resource.resp_status, ''),
|
||||||
'headers': self.headers,
|
'headers': self.resource.resp_headers,
|
||||||
'resource_name': self.resource.__class__.__name__,
|
'resource_name': self.resource.__class__.__name__,
|
||||||
'resource_doc': self.resource.__doc__,
|
'resource_doc': self.resource.__doc__,
|
||||||
'create_form': self.form,
|
'create_form': self.resource.form,
|
||||||
'update_form': self.form,
|
'update_form': self.resource.form,
|
||||||
'request': self.request,
|
'request': self.resource.request,
|
||||||
'resource': self.resource,
|
'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)
|
return template.render(context)
|
||||||
|
|
||||||
class JSONEmitter(BaseEmitter):
|
class JSONEmitter(BaseEmitter):
|
||||||
def emit(self, output):
|
def emit(self, output):
|
||||||
|
if output is None:
|
||||||
|
# Treat None as no message body, rather than serializing
|
||||||
|
return ''
|
||||||
return json.dumps(output)
|
return json.dumps(output)
|
||||||
|
|
||||||
class XMLEmitter(BaseEmitter):
|
class XMLEmitter(BaseEmitter):
|
||||||
def emit(self, output):
|
def emit(self, output):
|
||||||
|
if output is None:
|
||||||
|
# Treat None as no message body, rather than serializing
|
||||||
|
return ''
|
||||||
return dict2xml(output)
|
return dict2xml(output)
|
||||||
|
|
||||||
class HTMLEmitter(TemplatedEmitter):
|
class HTMLEmitter(TemplatedEmitter):
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class BaseParser(object):
|
class BaseParser(object):
|
||||||
def __init__(self, resource, request):
|
def __init__(self, resource):
|
||||||
self.resource = resource
|
self.resource = resource
|
||||||
self.request = request
|
|
||||||
|
|
||||||
def parse(self, input):
|
def parse(self, input):
|
||||||
return {}
|
return {}
|
||||||
|
@ -20,9 +19,9 @@ class FormParser(BaseParser):
|
||||||
"""The default parser for form data.
|
"""The default parser for form data.
|
||||||
Return a dict containing a single value for each non-reserved parameter
|
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
|
# Fix from piston to force Django to give PUT requests the same
|
||||||
# form processing that POST requests get...
|
# 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
|
# 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
|
# modpython.py). If it's set, the request has to be 'reset' to redo
|
||||||
# the query value parsing in POST mode.
|
# the query value parsing in POST mode.
|
||||||
if hasattr(request, '_post'):
|
if hasattr(resource.request, '_post'):
|
||||||
del request._post
|
del request._post
|
||||||
del request._files
|
del request._files
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request.method = "POST"
|
resource.request.method = "POST"
|
||||||
request._load_post_and_files()
|
resource.request._load_post_and_files()
|
||||||
request.method = "PUT"
|
resource.request.method = "PUT"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
request.META['REQUEST_METHOD'] = 'POST'
|
resource.request.META['REQUEST_METHOD'] = 'POST'
|
||||||
request._load_post_and_files()
|
resource.request._load_post_and_files()
|
||||||
request.META['REQUEST_METHOD'] = 'PUT'
|
resource.request.META['REQUEST_METHOD'] = 'PUT'
|
||||||
|
|
||||||
#
|
#
|
||||||
self.data = {}
|
self.data = {}
|
||||||
for (key, val) in request.POST.items():
|
for (key, val) in resource.request.POST.items():
|
||||||
if key not in resource.RESERVED_PARAMS:
|
if key not in resource.RESERVED_PARAMS:
|
||||||
self.data[key] = val
|
self.data[key] = val
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Resource(object):
|
||||||
"""Make the class callable so it can be used as a Django view."""
|
"""Make the class callable so it can be used as a Django view."""
|
||||||
self = object.__new__(cls)
|
self = object.__new__(cls)
|
||||||
self.__init__()
|
self.__init__()
|
||||||
self._request = request
|
self.request = request
|
||||||
try:
|
try:
|
||||||
return self._handle_request(request, *args, **kwargs)
|
return self._handle_request(request, *args, **kwargs)
|
||||||
except:
|
except:
|
||||||
|
@ -76,7 +76,7 @@ class Resource(object):
|
||||||
TODO: Add SITEMAP option.
|
TODO: Add SITEMAP option.
|
||||||
|
|
||||||
Provided for convienience."""
|
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):
|
def make_absolute(self, uri):
|
||||||
|
@ -84,7 +84,7 @@ class Resource(object):
|
||||||
TODO: Add SITEMAP option.
|
TODO: Add SITEMAP option.
|
||||||
|
|
||||||
Provided for convienience."""
|
Provided for convienience."""
|
||||||
return self._request.build_absolute_uri(uri)
|
return self.request.build_absolute_uri(uri)
|
||||||
|
|
||||||
|
|
||||||
def read(self, headers={}, *args, **kwargs):
|
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
|
"""Optionally return a Django Form instance, which may be used for validation
|
||||||
and/or rendered by an HTML/XHTML emitter.
|
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,
|
If data is not None the form will be bound to data. is_response indicates if data should be
|
||||||
or to a return object.
|
treated as the input data (bind to client input) or the response data (bind to an existing object).
|
||||||
"""
|
"""
|
||||||
if self.form:
|
if self.form:
|
||||||
if input_data:
|
if data:
|
||||||
return self.form(input_data)
|
return self.form(data)
|
||||||
elif return_data:
|
|
||||||
return self.form(return_data)
|
|
||||||
else:
|
else:
|
||||||
return self.form()
|
return self.form()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def cleanup_request(self, data, form=None):
|
def cleanup_request(self, data):
|
||||||
"""Perform any resource-specific data deserialization and/or validation
|
"""Perform any resource-specific data deserialization and/or validation
|
||||||
after the initial HTTP content-type deserialization has taken place.
|
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,
|
By default this uses form validation to filter the basic input into the required types."""
|
||||||
rather than using the data directly.
|
if self.form is None:
|
||||||
"""
|
|
||||||
return data
|
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):
|
def cleanup_response(self, data):
|
||||||
"""Perform any resource-specific data filtering prior to the standard HTTP
|
"""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
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -247,53 +252,57 @@ class Resource(object):
|
||||||
4. cleanup the response data
|
4. cleanup the response data
|
||||||
5. serialize response data into response content, using standard HTTP content negotiation
|
5. serialize response data into response content, using standard HTTP content negotiation
|
||||||
"""
|
"""
|
||||||
method = self.determine_method(request)
|
|
||||||
emitter = None
|
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:
|
try:
|
||||||
# Before we attempt anything else determine what format to emit our response data with.
|
# Before we attempt anything else determine what format to emit our response data with.
|
||||||
mimetype, emitter = self.determine_emitter(request)
|
mimetype, emitter = self.determine_emitter(request)
|
||||||
|
|
||||||
# Ensure the requested operation is permitted on this resource
|
# 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
|
# 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
|
# 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)
|
parser = self.determine_parser(request)
|
||||||
data = parser(self, request).parse(request.raw_post_data)
|
data = parser(self).parse(request.raw_post_data)
|
||||||
form = self.determine_form(input_data=data)
|
self.form = self.determine_form(data)
|
||||||
data = self.cleanup_request(data, form)
|
data = self.cleanup_request(data)
|
||||||
(status, ret, headers) = func(data, request.META, *args, **kwargs)
|
(self.resp_status, ret, self.resp_headers) = func(data, request.META, *args, **kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
(status, ret, headers) = func(request.META, *args, **kwargs)
|
(self.resp_status, ret, self.resp_headers) = func(request.META, *args, **kwargs)
|
||||||
form = self.determine_form(return_data=ret)
|
self.form = self.determine_form(ret, is_response=True)
|
||||||
|
|
||||||
|
|
||||||
except ResourceException, exc:
|
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)
|
||||||
|
|
||||||
# Use a default emitter if request failed without being able to determine an acceptable emitter
|
|
||||||
if emitter is None:
|
if emitter is None:
|
||||||
mimetype, emitter = self.emitters[0]
|
mimetype, emitter = self.emitters[0]
|
||||||
|
if self.form is None:
|
||||||
|
self.form = self.determine_form()
|
||||||
|
|
||||||
# Create an unbound form if one has not yet been created
|
|
||||||
if form is None:
|
|
||||||
form = self.determine_form()
|
|
||||||
|
|
||||||
# Always add the allow header
|
# 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
|
# Serialize the response content
|
||||||
ret = self.cleanup_response(ret)
|
ret = self.cleanup_response(ret)
|
||||||
content = emitter(self, request, status, headers, form).emit(ret)
|
content = emitter(self).emit(ret)
|
||||||
|
|
||||||
# Build the HTTP Response
|
# Build the HTTP Response
|
||||||
resp = HttpResponse(content, mimetype=mimetype, status=status)
|
resp = HttpResponse(content, mimetype=mimetype, status=self.resp_status)
|
||||||
for (key, val) in headers.items():
|
for (key, val) in self.resp_headers.items():
|
||||||
resp[key] = val
|
resp[key] = val
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
@ -313,10 +322,10 @@ class ModelResource(Resource):
|
||||||
fields = None
|
fields = None
|
||||||
form_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"""
|
"""Return a form that may be used in validation and/or rendering an html emitter"""
|
||||||
if self.form:
|
if self.form:
|
||||||
return self.form
|
return super(self.__class__, self).determine_form(data, is_response=is_response)
|
||||||
|
|
||||||
elif self.model:
|
elif self.model:
|
||||||
class NewModelForm(ModelForm):
|
class NewModelForm(ModelForm):
|
||||||
|
@ -324,26 +333,17 @@ class ModelResource(Resource):
|
||||||
model = self.model
|
model = self.model
|
||||||
fields = self.form_fields if self.form_fields else None #self.fields
|
fields = self.form_fields if self.form_fields else None #self.fields
|
||||||
|
|
||||||
if input_data:
|
if data and not is_response:
|
||||||
return NewModelForm(input_data)
|
return NewModelForm(data)
|
||||||
elif return_data:
|
elif data and is_response:
|
||||||
return NewModelForm(instance=return_data)
|
return NewModelForm(instance=data)
|
||||||
else:
|
else:
|
||||||
return NewModelForm()
|
return NewModelForm()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return None
|
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):
|
def cleanup_response(self, data):
|
||||||
"""
|
"""
|
||||||
|
@ -614,7 +614,7 @@ class ModelResource(Resource):
|
||||||
try:
|
try:
|
||||||
instance = self.model.objects.get(**kwargs)
|
instance = self.model.objects.get(**kwargs)
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
return (404, '', {})
|
return (404, None, {})
|
||||||
|
|
||||||
return (200, instance, {})
|
return (200, instance, {})
|
||||||
|
|
||||||
|
@ -633,13 +633,14 @@ class ModelResource(Resource):
|
||||||
def delete(self, headers={}, *args, **kwargs):
|
def delete(self, headers={}, *args, **kwargs):
|
||||||
instance = self.model.objects.get(**kwargs)
|
instance = self.model.objects.get(**kwargs)
|
||||||
instance.delete()
|
instance.delete()
|
||||||
return (204, '', {})
|
return (204, None, {})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QueryModelResource(ModelResource):
|
class QueryModelResource(ModelResource):
|
||||||
allowed_methods = ('read',)
|
allowed_methods = ('read',)
|
||||||
|
|
||||||
def determine_form(self, input_data=None, return_data=None):
|
def determine_form(self, data=None, is_response=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def read(self, headers={}, *args, **kwargs):
|
def read(self, headers={}, *args, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user