mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-30 01:49:50 +03:00
Merge branch 'master' of https://github.com/encode/django-rest-framework into py2
This commit is contained in:
commit
ebb1a23510
10
README.md
10
README.md
|
@ -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.
|
||||
*Every single sign-up makes a significant impact towards making that possible.*
|
||||
|
||||
[![][rover-img]][rover-url]
|
||||
[![][sentry-img]][sentry-url]
|
||||
[![][stream-img]][stream-url]
|
||||
[![][rollbar-img]][rollbar-url]
|
||||
[![][cadre-img]][cadre-url]
|
||||
[![][load-impact-img]][load-impact-url]
|
||||
[![][kloudless-img]][kloudless-url]
|
||||
[![][auklet-img]][auklet-url]
|
||||
[![][release-history-img]][release-history-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
|
||||
[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
|
||||
[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
|
||||
|
||||
[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/
|
||||
[load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf
|
||||
[kloudless-url]: https://hubs.ly/H0f30Lf0
|
||||
[auklet-url]: https://auklet.io/
|
||||
[release-history-url]: https://releasehistory.io
|
||||
[lightson-url]: https://lightsonsoftware.com
|
||||
|
||||
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||
|
|
|
@ -306,10 +306,11 @@ A date and time representation.
|
|||
|
||||
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.
|
||||
* `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.
|
||||
|
||||
|
@ -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-hstore]: https://github.com/djangonauts/django-hstore
|
||||
[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
|
||||
|
|
|
@ -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:
|
||||
|
||||
class CustomSearchFilter(self, view, request):
|
||||
if request.query_params.get('title_only'):
|
||||
return ('title',)
|
||||
return super(CustomSearchFilter, self).get_search_fields(view, request)
|
||||
from rest_framework import filters
|
||||
|
||||
class CustomSearchFilter(filters.SearchFilter):
|
||||
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].
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ You can determine your currently installed version using `pip show`:
|
|||
|
||||
### 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]
|
||||
* Upgrade Bootstrap to 3.4.0 to resolve XSS issue.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
BIN
docs/img/premium/release-history.png
Normal file
BIN
docs/img/premium/release-history.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -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.*
|
||||
|
||||
<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://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://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://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
|
||||
</ul>
|
||||
<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).*
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ At this point we've translated the model instance into Python native datatypes.
|
|||
|
||||
content = JSONRenderer().render(serializer.data)
|
||||
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...
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Pytest for running the tests.
|
||||
pytest==3.6.2
|
||||
pytest-django==3.3.2
|
||||
pytest-cov==2.5.1
|
||||
pytest==4.3.0
|
||||
pytest-django==3.4.8
|
||||
pytest-cov==2.6.1
|
||||
|
|
|
@ -1484,7 +1484,7 @@ class MultipleChoiceField(ChoiceField):
|
|||
return dictionary.get(self.field_name, empty)
|
||||
|
||||
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__)
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
self.fail('empty')
|
||||
|
@ -1658,7 +1658,7 @@ class ListField(Field):
|
|||
"""
|
||||
if html.is_html_input(data):
|
||||
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__)
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
self.fail('empty')
|
||||
|
|
|
@ -512,7 +512,7 @@ class ManyRelatedField(Field):
|
|||
return dictionary.get(self.field_name, empty)
|
||||
|
||||
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__)
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
self.fail('empty')
|
||||
|
|
|
@ -50,8 +50,10 @@ def field_to_schema(field):
|
|||
description=description
|
||||
)
|
||||
elif isinstance(field, serializers.ManyRelatedField):
|
||||
related_field_schema = field_to_schema(field.child_relation)
|
||||
|
||||
return coreschema.Array(
|
||||
items=coreschema.String(),
|
||||
items=related_field_schema,
|
||||
title=title,
|
||||
description=description
|
||||
)
|
||||
|
|
|
@ -462,7 +462,7 @@ class APIView(View):
|
|||
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
|
||||
raise exc
|
||||
|
||||
# Note: Views are made CSRF exempt from within `as_view` as to prevent
|
||||
# accidental removal of this exemption in cases where `dispatch` needs to
|
||||
|
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
from django.core.paginator import Paginator as DjangoPaginator
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from rest_framework import (
|
||||
exceptions, filters, generics, pagination, serializers, status
|
||||
|
@ -204,7 +205,7 @@ class TestPageNumberPagination:
|
|||
]
|
||||
}
|
||||
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):
|
||||
request = Request(factory.get('/', {'page': 2}))
|
||||
|
@ -310,7 +311,7 @@ class TestPageNumberPaginationOverride:
|
|||
]
|
||||
}
|
||||
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):
|
||||
request = Request(factory.get('/', {'page': 'invalid'}))
|
||||
|
@ -365,7 +366,7 @@ class TestLimitOffset:
|
|||
]
|
||||
}
|
||||
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):
|
||||
class MockPagination(pagination.LimitOffsetPagination):
|
||||
|
@ -628,7 +629,7 @@ class CursorPaginationTestsMixin:
|
|||
assert current == [1, 1, 1, 1, 1]
|
||||
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):
|
||||
(previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20')
|
||||
|
|
|
@ -24,7 +24,7 @@ from rest_framework.utils import formatting
|
|||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from .models import BasicModel, ForeignKeySource
|
||||
from .models import BasicModel, ForeignKeySource, ManyToManySource
|
||||
|
||||
factory = APIRequestFactory()
|
||||
|
||||
|
@ -701,6 +701,51 @@ class TestSchemaGeneratorWithForeignKey(TestCase):
|
|||
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')
|
||||
class Test4605Regression(TestCase):
|
||||
def test_4605_regression(self):
|
||||
|
|
|
@ -3,6 +3,7 @@ import re
|
|||
from django.core.validators import MaxValueValidator, RegexValidator
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from rest_framework import generics, serializers, status
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
@ -109,7 +110,7 @@ class TestAvoidValidation(TestCase):
|
|||
assert not serializer.is_valid()
|
||||
assert serializer.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__
|
||||
]
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user