mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-16 19:41:06 +03:00
Fix fiddly content-overloading bug
This commit is contained in:
parent
b581ffe323
commit
4a21b3557e
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
# """
|
# """
|
||||||
|
|
|
@ -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')
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user