Fix fiddly content-overloading bug

This commit is contained in:
Tom Christie 2012-10-08 17:10:50 +01:00
parent b581ffe323
commit 4a21b3557e
3 changed files with 86 additions and 129 deletions

View File

@ -227,16 +227,14 @@ class Request(object):
self._method = self._data[self._METHOD_PARAM].upper() self._method = self._data[self._METHOD_PARAM].upper()
self._data.pop(self._METHOD_PARAM) self._data.pop(self._METHOD_PARAM)
# Content overloading - modify the content type, and re-parse. # Content overloading - modify the content type, and force re-parse.
if (self._CONTENT_PARAM and if (self._CONTENT_PARAM and
self._CONTENTTYPE_PARAM and self._CONTENTTYPE_PARAM and
self._CONTENT_PARAM in self._data and self._CONTENT_PARAM in self._data and
self._CONTENTTYPE_PARAM in self._data): self._CONTENTTYPE_PARAM in self._data):
self._content_type = self._data[self._CONTENTTYPE_PARAM] self._content_type = self._data[self._CONTENTTYPE_PARAM]
self._stream = StringIO(self._data[self._CONTENT_PARAM]) self._stream = StringIO(self._data[self._CONTENT_PARAM])
self._data.pop(self._CONTENTTYPE_PARAM) self._data, self._files = (Empty, Empty)
self._data.pop(self._CONTENT_PARAM)
self._data, self._files = self._parse()
def _parse(self): def _parse(self):
""" """
@ -250,7 +248,7 @@ class Request(object):
parser = self.negotiator.select_parser(self.parsers, self.content_type) parser = self.negotiator.select_parser(self.parsers, self.content_type)
if not parser: if not parser:
raise exceptions.UnsupportedMediaType(self._content_type) raise exceptions.UnsupportedMediaType(self.content_type)
parsed = parser.parse(self.stream, meta=self.META, parsed = parser.parse(self.stream, meta=self.META,
upload_handlers=self.upload_handlers) upload_handlers=self.upload_handlers)

View File

@ -4,6 +4,7 @@ Tests for content parsing, and form-overloaded content parsing.
from django.conf.urls.defaults import patterns from django.conf.urls.defaults import patterns
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils import simplejson as json
from rest_framework import status from rest_framework import status
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
@ -12,9 +13,11 @@ from rest_framework.parsers import (
FormParser, FormParser,
MultiPartParser, MultiPartParser,
PlainTextParser, PlainTextParser,
JSONParser
) )
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView from rest_framework.views import APIView
@ -36,7 +39,7 @@ class TestMethodOverloading(TestCase):
POST requests can be overloaded to another method by setting a POST requests can be overloaded to another method by setting a
reserved form field reserved form field
""" """
request = Request(factory.post('/', {Request._METHOD_PARAM: 'DELETE'})) request = Request(factory.post('/', {api_settings.FORM_METHOD_OVERRIDE: 'DELETE'}))
self.assertEqual(request.method, 'DELETE') self.assertEqual(request.method, 'DELETE')
@ -117,15 +120,16 @@ class TestContentParsing(TestCase):
""" """
Ensure request.DATA returns content for overloaded POST request. Ensure request.DATA returns content for overloaded POST request.
""" """
content = 'qwerty' json_data = {'foobar': 'qwerty'}
content_type = 'text/plain' content = json.dumps(json_data)
data = { content_type = 'application/json'
Request._CONTENT_PARAM: content, form_data = {
Request._CONTENTTYPE_PARAM: content_type api_settings.FORM_CONTENT_OVERRIDE: content,
api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
} }
request = Request(factory.post('/', data)) request = Request(factory.post('/', form_data))
request.parsers = (PlainTextParser(), ) request.parsers = (JSONParser(), )
self.assertEqual(request.DATA, content) self.assertEqual(request.DATA, json_data)
# def test_accessing_post_after_data_form(self): # def test_accessing_post_after_data_form(self):
# """ # """

View File

@ -1,128 +1,83 @@
# from django.core.urlresolvers import reverse from django.test import TestCase
# from django.conf.urls.defaults import patterns, url, include from django.test.client import RequestFactory
# from django.http import HttpResponse from rest_framework import status
# from django.test import TestCase from rest_framework.decorators import api_view
# from django.utils import simplejson as json from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
# from rest_framework.views import View factory = RequestFactory()
# class MockView(View): class BasicView(APIView):
# """This is a basic mock view""" def get(self, request, *args, **kwargs):
# pass return Response({'method': 'GET'})
def post(self, request, *args, **kwargs):
return Response({'method': 'POST', 'data': request.DATA})
# class MockViewFinal(View): @api_view(['GET', 'POST'])
# """View with final() override""" def basic_view(request):
if request.method == 'GET':
# def final(self, request, response, *args, **kwargs): return {'method': 'GET'}
# return HttpResponse('{"test": "passed"}', content_type="application/json") elif request.method == 'POST':
return {'method': 'POST', 'data': request.DATA}
# # class ResourceMockView(View): class ClassBasedViewIntegrationTests(TestCase):
# # """This is a resource-based mock view""" def setUp(self):
self.view = BasicView.as_view()
# # class MockForm(forms.Form): def test_400_parse_error(self):
# # foo = forms.BooleanField(required=False) request = factory.post('/', 'f00bar', content_type='application/json')
# # bar = forms.IntegerField(help_text='Must be an integer.') response = self.view(request)
# # baz = forms.CharField(max_length=32) expected = {
'detail': u'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(response.data, expected)
# # form = MockForm def test_400_parse_error_tunneled_content(self):
content = 'f00bar'
content_type = 'application/json'
form_data = {
api_settings.FORM_CONTENT_OVERRIDE: content,
api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
}
request = factory.post('/', form_data)
response = self.view(request)
expected = {
'detail': u'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(response.data, expected)
# # class MockResource(ModelResource): class FunctionBasedViewIntegrationTests(TestCase):
# # """This is a mock model-based resource""" def setUp(self):
self.view = basic_view
# # class MockResourceModel(models.Model): def test_400_parse_error(self):
# # foo = models.BooleanField() request = factory.post('/', 'f00bar', content_type='application/json')
# # bar = models.IntegerField(help_text='Must be an integer.') response = self.view(request)
# # baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.') expected = {
'detail': u'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEquals(response.data, expected)
# # model = MockResourceModel def test_400_parse_error_tunneled_content(self):
# # fields = ('foo', 'bar', 'baz') content = 'f00bar'
content_type = 'application/json'
# urlpatterns = patterns('', form_data = {
# url(r'^mock/$', MockView.as_view()), api_settings.FORM_CONTENT_OVERRIDE: content,
# url(r'^mock/final/$', MockViewFinal.as_view()), api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
# # url(r'^resourcemock/$', ResourceMockView.as_view()), }
# # url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)), request = factory.post('/', form_data)
# # url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)), response = self.view(request)
# url(r'^restframework/', include('rest_framework.urls', namespace='rest_framework')), expected = {
# ) 'detail': u'JSON parse error - No JSON object could be decoded'
}
self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST)
# class BaseViewTests(TestCase): self.assertEquals(response.data, expected)
# """Test the base view class of rest_framework"""
# urls = 'rest_framework.tests.views'
# def test_view_call_final(self):
# response = self.client.options('/mock/final/')
# self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
# data = json.loads(response.content)
# self.assertEqual(data['test'], 'passed')
# def test_options_method_simple_view(self):
# response = self.client.options('/mock/')
# self._verify_options_response(response,
# name='Mock',
# description='This is a basic mock view')
# def test_options_method_resource_view(self):
# response = self.client.options('/resourcemock/')
# self._verify_options_response(response,
# name='Resource Mock',
# description='This is a resource-based mock view',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
# def test_options_method_model_resource_list_view(self):
# response = self.client.options('/model/')
# self._verify_options_response(response,
# name='Mock List',
# description='This is a mock model-based resource',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
# def test_options_method_model_resource_detail_view(self):
# response = self.client.options('/model/0/')
# self._verify_options_response(response,
# name='Mock Instance',
# description='This is a mock model-based resource',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
# def _verify_options_response(self, response, name, description, fields=None, status=200,
# mime_type='application/json'):
# self.assertEqual(response.status_code, status)
# self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
# data = json.loads(response.content)
# self.assertTrue('application/json' in data['renders'])
# self.assertEqual(name, data['name'])
# self.assertEqual(description, data['description'])
# if fields is None:
# self.assertFalse(hasattr(data, 'fields'))
# else:
# self.assertEqual(data['fields'], fields)
# class ExtraViewsTests(TestCase):
# """Test the extra views rest_framework provides"""
# urls = 'rest_framework.tests.views'
# def test_login_view(self):
# """Ensure the login view exists"""
# response = self.client.get(reverse('rest_framework:login'))
# self.assertEqual(response.status_code, 200)
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
# def test_logout_view(self):
# """Ensure the logout view exists"""
# response = self.client.get(reverse('rest_framework:logout'))
# self.assertEqual(response.status_code, 200)
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')