mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-01 00:17:40 +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,7 +112,6 @@ 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: | ||||||
|  | @ -121,73 +124,33 @@ class RequestMixin(object): | ||||||
|         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