mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-25 19:14:01 +03:00
Various cleanups
This commit is contained in:
parent
42825e44e1
commit
5557dfb54c
|
@ -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):
|
||||
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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
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)
|
||||
|
||||
# Use a default emitter if request failed without being able to determine an acceptable emitter
|
||||
(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()
|
||||
|
||||
# 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):
|
||||
|
|
Loading…
Reference in New Issue
Block a user