This commit is contained in:
Asif Saif Uddin 2019-03-20 17:24:26 +06:00
commit ebb1a23510
16 changed files with 81 additions and 31 deletions

View File

@ -19,17 +19,15 @@ continued development by [signing up for a paid plan][funding].
The initial aim is to provide a single full-time position on REST framework. The initial aim is to provide a single full-time position on REST framework.
*Every single sign-up makes a significant impact towards making that possible.* *Every single sign-up makes a significant impact towards making that possible.*
[![][rover-img]][rover-url]
[![][sentry-img]][sentry-url] [![][sentry-img]][sentry-url]
[![][stream-img]][stream-url] [![][stream-img]][stream-url]
[![][rollbar-img]][rollbar-url] [![][rollbar-img]][rollbar-url]
[![][cadre-img]][cadre-url] [![][cadre-img]][cadre-url]
[![][load-impact-img]][load-impact-url]
[![][kloudless-img]][kloudless-url] [![][kloudless-img]][kloudless-url]
[![][auklet-img]][auklet-url] [![][release-history-img]][release-history-url]
[![][lightson-img]][lightson-url] [![][lightson-img]][lightson-url]
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover][rover-url], [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Load Impact][load-impact-url], [Kloudless][kloudless-url], [Auklet][auklet-url], and [Lights On Software][lightson-url]. Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [Release History][release-history-url], and [Lights On Software][lightson-url].
--- ---
@ -202,7 +200,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png [cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png
[load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png [load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png
[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png [kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
[auklet-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/auklet-readme.png [release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history.png
[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png [lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
[rover-url]: http://jobs.rover.com/ [rover-url]: http://jobs.rover.com/
@ -212,7 +210,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[cadre-url]: https://cadre.com/ [cadre-url]: https://cadre.com/
[load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf [load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf
[kloudless-url]: https://hubs.ly/H0f30Lf0 [kloudless-url]: https://hubs.ly/H0f30Lf0
[auklet-url]: https://auklet.io/ [release-history-url]: https://releasehistory.io
[lightson-url]: https://lightsonsoftware.com [lightson-url]: https://lightsonsoftware.com
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth

View File

@ -306,10 +306,11 @@ A date and time representation.
Corresponds to `django.db.models.fields.DateTimeField`. Corresponds to `django.db.models.fields.DateTimeField`.
**Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)` **Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None, default_timezone=None)`
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. * `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer.
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
* `default_timezone` - A `pytz.timezone` representing the timezone. If not specified and the `USE_TZ` setting is enabled, this defaults to the [current timezone][django-current-timezone]. If `USE_TZ` is disabled, then datetime objects will be naive.
#### `DateTimeField` format strings. #### `DateTimeField` format strings.
@ -835,3 +836,4 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore [django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
[django-hstore]: https://github.com/djangonauts/django-hstore [django-hstore]: https://github.com/djangonauts/django-hstore
[python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes [python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes
[django-current-timezone]: https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone

View File

@ -220,10 +220,13 @@ By default, the search parameter is named `'search`', but this may be overridden
To dynamically change search fields based on request content, it's possible to subclass the `SearchFilter` and override the `get_search_fields()` function. For example, the following subclass will only search on `title` if the query parameter `title_only` is in the request: To dynamically change search fields based on request content, it's possible to subclass the `SearchFilter` and override the `get_search_fields()` function. For example, the following subclass will only search on `title` if the query parameter `title_only` is in the request:
class CustomSearchFilter(self, view, request): from rest_framework import filters
if request.query_params.get('title_only'):
return ('title',) class CustomSearchFilter(filters.SearchFilter):
return super(CustomSearchFilter, self).get_search_fields(view, request) def get_search_fields(self, view, request):
if request.query_params.get('title_only'):
return ('title',)
return super(CustomSearchFilter, self).get_search_fields(view, request)
For more details, see the [Django documentation][search-django-admin]. For more details, see the [Django documentation][search-django-admin].

View File

@ -62,7 +62,7 @@ You can determine your currently installed version using `pip show`:
### 3.9.1 ### 3.9.1
**Date**: [16th Janurary 2019][3.9.1-milestone] **Date**: [16th January 2019][3.9.1-milestone]
* Resolve XSS issue in browsable API. [#6330][gh6330] * Resolve XSS issue in browsable API. [#6330][gh6330]
* Upgrade Bootstrap to 3.4.0 to resolve XSS issue. * Upgrade Bootstrap to 3.4.0 to resolve XSS issue.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -66,19 +66,17 @@ continued development by **[signing up for a paid plan][funding]**.
*Every single sign-up helps us make REST framework long-term financially sustainable.* *Every single sign-up helps us make REST framework long-term financially sustainable.*
<ul class="premium-promo promo"> <ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li> <li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li> <li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="https://auklet.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/auklet-new.png)">Auklet</a></li> <li><a href="https://releasehistory.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/release-history.png)">Release History</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li> <li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li> <li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
<li><a href="https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/load-impact.png)">Load Impact</a></li>
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li> <li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li> <li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
</ul> </ul>
<div style="clear: both; padding-bottom: 20px;"></div> <div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Auklet](https://auklet.io/), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Load Impact](https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).* *Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Release History](https://releasehistory.io), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).*
--- ---

View File

@ -150,7 +150,7 @@ At this point we've translated the model instance into Python native datatypes.
content = JSONRenderer().render(serializer.data) content = JSONRenderer().render(serializer.data)
content content
# '{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}' # b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
Deserialization is similar. First we parse a stream into Python native datatypes... Deserialization is similar. First we parse a stream into Python native datatypes...

View File

@ -1,4 +1,4 @@
# Pytest for running the tests. # Pytest for running the tests.
pytest==3.6.2 pytest==4.3.0
pytest-django==3.3.2 pytest-django==3.4.8
pytest-cov==2.5.1 pytest-cov==2.6.1

View File

@ -1484,7 +1484,7 @@ class MultipleChoiceField(ChoiceField):
return dictionary.get(self.field_name, empty) return dictionary.get(self.field_name, empty)
def to_internal_value(self, data): def to_internal_value(self, data):
if isinstance(data, type('')) or not hasattr(data, '__iter__'): if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__) self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0: if not self.allow_empty and len(data) == 0:
self.fail('empty') self.fail('empty')
@ -1658,7 +1658,7 @@ class ListField(Field):
""" """
if html.is_html_input(data): if html.is_html_input(data):
data = html.parse_html_list(data, default=[]) data = html.parse_html_list(data, default=[])
if isinstance(data, (type(''), Mapping)) or not hasattr(data, '__iter__'): if isinstance(data, (six.text_type, Mapping)) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__) self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0: if not self.allow_empty and len(data) == 0:
self.fail('empty') self.fail('empty')

View File

@ -512,7 +512,7 @@ class ManyRelatedField(Field):
return dictionary.get(self.field_name, empty) return dictionary.get(self.field_name, empty)
def to_internal_value(self, data): def to_internal_value(self, data):
if isinstance(data, type('')) or not hasattr(data, '__iter__'): if isinstance(data, six.text_type) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__) self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0: if not self.allow_empty and len(data) == 0:
self.fail('empty') self.fail('empty')

View File

@ -50,8 +50,10 @@ def field_to_schema(field):
description=description description=description
) )
elif isinstance(field, serializers.ManyRelatedField): elif isinstance(field, serializers.ManyRelatedField):
related_field_schema = field_to_schema(field.child_relation)
return coreschema.Array( return coreschema.Array(
items=coreschema.String(), items=related_field_schema,
title=title, title=title,
description=description description=description
) )

View File

@ -462,7 +462,7 @@ class APIView(View):
renderer_format = getattr(request.accepted_renderer, 'format') renderer_format = getattr(request.accepted_renderer, 'format')
use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin') use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
request.force_plaintext_errors(use_plaintext_traceback) request.force_plaintext_errors(use_plaintext_traceback)
raise raise exc
# 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

View File

@ -2,6 +2,7 @@ import pytest
from django.core.paginator import Paginator as DjangoPaginator from django.core.paginator import Paginator as DjangoPaginator
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils import six
from rest_framework import ( from rest_framework import (
exceptions, filters, generics, pagination, serializers, status exceptions, filters, generics, pagination, serializers, status
@ -204,7 +205,7 @@ class TestPageNumberPagination:
] ]
} }
assert self.pagination.display_page_controls assert self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), type('')) assert isinstance(self.pagination.to_html(), six.text_type)
def test_second_page(self): def test_second_page(self):
request = Request(factory.get('/', {'page': 2})) request = Request(factory.get('/', {'page': 2}))
@ -310,7 +311,7 @@ class TestPageNumberPaginationOverride:
] ]
} }
assert not self.pagination.display_page_controls assert not self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), type('')) assert isinstance(self.pagination.to_html(), six.text_type)
def test_invalid_page(self): def test_invalid_page(self):
request = Request(factory.get('/', {'page': 'invalid'})) request = Request(factory.get('/', {'page': 'invalid'}))
@ -365,7 +366,7 @@ class TestLimitOffset:
] ]
} }
assert self.pagination.display_page_controls assert self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), type('')) assert isinstance(self.pagination.to_html(), six.text_type)
def test_pagination_not_applied_if_limit_or_default_limit_not_set(self): def test_pagination_not_applied_if_limit_or_default_limit_not_set(self):
class MockPagination(pagination.LimitOffsetPagination): class MockPagination(pagination.LimitOffsetPagination):
@ -628,7 +629,7 @@ class CursorPaginationTestsMixin:
assert current == [1, 1, 1, 1, 1] assert current == [1, 1, 1, 1, 1]
assert next == [1, 2, 3, 4, 4] assert next == [1, 2, 3, 4, 4]
assert isinstance(self.pagination.to_html(), type('')) assert isinstance(self.pagination.to_html(), six.text_type)
def test_cursor_pagination_with_page_size(self): def test_cursor_pagination_with_page_size(self):
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20') (previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20')

View File

@ -24,7 +24,7 @@ from rest_framework.utils import formatting
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from .models import BasicModel, ForeignKeySource from .models import BasicModel, ForeignKeySource, ManyToManySource
factory = APIRequestFactory() factory = APIRequestFactory()
@ -701,6 +701,51 @@ class TestSchemaGeneratorWithForeignKey(TestCase):
assert schema == expected assert schema == expected
class ManyToManySourceSerializer(serializers.ModelSerializer):
class Meta:
model = ManyToManySource
fields = ('id', 'name', 'targets')
class ManyToManySourceView(generics.CreateAPIView):
queryset = ManyToManySource.objects.all()
serializer_class = ManyToManySourceSerializer
@unittest.skipUnless(coreapi, 'coreapi is not installed')
class TestSchemaGeneratorWithManyToMany(TestCase):
def setUp(self):
self.patterns = [
url(r'^example/?$', ManyToManySourceView.as_view()),
]
def test_schema_for_regular_views(self):
"""
Ensure that AutoField many to many fields are output as Integer.
"""
generator = SchemaGenerator(title='Example API', patterns=self.patterns)
schema = generator.get_schema()
expected = coreapi.Document(
url='',
title='Example API',
content={
'example': {
'create': coreapi.Link(
url='/example/',
action='post',
encoding='application/json',
fields=[
coreapi.Field('name', required=True, location='form', schema=coreschema.String(title='Name')),
coreapi.Field('targets', required=True, location='form', schema=coreschema.Array(title='Targets', items=coreschema.Integer())),
]
)
}
}
)
assert schema == expected
@unittest.skipUnless(coreapi, 'coreapi is not installed') @unittest.skipUnless(coreapi, 'coreapi is not installed')
class Test4605Regression(TestCase): class Test4605Regression(TestCase):
def test_4605_regression(self): def test_4605_regression(self):

View File

@ -3,6 +3,7 @@ import re
from django.core.validators import MaxValueValidator, RegexValidator from django.core.validators import MaxValueValidator, RegexValidator
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.utils import six
from rest_framework import generics, serializers, status from rest_framework import generics, serializers, status
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
@ -109,7 +110,7 @@ class TestAvoidValidation(TestCase):
assert not serializer.is_valid() assert not serializer.is_valid()
assert serializer.errors == { assert serializer.errors == {
'non_field_errors': [ 'non_field_errors': [
'Invalid data. Expected a dictionary, but got %s.' % type('').__name__ 'Invalid data. Expected a dictionary, but got %s.' % six.text_type.__name__
] ]
} }