mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 16:24:18 +03:00
_perform_form_overloading becomes transparent
This commit is contained in:
parent
a31a68d6cb
commit
44c8b89c60
|
@ -54,7 +54,7 @@ class RequestMixin(object):
|
||||||
Returns the HTTP method.
|
Returns the HTTP method.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_method'):
|
if not hasattr(self, '_method'):
|
||||||
self._load_metadata()
|
self._load_method_and_content_type()
|
||||||
return self._method
|
return self._method
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ class RequestMixin(object):
|
||||||
Returns the content type header.
|
Returns the content type header.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_content_type'):
|
if not hasattr(self, '_content_type'):
|
||||||
self._load_metadata()
|
self._load_method_and_content_type()
|
||||||
return self._content_type
|
return self._content_type
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,12 +92,16 @@ class RequestMixin(object):
|
||||||
"""
|
"""
|
||||||
Parse the request content into self.DATA and self.FILES.
|
Parse the request content into self.DATA and self.FILES.
|
||||||
"""
|
"""
|
||||||
stream = self._get_stream()
|
if not hasattr(self, '_content_type'):
|
||||||
(self._data, self._files) = self._parse(stream, self.content_type)
|
self._load_method_and_content_type()
|
||||||
|
|
||||||
def _load_metadata(self):
|
if not hasattr(self, '_data'):
|
||||||
|
(self._data, self._files) = self._parse(self._get_stream(), self._content_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_method_and_content_type(self):
|
||||||
"""
|
"""
|
||||||
Set the method and content_type and then check if they've been overridden.
|
Set the method and content_type, and then check if they've been overridden.
|
||||||
"""
|
"""
|
||||||
self._method = self.request.method
|
self._method = self.request.method
|
||||||
self._content_type = self.request.META.get('HTTP_CONTENT_TYPE', self.request.META.get('CONTENT_TYPE', ''))
|
self._content_type = self.request.META.get('HTTP_CONTENT_TYPE', self.request.META.get('CONTENT_TYPE', ''))
|
||||||
|
@ -108,86 +112,45 @@ class RequestMixin(object):
|
||||||
"""
|
"""
|
||||||
Returns an object that may be used to stream the request content.
|
Returns an object that may be used to stream the request content.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, '_stream'):
|
request = self.request
|
||||||
request = self.request
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content_length = int(request.META.get('CONTENT_LENGTH', request.META.get('HTTP_CONTENT_LENGTH')))
|
content_length = int(request.META.get('CONTENT_LENGTH', request.META.get('HTTP_CONTENT_LENGTH')))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
content_length = 0
|
content_length = 0
|
||||||
|
|
||||||
# TODO: Add 1.3's LimitedStream to compat and use that.
|
# TODO: Add 1.3's LimitedStream to compat and use that.
|
||||||
# Currently only supports parsing request body as a stream with 1.3
|
# Currently only supports parsing request body as a stream with 1.3
|
||||||
if content_length == 0:
|
if content_length == 0:
|
||||||
return None
|
return None
|
||||||
elif hasattr(request, 'read'):
|
elif hasattr(request, 'read'):
|
||||||
# UPDATE BASED ON COMMENT BELOW:
|
return request
|
||||||
#
|
return StringIO(request.raw_post_data)
|
||||||
# Yup, this was a bug in Django - fixed and waiting check in - see ticket 15785.
|
|
||||||
# http://code.djangoproject.com/ticket/15785
|
|
||||||
#
|
|
||||||
# COMMENT:
|
|
||||||
#
|
|
||||||
# It's not at all clear if this needs to be byte limited or not.
|
|
||||||
# Maybe I'm just being dumb but it looks to me like there's some issues
|
|
||||||
# with that in Django.
|
|
||||||
#
|
|
||||||
# Either:
|
|
||||||
# 1. It *can't* be treated as a limited byte stream, and you _do_ need to
|
|
||||||
# respect CONTENT_LENGTH, in which case that ought to be documented,
|
|
||||||
# and there probably ought to be a feature request for it to be
|
|
||||||
# treated as a limited byte stream.
|
|
||||||
# 2. It *can* be treated as a limited byte stream, in which case there's a
|
|
||||||
# minor bug in the test client, and potentially some redundant
|
|
||||||
# code in MultiPartParser.
|
|
||||||
#
|
|
||||||
# It's an issue because it affects if you can pass a request off to code that
|
|
||||||
# does something like:
|
|
||||||
#
|
|
||||||
# while stream.read(BUFFER_SIZE):
|
|
||||||
# [do stuff]
|
|
||||||
#
|
|
||||||
#try:
|
|
||||||
# content_length = int(request.META.get('CONTENT_LENGTH',0))
|
|
||||||
#except (ValueError, TypeError):
|
|
||||||
# content_length = 0
|
|
||||||
# self._stream = LimitedStream(request, content_length)
|
|
||||||
self._stream = request
|
|
||||||
else:
|
|
||||||
self._stream = StringIO(request.raw_post_data)
|
|
||||||
return self._stream
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Modify this so that it happens implictly, rather than being called explicitly
|
|
||||||
# ie accessing any of .DATA, .FILES, .content_type, .method will force
|
|
||||||
# form overloading.
|
|
||||||
def _perform_form_overloading(self):
|
def _perform_form_overloading(self):
|
||||||
"""
|
"""
|
||||||
Check the request to see if it is using form POST '_method'/'_content'/'_content_type' overrides.
|
If this is a form POST request, then we need to check if the method and content/content_type have been
|
||||||
If it is then alter self.method, self.content_type, self.CONTENT to reflect that rather than simply
|
overridden by setting them in hidden form fields or not.
|
||||||
delegating them to the original request.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We only need to use form overloading on form POST requests
|
# We only need to use form overloading on form POST requests.
|
||||||
if not self._USE_FORM_OVERLOADING or self._method != 'POST' or not not is_form_media_type(self._content_type):
|
if not self._USE_FORM_OVERLOADING or self._method != 'POST' or not is_form_media_type(self._content_type):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Temporarily switch to using the form parsers, then parse the content
|
# At this point we're committed to parsing the request as form data.
|
||||||
parsers = self.parsers
|
self._data = data = self.request.POST
|
||||||
self.parsers = (FormParser, MultiPartParser)
|
self._files = self.request.FILES
|
||||||
content = self.DATA
|
|
||||||
self.parsers = parsers
|
|
||||||
|
|
||||||
# Method overloading - change the method and remove the param from the content
|
# Method overloading - change the method and remove the param from the content.
|
||||||
if self._METHOD_PARAM in content:
|
if self._METHOD_PARAM in data:
|
||||||
self._method = content[self._METHOD_PARAM].upper()
|
self._method = data[self._METHOD_PARAM].upper()
|
||||||
del self._data[self._METHOD_PARAM]
|
|
||||||
|
|
||||||
# Content overloading - rewind the stream and modify the content type
|
# Content overloading - modify the content type, and re-parse.
|
||||||
if self._CONTENT_PARAM in content and self._CONTENTTYPE_PARAM in content:
|
if self._CONTENT_PARAM in data and self._CONTENTTYPE_PARAM in data:
|
||||||
self._content_type = content[self._CONTENTTYPE_PARAM]
|
self._content_type = data[self._CONTENTTYPE_PARAM]
|
||||||
self._stream = StringIO(content[self._CONTENT_PARAM])
|
stream = StringIO(data[self._CONTENT_PARAM])
|
||||||
del(self._data)
|
(self._data, self._files) = self._parse(stream, self._content_type)
|
||||||
|
|
||||||
|
|
||||||
def _parse(self, stream, content_type):
|
def _parse(self, stream, content_type):
|
||||||
|
|
|
@ -12,16 +12,16 @@ class TestContentParsing(TestCase):
|
||||||
self.req = RequestFactory()
|
self.req = RequestFactory()
|
||||||
|
|
||||||
def ensure_determines_no_content_GET(self, view):
|
def ensure_determines_no_content_GET(self, view):
|
||||||
"""Ensure view.RAW_CONTENT returns None for GET request with no content."""
|
"""Ensure view.DATA returns None for GET request with no content."""
|
||||||
view.request = self.req.get('/')
|
view.request = self.req.get('/')
|
||||||
self.assertEqual(view.DATA, None)
|
self.assertEqual(view.DATA, None)
|
||||||
|
|
||||||
def ensure_determines_form_content_POST(self, view):
|
def ensure_determines_form_content_POST(self, view):
|
||||||
"""Ensure view.RAW_CONTENT returns content for POST request with form content."""
|
"""Ensure view.DATA returns content for POST request with form content."""
|
||||||
form_data = {'qwerty': 'uiop'}
|
form_data = {'qwerty': 'uiop'}
|
||||||
view.parsers = (FormParser, MultiPartParser)
|
view.parsers = (FormParser, MultiPartParser)
|
||||||
view.request = self.req.post('/', data=form_data)
|
view.request = self.req.post('/', data=form_data)
|
||||||
self.assertEqual(view.DATA, form_data)
|
self.assertEqual(view.DATA.items(), form_data.items())
|
||||||
|
|
||||||
def ensure_determines_non_form_content_POST(self, view):
|
def ensure_determines_non_form_content_POST(self, view):
|
||||||
"""Ensure view.RAW_CONTENT returns content for POST request with non-form content."""
|
"""Ensure view.RAW_CONTENT returns content for POST request with non-form content."""
|
||||||
|
@ -75,5 +75,4 @@ class TestContentParsing(TestCase):
|
||||||
view._CONTENTTYPE_PARAM: content_type}
|
view._CONTENTTYPE_PARAM: content_type}
|
||||||
view.request = self.req.post('/', form_data)
|
view.request = self.req.post('/', form_data)
|
||||||
view.parsers = (PlainTextParser,)
|
view.parsers = (PlainTextParser,)
|
||||||
view._perform_form_overloading()
|
|
||||||
self.assertEqual(view.DATA, content)
|
self.assertEqual(view.DATA, content)
|
||||||
|
|
|
@ -24,8 +24,8 @@ class UploadFilesTests(TestCase):
|
||||||
resource = MockResource
|
resource = MockResource
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
return {'FILE_NAME': self.CONTENT['file'].name,
|
return {'FILE_NAME': self.CONTENT['file'][0].name,
|
||||||
'FILE_CONTENT': self.CONTENT['file'].read()}
|
'FILE_CONTENT': self.CONTENT['file'][0].read()}
|
||||||
|
|
||||||
file = StringIO.StringIO('stuff')
|
file = StringIO.StringIO('stuff')
|
||||||
file.name = 'stuff.txt'
|
file.name = 'stuff.txt'
|
||||||
|
|
|
@ -23,5 +23,4 @@ class TestMethodOverloading(TestCase):
|
||||||
"""POST requests can be overloaded to another method by setting a reserved form field"""
|
"""POST requests can be overloaded to another method by setting a reserved form field"""
|
||||||
view = RequestMixin()
|
view = RequestMixin()
|
||||||
view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
|
view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
|
||||||
view._perform_form_overloading()
|
|
||||||
self.assertEqual(view.method, 'DELETE')
|
self.assertEqual(view.method, 'DELETE')
|
||||||
|
|
|
@ -78,10 +78,6 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
|
||||||
set_script_prefix(prefix)
|
set_script_prefix(prefix)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter
|
|
||||||
# self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately.
|
|
||||||
self._perform_form_overloading()
|
|
||||||
|
|
||||||
# Authenticate and check request is has the relevant permissions
|
# Authenticate and check request is has the relevant permissions
|
||||||
self._check_permissions()
|
self._check_permissions()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user