This commit is contained in:
Richard O'Dwyer 2016-01-08 17:10:41 +00:00
parent 69fb34b0db
commit f8854f11f3
2 changed files with 86 additions and 2 deletions

View File

@ -86,7 +86,7 @@ def clone_request(request, method):
ret._full_data = request._full_data ret._full_data = request._full_data
ret._content_type = request._content_type ret._content_type = request._content_type
ret._stream = request._stream ret._stream = request._stream
ret.method = method ret._method = method
if hasattr(request, '_user'): if hasattr(request, '_user'):
ret._user = request._user ret._user = request._user
if hasattr(request, '_auth'): if hasattr(request, '_auth'):
@ -129,6 +129,11 @@ class Request(object):
- authentication_classes(list/tuple). The authentications used to try - authentication_classes(list/tuple). The authentications used to try
authenticating the request's user. authenticating the request's user.
""" """
_METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE
_CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE
_CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE
def __init__(self, request, parsers=None, authenticators=None, def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None): negotiator=None, parser_context=None):
self._request = request self._request = request
@ -139,6 +144,7 @@ class Request(object):
self._data = Empty self._data = Empty
self._files = Empty self._files = Empty
self._full_data = Empty self._full_data = Empty
self._method = Empty
self._content_type = Empty self._content_type = Empty
self._stream = Empty self._stream = Empty
@ -156,6 +162,18 @@ class Request(object):
def _default_negotiator(self): def _default_negotiator(self):
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
@property
def method(self):
"""
Returns the HTTP method.
This allows the `method` to be overridden by using a hidden `form`
field on a form POST request.
"""
if not _hasattr(self, '_method'):
self._load_method_and_content_type()
return self._method
@property @property
def content_type(self): def content_type(self):
meta = self._request.META meta = self._request.META
@ -247,6 +265,23 @@ class Request(object):
else: else:
self._full_data = self._data self._full_data = self._data
def _load_method_and_content_type(self):
"""
Sets the method and content_type, and then check if they've
been overridden.
"""
self._content_type = self.META.get('HTTP_CONTENT_TYPE',
self.META.get('CONTENT_TYPE', ''))
self._perform_form_overloading()
if not _hasattr(self, '_method'):
self._method = self._request.method
# Allow X-HTTP-METHOD-OVERRIDE header
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in self.META:
self._method = self.META['HTTP_X_HTTP_METHOD_OVERRIDE'].upper()
def _load_stream(self): def _load_stream(self):
""" """
Return the content body of the request, as a stream. Return the content body of the request, as a stream.
@ -254,7 +289,9 @@ class Request(object):
meta = self._request.META meta = self._request.META
try: try:
content_length = int( content_length = int(
meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)) meta.get(
'CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH')
)
) )
except (ValueError, TypeError): except (ValueError, TypeError):
content_length = 0 content_length = 0
@ -266,6 +303,50 @@ class Request(object):
else: else:
self._stream = six.BytesIO(self.raw_post_data) self._stream = six.BytesIO(self.raw_post_data)
def _perform_form_overloading(self):
"""
If this is a form POST request, then we need to check if the method and
content/content_type have been overridden by setting them in hidden
form fields or not.
"""
USE_FORM_OVERLOADING = (
self._METHOD_PARAM or
(self._CONTENT_PARAM and self._CONTENTTYPE_PARAM)
)
# We only need to use form overloading on form POST requests.
if (
self._request.method != 'POST' or
not USE_FORM_OVERLOADING or
not is_form_media_type(self._content_type)
):
return
# At this point we're committed to parsing the request as form data.
self._data = self._request.POST
self._files = self._request.FILES
self._full_data = self._data.copy()
self._full_data.update(self._files)
# Method overloading - change the method and remove the param from the content.
if (
self._METHOD_PARAM and
self._METHOD_PARAM in self._data
):
self._method = self._data[self._METHOD_PARAM].upper()
# Content overloading - modify the content type, and force re-parse.
if (
self._CONTENT_PARAM and
self._CONTENTTYPE_PARAM and
self._CONTENT_PARAM in self._data and
self._CONTENTTYPE_PARAM in self._data
):
self._content_type = self._data[self._CONTENTTYPE_PARAM]
self._stream = six.BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding']))
self._data, self._files, self._full_data = (Empty, Empty, Empty)
def _parse(self): def _parse(self):
""" """
Parse the request content, returning a two-tuple of (data, files) Parse the request content, returning a two-tuple of (data, files)

View File

@ -92,6 +92,9 @@ DEFAULTS = {
'rest_framework.renderers.JSONRenderer' 'rest_framework.renderers.JSONRenderer'
), ),
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', 'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',
'FORM_METHOD_OVERRIDE': '_method',
'FORM_CONTENT_OVERRIDE': '_content',
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
# Hyperlink settings # Hyperlink settings
'URL_FORMAT_OVERRIDE': 'format', 'URL_FORMAT_OVERRIDE': 'format',