mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 17:47:04 +03:00
Merge pull request #3453 from tomchristie/remove-content-overriding
Remove content overriding
This commit is contained in:
commit
bae47b7f36
|
@ -69,6 +69,16 @@ If using the `i18n_patterns` function provided by Django, as well as `format_suf
|
|||
|
||||
---
|
||||
|
||||
## Query parameter formats
|
||||
|
||||
An alternative to the format suffixes is to include the requested format in a query parameter. REST framework provides this option by default, and it is used in the browsable API to switch between differing available representations.
|
||||
|
||||
To select a representation using its short format, use the `format` query parameter. For example: `http://example.com/organizations/?format=csv`.
|
||||
|
||||
The name of this query parameter can be modified using the `URL_FORMAT_OVERRIDE` setting. Set the value to `None` to disable this behavior.
|
||||
|
||||
---
|
||||
|
||||
## Accept headers vs. format suffixes
|
||||
|
||||
There seems to be a view among some of the Web community that filename extensions are not a RESTful pattern, and that `HTTP Accept` headers should always be used instead.
|
||||
|
|
|
@ -249,47 +249,23 @@ Default:
|
|||
|
||||
---
|
||||
|
||||
## Browser overrides
|
||||
|
||||
*The following settings provide URL or form-based overrides of the default browser behavior.*
|
||||
|
||||
#### FORM_METHOD_OVERRIDE
|
||||
|
||||
The name of a form field that may be used to override the HTTP method of the form.
|
||||
|
||||
If the value of this setting is `None` then form method overloading will be disabled.
|
||||
|
||||
Default: `'_method'`
|
||||
|
||||
#### FORM_CONTENT_OVERRIDE
|
||||
|
||||
The name of a form field that may be used to override the content of the form payload. Must be used together with `FORM_CONTENTTYPE_OVERRIDE`.
|
||||
|
||||
If either setting is `None` then form content overloading will be disabled.
|
||||
|
||||
Default: `'_content'`
|
||||
|
||||
#### FORM_CONTENTTYPE_OVERRIDE
|
||||
|
||||
The name of a form field that may be used to override the content type of the form payload. Must be used together with `FORM_CONTENT_OVERRIDE`.
|
||||
|
||||
If either setting is `None` then form content overloading will be disabled.
|
||||
|
||||
Default: `'_content_type'`
|
||||
|
||||
#### URL_ACCEPT_OVERRIDE
|
||||
|
||||
The name of a URL parameter that may be used to override the HTTP `Accept` header.
|
||||
|
||||
If the value of this setting is `None` then URL accept overloading will be disabled.
|
||||
|
||||
Default: `'accept'`
|
||||
## Content type controls
|
||||
|
||||
#### URL_FORMAT_OVERRIDE
|
||||
|
||||
The name of a URL parameter that may be used to override the default `Accept` header based content negotiation.
|
||||
The name of a URL parameter that may be used to override the default content negotiation `Accept` header behavior, by using a `format=…` query parameter in the request URL.
|
||||
|
||||
If the value of this setting is `None` then URL format overloading will be disabled.
|
||||
For example: `http://example.com/organizations/?format=csv`
|
||||
|
||||
If the value of this setting is `None` then URL format overrides will be disabled.
|
||||
|
||||
Default: `'format'`
|
||||
|
||||
#### FORMAT_SUFFIX_KWARG
|
||||
|
||||
The name of a parameter in the URL conf that may be used to provide a format suffix. This setting is applied when using `format_suffix_patterns` to include suffixed URL patterns.
|
||||
|
||||
For example: `http://example.com/organizations.csv/`
|
||||
|
||||
Default: `'format'`
|
||||
|
||||
|
@ -451,12 +427,6 @@ A string representing the key that should be used for the URL fields generated b
|
|||
|
||||
Default: `'url'`
|
||||
|
||||
#### FORMAT_SUFFIX_KWARG
|
||||
|
||||
The name of a parameter in the URL conf that may be used to provide a format suffix.
|
||||
|
||||
Default: `'format'`
|
||||
|
||||
#### NUM_PROXIES
|
||||
|
||||
An integer of 0 or more, that may be used to specify the number of application proxies that the API runs behind. This allows throttling to more accurately identify client IP addresses. If set to `None` then less strict IP matching will be used by the throttle classes.
|
||||
|
|
|
@ -4,58 +4,36 @@
|
|||
>
|
||||
> — [RESTful Web Services][cite], Leonard Richardson & Sam Ruby.
|
||||
|
||||
In order to allow the browsable API to function, there are a couple of browser enhancements that REST framework needs to provide.
|
||||
|
||||
As of version 3.3.0 onwards these are enabled with javascript, using the [ajax-form][ajax-form] library.
|
||||
|
||||
## Browser based PUT, DELETE, etc...
|
||||
|
||||
REST framework supports browser-based `PUT`, `DELETE` and other methods, by
|
||||
overloading `POST` requests using a hidden form field.
|
||||
The [AJAX form library][ajax-form] supports browser-based `PUT`, `DELETE` and other methods on HTML forms.
|
||||
|
||||
Note that this is the same strategy as is used in [Ruby on Rails][rails].
|
||||
After including the library, use the `data-method` attribute on the form, like so:
|
||||
|
||||
For example, given the following form:
|
||||
|
||||
<form action="/news-items/5" method="POST">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<form action="/" data-method="PUT">
|
||||
<input name='foo'/>
|
||||
...
|
||||
</form>
|
||||
|
||||
`request.method` would return `"DELETE"`.
|
||||
|
||||
## HTTP header based method overriding
|
||||
|
||||
REST framework also supports method overriding via the semi-standard `X-HTTP-Method-Override` header. This can be useful if you are working with non-form content such as JSON and are working with an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as `PATCH`. For example [Amazon Web Services ELB][aws_elb].
|
||||
|
||||
To use it, make a `POST` request, setting the `X-HTTP-Method-Override` header.
|
||||
|
||||
For example, making a `PATCH` request via `POST` in jQuery:
|
||||
|
||||
$.ajax({
|
||||
url: '/myresource/',
|
||||
method: 'POST',
|
||||
headers: {'X-HTTP-Method-Override': 'PATCH'},
|
||||
...
|
||||
});
|
||||
Note that prior to 3.3.0, this support was server-side rather than javascript based. The method overloading style (as used in [Ruby on Rails][rails]) is no longer supported due to subtle issues that it introduces in request parsing.
|
||||
|
||||
## Browser based submission of non-form content
|
||||
|
||||
Browser-based submission of content types other than form are supported by
|
||||
using form fields named `_content` and `_content_type`:
|
||||
Browser-based submission of content types such as JSON are supported by the [AJAX form library][ajax-form], using form fields with `data-override='content-type'` and `data-override='content'` attributes.
|
||||
|
||||
For example, given the following form:
|
||||
For example:
|
||||
|
||||
<form action="/news-items/5" method="PUT">
|
||||
<input type="hidden" name="_content_type" value="application/json">
|
||||
<input name="_content" value="{'count': 1}">
|
||||
</form>
|
||||
<form action="/">
|
||||
<input data-override='content-type' value='application/json' type='hidden'/>
|
||||
<textarea data-override='content'>{}</textarea>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
|
||||
`request.content_type` would return `"application/json"`, and
|
||||
`request.stream` would return `"{'count': 1}"`
|
||||
|
||||
## URL based accept headers
|
||||
|
||||
REST framework can take `?accept=application/json` style URL parameters,
|
||||
which allow the `Accept` header to be overridden.
|
||||
|
||||
This can be useful for testing the API from a web browser, where you don't
|
||||
have any control over what is sent in the `Accept` header.
|
||||
Note that prior to 3.3.0, this support was server-side rather than javascript based.
|
||||
|
||||
## URL based format suffixes
|
||||
|
||||
|
@ -63,8 +41,37 @@ REST framework can take `?format=json` style URL parameters, which can be a
|
|||
useful shortcut for determining which content type should be returned from
|
||||
the view.
|
||||
|
||||
This is a more concise than using the `accept` override, but it also gives
|
||||
you less control. (For example you can't specify any media type parameters)
|
||||
This behavior is controlled using the `URL_FORMAT_OVERRIDE` setting.
|
||||
|
||||
## HTTP header based method overriding
|
||||
|
||||
Prior to version 3.3.0 the semi extension header `X-HTTP-Method-Override` was supported for overriding the request method. This behavior is no longer in core, but can be adding if needed using middleware.
|
||||
|
||||
For example:
|
||||
|
||||
METHOD_OVERRIDE_HEADER = 'HTTP_X_HTTP_METHOD_OVERRIDE'
|
||||
|
||||
class MethodOverrideMiddleware(object):
|
||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||
if request.method != 'POST':
|
||||
return
|
||||
if METHOD_OVERRIDE_HEADER not in request.META:
|
||||
return
|
||||
request.method = request.META[METHOD_OVERRIDE_HEADER]
|
||||
|
||||
## URL based accept headers
|
||||
|
||||
Until version 3.3.0 REST framework included built-in support for `?accept=application/json` style URL parameters, which would allow the `Accept` header to be overridden.
|
||||
|
||||
Since the introduction of the content negotiation API this behavior is no longer included in core, but may be added using a custom content negotiation class, if needed.
|
||||
|
||||
For example:
|
||||
|
||||
class AcceptQueryParamOverride()
|
||||
def get_accept_list(self, request):
|
||||
header = request.META.get('HTTP_ACCEPT', '*/*')
|
||||
header = request.query_params.get('_accept', header)
|
||||
return [token.strip() for token in header.split(',')]
|
||||
|
||||
## Doesn't HTML5 support PUT and DELETE forms?
|
||||
|
||||
|
@ -74,7 +81,7 @@ was later [dropped from the spec][html5]. There remains
|
|||
as well as how to support content types other than form-encoded data.
|
||||
|
||||
[cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
|
||||
[ajax-form]: https://github.com/tomchristie/ajax-form
|
||||
[rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
|
||||
[html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24
|
||||
[put_delete]: http://amundsen.com/examples/put-delete-forms/
|
||||
[aws_elb]: https://forums.aws.amazon.com/thread.jspa?messageID=400724
|
||||
|
|
|
@ -92,9 +92,6 @@ class DefaultContentNegotiation(BaseContentNegotiation):
|
|||
"""
|
||||
Given the incoming request, return a tokenised list of media
|
||||
type strings.
|
||||
|
||||
Allows URL style accept override. eg. "?accept=application/json"
|
||||
"""
|
||||
header = request.META.get('HTTP_ACCEPT', '*/*')
|
||||
header = request.query_params.get(self.settings.URL_ACCEPT_OVERRIDE, header)
|
||||
return [token.strip() for token in header.split(',')]
|
||||
|
|
|
@ -420,9 +420,6 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
if method not in view.allowed_methods:
|
||||
return # Not a valid method
|
||||
|
||||
if not api_settings.FORM_METHOD_OVERRIDE:
|
||||
return # Cannot use form overloading
|
||||
|
||||
try:
|
||||
view.check_permissions(request)
|
||||
if obj is not None:
|
||||
|
@ -530,13 +527,6 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
instance = None
|
||||
|
||||
with override_method(view, request, method) as request:
|
||||
# If we're not using content overloading there's no point in
|
||||
# supplying a generic form, as the view won't treat the form's
|
||||
# value as the content of the request.
|
||||
if not (api_settings.FORM_CONTENT_OVERRIDE and
|
||||
api_settings.FORM_CONTENTTYPE_OVERRIDE):
|
||||
return None
|
||||
|
||||
# Check permissions
|
||||
if not self.show_form_for_method(view, method, request, instance):
|
||||
return
|
||||
|
@ -564,28 +554,22 @@ class BrowsableAPIRenderer(BaseRenderer):
|
|||
|
||||
# Generate a generic form that includes a content type field,
|
||||
# and a content field.
|
||||
content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE
|
||||
content_field = api_settings.FORM_CONTENT_OVERRIDE
|
||||
|
||||
media_types = [parser.media_type for parser in view.parser_classes]
|
||||
choices = [(media_type, media_type) for media_type in media_types]
|
||||
initial = media_types[0]
|
||||
|
||||
# NB. http://jacobian.org/writing/dynamic-form-generation/
|
||||
class GenericContentForm(forms.Form):
|
||||
def __init__(self):
|
||||
super(GenericContentForm, self).__init__()
|
||||
|
||||
self.fields[content_type_field] = forms.ChoiceField(
|
||||
label='Media type',
|
||||
choices=choices,
|
||||
initial=initial
|
||||
)
|
||||
self.fields[content_field] = forms.CharField(
|
||||
label='Content',
|
||||
widget=forms.Textarea,
|
||||
initial=content
|
||||
)
|
||||
_content_type = forms.ChoiceField(
|
||||
label='Media type',
|
||||
choices=choices,
|
||||
initial=initial,
|
||||
widget=forms.Select(attrs={'data-override': 'content-type'})
|
||||
)
|
||||
_content = forms.CharField(
|
||||
label='Content',
|
||||
widget=forms.Textarea(attrs={'data-override': 'content'}),
|
||||
initial=content
|
||||
)
|
||||
|
||||
return GenericContentForm()
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ def clone_request(request, method):
|
|||
ret._full_data = request._full_data
|
||||
ret._content_type = request._content_type
|
||||
ret._stream = request._stream
|
||||
ret._method = method
|
||||
ret.method = method
|
||||
if hasattr(request, '_user'):
|
||||
ret._user = request._user
|
||||
if hasattr(request, '_auth'):
|
||||
|
@ -129,11 +129,6 @@ class Request(object):
|
|||
- authentication_classes(list/tuple). The authentications used to try
|
||||
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,
|
||||
negotiator=None, parser_context=None):
|
||||
self._request = request
|
||||
|
@ -144,7 +139,6 @@ class Request(object):
|
|||
self._data = Empty
|
||||
self._files = Empty
|
||||
self._full_data = Empty
|
||||
self._method = Empty
|
||||
self._content_type = Empty
|
||||
self._stream = Empty
|
||||
|
||||
|
@ -162,30 +156,10 @@ class Request(object):
|
|||
def _default_negotiator(self):
|
||||
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
|
||||
def content_type(self):
|
||||
"""
|
||||
Returns the content type header.
|
||||
|
||||
This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`,
|
||||
as it allows the content type to be overridden by using a hidden form
|
||||
field on a form POST request.
|
||||
"""
|
||||
if not _hasattr(self, '_content_type'):
|
||||
self._load_method_and_content_type()
|
||||
return self._content_type
|
||||
meta = self._request.META
|
||||
return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
|
@ -265,9 +239,6 @@ class Request(object):
|
|||
"""
|
||||
Parses the request content into `self.data`.
|
||||
"""
|
||||
if not _hasattr(self, '_content_type'):
|
||||
self._load_method_and_content_type()
|
||||
|
||||
if not _hasattr(self, '_data'):
|
||||
self._data, self._files = self._parse()
|
||||
if self._files:
|
||||
|
@ -276,32 +247,14 @@ class Request(object):
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
Return the content body of the request, as a stream.
|
||||
"""
|
||||
meta = self._request.META
|
||||
try:
|
||||
content_length = int(
|
||||
self.META.get(
|
||||
'CONTENT_LENGTH', self.META.get('HTTP_CONTENT_LENGTH')
|
||||
)
|
||||
meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
content_length = 0
|
||||
|
@ -313,50 +266,6 @@ class Request(object):
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
Parse the request content, returning a two-tuple of (data, files)
|
||||
|
|
|
@ -22,7 +22,6 @@ def preserve_builtin_query_params(url, request=None):
|
|||
|
||||
overrides = [
|
||||
api_settings.URL_FORMAT_OVERRIDE,
|
||||
api_settings.URL_ACCEPT_OVERRIDE
|
||||
]
|
||||
|
||||
for param in overrides:
|
||||
|
|
|
@ -91,13 +91,8 @@ DEFAULTS = {
|
|||
),
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',
|
||||
|
||||
# Browser enhancements
|
||||
'FORM_METHOD_OVERRIDE': '_method',
|
||||
'FORM_CONTENT_OVERRIDE': '_content',
|
||||
'FORM_CONTENTTYPE_OVERRIDE': '_content_type',
|
||||
'URL_ACCEPT_OVERRIDE': 'accept',
|
||||
# Hyperlink settings
|
||||
'URL_FORMAT_OVERRIDE': 'format',
|
||||
|
||||
'FORMAT_SUFFIX_KWARG': 'format',
|
||||
'URL_FIELD_NAME': 'url',
|
||||
|
||||
|
|
97
rest_framework/static/rest_framework/js/ajax-form.js
Normal file
97
rest_framework/static/rest_framework/js/ajax-form.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
function replaceDocument(docString) {
|
||||
var doc = document.open("text/html");
|
||||
doc.write(docString);
|
||||
doc.close();
|
||||
}
|
||||
|
||||
|
||||
function doAjaxSubmit(e) {
|
||||
var form = $(this);
|
||||
var btn = $(this.clk);
|
||||
var method = btn.data('method') || form.data('method') || form.attr('method') || 'GET';
|
||||
method = method.toUpperCase()
|
||||
if (method === 'GET') {
|
||||
// GET requests can always use standard form submits.
|
||||
return;
|
||||
}
|
||||
|
||||
var contentType =
|
||||
form.find('input[data-override="content-type"]').val() ||
|
||||
form.find('select[data-override="content-type"] option:selected').text();
|
||||
if (method === 'POST' && !contentType) {
|
||||
// POST requests can use standard form submits, unless we have
|
||||
// overridden the content type.
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point we need to make an AJAX form submission.
|
||||
e.preventDefault();
|
||||
|
||||
var url = form.attr('action');
|
||||
var data;
|
||||
if (contentType) {
|
||||
data = form.find('[data-override="content"]').val() || ''
|
||||
} else {
|
||||
contentType = form.attr('enctype') || form.attr('encoding')
|
||||
if (contentType === 'multipart/form-data') {
|
||||
if (!window.FormData) {
|
||||
alert('Your browser does not support AJAX multipart form submissions');
|
||||
return;
|
||||
}
|
||||
// Use the FormData API and allow the content type to be set automatically,
|
||||
// so it includes the boundary string.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
|
||||
contentType = false;
|
||||
data = new FormData(form[0]);
|
||||
} else {
|
||||
contentType = 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
data = form.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
var ret = $.ajax({
|
||||
url: url,
|
||||
method: method,
|
||||
data: data,
|
||||
contentType: contentType,
|
||||
processData: false,
|
||||
headers: {'Accept': 'text/html; q=1.0, */*'},
|
||||
});
|
||||
ret.always(function(data, textStatus, jqXHR) {
|
||||
if (textStatus != 'success') {
|
||||
jqXHR = data;
|
||||
}
|
||||
var responseContentType = jqXHR.getResponseHeader("content-type") || "";
|
||||
if (responseContentType.toLowerCase().indexOf('text/html') === 0) {
|
||||
replaceDocument(jqXHR.responseText);
|
||||
try {
|
||||
// Modify the location and scroll to top, as if after page load.
|
||||
history.replaceState({}, '', url);
|
||||
scroll(0,0);
|
||||
} catch(err) {
|
||||
// History API not supported, so redirect.
|
||||
window.location = url;
|
||||
}
|
||||
} else {
|
||||
// Not HTML content. We can't open this directly, so redirect.
|
||||
window.location = url;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function captureSubmittingElement(e) {
|
||||
var target = e.target;
|
||||
var form = this;
|
||||
form.clk = target;
|
||||
}
|
||||
|
||||
|
||||
$.fn.ajaxForm = function() {
|
||||
var options = {}
|
||||
return this
|
||||
.unbind('submit.form-plugin click.form-plugin')
|
||||
.bind('submit.form-plugin', options, doAjaxSubmit)
|
||||
.bind('click.form-plugin', options, captureSubmittingElement);
|
||||
};
|
47
rest_framework/static/rest_framework/js/csrf.js
Normal file
47
rest_framework/static/rest_framework/js/csrf.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
||||
|
||||
function sameOrigin(url) {
|
||||
// test that a given url is a same-origin URL
|
||||
// url could be relative or scheme relative or absolute
|
||||
var host = document.location.host; // host + port
|
||||
var protocol = document.location.protocol;
|
||||
var sr_origin = '//' + host;
|
||||
var origin = protocol + sr_origin;
|
||||
// Allow absolute or scheme relative URLs to same origin
|
||||
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
|
||||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
|
||||
// or any other URL that isn't scheme relative or absolute i.e relative.
|
||||
!(/^(\/\/|http:|https:).*/.test(url));
|
||||
}
|
||||
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
|
||||
// Send the token to same-origin, relative URLs only.
|
||||
// Send the token only if the method warrants CSRF protection
|
||||
// Using the CSRFToken value acquired earlier
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
});
|
5
rest_framework/static/rest_framework/js/jquery-1.11.3.min.js
vendored
Normal file
5
rest_framework/static/rest_framework/js/jquery-1.11.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -104,9 +104,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if delete_form %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
||||
<button class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete
|
||||
</button>
|
||||
|
@ -180,7 +178,7 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Edit</h4>
|
||||
</div>
|
||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
{{ put_form }}
|
||||
|
@ -188,7 +186,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -204,7 +202,7 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">{{ error_title }}</h4>
|
||||
</div>
|
||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<form action="{{ request.get_full_path }}" data-method="{{ request.method }}" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
{{ error_form }}
|
||||
|
@ -212,7 +210,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="{{ request.method }}" type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -221,10 +219,17 @@
|
|||
{% endif %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/jquery-1.11.3-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/default.js" %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('form').ajaxForm();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
|
|
@ -94,17 +94,13 @@
|
|||
{% endif %}
|
||||
|
||||
{% if options_form %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="OPTIONS">
|
||||
<button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if delete_form %}
|
||||
<form class="button-form" action="{{ request.get_full_path }}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />
|
||||
<form class="button-form" action="{{ request.get_full_path }}" data-method="DELETE">
|
||||
<button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
@ -168,7 +164,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div {% if post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
|
||||
<div {% if raw_data_post_form %}class="tab-pane"{% endif %} id="post-generic-content-form">
|
||||
{% with form=raw_data_post_form %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
||||
<fieldset>
|
||||
|
@ -200,11 +196,11 @@
|
|||
<div class="well tab-content">
|
||||
{% if put_form %}
|
||||
<div class="tab-pane" id="put-object-form">
|
||||
<form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<form action="{{ request.get_full_path }}" data-method="PUT" enctype="multipart/form-data" class="form-horizontal" novalidate>
|
||||
<fieldset>
|
||||
{{ put_form }}
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
@ -213,15 +209,15 @@
|
|||
|
||||
<div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form">
|
||||
{% with form=raw_data_put_or_patch_form %}
|
||||
<form action="{{ request.get_full_path }}" method="POST" class="form-horizontal">
|
||||
<form action="{{ request.get_full_path }}" data-method="PUT" class="form-horizontal">
|
||||
<fieldset>
|
||||
{% include "rest_framework/raw_data_form.html" %}
|
||||
<div class="form-actions">
|
||||
{% if raw_data_put_form %}
|
||||
<button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
<button class="btn btn-primary js-tooltip" title="Make a PUT request on the {{ name }} resource">PUT</button>
|
||||
{% endif %}
|
||||
{% if raw_data_patch_form %}
|
||||
<button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PATCH" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
|
||||
<button data-method="PATCH" class="btn btn-primary js-tooltip" title="Make a PATCH request on the {{ name }} resource">PATCH</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -237,10 +233,17 @@
|
|||
</div><!-- ./wrapper -->
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/jquery-1.11.3.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/ajax-form.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/csrf.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/prettify-min.js" %}"></script>
|
||||
<script src="{% static "rest_framework/js/default.js" %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('form').ajaxForm();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{% load rest_framework %}
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for field in form %}
|
||||
<div class="form-group">
|
||||
|
|
|
@ -191,17 +191,6 @@ class RendererEndToEndTests(TestCase):
|
|||
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEqual(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_accept_query(self):
|
||||
"""The '_accept' query string should behave in the same way as the Accept header."""
|
||||
param = '?%s=%s' % (
|
||||
api_settings.URL_ACCEPT_OVERRIDE,
|
||||
RendererB.media_type
|
||||
)
|
||||
resp = self.client.get('/' + param)
|
||||
self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
|
||||
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEqual(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
|
||||
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||
|
|
|
@ -3,27 +3,20 @@ Tests for content parsing, and form-overloaded content parsing.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
import django
|
||||
import pytest
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.parsers import (
|
||||
BaseParser, FormParser, JSONParser, MultiPartParser
|
||||
)
|
||||
from rest_framework.request import Empty, Request
|
||||
from rest_framework.parsers import BaseParser, FormParser, MultiPartParser
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.test import APIClient, APIRequestFactory
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
@ -43,36 +36,6 @@ class PlainTextParser(BaseParser):
|
|||
return stream.read()
|
||||
|
||||
|
||||
class TestMethodOverloading(TestCase):
|
||||
def test_method(self):
|
||||
"""
|
||||
Request methods should be same as underlying request.
|
||||
"""
|
||||
request = Request(factory.get('/'))
|
||||
self.assertEqual(request.method, 'GET')
|
||||
request = Request(factory.post('/'))
|
||||
self.assertEqual(request.method, 'POST')
|
||||
|
||||
def test_overloaded_method(self):
|
||||
"""
|
||||
POST requests can be overloaded to another method by setting a
|
||||
reserved form field
|
||||
"""
|
||||
request = Request(factory.post('/', {api_settings.FORM_METHOD_OVERRIDE: 'DELETE'}))
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
def test_x_http_method_override_header(self):
|
||||
"""
|
||||
POST requests can also be overloaded to another method by setting
|
||||
the X-HTTP-Method-Override header.
|
||||
"""
|
||||
request = Request(factory.post('/', {'foo': 'bar'}, HTTP_X_HTTP_METHOD_OVERRIDE='DELETE'))
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
request = Request(factory.get('/', {'foo': 'bar'}, HTTP_X_HTTP_METHOD_OVERRIDE='DELETE'))
|
||||
self.assertEqual(request.method, 'DELETE')
|
||||
|
||||
|
||||
class TestContentParsing(TestCase):
|
||||
def test_standard_behaviour_determines_no_content_GET(self):
|
||||
"""
|
||||
|
@ -137,49 +100,6 @@ class TestContentParsing(TestCase):
|
|||
request.parsers = (PlainTextParser(), )
|
||||
self.assertEqual(request.data, content)
|
||||
|
||||
def test_overloaded_behaviour_allows_content_tunnelling(self):
|
||||
"""
|
||||
Ensure request.data returns content for overloaded POST request.
|
||||
"""
|
||||
json_data = {'foobar': 'qwerty'}
|
||||
content = json.dumps(json_data)
|
||||
content_type = 'application/json'
|
||||
form_data = {
|
||||
api_settings.FORM_CONTENT_OVERRIDE: content,
|
||||
api_settings.FORM_CONTENTTYPE_OVERRIDE: content_type
|
||||
}
|
||||
request = Request(factory.post('/', form_data))
|
||||
request.parsers = (JSONParser(), )
|
||||
self.assertEqual(request.data, json_data)
|
||||
|
||||
def test_form_POST_unicode(self):
|
||||
"""
|
||||
JSON POST via default web interface with unicode data
|
||||
"""
|
||||
# Note: environ and other variables here have simplified content compared to real Request
|
||||
CONTENT = b'_content_type=application%2Fjson&_content=%7B%22request%22%3A+4%2C+%22firm%22%3A+1%2C+%22text%22%3A+%22%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%21%22%7D'
|
||||
environ = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
|
||||
'CONTENT_LENGTH': len(CONTENT),
|
||||
'wsgi.input': BytesIO(CONTENT),
|
||||
}
|
||||
wsgi_request = WSGIRequest(environ=environ)
|
||||
wsgi_request._load_post_and_files()
|
||||
parsers = (JSONParser(), FormParser(), MultiPartParser())
|
||||
parser_context = {
|
||||
'encoding': 'utf-8',
|
||||
'kwargs': {},
|
||||
'args': (),
|
||||
}
|
||||
request = Request(wsgi_request, parsers=parsers, parser_context=parser_context)
|
||||
method = request.method
|
||||
self.assertEqual(method, 'POST')
|
||||
self.assertEqual(request._content_type, 'application/json')
|
||||
self.assertEqual(request._stream.getvalue(), b'{"request": 4, "firm": 1, "text": "\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82!"}')
|
||||
self.assertEqual(request._data, Empty)
|
||||
self.assertEqual(request._files, Empty)
|
||||
|
||||
|
||||
class MockView(APIView):
|
||||
authentication_classes = (SessionAuthentication,)
|
||||
|
|
|
@ -5,11 +5,11 @@ from django.test import TestCase
|
|||
from django.utils import six
|
||||
|
||||
from rest_framework import generics, routers, serializers, status, viewsets
|
||||
from rest_framework.parsers import JSONParser
|
||||
from rest_framework.renderers import (
|
||||
BaseRenderer, BrowsableAPIRenderer, JSONRenderer
|
||||
)
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
from tests.models import BasicModel
|
||||
|
||||
|
@ -79,6 +79,14 @@ class MockViewSettingContentType(APIView):
|
|||
return Response(DUMMYCONTENT, status=DUMMYSTATUS, content_type='setbyview')
|
||||
|
||||
|
||||
class JSONView(APIView):
|
||||
parser_classes = (JSONParser,)
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
assert request.data
|
||||
return Response(DUMMYCONTENT)
|
||||
|
||||
|
||||
class HTMLView(APIView):
|
||||
renderer_classes = (BrowsableAPIRenderer, )
|
||||
|
||||
|
@ -114,6 +122,7 @@ urlpatterns = [
|
|||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^json$', JSONView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
url(r'^html_new_model$', HTMLNewModelView.as_view()),
|
||||
url(r'^html_new_model_viewset', include(new_model_viewset_router.urls)),
|
||||
|
@ -166,17 +175,6 @@ class RendererIntegrationTests(TestCase):
|
|||
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEqual(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_accept_query(self):
|
||||
"""The '_accept' query string should behave in the same way as the Accept header."""
|
||||
param = '?%s=%s' % (
|
||||
api_settings.URL_ACCEPT_OVERRIDE,
|
||||
RendererB.media_type
|
||||
)
|
||||
resp = self.client.get('/' + param)
|
||||
self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
|
||||
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEqual(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_format_query(self):
|
||||
"""If a 'format' query is specified, the renderer with the matching
|
||||
format attribute should serialize the response."""
|
||||
|
@ -203,6 +201,25 @@ class RendererIntegrationTests(TestCase):
|
|||
self.assertEqual(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
|
||||
class UnsupportedMediaTypeTests(TestCase):
|
||||
urls = 'tests.test_response'
|
||||
|
||||
def test_should_allow_posting_json(self):
|
||||
response = self.client.post('/json', data='{"test": 123}', content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_should_not_allow_posting_xml(self):
|
||||
response = self.client.post('/json', data='<test>123</test>', content_type='application/xml')
|
||||
|
||||
self.assertEqual(response.status_code, 415)
|
||||
|
||||
def test_should_not_allow_posting_a_form(self):
|
||||
response = self.client.post('/json', data={'test': 123})
|
||||
|
||||
self.assertEqual(response.status_code, 415)
|
||||
|
||||
|
||||
class Issue122Tests(TestCase):
|
||||
"""
|
||||
Tests that covers #122.
|
||||
|
@ -270,16 +287,6 @@ class Issue807Tests(TestCase):
|
|||
resp = self.client.get('/setbyview', **headers)
|
||||
self.assertEqual('setbyview', resp['Content-Type'])
|
||||
|
||||
def test_viewset_label_help_text(self):
|
||||
param = '?%s=%s' % (
|
||||
api_settings.URL_ACCEPT_OVERRIDE,
|
||||
'text/html'
|
||||
)
|
||||
resp = self.client.get('/html_new_model_viewset/' + param)
|
||||
self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
|
||||
# self.assertContains(resp, 'Text comes here')
|
||||
# self.assertContains(resp, 'Text description.')
|
||||
|
||||
def test_form_has_label_and_help_text(self):
|
||||
resp = self.client.get('/html_new_model')
|
||||
self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
|
||||
|
|
|
@ -74,21 +74,6 @@ class ClassBasedViewIntegrationTests(TestCase):
|
|||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(sanitise_json_error(response.data), expected)
|
||||
|
||||
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': JSON_ERROR
|
||||
}
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(sanitise_json_error(response.data), expected)
|
||||
|
||||
|
||||
class FunctionBasedViewIntegrationTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -103,21 +88,6 @@ class FunctionBasedViewIntegrationTests(TestCase):
|
|||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(sanitise_json_error(response.data), expected)
|
||||
|
||||
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': JSON_ERROR
|
||||
}
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(sanitise_json_error(response.data), expected)
|
||||
|
||||
|
||||
class TestCustomExceptionHandler(TestCase):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue
Block a user