Merge remote-tracking branch 'upstream/master' into schema_multiple_http_methods_per_action

This commit is contained in:
Nik 2016-08-24 18:23:12 +03:00
commit ad3eec9afa
17 changed files with 114 additions and 23 deletions

View File

@ -169,7 +169,7 @@ You may also want to [follow the author on Twitter][twitter].
# Security # Security
If you believe youve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.

View File

@ -261,6 +261,10 @@ The [REST Condition][rest-condition] package is another extension for building c
The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrive per user. The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrive per user.
## Django Rest Framework Roles
The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users.
[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
[authentication]: authentication.md [authentication]: authentication.md
[throttling]: throttling.md [throttling]: throttling.md
@ -275,3 +279,4 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to
[composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions [composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions
[rest-condition]: https://github.com/caxap/rest_condition [rest-condition]: https://github.com/caxap/rest_condition
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions [dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles

View File

@ -40,6 +40,27 @@ You can determine your currently installed version using `pip freeze`:
## 3.4.x series ## 3.4.x series
### 3.4.6
**Date**: [23rd August 2016][3.4.6-milestone]
* Fix malformed Javascript in browsable API. ([#4435][gh4435])
* Skip HiddenField from Schema fields. ([#4425][gh4425], [#4429][gh4429])
* Improve Create to show the original exception traceback. ([#3508][gh3508])
* Fix `AdminRenderer` display of PK only related fields. ([#4419][gh4419], [#4423][gh4423])
### 3.4.5
**Date**: [19th August 2016][3.4.5-milestone]
* Improve debug error handling. ([#4416][gh4416], [#4409][gh4409])
* Allow custom CSRF_HEADER_NAME setting. ([#4415][gh4415], [#4410][gh4410])
* Include .action attribute on viewsets when generating schemas. ([#4408][gh4408], [#4398][gh4398])
* Do not include request.FILES items in request.POST. ([#4407][gh4407])
* Fix rendering of checkbox multiple. ([#4403][gh4403])
* Fix docstring of Field.get_default. ([#4404][gh4404])
* Replace utf8 character with its ascii counterpart in README. ([#4412][gh4412])
### 3.4.4 ### 3.4.4
**Date**: [12th August 2016][3.4.4-milestone] **Date**: [12th August 2016][3.4.4-milestone]
@ -560,6 +581,8 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.4.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.2+Release%22 [3.4.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.2+Release%22
[3.4.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.3+Release%22 [3.4.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.3+Release%22
[3.4.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.4+Release%22 [3.4.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.4+Release%22
[3.4.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.5+Release%22
[3.4.6-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.6+Release%22
<!-- 3.0.1 --> <!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013 [gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@ -1065,3 +1088,24 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh4392]: https://github.com/tomchristie/django-rest-framework/issues/4392 [gh4392]: https://github.com/tomchristie/django-rest-framework/issues/4392
[gh4393]: https://github.com/tomchristie/django-rest-framework/issues/4393 [gh4393]: https://github.com/tomchristie/django-rest-framework/issues/4393
[gh4394]: https://github.com/tomchristie/django-rest-framework/issues/4394 [gh4394]: https://github.com/tomchristie/django-rest-framework/issues/4394
<!-- 3.4.5 -->
[gh4416]: https://github.com/tomchristie/django-rest-framework/issues/4416
[gh4409]: https://github.com/tomchristie/django-rest-framework/issues/4409
[gh4415]: https://github.com/tomchristie/django-rest-framework/issues/4415
[gh4410]: https://github.com/tomchristie/django-rest-framework/issues/4410
[gh4408]: https://github.com/tomchristie/django-rest-framework/issues/4408
[gh4398]: https://github.com/tomchristie/django-rest-framework/issues/4398
[gh4407]: https://github.com/tomchristie/django-rest-framework/issues/4407
[gh4403]: https://github.com/tomchristie/django-rest-framework/issues/4403
[gh4404]: https://github.com/tomchristie/django-rest-framework/issues/4404
[gh4412]: https://github.com/tomchristie/django-rest-framework/issues/4412
<!-- 3.4.6 -->
[gh4435]: https://github.com/tomchristie/django-rest-framework/issues/4435
[gh4425]: https://github.com/tomchristie/django-rest-framework/issues/4425
[gh4429]: https://github.com/tomchristie/django-rest-framework/issues/4429
[gh3508]: https://github.com/tomchristie/django-rest-framework/issues/3508
[gh4419]: https://github.com/tomchristie/django-rest-framework/issues/4419
[gh4423]: https://github.com/tomchristie/django-rest-framework/issues/4423

View File

@ -8,7 +8,7 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.4.4' __version__ = '3.4.6'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2016 Tom Christie' __copyright__ = 'Copyright 2011-2016 Tom Christie'

View File

@ -432,7 +432,7 @@ class Field(object):
is provided for this field. is provided for this field.
If a default has not been set for this field then this will simply If a default has not been set for this field then this will simply
return `empty`, indicating that no value should be set in the raise `SkipField`, indicating that no value should be set in the
validated data for this field. validated data for this field.
""" """
if self.default is empty or getattr(self.root, 'partial', False): if self.default is empty or getattr(self.root, 'partial', False):

View File

@ -10,7 +10,7 @@ from django.core.urlresolvers import (
from django.db.models import Manager from django.db.models import Manager
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils import six from django.utils import six
from django.utils.encoding import smart_text from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.six.moves.urllib import parse as urlparse from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -47,6 +47,7 @@ class Hyperlink(six.text_type):
is_hyperlink = True is_hyperlink = True
@python_2_unicode_compatible
class PKOnlyObject(object): class PKOnlyObject(object):
""" """
This is a mock object, used for when we only need the pk of the object This is a mock object, used for when we only need the pk of the object
@ -56,6 +57,9 @@ class PKOnlyObject(object):
def __init__(self, pk): def __init__(self, pk):
self.pk = pk self.pk = pk
def __str__(self):
return "%s" % self.pk
# We assume that 'validators' are intended for the child serializer, # We assume that 'validators' are intended for the child serializer,
# rather than the parent serializer. # rather than the parent serializer.

View File

@ -645,6 +645,12 @@ class BrowsableAPIRenderer(BaseRenderer):
else: else:
paginator = None paginator = None
csrf_cookie_name = settings.CSRF_COOKIE_NAME
csrf_header_name = getattr(settings, 'CSRF_HEADER_NAME', 'HTTP_X_CSRFToken') # Fallback for Django 1.8
if csrf_header_name.startswith('HTTP_'):
csrf_header_name = csrf_header_name[5:]
csrf_header_name = csrf_header_name.replace('_', '-')
context = { context = {
'content': self.get_content(renderer, data, accepted_media_type, renderer_context), 'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
'view': view, 'view': view,
@ -675,7 +681,8 @@ class BrowsableAPIRenderer(BaseRenderer):
'display_edit_forms': bool(response.status_code != 403), 'display_edit_forms': bool(response.status_code != 403),
'api_settings': api_settings, 'api_settings': api_settings,
'csrf_cookie_name': settings.CSRF_COOKIE_NAME, 'csrf_cookie_name': csrf_cookie_name,
'csrf_header_name': csrf_header_name
} }
return context return context

View File

@ -373,7 +373,7 @@ class Request(object):
if not _hasattr(self, '_data'): if not _hasattr(self, '_data'):
self._load_data_and_files() self._load_data_and_files()
if is_form_media_type(self.content_type): if is_form_media_type(self.content_type):
return self.data return self._data
return QueryDict('', encoding=self._request._encoding) return QueryDict('', encoding=self._request._encoding)
@property @property
@ -391,3 +391,8 @@ class Request(object):
'`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` ' '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
'since version 3.0, and has been fully removed as of version 3.2.' 'since version 3.0, and has been fully removed as of version 3.2.'
) )
def force_plaintext_errors(self, value):
# Hack to allow our exception handler to force choice of
# plaintext or html error responses.
self._request.is_ajax = lambda: value

View File

@ -79,6 +79,13 @@ class SchemaGenerator(object):
view.kwargs = {} view.kwargs = {}
view.format_kwarg = None view.format_kwarg = None
actions = getattr(callback, 'actions', None)
if actions is not None:
if method == 'OPTIONS':
view.action = 'metadata'
else:
view.action = actions.get(method.lower())
if request is not None: if request is not None:
view.request = clone_request(request, method) view.request = clone_request(request, method)
try: try:
@ -289,8 +296,9 @@ class SchemaGenerator(object):
fields = [] fields = []
for field in serializer.fields.values(): for field in serializer.fields.values():
if field.read_only: if field.read_only or isinstance(field, serializers.HiddenField):
continue continue
required = field.required and method != 'PATCH' required = field.required and method != 'PATCH'
description = force_text(field.help_text) if field.help_text else '' description = force_text(field.help_text) if field.help_text else ''
field = coreapi.Field( field = coreapi.Field(

View File

@ -12,6 +12,7 @@ response content is handled by parsers and renderers.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import traceback
import warnings import warnings
from django.db import models from django.db import models
@ -870,19 +871,20 @@ class ModelSerializer(Serializer):
try: try:
instance = ModelClass.objects.create(**validated_data) instance = ModelClass.objects.create(**validated_data)
except TypeError as exc: except TypeError:
tb = traceback.format_exc()
msg = ( msg = (
'Got a `TypeError` when calling `%s.objects.create()`. ' 'Got a `TypeError` when calling `%s.objects.create()`. '
'This may be because you have a writable field on the ' 'This may be because you have a writable field on the '
'serializer class that is not a valid argument to ' 'serializer class that is not a valid argument to '
'`%s.objects.create()`. You may need to make the field ' '`%s.objects.create()`. You may need to make the field '
'read-only, or override the %s.create() method to handle ' 'read-only, or override the %s.create() method to handle '
'this correctly.\nOriginal exception text was: %s.' % 'this correctly.\nOriginal exception was:\n %s' %
( (
ModelClass.__name__, ModelClass.__name__,
ModelClass.__name__, ModelClass.__name__,
self.__class__.__name__, self.__class__.__name__,
exc tb
) )
) )
raise TypeError(msg) raise TypeError(msg)

View File

@ -46,7 +46,7 @@ $.ajaxSetup({
// Send the token to same-origin, relative URLs only. // Send the token to same-origin, relative URLs only.
// Send the token only if the method warrants CSRF protection // Send the token only if the method warrants CSRF protection
// Using the CSRFToken value acquired earlier // Using the CSRFToken value acquired earlier
xhr.setRequestHeader("X-CSRFToken", csrftoken); xhr.setRequestHeader(window.drf.csrfHeaderName, csrftoken);
} }
} }
}); });

View File

@ -232,6 +232,7 @@
{% block script %} {% block script %}
<script> <script>
window.drf = { window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}"
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}" csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
}; };
</script> </script>

View File

@ -263,6 +263,7 @@
{% block script %} {% block script %}
<script> <script>
window.drf = { window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}" csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
}; };
</script> </script>

View File

@ -9,7 +9,7 @@
<div> <div>
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<label class="checkbox-inline"> <label class="checkbox-inline">
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_stringsg %}checked{% endif %}> <input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_strings %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
{% endfor %} {% endfor %}
@ -18,7 +18,7 @@
{% for key, text in field.choices.items %} {% for key, text in field.choices.items %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_stringsg %}checked{% endif %}> <input type="checkbox" name="{{ field.name }}" value="{{ key }}" {% if key|as_string in field.value|as_list_of_strings %}checked{% endif %}>
{{ text }} {{ text }}
</label> </label>
</div> </div>

View File

@ -3,17 +3,14 @@ Provides an APIView class that is the base of all views in REST framework.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import models from django.db import models
from django.http import Http404 from django.http import Http404
from django.http.response import HttpResponse, HttpResponseBase from django.http.response import HttpResponseBase
from django.utils import six from django.utils import six
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views import debug
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View from django.views.generic import View
@ -95,11 +92,6 @@ def exception_handler(exc, context):
set_rollback() set_rollback()
return Response(data, status=status.HTTP_403_FORBIDDEN) return Response(data, status=status.HTTP_403_FORBIDDEN)
# throw django's error page if debug is True
if settings.DEBUG:
exception_reporter = debug.ExceptionReporter(context.get('request'), *sys.exc_info())
return HttpResponse(exception_reporter.get_traceback_html(), status=500)
return None return None
@ -439,11 +431,19 @@ class APIView(View):
response = exception_handler(exc, context) response = exception_handler(exc, context)
if response is None: if response is None:
raise self.raise_uncaught_exception(exc)
response.exception = True response.exception = True
return response return response
def raise_uncaught_exception(self, exc):
if settings.DEBUG:
request = self.request
renderer_format = getattr(request.accepted_renderer, 'format')
use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
request.force_plaintext_errors(use_plaintext_traceback)
raise
# Note: Views are made CSRF exempt from within `as_view` as to prevent # Note: Views are made CSRF exempt from within `as_view` as to prevent
# accidental removal of this exemption in cases where `dispatch` needs to # accidental removal of this exemption in cases where `dispatch` needs to
# be overridden. # be overridden.

View File

@ -7,6 +7,7 @@ from django.conf.urls import url
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils import six from django.utils import six
@ -78,6 +79,16 @@ class TestContentParsing(TestCase):
request.parsers = (FormParser(), MultiPartParser()) request.parsers = (FormParser(), MultiPartParser())
self.assertEqual(list(request.POST.items()), list(data.items())) self.assertEqual(list(request.POST.items()), list(data.items()))
def test_request_POST_with_files(self):
"""
Ensure request.POST returns no content for POST request with file content.
"""
upload = SimpleUploadedFile("file.txt", b"file_content")
request = Request(factory.post('/', {'upload': upload}))
request.parsers = (FormParser(), MultiPartParser())
self.assertEqual(list(request.POST.keys()), [])
self.assertEqual(list(request.FILES.keys()), ['upload'])
def test_standard_behaviour_determines_form_content_PUT(self): def test_standard_behaviour_determines_form_content_PUT(self):
""" """
Ensure request.data returns content for PUT request with form content. Ensure request.data returns content for PUT request with form content.

View File

@ -26,6 +26,8 @@ class ExamplePagination(pagination.PageNumberPagination):
class ExampleSerializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
a = serializers.CharField(required=True, help_text='A field description') a = serializers.CharField(required=True, help_text='A field description')
b = serializers.CharField(required=False) b = serializers.CharField(required=False)
read_only = serializers.CharField(read_only=True)
hidden = serializers.HiddenField(default='hello')
class AnotherSerializer(serializers.Serializer): class AnotherSerializer(serializers.Serializer):
@ -49,6 +51,7 @@ class ExampleViewSet(ModelViewSet):
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
assert self.request assert self.request
assert self.action
return super(ExampleViewSet, self).get_serializer(*args, **kwargs) return super(ExampleViewSet, self).get_serializer(*args, **kwargs)