Merge pull request #1 from encode/master

merge
This commit is contained in:
老广 2019-05-16 13:47:10 +08:00 committed by GitHub
commit 15e044b231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
217 changed files with 4362 additions and 2208 deletions

View File

@ -1,31 +1,31 @@
language: python
cache: pip
python:
- "2.7"
- "3.4"
- "3.5"
sudo: false
env:
- DJANGO=1.11
- DJANGO=2.0
- DJANGO=2.1
- DJANGO=master
dist: xenial
matrix:
fast_finish: true
include:
- { python: "3.6", env: DJANGO=master }
- { python: "3.5", env: DJANGO=1.11 }
- { python: "3.5", env: DJANGO=2.0 }
- { python: "3.5", env: DJANGO=2.1 }
- { python: "3.5", env: DJANGO=2.2 }
- { python: "3.6", env: DJANGO=1.11 }
- { python: "3.6", env: DJANGO=2.0 }
- { python: "3.6", env: DJANGO=2.1 }
- { python: "3.6", env: TOXENV=base }
- { python: "2.7", env: TOXENV=lint }
- { python: "2.7", env: TOXENV=docs }
- { python: "3.6", env: DJANGO=2.2 }
- { python: "3.6", env: DJANGO=master }
- python: "3.6"
- { python: "3.7", env: DJANGO=2.0 }
- { python: "3.7", env: DJANGO=2.1 }
- { python: "3.7", env: DJANGO=2.2 }
- { python: "3.7", env: DJANGO=master }
- { python: "3.7", env: TOXENV=base }
- { python: "3.7", env: TOXENV=lint }
- { python: "3.7", env: TOXENV=docs }
- python: "3.7"
env: TOXENV=dist
script:
- python setup.py bdist_wheel
@ -33,16 +33,8 @@ matrix:
- tox --installpkg ./dist/djangorestframework-*.whl
- tox # test sdist
exclude:
- { python: "2.7", env: DJANGO=master }
- { python: "2.7", env: DJANGO=2.0 }
- { python: "2.7", env: DJANGO=2.1 }
- { python: "3.4", env: DJANGO=master }
- { python: "3.4", env: DJANGO=2.1 }
allow_failures:
- env: DJANGO=master
- env: DJANGO=2.1
install:
- pip install tox tox-venv tox-travis

1
CHANGELOG.md Symbolic link
View File

@ -0,0 +1 @@
docs/community/release-notes.md

View File

@ -20,7 +20,7 @@ When answering questions make sure to help future contributors find their way ar
Please keep the tone polite & professional. For some users a discussion on the REST framework mailing list or ticket tracker may be their first engagement with the open source community. First impressions count, so let's try to make everyone feel welcome.
Be mindful in the language you choose. As an example, in an environment that is heavily male-dominated, posts that start 'Hey guys,' can come across as unintentionally exclusive. It's just as easy, and more inclusive to use gender neutral language in those situations.
Be mindful in the language you choose. As an example, in an environment that is heavily male-dominated, posts that start 'Hey guys,' can come across as unintentionally exclusive. It's just as easy, and more inclusive to use gender neutral language in those situations. (e.g. 'Hey folks,')
The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines for participating in community forums.
@ -50,7 +50,7 @@ Getting involved in triaging incoming issues is a good way to start contributing
To start developing on Django REST framework, clone the repo:
git clone git@github.com:encode/django-rest-framework.git
git clone https://github.com/encode/django-rest-framework
Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
@ -59,7 +59,7 @@ Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recom
To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
python3 -m venv env
source env/bin/activate
pip install django
pip install -r requirements.txt
@ -115,7 +115,7 @@ It's also useful to remember that if you have an outstanding pull request then p
GitHub's documentation for working on pull requests is [available here][pull-requests].
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django.
Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.

View File

@ -3,7 +3,7 @@
- [ ] I have verified that that issue exists against the `master` branch of Django REST framework.
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [ ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [ ] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

View File

@ -1,6 +1,6 @@
# License
Copyright © 2011-present, [Encode OSS Ltd](http://www.encode.io/).
Copyright © 2011-present, [Encode OSS Ltd](https://www.encode.io/).
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -6,7 +6,7 @@
**Awesome web-browsable Web APIs.**
Full documentation for the project is available at [http://www.django-rest-framework.org][docs].
Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
---
@ -19,14 +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]
[![][machinalis-img]][machinalis-url]
[![][rollbar-img]][rollbar-url]
[![][cadre-img]][cadre-url]
[![][kloudless-img]][kloudless-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], [Machinalis][machinalis-url], [Rollbar][rollbar-url], and [Cadre][cadre-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].
---
@ -52,8 +53,11 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (2.7, 3.4, 3.5, 3.6)
* Django (1.11, 2.0, 2.1)
* Python (3.5, 3.6, 3.7)
* Django (1.11, 2.0, 2.1, 2.2)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
# Installation
@ -76,7 +80,7 @@ Startup up a new project like so...
pip install django
pip install djangorestframework
django-admin.py startproject example .
django-admin startproject example .
./manage.py migrate
./manage.py createsuperuser
@ -142,14 +146,14 @@ You can now open the API in your browser at `http://127.0.0.1:8000/`, and view y
You can also interact with the API using command line tools such as [`curl`](https://curl.haxx.se/). For example, to list the users endpoint:
$ curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
[
{
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin",
"email": "admin@example.com",
"is_staff": true,
}
]
[
{
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin",
"email": "admin@example.com",
"is_staff": true,
}
]
Or to create a new user:
@ -163,7 +167,7 @@ Or to create a new user:
# Documentation & Support
Full documentation for the project is available at [http://www.django-rest-framework.org][docs].
Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC.
@ -191,28 +195,34 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[rover-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png
[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png
[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
[machinalis-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/machinalis-readme.png
[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-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
[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-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/
[sentry-url]: https://getsentry.com/welcome/
[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
[machinalis-url]: https://hello.machinalis.co.uk/
[rollbar-url]: https://rollbar.com/
[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
[release-history-url]: https://releasehistory.io
[lightson-url]: https://lightsonsoftware.com
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
[modelserializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer
[functionview-section]: http://www.django-rest-framework.org/api-guide/views/#function-based-views
[generic-views]: http://www.django-rest-framework.org/api-guide/generic-views/
[viewsets]: http://www.django-rest-framework.org/api-guide/viewsets/
[routers]: http://www.django-rest-framework.org/api-guide/routers/
[serializers]: http://www.django-rest-framework.org/api-guide/serializers/
[authentication]: http://www.django-rest-framework.org/api-guide/authentication/
[image]: http://www.django-rest-framework.org/img/quickstart.png
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
[serializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#serializers
[modelserializer-section]: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
[functionview-section]: https://www.django-rest-framework.org/api-guide/views/#function-based-views
[generic-views]: https://www.django-rest-framework.org/api-guide/generic-views/
[viewsets]: https://www.django-rest-framework.org/api-guide/viewsets/
[routers]: https://www.django-rest-framework.org/api-guide/routers/
[serializers]: https://www.django-rest-framework.org/api-guide/serializers/
[authentication]: https://www.django-rest-framework.org/api-guide/authentication/
[image]: https://www.django-rest-framework.org/img/quickstart.png
[docs]: http://www.django-rest-framework.org/
[docs]: https://www.django-rest-framework.org/
[security-mail]: mailto:rest-framework-security@googlegroups.com

View File

@ -1,8 +1,11 @@
coverage:
status:
project: false
patch: true
changes: true
precision: 2
round: down
range: "80...100"
comment:
layout: "diff"
status:
project: yes
patch: no
changes: no
comment: off

View File

@ -137,7 +137,7 @@ You'll also need to create tokens for your users.
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=...)
print token.key
print(token.key)
For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example:
@ -354,7 +354,7 @@ The following third party packages are also available.
## Django OAuth Toolkit
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
#### Installation & configuration
@ -391,13 +391,9 @@ Install the package using `pip`.
For details on configuration and usage see the Django REST framework OAuth documentation for [authentication][django-rest-framework-oauth-authentication] and [permissions][django-rest-framework-oauth-permissions].
## Digest Authentication
HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.
## JSON Web Token Authentication
JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn't need to use a database to validate a token. [Blimp][blimp] maintains the [djangorestframework-jwt][djangorestframework-jwt] package which provides a JWT Authentication class as well as a mechanism for clients to obtain a JWT given the username and password. An alternative package for JWT authentication is [djangorestframework-simplejwt][djangorestframework-simplejwt] which provides different features as well as a pluggable token blacklist app.
JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn't need to use a database to validate a token. A package for JWT authentication is [djangorestframework-simplejwt][djangorestframework-simplejwt] which provides some features as well as a pluggable token blacklist app.
## Hawk HTTP Authentication
@ -445,8 +441,6 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
[evonove]: https://github.com/evonove/
[oauthlib]: https://github.com/idan/oauthlib
[blimp]: https://github.com/GetBlimp
[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt
[etoccalino]: https://github.com/etoccalino/
[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature

View File

@ -13,7 +13,7 @@ provided in Django.
Django provides a [`method_decorator`][decorator] to use
decorators with class based views. This can be used with
with other cache decorators such as [`cache_page`][page] and
other cache decorators such as [`cache_page`][page] and
[`vary_on_cookie`][cookie].
```python

View File

@ -124,7 +124,14 @@ A boolean representation.
When using HTML encoded form input be aware that omitting a value will always be treated as setting a field to `False`, even if it has a `default=True` option specified. This is because HTML checkbox inputs represent the unchecked state by omitting the value, so REST framework treats omission as if it is an empty checkbox input.
Note that default `BooleanField` instances will be generated with a `required=False` option (since Django `models.BooleanField` is always `blank=True`). If you want to change this behaviour explicitly declare the `BooleanField` on the serializer class.
Note that Django 2.1 removed the `blank` kwarg from `models.BooleanField`.
Prior to Django 2.1 `models.BooleanField` fields were always `blank=True`. Thus
since Django 2.1 default `serializers.BooleanField` instances will be generated
without the `required` kwarg (i.e. equivalent to `required=True`) whereas with
previous versions of Django, default `BooleanField` instances will be generated
with a `required=False` option. If you want to control this behaviour manually,
explicitly declare the `BooleanField` on the serializer class, or use the
`extra_kwargs` option to set the `required` flag.
Corresponds to `django.db.models.fields.BooleanField`.
@ -299,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.
@ -440,9 +448,10 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is
A field class that validates a list of objects.
**Signature**: `ListField(child=<A_FIELD_INSTANCE>, min_length=None, max_length=None)`
**Signature**: `ListField(child=<A_FIELD_INSTANCE>, allow_empty=True, min_length=None, max_length=None)`
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
- `allow_empty` - Designates if empty lists are allowed.
- `min_length` - Validates that the list contains no fewer than this number of elements.
- `max_length` - Validates that the list contains no more than this number of elements.
@ -463,9 +472,10 @@ We can now reuse our custom `StringListField` class throughout our application,
A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.
**Signature**: `DictField(child=<A_FIELD_INSTANCE>)`
**Signature**: `DictField(child=<A_FIELD_INSTANCE>, allow_empty=True)`
- `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated.
- `allow_empty` - Designates if empty dictionaries are allowed.
For example, to create a field that validates a mapping of strings to strings, you would write something like this:
@ -480,9 +490,10 @@ You can also use the declarative style, as with `ListField`. For example:
A preconfigured `DictField` that is compatible with Django's postgres `HStoreField`.
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>)`
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>, allow_empty=True)`
- `child` - A field instance that is used for validating the values in the dictionary. The default child field accepts both empty strings and null values.
- `allow_empty` - Designates if empty dictionaries are allowed.
Note that the child field **must** be an instance of `CharField`, as the hstore extension stores values as strings.
@ -621,7 +632,7 @@ Our `ColorField` class above currently does not perform any data validation.
To indicate invalid data, we should raise a `serializers.ValidationError`, like so:
def to_internal_value(self, data):
if not isinstance(data, six.text_type):
if not isinstance(data, str):
msg = 'Incorrect type. Expected a string, but got %s'
raise ValidationError(msg % type(data).__name__)
@ -645,7 +656,7 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
}
def to_internal_value(self, data):
if not isinstance(data, six.text_type):
if not isinstance(data, str):
self.fail('incorrect_type', input_type=type(data).__name__)
if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
@ -828,3 +839,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

View File

@ -127,7 +127,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
"""
model = Product
serializer_class = ProductSerializer
filter_class = ProductFilter
filterset_class = ProductFilter
def get_queryset(self):
user = self.request.user
@ -160,13 +160,13 @@ Or add the filter backend to an individual View or ViewSet.
...
filter_backends = (DjangoFilterBackend,)
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
If all you need is simple equality-based filtering, you can set a `filterset_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('category', 'in_stock')
filterset_fields = ('category', 'in_stock')
This will automatically create a `FilterSet` class for the given fields, and will allow you to make requests such as:
@ -188,7 +188,7 @@ When in use, the browsable API will include a `SearchFilter` control:
The `SearchFilter` class will only be applied if the view has a `search_fields` attribute set. The `search_fields` attribute should be a list of names of text type fields on the model, such as `CharField` or `TextField`.
from rest_framework import filters
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
@ -218,6 +218,16 @@ For example:
By default, the search parameter is named `'search`', but this may be overridden with the `SEARCH_PARAM` setting.
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:
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].
---
@ -285,6 +295,12 @@ The `ordering` attribute may be either a string or a list/tuple of strings.
The `DjangoObjectPermissionsFilter` is intended to be used together with the [`django-guardian`][guardian] package, with custom `'view'` permissions added. The filter will ensure that querysets only returns objects for which the user has the appropriate view permission.
---
**Note:** This filter has been deprecated as of version 3.9 and moved to the 3rd-party [`djangorestframework-guardian` package][django-rest-framework-guardian].
---
If you're using `DjangoObjectPermissionsFilter`, you'll probably also want to add an appropriate object permissions class, to ensure that users can only operate on instances if they have the appropriate object permissions. The easiest way to do this is to subclass `DjangoObjectPermissions` and add `'view'` permissions to the `perms_map` attribute.
A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectPermissions` might look something like this.
@ -292,9 +308,9 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP
**permissions.py**:
class CustomObjectPermissions(permissions.DjangoObjectPermissions):
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
@ -308,11 +324,11 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP
**views.py**:
class EventViewSet(viewsets.ModelViewSet):
"""
Viewset that only lists events if user has 'view' permissions, and only
allows operations on individual events if user has appropriate 'view', 'add',
'change' or 'delete' permissions.
"""
"""
Viewset that only lists events if user has 'view' permissions, and only
allows operations on individual events if user has appropriate 'view', 'add',
'change' or 'delete' permissions.
"""
queryset = Event.objects.all()
serializer_class = EventSerializer
filter_backends = (filters.DjangoObjectPermissionsFilter,)
@ -388,6 +404,7 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
[view-permissions-blogpost]: https://blog.nyaruka.com/adding-a-view-permission-to-django-models
[search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
[django-url-filter]: https://github.com/miki725/django-url-filter
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters

View File

@ -90,4 +90,4 @@ It is actually a misconception. For example, take the following quote from Roy
The quote does not mention Accept headers, but it does make it clear that format suffixes should be considered an acceptable pattern.
[cite]: http://tech.groups.yahoo.com/group/rest-discuss/message/5857
[cite2]: http://tech.groups.yahoo.com/group/rest-discuss/message/14844
[cite2]: https://groups.yahoo.com/neo/groups/rest-discuss/conversations/topics/14844

View File

@ -117,5 +117,5 @@ If you wish to do so, it also provides an exporter that can export those schema
[cite]: https://tools.ietf.org/html/rfc7231#section-4.3.7
[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
[json-schema]: http://json-schema.org/
[json-schema]: https://json-schema.org/
[drf-schema-adapter]: https://github.com/drf-forms/drf-schema-adapter

View File

@ -46,7 +46,7 @@ If you want to modify particular aspects of the pagination style, you'll want to
page_size_query_param = 'page_size'
max_page_size = 1000
You can then apply your new style to a view using the `.pagination_class` attribute:
You can then apply your new style to a view using the `pagination_class` attribute:
class BillingRecordsView(generics.ListAPIView):
queryset = Billing.objects.all()
@ -311,7 +311,7 @@ The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagi
## link-header-pagination
The [`django-rest-framework-link-header-pagination` package][drf-link-header-pagination] includes a `LinkHeaderPagination` class which provides pagination via an HTTP `Link` header as desribed in [Github's developer documentation](github-link-pagination).
The [`django-rest-framework-link-header-pagination` package][drf-link-header-pagination] includes a `LinkHeaderPagination` class which provides pagination via an HTTP `Link` header as described in [Github's developer documentation](github-link-pagination).
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
[link-header]: ../img/link-header-pagination.png
@ -319,5 +319,5 @@ The [`django-rest-framework-link-header-pagination` package][drf-link-header-pag
[paginate-by-max-mixin]: https://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
[drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination
[drf-link-header-pagination]: https://github.com/tbeadle/django-rest-framework-link-header-pagination
[disqus-cursor-api]: http://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
[disqus-cursor-api]: https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
[float_cursor_pagination_example]: https://gist.github.com/keturn/8bc88525a183fd41c73ffb729b8865be#file-fpcursorpagination-py

View File

@ -102,7 +102,7 @@ If it is called without a `filename` URL keyword argument, then the client must
##### Notes:
* The `FileUploadParser` is for usage with native clients that can upload the file as a raw data request. For web-based uploads, or for native clients with multipart upload support, you should use the `MultiPartParser` parser instead.
* The `FileUploadParser` is for usage with native clients that can upload the file as a raw data request. For web-based uploads, or for native clients with multipart upload support, you should use the `MultiPartParser` instead.
* Since this parser's `media_type` matches any content type, `FileUploadParser` should generally be the only parser set on an API view.
* `FileUploadParser` respects Django's standard `FILE_UPLOAD_HANDLERS` setting, and the `request.upload_handlers` attribute. See the [Django documentation][upload-handlers] for more details.

View File

@ -10,9 +10,9 @@ Together with [authentication] and [throttling], permissions determine whether a
Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted.
Permissions are used to grant or deny access different classes of users to different parts of the API.
Permissions are used to grant or deny access for different classes of users to different parts of the API.
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds the `IsAuthenticated` class in REST framework.
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds to the `IsAuthenticated` class in REST framework.
A slightly less strict style of permission would be to allow full access to authenticated users, but allow read-only access to unauthenticated users. This corresponds to the `IsAuthenticatedOrReadOnly` class in REST framework.
@ -48,6 +48,19 @@ For example:
self.check_object_permissions(self.request, obj)
return obj
---
**Note**: With the exception of `DjangoObjectPermissions`, the provided
permission classes in `rest_framework.permissions` **do not** implement the
methods necessary to check object permissions.
If you wish to use the provided permission classes in order to check object
permissions, **you must** subclass them and implement the
`has_object_permission()` method described in the [_Custom
permissions_](#custom-permissions) section (below).
---
#### Limitations of object level permissions
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
@ -102,6 +115,27 @@ Or, if you're using the `@api_view` decorator with function based views.
__Note:__ when you set new permission classes through class attribute or decorators you're telling the view to ignore the default list set over the __settings.py__ file.
Provided they inherit from `rest_framework.permissions.BasePermission`, permissions can be composed using standard Python bitwise operators. For example, `IsAuthenticatedOrReadOnly` could be written:
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
from rest_framework.response import Response
from rest_framework.views import APIView
class ReadOnly(BasePermission):
def has_permission(self, request, view):
return request.method in SAFE_METHODS
class ExampleView(APIView):
permission_classes = (IsAuthenticated|ReadOnly,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
__Note:__ it supports & (and), | (or) and ~ (not).
---
# API Reference
@ -168,9 +202,7 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid
---
**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests, you'll want to consider also adding the `DjangoObjectPermissionsFilter` class to ensure that list endpoints only return results including objects for which the user has appropriate view permissions.
---
**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests and are using django-guardian for your object-level permissions backend, you'll want to consider using the `DjangoObjectPermissionsFilter` class provided by the [`djangorestframework-guardian` package][django-rest-framework-guardian]. It ensures that list endpoints only return results including objects for which the user has appropriate view permissions.
---
@ -255,7 +287,7 @@ The [Composed Permissions][composed-permissions] package provides a simple way t
## REST Condition
The [REST Condition][rest-condition] package is another extension for building complex permissions in a simple and convenient way. The extension allows you to combine permissions with logical operators.
The [REST Condition][rest-condition] package is another extension for building complex permissions in a simple and convenient way. The extension allows you to combine permissions with logical operators.
## DRY Rest Permissions
@ -265,9 +297,9 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to
The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users.
## Django Rest Framework API Key
## Django REST Framework API Key
The [Django Rest Framework API Key][django-rest-framework-api-key] package allows you to ensure that every request made to the server requires an API key header. You can generate one from the django admin interface.
The [Django REST Framework API Key][djangorestframework-api-key] package provides the ability to authorize clients based on customizable API key headers. This package is targeted at situations in which regular user-based authentication (e.g. `TokenAuthentication`) is not suitable, e.g. allowing non-human clients to safely use your API. API keys are generated and validated through cryptographic methods and can be created and revoked from the Django admin interface at anytime.
## Django Rest Framework Role Filters
@ -285,5 +317,6 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac
[rest-condition]: https://github.com/caxap/rest_condition
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles
[django-rest-framework-api-key]: https://github.com/manosim/django-rest-framework-api-key
[djangorestframework-api-key]: https://github.com/florimondmanca/djangorestframework-api-key
[django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian

View File

@ -2,11 +2,9 @@ source: relations.py
# Serializer relations
> Bad programmers worry about the code.
> Good programmers worry about data structures and their relationships.
> Data structures, not algorithms, are central to programming.
>
> &mdash; [Linus Torvalds][cite]
> &mdash; [Rob Pike][cite]
Relational fields are used to represent model relationships. They can be applied to `ForeignKey`, `ManyToManyField` and `OneToOneField` relationships, as well as to reverse relationships, and custom relationships such as `GenericForeignKey`.
@ -24,7 +22,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print repr(serializer) # Or `print(repr(serializer))` in Python 3.x.
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
@ -48,12 +46,12 @@ In order to explain the various types of relational fields, we'll use a couple o
unique_together = ('album', 'order')
ordering = ['order']
def __unicode__(self):
def __str__(self):
return '%d: %s' % (self.order, self.title)
## StringRelatedField
`StringRelatedField` may be used to represent the target of the relationship using its `__unicode__` method.
`StringRelatedField` may be used to represent the target of the relationship using its `__str__` method.
For example, the following serializer.
@ -512,7 +510,7 @@ For example, given the following model for a tag, which has a generic relationsh
object_id = models.PositiveIntegerField()
tagged_object = GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
def __str__(self):
return self.tag_name
And the following two models, which may have associated tags:
@ -578,6 +576,8 @@ If you explicitly specify a relational field pointing to a
``ManyToManyField`` with a through model, be sure to set ``read_only``
to ``True``.
If you wish to represent [extra fields on a through model][django-intermediary-manytomany] then you may serialize the through model as [a nested object][dealing-with-nested-objects].
---
# Third Party Packages
@ -592,9 +592,11 @@ The [drf-nested-routers package][drf-nested-routers] provides routers and relati
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
[cite]: https://lwn.net/Articles/193245/
[cite]: http://users.ece.utexas.edu/~adnan/pike.html
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: http://www.django-rest-framework.org/api-guide/routers#defaultrouter
[routers]: https://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations
[django-intermediary-manytomany]: https://docs.djangoproject.com/en/2.2/topics/db/models/#intermediary-manytomany
[dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects

View File

@ -89,7 +89,7 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM
**.media_type**: `application/json`
**.format**: `'.json'`
**.format**: `'json'`
**.charset**: `None`
@ -127,7 +127,7 @@ See the [_HTML & Forms_ Topic Page][html-and-forms] for further examples of `Tem
**.media_type**: `text/html`
**.format**: `'.html'`
**.format**: `'html'`
**.charset**: `utf-8`
@ -149,7 +149,7 @@ You can use `StaticHTMLRenderer` either to return regular HTML pages using REST
**.media_type**: `text/html`
**.format**: `'.html'`
**.format**: `'html'`
**.charset**: `utf-8`
@ -165,7 +165,7 @@ This renderer will determine which other renderer would have been given highest
**.media_type**: `text/html`
**.format**: `'.api'`
**.format**: `'api'`
**.charset**: `utf-8`
@ -200,7 +200,7 @@ Note that views that have nested or list serializers for their input won't work
**.media_type**: `text/html`
**.format**: `'.admin'`
**.format**: `'admin'`
**.charset**: `utf-8`
@ -224,7 +224,7 @@ For more information see the [HTML & Forms][html-and-forms] documentation.
**.media_type**: `text/html`
**.format**: `'.form'`
**.format**: `'form'`
**.charset**: `utf-8`
@ -236,7 +236,7 @@ This renderer is used for rendering HTML multipart form data. **It is not suita
**.media_type**: `multipart/form-data; boundary=BoUnDaRyStRiNg`
**.format**: `'.multipart'`
**.format**: `'multipart'`
**.charset**: `utf-8`
@ -521,13 +521,13 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
[testing]: testing.md
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[quote]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[application/vnd.github+json]: https://developer.github.com/v3/media/
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
[rest-framework-jsonp]: https://jpadilla.github.io/django-rest-framework-jsonp/
[cors]: https://www.w3.org/TR/cors/
[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/
[cors-docs]: https://www.django-rest-framework.org/topics/ajax-csrf-cors/
[jsonp-security]: https://stackoverflow.com/questions/613962/is-jsonp-safe-to-use
[rest-framework-yaml]: https://jpadilla.github.io/django-rest-framework-yaml/
[rest-framework-xml]: https://jpadilla.github.io/django-rest-framework-xml/

View File

@ -50,7 +50,7 @@ The request exposes some properties that allow you to determine the result of th
## .accepted_renderer
The renderer instance what was selected by the content negotiation stage.
The renderer instance that was selected by the content negotiation stage.
## .accepted_media_type
@ -90,7 +90,7 @@ You won't typically need to access this property.
---
**Note:** You may see a `WrappedAttributeError` raised when calling the `.user` or `.auth` properties. These errors originate from an authenticator as a standard `AttributeError`, however it's necessary that they be re-raised as a different exception type in order to prevent them from being suppressed by the outer property access. Python will not recognize that the `AttributeError` orginates from the authenticator and will instaed assume that the request object does not have a `.user` or `.auth` property. The authenticator will need to be fixed.
**Note:** You may see a `WrappedAttributeError` raised when calling the `.user` or `.auth` properties. These errors originate from an authenticator as a standard `AttributeError`, however it's necessary that they be re-raised as a different exception type in order to prevent them from being suppressed by the outer property access. Python will not recognize that the `AttributeError` orginates from the authenticator and will instead assume that the request object does not have a `.user` or `.auth` property. The authenticator will need to be fixed.
---

View File

@ -325,7 +325,7 @@ The [wq.db package][wq.db] provides an advanced [ModelRouter][wq.db-router] clas
The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names].
[cite]: http://guides.rubyonrails.org/routing.html
[cite]: https://guides.rubyonrails.org/routing.html
[route-decorators]: viewsets.md#marking-extra-actions-for-routing
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[wq.db]: https://wq.io/wq.db

View File

@ -10,12 +10,50 @@ API schemas are a useful tool that allow for a range of use cases, including
generating reference documentation, or driving dynamic client libraries that
can interact with your API.
## Install Core API
## Install Core API & PyYAML
You'll need to install the `coreapi` package in order to add schema support
for REST framework.
for REST framework. You probably also want to install `pyyaml`, so that you
can render the schema into the commonly used YAML-based OpenAPI format.
pip install coreapi
pip install coreapi pyyaml
## Quickstart
There are two different ways you can serve a schema description for your API.
### Generating a schema with the `generateschema` management command
To generate a static API schema, use the `generateschema` management command.
```shell
$ python manage.py generateschema > schema.yml
```
Once you've generated a schema in this way you can annotate it with any
additional information that cannot be automatically inferred by the schema
generator.
You might want to check your API schema into version control and update it
with each new release, or serve the API schema from your site's static media.
### Adding a view with `get_schema_view`
To add a dynamically generated schema view to your API, use `get_schema_view`.
```python
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title="Example API")
urlpatterns = [
url('^schema$', schema_view),
...
]
```
See below [for more details](#the-get_schema_view-shortcut) on customizing a
dynamically generated schema view.
## Internal schema representation
@ -71,38 +109,19 @@ endpoint:
In order to be presented in an HTTP response, the internal representation
has to be rendered into the actual bytes that are used in the response.
REST framework includes a few different renderers that you can use for
encoding the API schema.
* `renderers.OpenAPIRenderer` - Renders into YAML-based [OpenAPI][open-api], the most widely used API schema format.
* `renderers.JSONOpenAPIRenderer` - Renders into JSON-based [OpenAPI][open-api].
* `renderers.CoreJSONRenderer` - Renders into [Core JSON][corejson], a format designed for
use with the `coreapi` client library.
[Core JSON][corejson] is designed as a canonical format for use with Core API.
REST framework includes a renderer class for handling this media type, which
is available as `renderers.CoreJSONRenderer`.
### Alternate schema formats
Other schema formats such as [Open API][open-api] ("Swagger"),
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can also
be supported by implementing a custom renderer class that handles converting a
`Document` instance into a bytestring representation.
If there is a Core API codec package that supports encoding into the format you
want to use then implementing the renderer class can be done by using the codec.
#### Example
For example, the `openapi_codec` package provides support for encoding or decoding
to the Open API ("Swagger") format:
from rest_framework import renderers
from openapi_codec import OpenAPICodec
class SwaggerRenderer(renderers.BaseRenderer):
media_type = 'application/openapi+json'
format = 'swagger'
def render(self, data, media_type=None, renderer_context=None):
codec = OpenAPICodec()
return codec.dump(data)
## Schemas vs Hypermedia
@ -146,7 +165,7 @@ example above.
Automatic schema generation is provided by the `SchemaGenerator` class.
`SchemaGenerator` processes a list of routed URL pattterns and compiles the
`SchemaGenerator` processes a list of routed URL patterns and compiles the
appropriately structured Core API Document.
Basic usage is just to provide the title for your schema and call
@ -325,13 +344,12 @@ ROOT_URLCONF setting.
May be used to pass the set of renderer classes that can be used to render the API root endpoint.
from rest_framework.schemas import get_schema_view
from rest_framework.renderers import CoreJSONRenderer
from my_custom_package import APIBlueprintRenderer
from rest_framework.renderers import JSONOpenAPIRenderer
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
renderer_classes=[CoreJSONRenderer, APIBlueprintRenderer]
renderer_classes=[JSONOpenAPIRenderer]
)
#### `patterns`
@ -364,7 +382,6 @@ Defaults to `settings.DEFAULT_AUTHENTICATION_CLASSES`
May be used to specify the list of permission classes that will apply to the schema endpoint.
Defaults to `settings.DEFAULT_PERMISSION_CLASSES`
## Using an explicit schema view
If you need a little more control than the `get_schema_view()` shortcut gives you,
@ -386,7 +403,7 @@ return the schema.
generator = schemas.SchemaGenerator(title='Bookings API')
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
@renderer_classes([renderers.OpenAPIRenderer])
def schema_view(request):
schema = generator.get_schema(request)
return response.Response(schema)
@ -408,7 +425,7 @@ In order to present a schema with endpoints filtered by user permissions,
you need to pass the `request` argument to the `get_schema()` method, like so:
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
@renderer_classes([renderers.OpenAPIRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='Bookings API')
return response.Response(generator.get_schema(request=request))
@ -432,21 +449,10 @@ representation.
)
@api_view()
@renderer_classes([renderers.CoreJSONRenderer])
@renderer_classes([renderers.OpenAPIRenderer])
def schema_view(request):
return response.Response(schema)
## Static schema file
A final option is to write your API schema as a static file, using one
of the available formats, such as Core JSON or Open API.
You could then either:
* Write a schema definition as a static file, and [serve the static file directly][static-files].
* Write a schema definition that is loaded using `Core API`, and then
rendered to one of many available formats, depending on the client request.
---
# Schemas as documentation
@ -535,7 +541,7 @@ Arguments:
Returns a `coreapi.Document` instance that represents the API schema.
@api_view
@renderer_classes([renderers.CoreJSONRenderer])
@renderer_classes([renderers.OpenAPIRenderer])
def schema_view(request):
generator = schemas.SchemaGenerator(title='Bookings API')
return Response(generator.get_schema())
@ -818,23 +824,15 @@ A short description of the meaning and intended usage of the input field.
## drf-yasg - Yet Another Swagger Generator
[drf-yasg][drf-yasg] generates [OpenAPI][open-api] documents suitable for code generation - nested schemas,
[drf-yasg][drf-yasg] generates [OpenAPI][open-api] documents suitable for code generation - nested schemas,
named models, response bodies, enum/pattern/min/max validators, form parameters, etc.
## DRF OpenAPI
[DRF OpenAPI][drf-openapi] renders the schema generated by Django Rest Framework
in [OpenAPI][open-api] format.
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
[coreapi]: http://www.coreapi.org/
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[coreapi]: https://www.coreapi.org/
[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
[open-api]: https://openapis.org/
[drf-openapi]: https://github.com/limdauto/drf_openapi
[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[json-hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/stable/topics/http/urls/#named-groups

View File

@ -57,10 +57,10 @@ At this point we've translated the model instance into Python native datatypes.
Deserialization is similar. First we parse a stream into Python native datatypes...
from django.utils.six import BytesIO
import io
from rest_framework.parsers import JSONParser
stream = BytesIO(json)
stream = io.BytesIO(json)
data = JSONParser().parse(stream)
...then we restore those native datatypes into a dictionary of validated data.
@ -152,7 +152,7 @@ When deserializing data, you always need to call `is_valid()` before attempting
serializer.is_valid()
# False
serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field. The `non_field_errors` key may also be present, and will list any general validation errors. The name of the `non_field_errors` key may be customized using the `NON_FIELD_ERRORS_KEY` REST framework setting.
@ -208,7 +208,7 @@ To do any other validation that requires access to multiple fields, add a method
def validate(self, data):
"""
Check that the start is before the stop.
Check that start is before finish.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
@ -253,7 +253,7 @@ When passing data to a serializer instance, the unmodified data will be made ava
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates.
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
## Dealing with nested objects
@ -293,7 +293,7 @@ When dealing with nested representations that support deserializing the data, an
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}
Similarly, the `.validated_data` property will include nested data structures.
@ -415,7 +415,7 @@ You can provide arbitrary additional context by passing a `context` argument whe
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
# {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
The context dictionary can be used within any serializer field logic, such as a custom `.to_representation()` method, by accessing the `self.context` attribute.
@ -572,6 +572,8 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
user.save()
return user
Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored.
## Relational fields
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances.
@ -624,7 +626,7 @@ The default implementation returns a serializer class based on the `serializer_f
Called to generate a serializer field that maps to a relational model field.
The default implementation returns a serializer class based on the `serializer_relational_field` attribute.
The default implementation returns a serializer class based on the `serializer_related_field` attribute.
The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
@ -963,7 +965,7 @@ The following class is an example of a generic serializer that can handle coerci
def to_representation(self, obj):
for attribute_name in dir(obj):
attribute = getattr(obj, attribute_name)
if attribute_name('_'):
if attribute_name.startswith('_'):
# Ignore private attributes.
pass
elif hasattr(attribute, '__call__'):
@ -1094,10 +1096,10 @@ This would then allow you to do the following:
>>> model = User
>>> fields = ('id', 'username', 'email')
>>>
>>> print UserSerializer(user)
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print UserSerializer(user, fields=('id', 'email'))
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}
## Customizing the default fields

View File

@ -119,7 +119,7 @@ Extends [Django's existing `Client` class][client].
## Making requests
The `APIClient` class supports the same request interface as Django's standard `Client` class. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example:
The `APIClient` class supports the same request interface as Django's standard `Client` class. This means that the standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example:
from rest_framework.test import APIClient
@ -209,7 +209,7 @@ directly.
Note that the requests client requires you to pass fully qualified URLs.
## `RequestsClient` and working with the database
## RequestsClient and working with the database
The `RequestsClient` class is useful if you want to write tests that solely interact with the service interface. This is a little stricter than using the standard Django test client, as it means that all interactions should be via the API.

View File

@ -82,8 +82,10 @@ The throttle classes provided by REST framework use Django's cache backend. You
If you need to use a cache other than `'default'`, you can do so by creating a custom throttle class and setting the `cache` attribute. For example:
from django.core.cache import caches
class CustomAnonRateThrottle(AnonRateThrottle):
cache = get_cache('alternate')
cache = caches['alternate']
You'll need to remember to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute.

View File

@ -100,7 +100,7 @@ The validator should be applied to *serializer classes*, like so:
---
**Note**: The `UniqueTogetherValidation` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
**Note**: The `UniqueTogetherValidator` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
---
@ -159,7 +159,7 @@ If you want the date field to be entirely hidden from the user, then use `Hidden
---
**Note**: The `UniqueFor<Range>Validation` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
**Note**: The `UniqueFor<Range>Validator` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
---
@ -275,7 +275,7 @@ A validator may be any callable that raises a `serializers.ValidationError` on f
You can specify custom field-level validation by adding `.validate_<field_name>` methods
to your `Serializer` subclass. This is documented in the
[Serializer docs](http://www.django-rest-framework.org/api-guide/serializers/#field-level-validation)
[Serializer docs](https://www.django-rest-framework.org/api-guide/serializers/#field-level-validation)
## Class-based

View File

@ -127,7 +127,7 @@ You may inspect these attributes to adjust behaviour based on the current action
## Marking extra actions for routing
If you have ad-hoc methods that should be routable, you can mark them as such with the `@action` decorator. Like regular actions, extra actions may be intended for either a list of objects, or a single instance. To indicate this, set the `detail` argument to `True` or `False`. The router will configure its URL patterns accordingly. e.g., the `DefaultRouter` will configure detail actions to contain `pk` in their URL patterns.
If you have ad-hoc methods that should be routable, you can mark them as such with the `@action` decorator. Like regular actions, extra actions may be intended for either a single object, or an entire collection. To indicate this, set the `detail` argument to `True` or `False`. The router will configure its URL patterns accordingly. e.g., the `DefaultRouter` will configure detail actions to contain `pk` in their URL patterns.
A more complete example of extra actions:
@ -158,7 +158,7 @@ A more complete example of extra actions:
@action(detail=False)
def recent_users(self, request):
recent_users = User.objects.all().order('-last_login')
recent_users = User.objects.all().order_by('-last_login')
page = self.paginate_queryset(recent_users)
if page is not None:
@ -174,7 +174,7 @@ The decorator can additionally take extra arguments that will be set for the rou
def set_password(self, request, pk=None):
...
These decorator will route `GET` requests by default, but may also accept other HTTP methods by setting the `methods` argument. For example:
The `action` decorator will route `GET` requests by default, but may also accept other HTTP methods by setting the `methods` argument. For example:
@action(detail=True, methods=['post', 'delete'])
def unset_password(self, request, pk=None):
@ -186,7 +186,7 @@ To view all extra actions, call the `.get_extra_actions()` method.
### Routing additional HTTP methods for extra actions
Extra actions can be mapped to different `ViewSet` methods. For example, the above password set/unset methods could be consolidated into a single route. Note that additional mappings do not accept arguments.
Extra actions can map additional HTTP methods to separate `ViewSet` methods. For example, the above password set/unset methods could be consolidated into a single route. Note that additional mappings do not accept arguments.
```python
@action(detail=True, methods=['put'], name='Change Password')
@ -314,5 +314,5 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple viewsets across your API.
[cite]: http://guides.rubyonrails.org/routing.html
[cite]: https://guides.rubyonrails.org/routing.html
[routers]: routers.md

View File

@ -389,7 +389,7 @@ You can include `expiry_date` as a field option on a `ModelSerializer` class.
These fields will be mapped to `serializers.ReadOnlyField()` instances.
>>> serializer = InvitationSerializer()
>>> print repr(serializer)
>>> print(repr(serializer))
InvitationSerializer():
to_email = EmailField(max_length=75)
message = CharField(max_length=1000)
@ -523,7 +523,7 @@ The following class is an example of a generic serializer that can handle coerci
def to_representation(self, obj):
for attribute_name in dir(obj):
attribute = getattr(obj, attribute_name)
if attribute_name('_'):
if attribute_name.startswith('_'):
# Ignore private attributes.
pass
elif hasattr(attribute, '__call__'):
@ -960,6 +960,6 @@ The 3.2 release is planned to introduce an alternative admin-style interface to
You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/encode/django-rest-framework/milestones).
[kickstarter]: https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[sponsors]: https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files

View File

@ -30,7 +30,7 @@ Note that as a result of this work a number of settings keys and generic view at
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api) on the subject.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api) on the subject.
#### Pagination controls in the browsable API.
@ -114,7 +114,7 @@ Note that the structure of the error responses is still the same. We still have
We include built-in translations both for standard exception cases, and for serializer validation errors.
The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/projects/p/django-rest-framework/).
The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/django-rest-framework-1/django-rest-framework/).
If you only wish to support a subset of the supported languages, use Django's standard `LANGUAGES` setting:
@ -155,14 +155,14 @@ We've now moved a number of packages out of the core of REST framework, and into
We're making this change in order to help distribute the maintenance workload, and keep better focus of the core essentials of the framework.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/jazzband/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.
The following packages are now moved out of core and should be separately installed:
* OAuth - [djangorestframework-oauth](https://jpadilla.github.io/django-rest-framework-oauth/)
* XML - [djangorestframework-xml](https://jpadilla.github.io/django-rest-framework-xml)
* YAML - [djangorestframework-yaml](https://jpadilla.github.io/django-rest-framework-yaml)
* JSONP - [djangorestframework-jsonp](https://jpadilla.github.io/django-rest-framework-jsonp)
* XML - [djangorestframework-xml](https://jpadilla.github.io/django-rest-framework-xml/)
* YAML - [djangorestframework-yaml](https://jpadilla.github.io/django-rest-framework-yaml/)
* JSONP - [djangorestframework-jsonp](https://jpadilla.github.io/django-rest-framework-jsonp/)
It's worth reiterating that this change in policy shouldn't mean any work in your codebase other than adding a new requirement and modifying some import paths. For example to install XML rendering, you would now do:

View File

@ -10,7 +10,7 @@ We've also fixed a huge number of issues, and made numerous cleanups and improve
Over the course of the 3.1.x series we've [resolved nearly 600 tickets](https://github.com/encode/django-rest-framework/issues?utf8=%E2%9C%93&q=closed%3A%3E2015-03-05) on our GitHub issue tracker. This means we're currently running at a rate of **closing around 100 issues or pull requests per month**.
None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors) and finding out who's hiring.
None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors) and finding out who's hiring.
## AdminRenderer

View File

@ -36,13 +36,13 @@ Right now we're over 60% of the way towards achieving that.
*Every single sign-up makes a significant impact.*
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?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>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).*
*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).*
---
@ -178,12 +178,12 @@ The full set of itemized release notes [are available here][release-notes].
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[moss]: mozilla-grant.md
[funding]: funding.md
[core-api]: http://www.coreapi.org/
[core-api]: https://www.coreapi.org/
[command-line-client]: api-clients#command-line-client
[client-library]: api-clients#python-client-library
[core-json]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[core-json]: https://www.coreapi.org/specification/encoding/#core-json-encoding
[swagger]: https://openapis.org/specification
[hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[tut-7]: ../tutorial/7-schemas-and-client-libraries/
[schema-generation]: ../api-guide/schemas/

View File

@ -32,14 +32,14 @@ we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?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://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [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), and [Machinalis](https://www.machinalis.com/#services).*
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](https://www.machinalis.com/#services).*
---

View File

@ -39,16 +39,16 @@ we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/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://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://machinalis.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
<li><a href="https://micropyramid.com/django-rest-framework-development-services/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/mp-text-logo.png)">MicroPyramid</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [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), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
---

View File

@ -33,15 +33,15 @@ If you use REST framework commercially and would like to see this work continue,
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/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://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://machinalis.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*As well as our release sponsor, we'd like to say thanks in particular 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), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
*As well as our release sponsor, we'd like to say thanks in particular our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), and [Rollbar](https://rollbar.com).*
---

View File

@ -30,7 +30,7 @@ If you use REST framework commercially and would like to see this work continue,
**[signing up for a paid&nbsp;plan][funding]**.
*We'd like to say thanks in particular 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), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
*We'd like to say thanks in particular our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), and [Rollbar](https://rollbar.com).*
---

View File

@ -0,0 +1,212 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.9
The 3.9 release gives access to _extra actions_ in the Browsable API, introduces composable permissions and built-in [OpenAPI][openapi] schema support. (Formerly known as Swagger)
---
## Funding
If you use REST framework commercially and would like to see this work continue, we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<ul class="premium-promo promo">
<li><a href="https://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/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://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.png)">Kloudless</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](https://www.rover.com/careers/), [Sentry](https://sentry.io/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), and [Kloudless](https://hubs.ly/H0f30Lf0).*
---
## Built-in OpenAPI schema support
REST framework now has a first-pass at directly including OpenAPI schema support. (Formerly known as Swagger)
Specifically:
* There are now `OpenAPIRenderer`, and `JSONOpenAPIRenderer` classes that deal with encoding `coreapi.Document` instances into OpenAPI YAML or OpenAPI JSON.
* The `get_schema_view(...)` method now defaults to OpenAPI YAML, with CoreJSON as a secondary
option if it is selected via HTTP content negotiation.
* There is a new management command `generateschema`, which you can use to dump
the schema into your repository.
Here's an example of adding an OpenAPI schema to the URL conf:
```python
from rest_framework.schemas import get_schema_view
from rest_framework.renderers import JSONOpenAPIRenderer
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
renderer_classes=[JSONOpenAPIRenderer]
)
urlpatterns = [
url('^schema.json$', schema_view),
...
]
```
And here's how you can use the `generateschema` management command:
```shell
$ python manage.py generateschema --format openapi > schema.yml
```
There's lots of different tooling that you can use for working with OpenAPI
schemas. One option that we're working on is the [API Star](https://docs.apistar.com/)
command line tool.
You can use `apistar` to validate your API schema:
```shell
$ apistar validate --path schema.json --format openapi
✓ Valid OpenAPI schema.
```
Or to build API documentation:
```shell
$ apistar docs --path schema.json --format openapi
✓ Documentation built at "build/index.html".
```
API Star also includes a [dynamic client library](https://docs.apistar.com/client-library/)
that uses an API schema to automatically provide a client library interface for making requests.
## Composable permission classes
You can now compose permission classes using the and/or operators, `&` and `|`.
For example...
```python
permission_classes = [IsAuthenticated & (ReadOnly | IsAdmin)]
```
If you're using custom permission classes then make sure that you are subclassing
from `BasePermission` in order to enable this support.
## ViewSet _Extra Actions_ available in the Browsable API
Following the introduction of the `action` decorator in v3.8, _extra actions_ defined on a ViewSet are now available
from the Browsable API.
![Extra Actions displayed in the Browsable API](https://user-images.githubusercontent.com/2370209/32976956-1ca9ab7e-cbf1-11e7-981a-a20cb1e83d63.png)
When defined, a dropdown of "Extra Actions", appropriately filtered to detail/non-detail actions, is displayed.
---
## Supported Versions
REST framework 3.9 supports Django versions 1.11, 2.0, and 2.1.
---
## Deprecations
### `DjangoObjectPermissionsFilter` moved to third-party package.
The `DjangoObjectPermissionsFilter` class is pending deprecation, will be deprecated in 3.10 and removed entirely in 3.11.
It has been moved to the third-party [`djangorestframework-guardian`](https://github.com/rpkilby/django-rest-framework-guardian)
package. Please use this instead.
### Router argument/method renamed to use `basename` for consistency.
* The `Router.register` `base_name` argument has been renamed in favor of `basename`.
* The `Router.get_default_base_name` method has been renamed in favor of `Router.get_default_basename`. [#5990][gh5990]
See [#5990][gh5990].
[gh5990]: https://github.com/encode/django-rest-framework/pull/5990
`base_name` and `get_default_base_name()` are pending deprecation. They will be deprecated in 3.10 and removed entirely in 3.11.
### `action` decorator replaces `list_route` and `detail_route`
Both `list_route` and `detail_route` are now deprecated in favour of the single `action` decorator.
They will be removed entirely in 3.10.
The `action` decorator takes a boolean `detail` argument.
* Replace `detail_route` uses with `@action(detail=True)`.
* Replace `list_route` uses with `@action(detail=False)`.
### `exclude_from_schema`
Both `APIView.exclude_from_schema` and the `exclude_from_schema` argument to the `@api_view` have now been removed.
For `APIView` you should instead set a `schema = None` attribute on the view class.
For function-based views the `@schema` decorator can be used to exclude the view from the schema, by using `@schema(None)`.
---
## Minor fixes and improvements
There are a large number of minor fixes and improvements in this release. See the [release notes](release-notes.md) page for a complete listing.
## What's next
We're planning to iteratively work towards OpenAPI becoming the standard schema
representation. This will mean that the `coreapi` dependency will gradually become
removed, and we'll instead generate the schema directly, rather than building
a CoreAPI `Document` object.
OpenAPI has clearly become the standard for specifying Web APIs, so there's not
much value any more in our schema-agnostic document model. Making this change
will mean that we'll more easily be able to take advantage of the full set of
OpenAPI functionality.
This will also make a wider range of tooling available.
We'll focus on continuing to develop the [API Star](https://docs.apistar.com/)
library and client tool into a recommended option for generating API docs,
validating API schemas, and providing a dynamic client library.
There's also a huge amount of ongoing work on maturing the ASGI landscape,
with the possibility that some of this work will eventually [feed back into
Django](https://www.aeracode.org/2018/06/04/django-async-roadmap/).
There will be further work on the [Uvicorn](https://www.uvicorn.org/)
web server, as well as lots of functionality planned for the [Starlette](https://www.starlette.io/)
web framework, which is building a foundational set of tooling for working with
ASGI.
[funding]: funding.md
[gh5886]: https://github.com/encode/django-rest-framework/issues/5886
[gh5705]: https://github.com/encode/django-rest-framework/issues/5705
[openapi]: https://www.openapis.org/
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors

View File

@ -65,7 +65,7 @@ Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recom
To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
python3 -m venv env
source env/bin/activate
pip install django
pip install -r requirements.txt
@ -121,7 +121,7 @@ It's also useful to remember that if you have an outstanding pull request then p
GitHub's documentation for working on pull requests is [available here][pull-requests].
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django.
Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.

View File

@ -123,10 +123,10 @@ REST framework continues to be open-source and permissively licensed, but we fir
## What funding has enabled so far
* The [3.4](http://www.django-rest-framework.org/topics/3.4-announcement/) and [3.5](http://www.django-rest-framework.org/topics/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
* The [3.6](http://www.django-rest-framework.org/topics/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
* The [3.7 release](http://www.django-rest-framework.org/topics/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
* The recent [3.8 release](http://www.django-rest-framework.org/topics/3.8-announcement/).
* The [3.4](https://www.django-rest-framework.org/community/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/community/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
* The [3.7 release](https://www.django-rest-framework.org/community/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
* The recent [3.8 release](https://www.django-rest-framework.org/community/3.8-announcement/).
* Tom Christie, the creator of Django REST framework, working on the project full-time.
* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
@ -341,7 +341,7 @@ For further enquires please contact <a href=mailto:funding@django-rest-framework
## Accountability
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/march-2018) and regularly include financial reports and cost breakdowns.
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](https://www.encode.io/reports/march-2018/) and regularly include financial reports and cost breakdowns.
<!-- Begin MailChimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">

View File

@ -9,14 +9,12 @@ Looking for a new Django REST Framework related role? On this site we provide a
* [https://www.python.org/jobs/][python-org-jobs]
* [https://djangogigs.com][django-gigs-com]
* [https://djangojobs.net/jobs/][django-jobs-net]
* [http://djangojobbers.com][django-jobbers-com]
* [https://www.indeed.com/q-Django-jobs.html][indeed-com]
* [https://stackoverflow.com/jobs/developer-jobs-using-django][stackoverflow-com]
* [https://www.upwork.com/o/jobs/browse/skill/django-framework/][upwork-com]
* [https://www.technojobs.co.uk/django-jobs][technobjobs-co-uk]
* [https://remoteok.io/remote-django-jobs][remoteok-io]
* [https://www.remotepython.com/jobs/][remotepython-com]
* [https://weworkcontract.com/python-contract-jobs][weworkcontract-com]
Know of any other great resources for Django REST Framework jobs that are missing in our list? Please [submit a pull request][submit-pr] or [email us][anna-email].
@ -28,14 +26,12 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram
[python-org-jobs]: https://www.python.org/jobs/
[django-gigs-com]: https://djangogigs.com
[django-jobs-net]: https://djangojobs.net/jobs/
[django-jobbers-com]: http://djangojobbers.com
[indeed-com]: https://www.indeed.com/q-Django-jobs.html
[stackoverflow-com]: https://stackoverflow.com/jobs/developer-jobs-using-django
[upwork-com]: https://www.upwork.com/o/jobs/browse/skill/django-framework/
[technobjobs-co-uk]: https://www.technojobs.co.uk/django-jobs
[remoteok-io]: https://remoteok.io/remote-django-jobs
[remotepython-com]: https://www.remotepython.com/jobs/
[weworkcontract-com]: https://weworkcontract.com/python-contract-jobs
[drf-funding]: https://fund.django-rest-framework.org/topics/funding/
[submit-pr]: https://github.com/encode/django-rest-framework
[anna-email]: mailto:anna@django-rest-framework.org

View File

@ -47,8 +47,8 @@ Our platinum sponsors have each made a hugely substantial contribution to the fu
</ul>
<ul class="sponsor platinum">
<li><a href="https://www.divio.ch/" rel="nofollow" style="background-image:url(../../img/sponsors/1-divio.png);">Divio</a></li>
<li><a href="http://company.onlulu.com/en/" rel="nofollow" style="background-image:url(../../img/sponsors/1-lulu.png);">Lulu</a></li>
<li><a href="https://www.divio.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-divio.png);">Divio</a></li>
<li><a href="https://onlulu.com" rel="nofollow" style="background-image:url(../../img/sponsors/1-lulu.png);">Lulu</a></li>
<li><a href="https://p.ota.to/" rel="nofollow" style="background-image:url(../../img/sponsors/1-potato.png);">Potato</a></li>
<li><a href="http://www.wiredrive.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-wiredrive.png);">Wiredrive</a></li>
<li><a href="http://www.cyaninc.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-cyan.png);">Cyan</a></li>
@ -78,11 +78,11 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="https://mirusresearch.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-mirus_research.png);">Mirus Research</a></li>
<li><a href="https://hipolabs.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipo.png);">Hipo</a></li>
<li><a href="https://www.byte.nl/" rel="nofollow" style="background-image:url(../../img/sponsors/2-byte.png);">Byte</a></li>
<li><a href="http://lightningkite.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-lightning_kite.png);">Lightning Kite</a></li>
<li><a href="https://www.lightningkite.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-lightning_kite.png);">Lightning Kite</a></li>
<li><a href="https://opbeat.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-opbeat.png);">Opbeat</a></li>
<li><a href="https://koordinates.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-koordinates.png);">Koordinates</a></li>
<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-heroku.png);">Heroku</a></li>
<li><a href="https://www.rheinwerk-verlag.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-rheinwerk_verlag.png);">Rheinwerk Verlag</a></li>
<li><a href="https://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-security_compass.png);">Security Compass</a></li>
@ -90,9 +90,9 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li>
<li><a href="http://www.crate.io/" rel="nofollow" style="background-image:url(../../img/sponsors/2-crate.png);">Crate</a></li>
<li><a href="http://crypticocorp.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-cryptico.png);">Cryptico Corp</a></li>
<li><a href="http://www.nexthub.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-nexthub.png);">NextHub</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-nexthub.png);">NextHub</a></li>
<li><a href="https://www.compile.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-compile.png);">Compile</a></li>
<li><a href="http://wusawork.org" rel="nofollow" style="background-image:url(../../img/sponsors/2-wusawork.png);">WusaWork</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-wusawork.png);">WusaWork</a></li>
<li><a href="http://envisionlinux.org/blog" rel="nofollow">Envision Linux</a></li>
</ul>
@ -116,7 +116,7 @@ The serious financial contribution that our silver sponsors have made is very mu
<li><a href="https://garfo.io/" rel="nofollow" style="background-image:url(../../img/sponsors/3-garfo.png);">Garfo</a></li>
<li><a href="https://goshippo.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-shippo.png);">Shippo</a></li>
<li><a href="http://www.gizmag.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-gizmag.png);">Gizmag</a></li>
<li><a href="http://www.tivix.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-tivix.png);">Tivix</a></li>
<li><a href="https://www.tivix.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-tivix.png);">Tivix</a></li>
<li><a href="https://www.safaribooksonline.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-safari.png);">Safari</a></li>
<li><a href="http://brightloop.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-brightloop.png);">Bright Loop</a></li>
<li><a href="http://www.aba-systems.com.au/" rel="nofollow" style="background-image:url(../../img/sponsors/3-aba.png);">ABA Systems</a></li>
@ -131,7 +131,7 @@ The serious financial contribution that our silver sponsors have made is very mu
<li><a href="https://fluxility.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-fluxility.png);">Fluxility</a></li>
<li><a href="https://teonite.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-teonite.png);">Teonite</a></li>
<li><a href="https://trackmaven.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-trackmaven.png);">TrackMaven</a></li>
<li><a href="http://www.phurba.net/" rel="nofollow" style="background-image:url(../../img/sponsors/3-phurba.png);">Phurba</a></li>
<li><a href="https://www.phurba.net/" rel="nofollow" style="background-image:url(../../img/sponsors/3-phurba.png);">Phurba</a></li>
<li><a href="https://www.nephila.it/it/" rel="nofollow" style="background-image:url(../../img/sponsors/3-nephila.png);">Nephila</a></li>
<li><a href="http://www.aditium.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-aditium.png);">Aditium</a></li>
<li><a href="https://www.eyesopen.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-openeye.png);">OpenEye Scientific Software</a></li>

View File

@ -4,7 +4,7 @@ We have recently been [awarded a Mozilla grant](https://blog.mozilla.org/blog/20
Additionally, we will be building on the realtime support that Django Channels provides, supporting and documenting how to build realtime APIs with REST framework. Again, this will include supporting work in the associated client libraries, making it easier to build richly interactive applications.
The [Core API](http://www.coreapi.org) project will provide the foundations for our client library support, and will allow us to support interaction using a wide range of schemas and hypermedia formats. It's worth noting that these client libraries won't be tightly coupled to solely REST framework APIs either, and will be able to interact with *any* API that exposes a supported schema or hypermedia format.
The [Core API](https://www.coreapi.org/) project will provide the foundations for our client library support, and will allow us to support interaction using a wide range of schemas and hypermedia formats. It's worth noting that these client libraries won't be tightly coupled to solely REST framework APIs either, and will be able to interact with *any* API that exposes a supported schema or hypermedia format.
Specifically, the work includes:
@ -34,7 +34,7 @@ In order to ensure that I can be fully focused on trying to secure a sustainable
& well-funded open source business I will be leaving my current role at [DabApps](https://www.dabapps.com/)
at the end of May 2016.
I have formed a UK limited company, [Encode](http://www.encode.io), which will
I have formed a UK limited company, [Encode](https://www.encode.io/), which will
act as the business entity behind REST framework. I will be issuing monthly reports
from Encode on progress both towards the Mozilla grant, and for development time
funded via the [REST framework paid plans](funding.md).

View File

@ -39,7 +39,7 @@ The following template should be used for the description of the issue, and serv
This issue is for determining the maintenance team for the *** period.
Please see the [Project management](http://www.django-rest-framework.org/topics/project-management/) section of our documentation for more details.
Please see the [Project management](https://www.django-rest-framework.org/topics/project-management/) section of our documentation for more details.
---
@ -59,7 +59,7 @@ The following template should be used for the description of the issue, and serv
If you wish to be considered for this or a future date, please comment against this or subsequent issues.
To modify this process for future maintenance cycles make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
To modify this process for future maintenance cycles make a pull request to the [project management](https://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members
@ -99,7 +99,7 @@ The following template should be used for the description of the issue, and serv
During development cycle:
- [ ] Upload the new content to be translated to [transifex](http://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Upload the new content to be translated to [transifex](https://www.django-rest-framework.org/topics/project-management/#translations).
Checklist:
@ -110,7 +110,7 @@ The following template should be used for the description of the issue, and serv
- [ ] `setup.py` Python & Django version trove classifiers
- [ ] `README` Python & Django versions
- [ ] `docs` Python & Django versions
- [ ] Update the translations from [transifex](http://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Update the translations from [transifex](https://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py).
- [ ] Confirm with @tomchristie that release is finalized and ready to go.
- [ ] Ensure that release date is included in pull request.
@ -122,7 +122,7 @@ The following template should be used for the description of the issue, and serv
- [ ] Make a release announcement on twitter.
- [ ] Close the milestone on GitHub.
To modify this process for future releases make a pull request to the [project management](http://www.django-rest-framework.org/topics/project-management/) documentation.
To modify this process for future releases make a pull request to the [project management](https://www.django-rest-framework.org/topics/project-management/) documentation.
When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
@ -152,7 +152,7 @@ When any user visible strings are changed, they should be uploaded to Transifex
# 1. Update the source django.po file, which is the US English version.
cd rest_framework
django-admin.py makemessages -l en_US
django-admin makemessages -l en_US
# 2. Push the source django.po file to Transifex.
cd ..
tx push -s
@ -173,7 +173,7 @@ When a translator has finished translating their work needs to be downloaded fro
tx pull -a --minimum-perc 10
cd rest_framework
# 4. Compile the binary .mo files for all supported languages.
django-admin.py compilemessages
django-admin compilemessages
---

View File

@ -18,9 +18,9 @@ REST framework releases follow a formal deprecation policy, which is in line wit
The timeline for deprecation of a feature present in version 1.0 would work as follows:
* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `PendingDeprecationWarning` warnings if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `RemovedInDRF13Warning` warnings, subclassing `PendingDeprecationWarning`, if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
* Version 1.2 would escalate these warnings to `DeprecationWarning`, which is loud by default.
* Version 1.2 would escalate these warnings to subclass `DeprecationWarning`, which is loud by default.
* Version 1.3 would remove the deprecated bits of API entirely.
@ -40,12 +40,99 @@ You can determine your currently installed version using `pip show`:
## 3.9.x series
### 3.9.4
**Date**: [10th May 2019]
This is a maintenance release that fixes an error handling bug under Python 2.
### 3.9.3
**Date**: [29th April 2019]
This is the last Django REST Framework release that will support Python 2.
Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
* Adjusted the compat check for django-guardian to allow the last guardian
version (v1.4.9) compatible with Python 2. [#6613][gh6613]
### 3.9.2
**Date**: [3rd March 2019][3.9.1-milestone]
* Routers: invalidate `_urls` cache on `register()` [#6407][gh6407]
* Deferred schema renderer creation to avoid requiring pyyaml. [#6416][gh6416]
* Added 'request_forms' block to base.html [#6340][gh6340]
* Fixed SchemaView to reset renderer on exception. [#6429][gh6429]
* Update Django Guardian dependency. [#6430][gh6430]
* Ensured support for Django 2.2 [#6422][gh6422] & [#6455][gh6455]
* Made templates compatible with session-based CSRF. [#6207][gh6207]
* Adjusted field `validators` to accept non-list iterables. [#6282][gh6282]
* Added SearchFilter.get_search_fields() hook. [#6279][gh6279]
* Fix DeprecationWarning when accessing collections.abc classes via collections [#6268][gh6268]
* Allowed Q objects in limit_choices_to introspection. [#6472][gh6472]
* Added lazy evaluation to composed permissions. [#6463][gh6463]
* Add negation ~ operator to permissions composition [#6361][gh6361]
* Avoided calling distinct on annotated fields in SearchFilter. [#6240][gh6240]
* Introduced `RemovedInDRF…Warning` classes to simplify deprecations. [#6480][gh6480]
### 3.9.1
**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.
* Resolve issues with composable permissions. [#6299][gh6299]
* Respect `limit_choices_to` on foreign keys. [#6371][gh6371]
### 3.9.0
**Date**: Unreleased
**Date**: [18th October 2018][3.9.0-milestone]
* Improvements to ViewSet extra actions [#5605][gh5605]
* Fix `action` support for ViewSet suffixes [#6081][gh6081]
* Allow `action` docs sections [#6060][gh6060]
* Deprecate the `Router.register` `base_name` argument in favor of `basename`. [#5990][gh5990]
* Deprecate the `Router.get_default_base_name` method in favor of `Router.get_default_basename`. [#5990][gh5990]
* Change `CharField` to disallow null bytes. [#6073][gh6073]
To revert to the old behavior, subclass `CharField` and remove `ProhibitNullCharactersValidator` from the validators.
```python
class NullableCharField(serializers.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.validators = [v for v in self.validators if not isinstance(v, ProhibitNullCharactersValidator)]
```
* Add `OpenAPIRenderer` and `generate_schema` management command. [#6229][gh6229]
* Add OpenAPIRenderer by default, and add schema docs. [#6233][gh6233]
* Allow permissions to be composed [#5753][gh5753]
* Allow nullable BooleanField in Django 2.1 [#6183][gh6183]
* Add testing of Python 3.7 support [#6141][gh6141]
* Test using Django 2.1 final release. [#6109][gh6109]
* Added djangorestframework-datatables to third-party packages [#5931][gh5931]
* Change ISO 8601 date format to exclude year/month [#5936][gh5936]
* Update all pypi.python.org URLs to pypi.org [#5942][gh5942]
* Ensure that html forms (multipart form data) respect optional fields [#5927][gh5927]
* Allow hashing of ErrorDetail. [#5932][gh5932]
* Correct schema parsing for JSONField [#5878][gh5878]
* Render descriptions (from help_text) using safe [#5869][gh5869]
* Removed input value from deault_error_message [#5881][gh5881]
* Added min_value/max_value support in DurationField [#5643][gh5643]
* Fixed instance being overwritten in pk-only optimization try/except block [#5747][gh5747]
* Fixed AttributeError from items filter when value is None [#5981][gh5981]
* Fixed Javascript `e.indexOf` is not a function error [#5982][gh5982]
* Fix schemas for extra actions [#5992][gh5992]
* Improved get_error_detail to use error_dict/error_list [#5785][gh5785]
* Imprvied URLs in Admin renderer [#5988][gh5988]
* Add "Community" section to docs, minor cleanup [#5993][gh5993]
* Moved guardian imports out of compat [#6054][gh6054]
* Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075]
* Drop Django 1.10 support [#5657][gh5657]
* Only catch TypeError/ValueError for object lookups [#6028][gh6028]
* Handle models without .objects manager in ModelSerializer. [#6111][gh6111]
* Improve ModelSerializer.create() error message. [#6112][gh6112]
* Fix CSRF cookie check failure when using session auth with django 1.11.6+ [#6113][gh6113]
* Updated JWT docs. [#6138][gh6138]
* Fix autoescape not getting passed to urlize_quoted_links filter [#6191][gh6191]
## 3.8.x series
@ -1092,6 +1179,9 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.8.0-milestone]: https://github.com/encode/django-rest-framework/milestone/61?closed=1
[3.8.1-milestone]: https://github.com/encode/django-rest-framework/milestone/67?closed=1
[3.8.2-milestone]: https://github.com/encode/django-rest-framework/milestone/68?closed=1
[3.9.0-milestone]: https://github.com/encode/django-rest-framework/milestone/66?closed=1
[3.9.1-milestone]: https://github.com/encode/django-rest-framework/milestone/70?closed=1
[3.9.1-milestone]: https://github.com/encode/django-rest-framework/milestone/71?closed=1
<!-- 3.0.1 -->
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
@ -1973,4 +2063,65 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh5920]: https://github.com/encode/django-rest-framework/issues/5920
<!-- 3.9.0 -->
[gh6109]: https://github.com/encode/django-rest-framework/issues/6109
[gh6141]: https://github.com/encode/django-rest-framework/issues/6141
[gh6113]: https://github.com/encode/django-rest-framework/issues/6113
[gh6112]: https://github.com/encode/django-rest-framework/issues/6112
[gh6111]: https://github.com/encode/django-rest-framework/issues/6111
[gh6028]: https://github.com/encode/django-rest-framework/issues/6028
[gh5657]: https://github.com/encode/django-rest-framework/issues/5657
[gh6054]: https://github.com/encode/django-rest-framework/issues/6054
[gh5993]: https://github.com/encode/django-rest-framework/issues/5993
[gh5990]: https://github.com/encode/django-rest-framework/issues/5990
[gh5988]: https://github.com/encode/django-rest-framework/issues/5988
[gh5785]: https://github.com/encode/django-rest-framework/issues/5785
[gh5992]: https://github.com/encode/django-rest-framework/issues/5992
[gh5605]: https://github.com/encode/django-rest-framework/issues/5605
[gh5982]: https://github.com/encode/django-rest-framework/issues/5982
[gh5981]: https://github.com/encode/django-rest-framework/issues/5981
[gh5747]: https://github.com/encode/django-rest-framework/issues/5747
[gh5643]: https://github.com/encode/django-rest-framework/issues/5643
[gh5881]: https://github.com/encode/django-rest-framework/issues/5881
[gh5869]: https://github.com/encode/django-rest-framework/issues/5869
[gh5878]: https://github.com/encode/django-rest-framework/issues/5878
[gh5932]: https://github.com/encode/django-rest-framework/issues/5932
[gh5927]: https://github.com/encode/django-rest-framework/issues/5927
[gh5942]: https://github.com/encode/django-rest-framework/issues/5942
[gh5936]: https://github.com/encode/django-rest-framework/issues/5936
[gh5931]: https://github.com/encode/django-rest-framework/issues/5931
[gh6183]: https://github.com/encode/django-rest-framework/issues/6183
[gh6075]: https://github.com/encode/django-rest-framework/issues/6075
[gh6138]: https://github.com/encode/django-rest-framework/issues/6138
[gh6081]: https://github.com/encode/django-rest-framework/issues/6081
[gh6073]: https://github.com/encode/django-rest-framework/issues/6073
[gh6191]: https://github.com/encode/django-rest-framework/issues/6191
[gh6060]: https://github.com/encode/django-rest-framework/issues/6060
[gh6233]: https://github.com/encode/django-rest-framework/issues/6233
[gh5753]: https://github.com/encode/django-rest-framework/issues/5753
[gh6229]: https://github.com/encode/django-rest-framework/issues/6229
<!-- 3.9.1 -->
[gh6330]: https://github.com/encode/django-rest-framework/issues/6330
[gh6299]: https://github.com/encode/django-rest-framework/issues/6299
[gh6371]: https://github.com/encode/django-rest-framework/issues/6371
<!-- 3.9.2 -->
[gh6480]: https://github.com/encode/django-rest-framework/issues/6480
[gh6240]: https://github.com/encode/django-rest-framework/issues/6240
[gh6361]: https://github.com/encode/django-rest-framework/issues/6361
[gh6463]: https://github.com/encode/django-rest-framework/issues/6463
[gh6472]: https://github.com/encode/django-rest-framework/issues/6472
[gh6268]: https://github.com/encode/django-rest-framework/issues/6268
[gh6279]: https://github.com/encode/django-rest-framework/issues/6279
[gh6282]: https://github.com/encode/django-rest-framework/issues/6282
[gh6207]: https://github.com/encode/django-rest-framework/issues/6207
[gh6455]: https://github.com/encode/django-rest-framework/issues/6455
[gh6422]: https://github.com/encode/django-rest-framework/issues/6422
[gh6430]: https://github.com/encode/django-rest-framework/issues/6430
[gh6429]: https://github.com/encode/django-rest-framework/issues/6429
[gh6340]: https://github.com/encode/django-rest-framework/issues/6340
[gh6416]: https://github.com/encode/django-rest-framework/issues/6416
[gh6407]: https://github.com/encode/django-rest-framework/issues/6407
<!-- 3.9.3 -->
[gh6613]: https://github.com/encode/django-rest-framework/issues/6613

View File

@ -183,8 +183,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-digestauth][djangorestframework-digestauth] - Provides Digest Access Authentication support.
* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
* [djangorestframework-simplejwt][djangorestframework-simplejwt] - An alternative package that provides JSON Web Token Authentication support.
* [djangorestframework-simplejwt][djangorestframework-simplejwt] - Provides JSON Web Token Authentication support.
* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
@ -264,6 +263,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM.
* [djangorest-alchemy][djangorest-alchemy] - SQLAlchemy support for REST framework.
* [djangorestframework-datatables][djangorestframework-datatables] - Seamless integration between Django REST framework and [Datatables](https://datatables.net).
* [django-rest-framework-condition][django-rest-framework-condition] - Decorators for managing HTTP cache headers for Django REST framework (ETag and Last-modified).
* [django-rest-witchcraft][django-rest-witchcraft] - Provides DRF integration with SQLAlchemy with SQLAlchemy model serializers/viewsets and a bunch of other goodies
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
@ -337,3 +338,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
[djangorest-alchemy]: https://github.com/dealertrack/djangorest-alchemy
[djangorestframework-datatables]: https://github.com/izimobil/django-rest-framework-datatables
[django-rest-framework-condition]: https://github.com/jozo/django-rest-framework-condition
[django-rest-witchcraft]: https://github.com/shosca/django-rest-witchcraft

View File

@ -85,8 +85,8 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[beginners-guide-to-the-django-rest-framework]: https://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
[getting-started-with-django-rest-framework-and-angularjs]: https://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: https://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/
[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/
[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -66,16 +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://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</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://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), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [Cadre](https://cadre.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).*
---
@ -83,13 +84,16 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Python (2.7, 3.4, 3.5, 3.6)
* Django (1.11, 2.0, 2.1)
* Python (3.5, 3.6, 3.7)
* Django (1.11, 2.0, 2.1, 2.2)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
The following packages are optional:
* [coreapi][coreapi] (1.32.0+) - Schema generation support.
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [Markdown][markdown] (2.6.0+) - Markdown support for the browsable API.
* [django-filter][django-filter] (1.0.1+) - Filtering support.
* [django-crispy-forms][django-crispy-forms] - Improved HTML display for filtering.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
@ -104,7 +108,7 @@ Install using `pip`, including any optional packages you want...
...or clone the project from github.
git clone git@github.com:encode/django-rest-framework.git
git clone https://github.com/encode/django-rest-framework
Add `'rest_framework'` to your `INSTALLED_APPS` setting.
@ -200,17 +204,22 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
## License
Copyright (c) 2011-2017, Tom Christie
Copyright © 2011-present, [Encode OSS Ltd](https://www.encode.io/).
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

View File

@ -521,7 +521,7 @@ You'll either want to include the API schema in your codebase directly, by copyi
})
[heroku-api]: https://devcenter.heroku.com/categories/platform-api
[heroku-example]: http://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema
[core-api]: http://www.coreapi.org/
[heroku-example]: https://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema
[core-api]: https://www.coreapi.org/
[schema-generation]: ../api-guide/schemas.md
[transport-adaptors]: http://docs.python-requests.org/en/master/user/advanced/#transport-adapters

View File

@ -94,6 +94,8 @@ To add branding and customize the look-and-feel of the login template, create a
You can add your site name or branding by including the branding block:
{% extends "rest_framework/login_base.html" %}
{% block branding %}
<h3 style="margin: 0 0 20px;">My Site Name</h3>
{% endblock %}

View File

@ -81,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]: https://www.amazon.com/RESTful-Web-Services-Leonard-Richardson/dp/0596529260
[ajax-form]: https://github.com/encode/ajax-form
[rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
[ajax-form]: https://github.com/tomchristie/ajax-form
[rails]: https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
[html5]: https://www.w3.org/TR/html5-diff/#changes-2010-06-24
[put_delete]: http://amundsen.com/examples/put-delete-forms/

View File

@ -16,11 +16,11 @@ The built-in API documentation includes:
### Installation
The `coreapi` library is required as a dependancy for the API docs. Make sure
to install the latest version. The `pygments` and `markdown` libraries
The `coreapi` library is required as a dependency for the API docs. Make sure
to install the latest version. The `Pygments` and `Markdown` libraries
are optional but recommended.
To install the API documentation, you'll need to include it in your projects URLconf:
To install the API documentation, you'll need to include it in your project's URLconf:
from rest_framework.documentation import include_docs_urls
@ -39,7 +39,7 @@ This will include two different views:
**Note**: By default `include_docs_urls` configures the underlying `SchemaView` to generate _public_ schemas.
This means that views will not be instantiated with a `request` instance. i.e. Inside the view `self.request` will be `None`.
To be compatible with this behaviour methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case.
To be compatible with this behaviour, methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case.
You may ensure views are given a `request` instance by calling `include_docs_urls` with `public=False`:
@ -90,6 +90,28 @@ When using viewsets, you should use the relevant action names as delimiters.
Create a new user instance.
"""
Custom actions on viewsets can also be documented in a similar way using the method names
as delimiters or by attaching the documentation to action mapping methods.
class UserViewSet(viewsets.ModelViewset):
...
@action(detail=False, methods=['get', 'post'])
def some_action(self, request, *args, **kwargs):
"""
get:
A description of the get method on the custom action.
post:
A description of the post method on the custom action.
"""
@some_action.mapping.put
def put_some_action():
"""
A description of the put method on the custom action.
"""
### `documentation` API Reference
@ -171,34 +193,6 @@ This also translates into a very useful interactive documentation viewer in the
![Screenshot - drf-yasg][image-drf-yasg]
#### DRF OpenAPI
[DRF OpenAPI][drf-openapi] bridges the gap between OpenAPI specification and tool chain with the schema exposed
out-of-the-box by Django Rest Framework. Its goals are:
* To be dropped into any existing DRF project without any code change necessary.
* Provide clear disctinction between request schema and response schema.
* Provide a versioning mechanism for each schema. Support defining schema by version range syntax, e.g. >1.0, <=2.0
* Support multiple response codes, not just 200
* All this information should be bound to view methods, not view classes.
It also tries to stay current with the maturing schema generation mechanism provided by DRF.
![Screenshot - DRF OpenAPI][image-drf-openapi]
---
#### DRF Docs
[DRF Docs][drfdocs-repo] allows you to document Web APIs made with Django REST Framework and it is authored by Emmanouil Konstantinidis. It's made to work out of the box and its setup should not take more than a couple of minutes. Complete documentation can be found on the [website][drfdocs-website] while there is also a [demo][drfdocs-demo] available for people to see what it looks like. **Live API Endpoints** allow you to utilize the endpoints from within the documentation in a neat way.
Features include customizing the template with your branding, settings for hiding the docs depending on the environment and more.
Both this package and Django REST Swagger are fully documented, well supported, and come highly recommended.
![Screenshot - DRF docs][image-drf-docs]
---
#### Django REST Swagger
@ -209,7 +203,7 @@ Django REST Swagger supports REST framework versions 2.3 and above.
Mark is also the author of the [REST Framework Docs][rest-framework-docs] package which offers clean, simple autogenerated documentation for your API but is deprecated and has moved to Django REST Swagger.
Both this package and DRF docs are fully documented, well supported, and come highly recommended.
This package is fully documented, well supported, and comes highly recommended.
![Screenshot - Django REST Swagger][image-django-rest-swagger]
@ -271,7 +265,7 @@ When working with viewsets, an appropriate suffix is appended to each generated
The description in the browsable API is generated from the docstring of the view or viewset.
If the python `markdown` library is installed, then [markdown syntax][markdown] may be used in the docstring, and will be converted to HTML in the browsable API. For example:
If the python `Markdown` library is installed, then [markdown syntax][markdown] may be used in the docstring, and will be converted to HTML in the browsable API. For example:
class AccountListView(views.APIView):
"""
@ -313,23 +307,17 @@ In this approach, rather than documenting the available API endpoints up front,
To implement a hypermedia API you'll need to decide on an appropriate media type for the API, and implement a custom renderer and parser for that media type. The [REST, Hypermedia & HATEOAS][hypermedia-docs] section of the documentation includes pointers to background reading, as well as links to various hypermedia formats.
[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[cite]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
[image-drf-yasg]: ../img/drf-yasg.png
[drf-openapi]: https://github.com/limdauto/drf_openapi/
[image-drf-openapi]: ../img/drf-openapi.png
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
[drfdocs-website]: https://www.drfdocs.com/
[drfdocs-demo]: http://demo.drfdocs.com/
[drfautodocs-repo]: https://github.com/iMakedonsky/drf-autodocs
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
[swagger]: https://swagger.io/
[open-api]: https://openapis.org/
[rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs
[apiary]: https://apiary.io/
[markdown]: https://daringfireball.net/projects/markdown/
[markdown]: https://daringfireball.net/projects/markdown/syntax
[hypermedia-docs]: rest-hypermedia-hateoas.md
[image-drf-docs]: ../img/drfdocs.png
[image-django-rest-swagger]: ../img/django-rest-swagger.png
[image-apiary]: ../img/apiary.png
[image-self-describing-api]: ../img/self-describing.png

View File

@ -4,7 +4,7 @@ REST framework is suitable for returning both API style responses, and regular H
## Rendering HTML
In order to return HTML responses you'll need to either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
In order to return HTML responses you'll need to use either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
The `TemplateHTMLRenderer` class expects the response to contain a dictionary of context data, and renders an HTML page based on a template that must be specified either in the view or on the response.

View File

@ -43,7 +43,7 @@ REST framework includes these built-in translations both for standard exception
Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this:
{"detail": {"username": ["Esse campo deve ser unico."]}}
{"detail": {"username": ["Esse campo deve ser único."]}}
If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler].

View File

@ -36,7 +36,7 @@ What REST framework doesn't do is give you machine readable hypermedia formats s
[cite]: https://vimeo.com/channels/restfest/page:2
[dissertation]: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
[hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[hypertext-driven]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[restful-web-apis]: http://restfulwebapis.org/
[building-hypermedia-apis]: https://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578
[designing-hypermedia-apis]: http://designinghypermediaapis.com/

View File

@ -27,7 +27,7 @@ Nested data structures are easy enough to work with if they're read-only - simpl
Some example output from our serializer.
{
'title': 'Leaving party preperations',
'title': 'Leaving party preparations',
'items': [
{'text': 'Compile playlist', 'is_completed': True},
{'text': 'Send invites', 'is_completed': False},

View File

@ -8,24 +8,24 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
---
**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
**Note**: The code for this tutorial is available in the [encode/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
---
## Setting up a new environment
Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
Before we do anything else we'll create a new virtual environment, using [venv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
virtualenv env
python3 -m venv env
source env/bin/activate
Now that we're inside a virtualenv environment, we can install our package requirements.
Now that we're inside a virtual environment, we can install our package requirements.
pip install django
pip install djangorestframework
pip install pygments # We'll be using this for the code highlighting
**Note:** To exit the virtualenv environment at any time, just type `deactivate`. For more information see the [virtualenv documentation][virtualenv].
**Note:** To exit the virtual environment at any time, just type `deactivate`. For more information see the [venv documentation][venv].
## Getting started
@ -33,7 +33,7 @@ Okay, we're ready to get coding.
To get started, let's create a new project to work with.
cd ~
django-admin.py startproject tutorial
django-admin startproject tutorial
cd tutorial
Once that's done we can create an app that we'll use to create a simple Web API.
@ -137,26 +137,26 @@ Okay, once we've got a few imports out of the way, let's create a couple of code
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
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...
from django.utils.six import BytesIO
import io
stream = BytesIO(content)
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
...then we restore those native datatypes into a fully populated object instance.
@ -165,7 +165,7 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
@ -175,7 +175,7 @@ We can also serialize querysets instead of model instances. To do so we simply
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
## Using ModelSerializers
@ -218,7 +218,6 @@ Edit the `snippets/views.py` file, and add the following.
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@ -338,7 +337,7 @@ Finally, we can get a list of all of the snippets:
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@ -354,7 +353,7 @@ Or we can get a particular snippet by referencing its id:
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@ -373,7 +372,7 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[quickstart]: quickstart.md
[repo]: https://github.com/encode/rest-framework-tutorial
[sandbox]: https://restframework.herokuapp.com/
[virtualenv]: http://www.virtualenv.org/en/latest/index.html
[venv]: https://docs.python.org/3/library/venv.html
[tut-2]: 2-requests-and-responses.md
[httpie]: https://github.com/jakubroztocil/httpie#installation
[curl]: https://curl.haxx.se/

View File

@ -143,7 +143,7 @@ We can get a list of all of the snippets, as before.
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@ -163,24 +163,24 @@ Or by appending a format suffix:
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"id": 3,
"title": "",
"code": "print 123",
"code": "print(123)",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
{
"id": 4,
"title": "",
"code": "print 456",
"code": "print(456)",
"linenos": false,
"language": "python",
"style": "friendly"

View File

@ -197,7 +197,7 @@ If we're interacting with the API programmatically we need to explicitly provide
If we try to create a snippet without authenticating, we'll get an error:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
@ -205,13 +205,13 @@ If we try to create a snippet without authenticating, we'll get an error:
We can make a successful request by including the username and password of one of the users we created earlier.
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"

View File

@ -106,7 +106,7 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
After adding all those names into our URLconf, our final `snippets/urls.py` file should look like this:
from django.conf.urls import url, include
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

View File

@ -29,9 +29,10 @@ automatically generated schemas. Since we're using viewsets and routers,
we can simply use the automatic schema generation.
You'll need to install the `coreapi` python package in order to include an
API schema.
API schema, and `pyyaml` to render the schema into the commonly used
YAML-based OpenAPI format.
$ pip install coreapi
$ pip install coreapi pyyaml
We can now include a schema for our API, by including an autogenerated schema
view in our URL configuration.
@ -89,7 +90,7 @@ Now check that it is available on the command line...
Command line client for interacting with CoreAPI services.
Visit http://www.coreapi.org for more information.
Visit https://www.coreapi.org/ for more information.
Options:
--version Display the package version number.
@ -220,8 +221,8 @@ We've reached the end of our tutorial. If you want to get more involved in the
**Now go build awesome things.**
[coreapi]: http://www.coreapi.org
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[coreapi]: https://www.coreapi.org/
[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding
[openapi]: https://openapis.org/
[repo]: https://github.com/encode/rest-framework-tutorial
[sandbox]: https://restframework.herokuapp.com/

View File

@ -10,18 +10,18 @@ Create a new Django project named `tutorial`, then start a new app called `quick
mkdir tutorial
cd tutorial
# Create a virtualenv to isolate our package dependencies locally
virtualenv env
# Create a virtual environment to isolate our package dependencies locally
python3 -m venv env
source env/bin/activate # On Windows use `env\Scripts\activate`
# Install Django and Django REST framework into the virtualenv
# Install Django and Django REST framework into the virtual environment
pip install django
pip install djangorestframework
# Set up a new project with a single application
django-admin.py startproject tutorial . # Note the trailing '.' character
django-admin startproject tutorial . # Note the trailing '.' character
cd tutorial
django-admin.py startapp quickstart
django-admin startapp quickstart
cd ..
The project layout should look like:
@ -46,7 +46,7 @@ The project layout should look like:
./tutorial/urls.py
./tutorial/wsgi.py
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external module (topic goes outside the scope of the quickstart).
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart).
Now sync your database for the first time:
@ -77,7 +77,7 @@ First up we're going to define some serializers. Let's create a new module named
model = Group
fields = ('url', 'name')
Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
Notice that we're using hyperlinked relations in this case with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
## Views
@ -111,7 +111,7 @@ We can easily break these down into individual views if we need to, but using vi
Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
from django.conf.urls import url, include
from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views
@ -122,8 +122,8 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
@ -133,7 +133,7 @@ Again, if we need more control over the API URLs we can simply drop down to usin
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
## Pagination
Pagination allows you to control how many objects per page are returned. To enable it add following lines to the `tutorial/settings.py`
Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',

View File

@ -4,6 +4,6 @@
<h1 id="404-page-not-found" style="text-align: center">404</h1>
<p style="text-align: center"><strong>Page not found</strong></p>
<p style="text-align: center">Try the <a href="http://www.django-rest-framework.org/">homepage</a>, or <a href="#searchModal" data-toggle="modal">search the documentation</a>.</p>
<p style="text-align: center">Try the <a href="https://www.django-rest-framework.org/">homepage</a>, or <a href="#searchModal" data-toggle="modal">search the documentation</a>.</p>
{% endblock %}

2
docs_theme/css/bootstrap-responsive.css vendored Executable file → Normal file
View File

@ -3,7 +3,7 @@
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/

2
docs_theme/css/bootstrap.css vendored Executable file → Normal file
View File

@ -3,7 +3,7 @@
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/

0
docs_theme/js/bootstrap-2.1.1-min.js vendored Executable file → Normal file
View File

View File

@ -14,7 +14,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="http://www.django-rest-framework.org">Django REST framework</a>
<a class="brand" href="https://www.django-rest-framework.org/">Django REST framework</a>
<div class="nav-collapse collapse">
{% if nav|length>1 %}
<!-- Main navigation -->

View File

@ -1,5 +1,5 @@
site_name: Django REST framework
site_url: http://www.django-rest-framework.org/
site_url: https://www.django-rest-framework.org/
site_description: Django REST framework - Web APIs for Django
repo_url: https://github.com/encode/django-rest-framework
@ -65,6 +65,7 @@ pages:
- 'Contributing to REST framework': 'community/contributing.md'
- 'Project management': 'community/project-management.md'
- 'Release Notes': 'community/release-notes.md'
- '3.9 Announcement': 'community/3.9-announcement.md'
- '3.8 Announcement': 'community/3.8-announcement.md'
- '3.7 Announcement': 'community/3.7-announcement.md'
- '3.6 Announcement': 'community/3.6-announcement.md'

View File

@ -1,7 +1,8 @@
# Optional packages which may be used with REST framework.
psycopg2-binary==2.7.5
psycopg2-binary>=2.8.2, <2.9
markdown==2.6.11
django-guardian==1.4.9
django-filter==1.1.0
django-guardian==1.5.0
django-filter>=2.1.0, <2.2
coreapi==2.3.1
coreschema==0.0.4
pyyaml

View File

@ -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.5.0,<4.6
pytest-django>=3.4.8,<3.5
pytest-cov>=2.7.1

View File

@ -8,10 +8,10 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
__version__ = '3.8.2'
__version__ = '3.9.3'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2018 Tom Christie'
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
# Version synonym
VERSION = __version__
@ -23,3 +23,11 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
ISO_8601 = 'iso-8601'
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
class RemovedInDRF310Warning(DeprecationWarning):
pass
class RemovedInDRF311Warning(PendingDeprecationWarning):
pass

View File

@ -1,15 +1,12 @@
"""
Provides various authentication policies.
"""
from __future__ import unicode_literals
import base64
import binascii
from django.contrib.auth import authenticate, get_user_model
from django.middleware.csrf import CsrfViewMiddleware
from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@ -21,7 +18,7 @@ def get_authorization_header(request):
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, text_type):
if isinstance(auth, str):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
@ -33,7 +30,7 @@ class CSRFCheck(CsrfViewMiddleware):
return reason
class BaseAuthentication(object):
class BaseAuthentication:
"""
All authentication classes should extend BaseAuthentication.
"""

View File

@ -1,5 +1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class AuthTokenConfig(AppConfig):

View File

@ -38,8 +38,8 @@ class Command(BaseCommand):
token = self.create_user_token(username, reset_token)
except UserModel.DoesNotExist:
raise CommandError(
'Cannot create the Token: user {0} does not exist'.format(
'Cannot create the Token: user {} does not exist'.format(
username)
)
self.stdout.write(
'Generated token {0} for user {1}'.format(token.key, username))
'Generated token {} for user {}'.format(token.key, username))

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models

View File

@ -3,11 +3,9 @@ import os
from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
@python_2_unicode_compatible
class Token(models.Model):
"""
The default authorization token model.
@ -32,7 +30,7 @@ class Token(models.Model):
def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
return super(Token, self).save(*args, **kwargs)
return super().save(*args, **kwargs)
def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()

View File

@ -1,5 +1,5 @@
from django.contrib.auth import authenticate
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers

View File

@ -2,12 +2,10 @@
The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
from __future__ import unicode_literals
import sys
from django.conf import settings
from django.core import validators
from django.utils import six
from django.views.generic import View
try:
@ -22,6 +20,11 @@ except ImportError:
RegexURLResolver as URLResolver,
)
try:
from django.core.validators import ProhibitNullCharactersValidator # noqa
except ImportError:
ProhibitNullCharactersValidator = None
def get_original_route(urlpattern):
"""
@ -70,26 +73,9 @@ def make_url_resolver(regex, urlpatterns):
return URLResolver(regex, urlpatterns)
def unicode_repr(instance):
# Get the repr of an instance, but ensure it is a unicode string
# on both python 3 (already the case) and 2 (not the case).
if six.PY2:
return repr(instance).decode('utf-8')
return repr(instance)
def unicode_to_repr(value):
# Coerce a unicode string to the correct repr return type, depending on
# the Python version. We wrap all our `__repr__` implementations with
# this and then use unicode throughout internally.
if six.PY2:
return value.encode('utf-8')
return value
def unicode_http_header(value):
# Coerce HTTP header value to unicode.
if isinstance(value, six.binary_type):
if isinstance(value, bytes):
return value.decode('iso-8859-1')
return value
@ -124,6 +110,13 @@ except ImportError:
coreschema = None
# pyyaml is optional
try:
import yaml
except ImportError:
yaml = None
# django-crispy-forms is optional
try:
import crispy_forms
@ -150,19 +143,12 @@ if 'patch' not in View.http_method_names:
View.http_method_names = View.http_method_names + ['patch']
# Markdown is optional
# Markdown is optional (version 2.6+ required)
try:
import markdown
if markdown.version <= '2.2':
HEADERID_EXT_PATH = 'headerid'
LEVEL_PARAM = 'level'
elif markdown.version < '2.6':
HEADERID_EXT_PATH = 'markdown.extensions.headerid'
LEVEL_PARAM = 'level'
else:
HEADERID_EXT_PATH = 'markdown.extensions.toc'
LEVEL_PARAM = 'baselevel'
HEADERID_EXT_PATH = 'markdown.extensions.toc'
LEVEL_PARAM = 'baselevel'
def apply_markdown(text):
"""
@ -254,17 +240,12 @@ except ImportError:
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: https://bugs.python.org/issue22767
if six.PY3:
SHORT_SEPARATORS = (',', ':')
LONG_SEPARATORS = (', ', ': ')
INDENT_SEPARATORS = (',', ': ')
else:
SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ')
INDENT_SEPARATORS = (b',', b': ')
SHORT_SEPARATORS = (',', ':')
LONG_SEPARATORS = (', ', ': ')
INDENT_SEPARATORS = (',', ': ')
class CustomValidatorMessage(object):
class CustomValidatorMessage:
"""
We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`.
https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297
@ -274,7 +255,7 @@ class CustomValidatorMessage(object):
def __init__(self, *args, **kwargs):
self.message = kwargs.pop('message', self.message)
super(CustomValidatorMessage, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator):
@ -291,3 +272,7 @@ class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
pass
# Version Constants.
PY36 = sys.version_info >= (3, 6)

View File

@ -6,18 +6,16 @@ There are also various decorators for setting the API policies on function
based views, as well as the `@detail_route` and `@list_route` decorators, which are
used to annotate methods on viewsets that should be included by routers.
"""
from __future__ import unicode_literals
import types
import warnings
from django.forms.utils import pretty_name
from django.utils import six
from rest_framework import RemovedInDRF310Warning
from rest_framework.views import APIView
def api_view(http_method_names=None, exclude_from_schema=False):
def api_view(http_method_names=None):
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
@ -27,7 +25,7 @@ def api_view(http_method_names=None, exclude_from_schema=False):
def decorator(func):
WrappedAPIView = type(
six.PY3 and 'WrappedAPIView' or b'WrappedAPIView',
'WrappedAPIView',
(APIView,),
{'__doc__': func.__doc__}
)
@ -77,15 +75,8 @@ def api_view(http_method_names=None, exclude_from_schema=False):
WrappedAPIView.schema = getattr(func, 'schema',
APIView.schema)
if exclude_from_schema:
warnings.warn(
"The `exclude_from_schema` argument to `api_view` is deprecated. "
"Use the `schema` decorator instead, passing `None`.",
DeprecationWarning
)
WrappedAPIView.exclude_from_schema = exclude_from_schema
return WrappedAPIView.as_view()
return decorator
@ -131,7 +122,7 @@ def schema(view_inspector):
return decorator
def action(methods=None, detail=None, name=None, url_path=None, url_name=None, **kwargs):
def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
"""
Mark a ViewSet method as a routable action.
@ -145,18 +136,22 @@ def action(methods=None, detail=None, name=None, url_path=None, url_name=None, *
"@action() missing required argument: 'detail'"
)
# name and suffix are mutually exclusive
if 'name' in kwargs and 'suffix' in kwargs:
raise TypeError("`name` and `suffix` are mutually exclusive arguments.")
def decorator(func):
func.mapping = MethodMapper(func, methods)
func.detail = detail
func.name = name if name else pretty_name(func.__name__)
func.url_path = url_path if url_path else func.__name__
func.url_name = url_name if url_name else func.__name__.replace('_', '-')
func.kwargs = kwargs
func.kwargs.update({
'name': func.name,
'description': func.__doc__ or None
})
# Set descriptive arguments for viewsets
if 'name' not in kwargs and 'suffix' not in kwargs:
func.kwargs['name'] = pretty_name(func.__name__)
func.kwargs['description'] = func.__doc__ or None
return func
return decorator
@ -226,9 +221,9 @@ def detail_route(methods=None, **kwargs):
Used to mark a method on a ViewSet that should be routed for detail requests.
"""
warnings.warn(
"`detail_route` is pending deprecation and will be removed in 3.10 in favor of "
"`detail_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
PendingDeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
def decorator(func):
@ -244,9 +239,9 @@ def list_route(methods=None, **kwargs):
Used to mark a method on a ViewSet that should be routed for list requests.
"""
warnings.warn(
"`list_route` is pending deprecation and will be removed in 3.10 in favor of "
"`list_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
PendingDeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
def decorator(func):

View File

@ -4,18 +4,14 @@ Handled exceptions raised by REST framework.
In addition Django's built in 403 and 404 exceptions are handled.
(`django.http.Http404` and `django.core.exceptions.PermissionDenied`)
"""
from __future__ import unicode_literals
import math
from django.http import JsonResponse
from django.utils import six
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from rest_framework import status
from rest_framework.compat import unicode_to_repr
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
@ -64,19 +60,19 @@ def _get_full_details(detail):
}
class ErrorDetail(six.text_type):
class ErrorDetail(str):
"""
A string-like object that can additionally have a code.
"""
code = None
def __new__(cls, string, code=None):
self = super(ErrorDetail, cls).__new__(cls, string)
self = super().__new__(cls, string)
self.code = code
return self
def __eq__(self, other):
r = super(ErrorDetail, self).__eq__(other)
r = super().__eq__(other)
try:
return r and self.code == other.code
except AttributeError:
@ -86,10 +82,10 @@ class ErrorDetail(six.text_type):
return not self.__eq__(other)
def __repr__(self):
return unicode_to_repr('ErrorDetail(string=%r, code=%r)' % (
six.text_type(self),
return 'ErrorDetail(string=%r, code=%r)' % (
str(self),
self.code,
))
)
def __hash__(self):
return hash(str(self))
@ -113,7 +109,7 @@ class APIException(Exception):
self.detail = _get_error_details(detail, code)
def __str__(self):
return six.text_type(self.detail)
return str(self.detail)
def get_codes(self):
"""
@ -196,7 +192,7 @@ class MethodNotAllowed(APIException):
def __init__(self, method, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(method=method)
super(MethodNotAllowed, self).__init__(detail, code)
super().__init__(detail, code)
class NotAcceptable(APIException):
@ -206,7 +202,7 @@ class NotAcceptable(APIException):
def __init__(self, detail=None, code=None, available_renderers=None):
self.available_renderers = available_renderers
super(NotAcceptable, self).__init__(detail, code)
super().__init__(detail, code)
class UnsupportedMediaType(APIException):
@ -217,7 +213,7 @@ class UnsupportedMediaType(APIException):
def __init__(self, media_type, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail).format(media_type=media_type)
super(UnsupportedMediaType, self).__init__(detail, code)
super().__init__(detail, code)
class Throttled(APIException):
@ -234,11 +230,11 @@ class Throttled(APIException):
wait = math.ceil(wait)
detail = ' '.join((
detail,
force_text(ungettext(self.extra_detail_singular.format(wait=wait),
self.extra_detail_plural.format(wait=wait),
wait))))
force_text(ngettext(self.extra_detail_singular.format(wait=wait),
self.extra_detail_plural.format(wait=wait),
wait))))
self.wait = wait
super(Throttled, self).__init__(detail, code)
super().__init__(detail, code)
def server_error(request, *args, **kwargs):

View File

@ -1,6 +1,3 @@
from __future__ import unicode_literals
import collections
import copy
import datetime
import decimal
@ -9,6 +6,7 @@ import inspect
import re
import uuid
from collections import OrderedDict
from collections.abc import Mapping
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
@ -18,7 +16,7 @@ from django.core.validators import (
)
from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField
from django.utils import six, timezone
from django.utils import timezone
from django.utils.dateparse import (
parse_date, parse_datetime, parse_duration, parse_time
)
@ -28,13 +26,13 @@ from django.utils.formats import localize_input, sanitize_separators
from django.utils.functional import lazy
from django.utils.ipv6 import clean_ipv6_address
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from pytz.exceptions import InvalidTimeError
from rest_framework import ISO_8601
from rest_framework.compat import (
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator, unicode_repr, unicode_to_repr
MinValueValidator, ProhibitNullCharactersValidator
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
@ -51,39 +49,21 @@ class empty:
pass
if six.PY3:
def is_simple_callable(obj):
"""
True if the object is a callable that takes no arguments.
"""
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
return False
def is_simple_callable(obj):
"""
True if the object is a callable that takes no arguments.
"""
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
return False
sig = inspect.signature(obj)
params = sig.parameters.values()
return all(
param.kind == param.VAR_POSITIONAL or
param.kind == param.VAR_KEYWORD or
param.default != param.empty
for param in params
)
else:
def is_simple_callable(obj):
function = inspect.isfunction(obj)
method = inspect.ismethod(obj)
if not (function or method):
return False
if method:
is_unbound = obj.im_self is None
args, _, _, defaults = inspect.getargspec(obj)
len_args = len(args) if function or is_unbound else len(args) - 1
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
sig = inspect.signature(obj)
params = sig.parameters.values()
return all(
param.kind == param.VAR_POSITIONAL or
param.kind == param.VAR_KEYWORD or
param.default != param.empty
for param in params
)
def get_attribute(instance, attrs):
@ -95,7 +75,7 @@ def get_attribute(instance, attrs):
"""
for attr in attrs:
try:
if isinstance(instance, collections.Mapping):
if isinstance(instance, Mapping):
instance = instance[attr]
else:
instance = getattr(instance, attr)
@ -108,7 +88,7 @@ def get_attribute(instance, attrs):
# If we raised an Attribute or KeyError here it'd get treated
# as an omitted field in `Field.get_attribute()`. Instead we
# raise a ValueError to ensure the exception is not masked.
raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))
raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))
return instance
@ -185,18 +165,18 @@ def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
"""
Helper function for options and option groups in templates.
"""
class StartOptionGroup(object):
class StartOptionGroup:
start_option_group = True
end_option_group = False
def __init__(self, label):
self.label = label
class EndOptionGroup(object):
class EndOptionGroup:
start_option_group = False
end_option_group = True
class Option(object):
class Option:
start_option_group = False
end_option_group = False
@ -251,7 +231,7 @@ def get_error_detail(exc_info):
}
class CreateOnlyDefault(object):
class CreateOnlyDefault:
"""
This class may be used to provide default values that are only used
for create operations, but that do not return any value for update
@ -273,12 +253,10 @@ class CreateOnlyDefault(object):
return self.default
def __repr__(self):
return unicode_to_repr(
'%s(%s)' % (self.__class__.__name__, unicode_repr(self.default))
)
return '%s(%s)' % (self.__class__.__name__, repr(self.default))
class CurrentUserDefault(object):
class CurrentUserDefault:
def set_context(self, serializer_field):
self.user = serializer_field.context['request'].user
@ -286,7 +264,7 @@ class CurrentUserDefault(object):
return self.user
def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)
return '%s()' % self.__class__.__name__
class SkipField(Exception):
@ -305,7 +283,7 @@ MISSING_ERROR_MESSAGE = (
)
class Field(object):
class Field:
_creation_counter = 0
default_error_messages = {
@ -349,7 +327,7 @@ class Field(object):
self.default_empty_html = default
if validators is not None:
self.validators = validators[:]
self.validators = list(validators)
# These are set up by `.bind()` when the field is added to a serializer.
self.field_name = None
@ -409,7 +387,7 @@ class Field(object):
self._validators = validators
def get_validators(self):
return self.default_validators[:]
return list(self.default_validators)
def get_initial(self):
"""
@ -618,7 +596,7 @@ class Field(object):
When a field is instantiated, we store the arguments that were used,
so that we can present a helpful representation of the object.
"""
instance = super(Field, cls).__new__(cls)
instance = super().__new__(cls)
instance._args = args
instance._kwargs = kwargs
return instance
@ -636,7 +614,7 @@ class Field(object):
for item in self._args
]
kwargs = {
key: (copy.deepcopy(value) if (key not in ('validators', 'regex')) else value)
key: (copy.deepcopy(value, memo) if (key not in ('validators', 'regex')) else value)
for key, value in self._kwargs.items()
}
return self.__class__(*args, **kwargs)
@ -647,7 +625,7 @@ class Field(object):
This allows us to create descriptive representations for serializer
instances that show all the declared fields on the serializer.
"""
return unicode_to_repr(representation.field_repr(self))
return representation.field_repr(self)
# Boolean types...
@ -674,10 +652,7 @@ class BooleanField(Field):
'0', 0, 0.0,
False
}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.'
super(BooleanField, self).__init__(**kwargs)
NULL_VALUES = {'null', 'Null', 'NULL', '', None}
def to_internal_value(self, data):
try:
@ -685,6 +660,8 @@ class BooleanField(Field):
return True
elif data in self.FALSE_VALUES:
return False
elif data in self.NULL_VALUES and self.allow_null:
return None
except TypeError: # Input is an unhashable type
pass
self.fail('invalid', input=data)
@ -694,6 +671,8 @@ class BooleanField(Field):
return True
elif value in self.FALSE_VALUES:
return False
if value in self.NULL_VALUES and self.allow_null:
return None
return bool(value)
@ -718,12 +697,12 @@ class NullBooleanField(Field):
'0', 0, 0.0,
False
}
NULL_VALUES = {'n', 'N', 'null', 'Null', 'NULL', '', None}
NULL_VALUES = {'null', 'Null', 'NULL', '', None}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
kwargs['allow_null'] = True
super(NullBooleanField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
@ -754,7 +733,7 @@ class CharField(Field):
'invalid': _('Not a valid string.'),
'blank': _('This field may not be blank.'),
'max_length': _('Ensure this field has no more than {max_length} characters.'),
'min_length': _('Ensure this field has at least {min_length} characters.')
'min_length': _('Ensure this field has at least {min_length} characters.'),
}
initial = ''
@ -763,41 +742,42 @@ class CharField(Field):
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
super(CharField, self).__init__(**kwargs)
super().__init__(**kwargs)
if self.max_length is not None:
message = lazy(
self.error_messages['max_length'].format,
six.text_type)(max_length=self.max_length)
message = lazy(self.error_messages['max_length'].format, str)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))
if self.min_length is not None:
message = lazy(
self.error_messages['min_length'].format,
six.text_type)(min_length=self.min_length)
self.error_messages['min_length'].format, str)(min_length=self.min_length)
self.validators.append(
MinLengthValidator(self.min_length, message=message))
# ProhibitNullCharactersValidator is None on Django < 2.0
if ProhibitNullCharactersValidator is not None:
self.validators.append(ProhibitNullCharactersValidator())
def run_validation(self, data=empty):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
# inside the `to_internal_value()` method.
if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''):
if data == '' or (self.trim_whitespace and str(data).strip() == ''):
if not self.allow_blank:
self.fail('blank')
return ''
return super(CharField, self).run_validation(data)
return super().run_validation(data)
def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into strings,
# but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
# and composites such as lists are likely user error.
if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
if isinstance(data, bool) or not isinstance(data, (str, int, float,)):
self.fail('invalid')
value = six.text_type(data)
value = str(data)
return value.strip() if self.trim_whitespace else value
def to_representation(self, value):
return six.text_type(value)
return str(value)
class EmailField(CharField):
@ -806,7 +786,7 @@ class EmailField(CharField):
}
def __init__(self, **kwargs):
super(EmailField, self).__init__(**kwargs)
super().__init__(**kwargs)
validator = EmailValidator(message=self.error_messages['invalid'])
self.validators.append(validator)
@ -817,7 +797,7 @@ class RegexField(CharField):
}
def __init__(self, regex, **kwargs):
super(RegexField, self).__init__(**kwargs)
super().__init__(**kwargs)
validator = RegexValidator(regex, message=self.error_messages['invalid'])
self.validators.append(validator)
@ -829,7 +809,7 @@ class SlugField(CharField):
}
def __init__(self, allow_unicode=False, **kwargs):
super(SlugField, self).__init__(**kwargs)
super().__init__(**kwargs)
self.allow_unicode = allow_unicode
if self.allow_unicode:
validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode'])
@ -844,7 +824,7 @@ class URLField(CharField):
}
def __init__(self, **kwargs):
super(URLField, self).__init__(**kwargs)
super().__init__(**kwargs)
validator = URLValidator(message=self.error_messages['invalid'])
self.validators.append(validator)
@ -861,16 +841,16 @@ class UUIDField(Field):
if self.uuid_format not in self.valid_formats:
raise ValueError(
'Invalid format for uuid representation. '
'Must be one of "{0}"'.format('", "'.join(self.valid_formats))
'Must be one of "{}"'.format('", "'.join(self.valid_formats))
)
super(UUIDField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
if not isinstance(data, uuid.UUID):
try:
if isinstance(data, six.integer_types):
if isinstance(data, int):
return uuid.UUID(int=data)
elif isinstance(data, six.string_types):
elif isinstance(data, str):
return uuid.UUID(hex=data)
else:
self.fail('invalid', value=data)
@ -895,12 +875,12 @@ class IPAddressField(CharField):
def __init__(self, protocol='both', **kwargs):
self.protocol = protocol.lower()
self.unpack_ipv4 = (self.protocol == 'both')
super(IPAddressField, self).__init__(**kwargs)
super().__init__(**kwargs)
validators, error_message = ip_address_validators(protocol, self.unpack_ipv4)
self.validators.extend(validators)
def to_internal_value(self, data):
if not isinstance(data, six.string_types):
if not isinstance(data, str):
self.fail('invalid', value=data)
if ':' in data:
@ -910,7 +890,7 @@ class IPAddressField(CharField):
except DjangoValidationError:
self.fail('invalid', value=data)
return super(IPAddressField, self).to_internal_value(data)
return super().to_internal_value(data)
# Number types...
@ -928,22 +908,20 @@ class IntegerField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super(IntegerField, self).__init__(**kwargs)
super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
self.error_messages['max_value'].format, str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
self.error_messages['min_value'].format, str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
try:
@ -968,23 +946,23 @@ class FloatField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super(FloatField, self).__init__(**kwargs)
super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, data):
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH:
self.fail('max_string_length')
try:
@ -1026,18 +1004,17 @@ class DecimalField(Field):
else:
self.max_whole_digits = None
super(DecimalField, self).__init__(**kwargs)
super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
self.error_messages['min_value'].format, str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
@ -1116,7 +1093,7 @@ class DecimalField(Field):
coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING)
if not isinstance(value, decimal.Decimal):
value = decimal.Decimal(six.text_type(value).strip())
value = decimal.Decimal(str(value).strip())
quantized = self.quantize(value)
@ -1125,7 +1102,7 @@ class DecimalField(Field):
if self.localize:
return localize_input(quantized)
return '{0:f}'.format(quantized)
return '{:f}'.format(quantized)
def quantize(self, value):
"""
@ -1162,7 +1139,7 @@ class DateTimeField(Field):
self.input_formats = input_formats
if default_timezone is not None:
self.timezone = default_timezone
super(DateTimeField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def enforce_timezone(self, value):
"""
@ -1221,7 +1198,7 @@ class DateTimeField(Field):
output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT)
if output_format is None or isinstance(value, six.string_types):
if output_format is None or isinstance(value, str):
return value
value = self.enforce_timezone(value)
@ -1246,7 +1223,7 @@ class DateField(Field):
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super(DateField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
@ -1283,7 +1260,7 @@ class DateField(Field):
output_format = getattr(self, 'format', api_settings.DATE_FORMAT)
if output_format is None or isinstance(value, six.string_types):
if output_format is None or isinstance(value, str):
return value
# Applying a `DateField` to a datetime value is almost always
@ -1312,7 +1289,7 @@ class TimeField(Field):
self.format = format
if input_formats is not None:
self.input_formats = input_formats
super(TimeField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def to_internal_value(self, value):
input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
@ -1346,7 +1323,7 @@ class TimeField(Field):
output_format = getattr(self, 'format', api_settings.TIME_FORMAT)
if output_format is None or isinstance(value, six.string_types):
if output_format is None or isinstance(value, str):
return value
# Applying a `TimeField` to a datetime value is almost always
@ -1373,24 +1350,24 @@ class DurationField(Field):
def __init__(self, **kwargs):
self.max_value = kwargs.pop('max_value', None)
self.min_value = kwargs.pop('min_value', None)
super(DurationField, self).__init__(**kwargs)
super().__init__(**kwargs)
if self.max_value is not None:
message = lazy(
self.error_messages['max_value'].format,
six.text_type)(max_value=self.max_value)
str)(max_value=self.max_value)
self.validators.append(
MaxValueValidator(self.max_value, message=message))
if self.min_value is not None:
message = lazy(
self.error_messages['min_value'].format,
six.text_type)(min_value=self.min_value)
str)(min_value=self.min_value)
self.validators.append(
MinValueValidator(self.min_value, message=message))
def to_internal_value(self, value):
if isinstance(value, datetime.timedelta):
return value
parsed = parse_duration(six.text_type(value))
parsed = parse_duration(str(value))
if parsed is not None:
return parsed
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
@ -1415,21 +1392,21 @@ class ChoiceField(Field):
self.allow_blank = kwargs.pop('allow_blank', False)
super(ChoiceField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
if data == '' and self.allow_blank:
return ''
try:
return self.choice_strings_to_values[six.text_type(data)]
return self.choice_strings_to_values[str(data)]
except KeyError:
self.fail('invalid_choice', input=data)
def to_representation(self, value):
if value in ('', None):
return value
return self.choice_strings_to_values.get(six.text_type(value), value)
return self.choice_strings_to_values.get(str(value), value)
def iter_options(self):
"""
@ -1452,7 +1429,7 @@ class ChoiceField(Field):
# Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out.
self.choice_strings_to_values = {
six.text_type(key): key for key in self.choices
str(key): key for key in self.choices
}
choices = property(_get_choices, _set_choices)
@ -1468,7 +1445,7 @@ class MultipleChoiceField(ChoiceField):
def __init__(self, *args, **kwargs):
self.allow_empty = kwargs.pop('allow_empty', True)
super(MultipleChoiceField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def get_value(self, dictionary):
if self.field_name not in dictionary:
@ -1481,7 +1458,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, str) 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')
@ -1493,7 +1470,7 @@ class MultipleChoiceField(ChoiceField):
def to_representation(self, value):
return {
self.choice_strings_to_values.get(six.text_type(item), item) for item in value
self.choice_strings_to_values.get(str(item), item) for item in value
}
@ -1511,7 +1488,7 @@ class FilePathField(ChoiceField):
allow_folders=allow_folders, required=required
)
kwargs['choices'] = field.choices
super(FilePathField, self).__init__(**kwargs)
super().__init__(**kwargs)
# File types...
@ -1530,7 +1507,7 @@ class FileField(Field):
self.allow_empty_file = kwargs.pop('allow_empty_file', False)
if 'use_url' in kwargs:
self.use_url = kwargs.pop('use_url')
super(FileField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def to_internal_value(self, data):
try:
@ -1576,13 +1553,13 @@ class ImageField(FileField):
def __init__(self, *args, **kwargs):
self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
super(ImageField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def to_internal_value(self, data):
# Image validation is a bit grungy, so we'll just outright
# defer to Django's implementation so we don't need to
# consider it, or treat PIL as a test dependency.
file_object = super(ImageField, self).to_internal_value(data)
file_object = super().to_internal_value(data)
django_field = self._DjangoImageField()
django_field.error_messages = self.error_messages
return django_field.clean(file_object)
@ -1592,7 +1569,7 @@ class ImageField(FileField):
class _UnvalidatedField(Field):
def __init__(self, *args, **kwargs):
super(_UnvalidatedField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.allow_blank = True
self.allow_null = True
@ -1625,7 +1602,7 @@ class ListField(Field):
"Remove `source=` from the field declaration."
)
super(ListField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = self.error_messages['max_length'].format(max_length=self.max_length)
@ -1655,7 +1632,7 @@ class ListField(Field):
"""
if html.is_html_input(data):
data = html.parse_html_list(data, default=[])
if isinstance(data, type('')) or isinstance(data, collections.Mapping) or not hasattr(data, '__iter__'):
if isinstance(data, (str, 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')
@ -1686,11 +1663,13 @@ class DictField(Field):
child = _UnvalidatedField()
initial = {}
default_error_messages = {
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".')
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".'),
'empty': _('This dictionary may not be empty.'),
}
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
assert self.child.source is None, (
@ -1698,7 +1677,7 @@ class DictField(Field):
"Remove `source=` from the field declaration."
)
super(DictField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
def get_value(self, dictionary):
@ -1716,14 +1695,14 @@ class DictField(Field):
data = html.parse_html_dict(data)
if not isinstance(data, dict):
self.fail('not_a_dict', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
return self.run_child_validation(data)
def to_representation(self, value):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return {
six.text_type(key): self.child.to_representation(val) if val is not None else None
str(key): self.child.to_representation(val) if val is not None else None
for key, val in value.items()
}
@ -1732,7 +1711,7 @@ class DictField(Field):
errors = OrderedDict()
for key, value in data.items():
key = six.text_type(key)
key = str(key)
try:
result[key] = self.child.run_validation(value)
@ -1748,7 +1727,7 @@ class HStoreField(DictField):
child = CharField(allow_blank=True, allow_null=True)
def __init__(self, *args, **kwargs):
super(HStoreField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
assert isinstance(self.child, CharField), (
"The `child` argument must be an instance of `CharField`, "
"as the hstore extension stores values as strings."
@ -1762,15 +1741,15 @@ class JSONField(Field):
def __init__(self, *args, **kwargs):
self.binary = kwargs.pop('binary', False)
super(JSONField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:
# When HTML form input is used, mark up the input
# as being a JSON string, rather than a JSON primitive.
class JSONString(six.text_type):
class JSONString(str):
def __new__(self, value):
ret = six.text_type.__new__(self, value)
ret = str.__new__(self, value)
ret.is_json_string = True
return ret
return JSONString(dictionary[self.field_name])
@ -1779,8 +1758,8 @@ class JSONField(Field):
def to_internal_value(self, data):
try:
if self.binary or getattr(data, 'is_json_string', False):
if isinstance(data, six.binary_type):
data = data.decode('utf-8')
if isinstance(data, bytes):
data = data.decode()
return json.loads(data)
else:
json.dumps(data)
@ -1791,10 +1770,7 @@ class JSONField(Field):
def to_representation(self, value):
if self.binary:
value = json.dumps(value)
# On python 2.x the return type for json.dumps() is underspecified.
# On python 3.x json.dumps() returns unicode strings.
if isinstance(value, six.text_type):
value = bytes(value.encode('utf-8'))
value = value.encode()
return value
@ -1815,7 +1791,7 @@ class ReadOnlyField(Field):
def __init__(self, **kwargs):
kwargs['read_only'] = True
super(ReadOnlyField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_representation(self, value):
return value
@ -1832,7 +1808,7 @@ class HiddenField(Field):
def __init__(self, **kwargs):
assert 'default' in kwargs, 'default is a required argument.'
kwargs['write_only'] = True
super(HiddenField, self).__init__(**kwargs)
super().__init__(**kwargs)
def get_value(self, dictionary):
# We always use the default value for `HiddenField`.
@ -1862,7 +1838,7 @@ class SerializerMethodField(Field):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)
super().__init__(**kwargs)
def bind(self, field_name, parent):
# In order to enforce a consistent style, we error if a redundant
@ -1880,7 +1856,7 @@ class SerializerMethodField(Field):
if self.method_name is None:
self.method_name = default_method_name
super(SerializerMethodField, self).bind(field_name, parent)
super().bind(field_name, parent)
def to_representation(self, value):
method = getattr(self.parent, self.method_name)
@ -1903,11 +1879,10 @@ class ModelField(Field):
# The `max_length` option is supported by Django's base `Field` class,
# so we'd better support it here.
max_length = kwargs.pop('max_length', None)
super(ModelField, self).__init__(**kwargs)
super().__init__(**kwargs)
if max_length is not None:
message = lazy(
self.error_messages['max_length'].format,
six.text_type)(max_length=self.max_length)
self.error_messages['max_length'].format, str)(max_length=self.max_length)
self.validators.append(
MaxLengthValidator(self.max_length, message=message))

View File

@ -2,9 +2,8 @@
Provides generic filtering backends that can be used to filter the results
returned by list views.
"""
from __future__ import unicode_literals
import operator
import warnings
from functools import reduce
from django.core.exceptions import ImproperlyConfigured
@ -12,17 +11,17 @@ from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.sql.constants import ORDER_PATTERN
from django.template import loader
from django.utils import six
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework import RemovedInDRF310Warning
from rest_framework.compat import (
coreapi, coreschema, distinct, is_guardian_installed
)
from rest_framework.settings import api_settings
class BaseFilterBackend(object):
class BaseFilterBackend:
"""
A base class from which all filter backend classes should inherit.
"""
@ -38,6 +37,9 @@ class BaseFilterBackend(object):
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return []
def get_schema_operation_parameters(self, view):
return []
class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search.
@ -52,6 +54,14 @@ class SearchFilter(BaseFilterBackend):
search_title = _('Search')
search_description = _('A search term.')
def get_search_fields(self, view, request):
"""
Search fields are obtained from the view, but the request is always
passed to this method. Sub-classes can override this method to
dynamically change the search fields based on request content.
"""
return getattr(view, 'search_fields', None)
def get_search_terms(self, request):
"""
Search terms are set by a ?search=... query parameter,
@ -76,6 +86,9 @@ class SearchFilter(BaseFilterBackend):
opts = queryset.model._meta
if search_field[0] in self.lookup_prefixes:
search_field = search_field[1:]
# Annotated fields do not need to be distinct
if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
return False
parts = search_field.split(LOOKUP_SEP)
for part in parts:
field = opts.get_field(part)
@ -89,14 +102,14 @@ class SearchFilter(BaseFilterBackend):
return False
def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None)
search_fields = self.get_search_fields(view, request)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
return queryset
orm_lookups = [
self.construct_search(six.text_type(search_field))
self.construct_search(str(search_field))
for search_field in search_fields
]
@ -146,6 +159,19 @@ class SearchFilter(BaseFilterBackend):
)
]
def get_schema_operation_parameters(self, view):
return [
{
'name': self.search_param,
'required': False,
'in': 'query',
'description': force_text(self.search_description),
'schema': {
'type': 'string',
},
},
]
class OrderingFilter(BaseFilterBackend):
# The URL query parameter used for the ordering.
@ -175,7 +201,7 @@ class OrderingFilter(BaseFilterBackend):
def get_default_ordering(self, view):
ordering = getattr(view, 'ordering', None)
if isinstance(ordering, six.string_types):
if isinstance(ordering, str):
return (ordering,)
return ordering
@ -224,7 +250,7 @@ class OrderingFilter(BaseFilterBackend):
]
else:
valid_fields = [
(item, item) if isinstance(item, six.string_types) else item
(item, item) if isinstance(item, str) else item
for item in valid_fields
]
@ -277,6 +303,19 @@ class OrderingFilter(BaseFilterBackend):
)
]
def get_schema_operation_parameters(self, view):
return [
{
'name': self.ordering_param,
'required': False,
'in': 'query',
'description': force_text(self.ordering_description),
'schema': {
'type': 'string',
},
},
]
class DjangoObjectPermissionsFilter(BaseFilterBackend):
"""
@ -284,6 +323,11 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
has read object level permissions.
"""
def __init__(self):
warnings.warn(
"`DjangoObjectPermissionsFilter` has been deprecated and moved to "
"the 3rd-party django-rest-framework-guardian package.",
RemovedInDRF310Warning, stacklevel=2
)
assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed'
perm_format = '%(app_label)s.view_%(model_name)s'

View File

@ -1,8 +1,6 @@
"""
Generic views that provide commonly needed behaviour.
"""
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet
from django.http import Http404

View File

View File

@ -0,0 +1,56 @@
from django.core.management.base import BaseCommand
from rest_framework import renderers
from rest_framework.schemas import coreapi
from rest_framework.schemas.openapi import SchemaGenerator
OPENAPI_MODE = 'openapi'
COREAPI_MODE = 'coreapi'
class Command(BaseCommand):
help = "Generates configured API schema for project."
def get_mode(self):
return COREAPI_MODE if coreapi.is_enabled() else OPENAPI_MODE
def add_arguments(self, parser):
parser.add_argument('--title', dest="title", default='', type=str)
parser.add_argument('--url', dest="url", default=None, type=str)
parser.add_argument('--description', dest="description", default=None, type=str)
if self.get_mode() == COREAPI_MODE:
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json', 'corejson'], default='openapi', type=str)
else:
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json'], default='openapi', type=str)
def handle(self, *args, **options):
generator_class = self.get_generator_class()
generator = generator_class(
url=options['url'],
title=options['title'],
description=options['description']
)
schema = generator.get_schema(request=None, public=True)
renderer = self.get_renderer(options['format'])
output = renderer.render(schema, renderer_context={})
self.stdout.write(output.decode())
def get_renderer(self, format):
if self.get_mode() == COREAPI_MODE:
renderer_cls = {
'corejson': renderers.CoreJSONRenderer,
'openapi': renderers.CoreAPIOpenAPIRenderer,
'openapi-json': renderers.CoreAPIJSONOpenAPIRenderer,
}[format]
return renderer_cls()
renderer_cls = {
'openapi': renderers.OpenAPIRenderer,
'openapi-json': renderers.JSONOpenAPIRenderer,
}[format]
return renderer_cls()
def get_generator_class(self):
if self.get_mode() == COREAPI_MODE:
return coreapi.SchemaGenerator
return SchemaGenerator

View File

@ -6,8 +6,6 @@ some fairly ad-hoc information about the view.
Future implementations might use JSON schema or other definitions in order
to return this information in a more standardized way.
"""
from __future__ import unicode_literals
from collections import OrderedDict
from django.core.exceptions import PermissionDenied
@ -19,7 +17,7 @@ from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict
class BaseMetadata(object):
class BaseMetadata:
def determine_metadata(self, request, view):
"""
Return a dictionary of metadata about the view.

View File

@ -4,14 +4,12 @@ Basic building blocks for generic class based views.
We don't bind behaviour to http method handlers yet,
which allows mixin classes to be composed in interesting ways.
"""
from __future__ import unicode_literals
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object):
class CreateModelMixin:
"""
Create a model instance.
"""
@ -32,7 +30,7 @@ class CreateModelMixin(object):
return {}
class ListModelMixin(object):
class ListModelMixin:
"""
List a queryset.
"""
@ -48,7 +46,7 @@ class ListModelMixin(object):
return Response(serializer.data)
class RetrieveModelMixin(object):
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
@ -58,7 +56,7 @@ class RetrieveModelMixin(object):
return Response(serializer.data)
class UpdateModelMixin(object):
class UpdateModelMixin:
"""
Update a model instance.
"""
@ -84,7 +82,7 @@ class UpdateModelMixin(object):
return self.update(request, *args, **kwargs)
class DestroyModelMixin(object):
class DestroyModelMixin:
"""
Destroy a model instance.
"""

View File

@ -2,8 +2,6 @@
Content negotiation deals with selecting an appropriate renderer given the
incoming request. Typically this will be based on the request's Accept header.
"""
from __future__ import unicode_literals
from django.http import Http404
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@ -13,7 +11,7 @@ from rest_framework.utils.mediatypes import (
)
class BaseContentNegotiation(object):
class BaseContentNegotiation:
def select_parser(self, request, parsers):
raise NotImplementedError('.select_parser() must be implemented')
@ -66,7 +64,7 @@ class DefaultContentNegotiation(BaseContentNegotiation):
# Accepted media type is 'application/json'
full_media_type = ';'.join(
(renderer.media_type,) +
tuple('{0}={1}'.format(
tuple('{}={}'.format(
key, value.decode(HTTP_HEADER_ENCODING))
for key, value in media_type_wrapper.params.items()))
return renderer, full_media_type

View File

@ -1,20 +1,16 @@
# coding: utf-8
"""
Pagination serializers determine the structure of the output that should
be used for paginated responses.
"""
from __future__ import unicode_literals
from base64 import b64decode, b64encode
from collections import OrderedDict, namedtuple
from urllib import parse
from django.core.paginator import InvalidPage
from django.core.paginator import Paginator as DjangoPaginator
from django.template import loader
from django.utils import six
from django.utils.encoding import force_text
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from rest_framework.compat import coreapi, coreschema
from rest_framework.exceptions import NotFound
@ -133,7 +129,7 @@ PageLink = namedtuple('PageLink', ['url', 'number', 'is_active', 'is_break'])
PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True)
class BasePagination(object):
class BasePagination:
display_page_controls = False
def paginate_queryset(self, queryset, request, view=None): # pragma: no cover
@ -152,6 +148,9 @@ class BasePagination(object):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
return []
def get_schema_operation_parameters(self, view):
return []
class PageNumberPagination(BasePagination):
"""
@ -204,7 +203,7 @@ class PageNumberPagination(BasePagination):
self.page = paginator.page(page_number)
except InvalidPage as exc:
msg = self.invalid_page_message.format(
page_number=page_number, message=six.text_type(exc)
page_number=page_number, message=str(exc)
)
raise NotFound(msg)
@ -305,6 +304,32 @@ class PageNumberPagination(BasePagination):
)
return fields
def get_schema_operation_parameters(self, view):
parameters = [
{
'name': self.page_query_param,
'required': False,
'in': 'query',
'description': force_text(self.page_query_description),
'schema': {
'type': 'integer',
},
},
]
if self.page_size_query_param is not None:
parameters.append(
{
'name': self.page_size_query_param,
'required': False,
'in': 'query',
'description': force_text(self.page_size_query_description),
'schema': {
'type': 'integer',
},
},
)
return parameters
class LimitOffsetPagination(BasePagination):
"""
@ -434,6 +459,15 @@ class LimitOffsetPagination(BasePagination):
context = self.get_html_context()
return template.render(context)
def get_count(self, queryset):
"""
Determine an object count, supporting either querysets or regular lists.
"""
try:
return queryset.count()
except (AttributeError, TypeError):
return len(queryset)
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
@ -458,21 +492,35 @@ class LimitOffsetPagination(BasePagination):
)
]
def get_count(self, queryset):
"""
Determine an object count, supporting either querysets or regular lists.
"""
try:
return queryset.count()
except (AttributeError, TypeError):
return len(queryset)
def get_schema_operation_parameters(self, view):
parameters = [
{
'name': self.limit_query_param,
'required': False,
'in': 'query',
'description': force_text(self.limit_query_description),
'schema': {
'type': 'integer',
},
},
{
'name': self.offset_query_param,
'required': False,
'in': 'query',
'description': force_text(self.offset_query_description),
'schema': {
'type': 'integer',
},
},
]
return parameters
class CursorPagination(BasePagination):
"""
The cursor pagination implementation is necessarily complex.
For an overview of the position/offset style we use, see this post:
http://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
"""
cursor_query_param = 'cursor'
cursor_query_description = _('The pagination cursor value.')
@ -716,13 +764,13 @@ class CursorPagination(BasePagination):
'nearly-unique field on the model, such as "-created" or "pk".'
)
assert isinstance(ordering, (six.string_types, list, tuple)), (
assert isinstance(ordering, (str, list, tuple)), (
'Invalid ordering. Expected string or tuple, but got {type}'.format(
type=type(ordering).__name__
)
)
if isinstance(ordering, six.string_types):
if isinstance(ordering, str):
return (ordering,)
return tuple(ordering)
@ -737,7 +785,7 @@ class CursorPagination(BasePagination):
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
tokens = parse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset, cutoff=self.offset_cutoff)
@ -763,7 +811,7 @@ class CursorPagination(BasePagination):
if cursor.position is not None:
tokens['p'] = cursor.position
querystring = urlparse.urlencode(tokens, doseq=True)
querystring = parse.urlencode(tokens, doseq=True)
encoded = b64encode(querystring.encode('ascii')).decode('ascii')
return replace_query_param(self.base_url, self.cursor_query_param, encoded)
@ -773,7 +821,7 @@ class CursorPagination(BasePagination):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
return six.text_type(attr)
return str(attr)
def get_paginated_response(self, data):
return Response(OrderedDict([
@ -820,3 +868,29 @@ class CursorPagination(BasePagination):
)
)
return fields
def get_schema_operation_parameters(self, view):
parameters = [
{
'name': self.cursor_query_param,
'required': False,
'in': 'query',
'description': force_text(self.cursor_query_description),
'schema': {
'type': 'integer',
},
}
]
if self.page_size_query_param is not None:
parameters.append(
{
'name': self.page_size_query_param,
'required': False,
'in': 'query',
'description': force_text(self.page_size_query_description),
'schema': {
'type': 'integer',
},
}
)
return parameters

View File

@ -4,9 +4,8 @@ Parsers are used to parse the content of incoming HTTP requests.
They give us a generic way of being able to handle various media types
on the request, such as form content or json encoded data.
"""
from __future__ import unicode_literals
import codecs
from urllib import parse
from django.conf import settings
from django.core.files.uploadhandler import StopFutureHandlers
@ -15,9 +14,7 @@ from django.http.multipartparser import ChunkIter
from django.http.multipartparser import \
MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError, parse_header
from django.utils import six
from django.utils.encoding import force_text
from django.utils.six.moves.urllib import parse as urlparse
from rest_framework import renderers
from rest_framework.exceptions import ParseError
@ -25,13 +22,13 @@ from rest_framework.settings import api_settings
from rest_framework.utils import json
class DataAndFiles(object):
class DataAndFiles:
def __init__(self, data, files):
self.data = data
self.files = files
class BaseParser(object):
class BaseParser:
"""
All parsers should extend `BaseParser`, specifying a `media_type`
attribute, and overriding the `.parse()` method.
@ -67,7 +64,7 @@ class JSONParser(BaseParser):
parse_constant = json.strict_constant if self.strict else None
return json.load(decoded_stream, parse_constant=parse_constant)
except ValueError as exc:
raise ParseError('JSON parse error - %s' % six.text_type(exc))
raise ParseError('JSON parse error - %s' % str(exc))
class FormParser(BaseParser):
@ -83,8 +80,7 @@ class FormParser(BaseParser):
"""
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
data = QueryDict(stream.read(), encoding=encoding)
return data
return QueryDict(stream.read(), encoding=encoding)
class MultiPartParser(BaseParser):
@ -113,7 +109,7 @@ class MultiPartParser(BaseParser):
data, files = parser.parse()
return DataAndFiles(data, files)
except MultiPartParserError as exc:
raise ParseError('Multipart form parse error - %s' % six.text_type(exc))
raise ParseError('Multipart form parse error - %s' % str(exc))
class FileUploadParser(BaseParser):
@ -205,7 +201,7 @@ class FileUploadParser(BaseParser):
try:
meta = parser_context['request'].META
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8'))
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode())
filename_parm = disposition[1]
if 'filename*' in filename_parm:
return self.get_encoded_filename(filename_parm)
@ -221,7 +217,7 @@ class FileUploadParser(BaseParser):
encoded_filename = force_text(filename_parm['filename*'])
try:
charset, lang, filename = encoded_filename.split('\'', 2)
filename = urlparse.unquote(filename)
filename = parse.unquote(filename)
except (ValueError, LookupError):
filename = force_text(filename_parm['filename'])
return filename

View File

@ -1,8 +1,6 @@
"""
Provides a set of pluggable permission policies.
"""
from __future__ import unicode_literals
from django.http import Http404
from rest_framework import exceptions
@ -10,7 +8,97 @@ from rest_framework import exceptions
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class BasePermission(object):
class OperationHolderMixin:
def __and__(self, other):
return OperandHolder(AND, self, other)
def __or__(self, other):
return OperandHolder(OR, self, other)
def __rand__(self, other):
return OperandHolder(AND, other, self)
def __ror__(self, other):
return OperandHolder(OR, other, self)
def __invert__(self):
return SingleOperandHolder(NOT, self)
class SingleOperandHolder(OperationHolderMixin):
def __init__(self, operator_class, op1_class):
self.operator_class = operator_class
self.op1_class = op1_class
def __call__(self, *args, **kwargs):
op1 = self.op1_class(*args, **kwargs)
return self.operator_class(op1)
class OperandHolder(OperationHolderMixin):
def __init__(self, operator_class, op1_class, op2_class):
self.operator_class = operator_class
self.op1_class = op1_class
self.op2_class = op2_class
def __call__(self, *args, **kwargs):
op1 = self.op1_class(*args, **kwargs)
op2 = self.op2_class(*args, **kwargs)
return self.operator_class(op1, op2)
class AND:
def __init__(self, op1, op2):
self.op1 = op1
self.op2 = op2
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) and
self.op2.has_permission(request, view)
)
def has_object_permission(self, request, view, obj):
return (
self.op1.has_object_permission(request, view, obj) and
self.op2.has_object_permission(request, view, obj)
)
class OR:
def __init__(self, op1, op2):
self.op1 = op1
self.op2 = op2
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) or
self.op2.has_permission(request, view)
)
def has_object_permission(self, request, view, obj):
return (
self.op1.has_object_permission(request, view, obj) or
self.op2.has_object_permission(request, view, obj)
)
class NOT:
def __init__(self, op1):
self.op1 = op1
def has_permission(self, request, view):
return not self.op1.has_permission(request, view)
def has_object_permission(self, request, view, obj):
return not self.op1.has_object_permission(request, view, obj)
class BasePermissionMetaclass(OperationHolderMixin, type):
pass
class BasePermission(metaclass=BasePermissionMetaclass):
"""
A base class from which all permission classes should inherit.
"""
@ -46,7 +134,7 @@ class IsAuthenticated(BasePermission):
"""
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
return bool(request.user and request.user.is_authenticated)
class IsAdminUser(BasePermission):
@ -55,7 +143,7 @@ class IsAdminUser(BasePermission):
"""
def has_permission(self, request, view):
return request.user and request.user.is_staff
return bool(request.user and request.user.is_staff)
class IsAuthenticatedOrReadOnly(BasePermission):
@ -64,7 +152,7 @@ class IsAuthenticatedOrReadOnly(BasePermission):
"""
def has_permission(self, request, view):
return (
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated

View File

@ -1,19 +1,13 @@
# coding: utf-8
from __future__ import unicode_literals
import sys
from collections import OrderedDict
from urllib import parse
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db.models import Manager
from django.db.models.query import QuerySet
from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve
from django.utils import six
from django.utils.encoding import (
python_2_unicode_compatible, smart_text, uri_to_iri
)
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text, uri_to_iri
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import (
Field, empty, get_attribute, is_simple_callable, iter_options
@ -46,14 +40,14 @@ class ObjectTypeError(TypeError):
"""
class Hyperlink(six.text_type):
class Hyperlink(str):
"""
A string like object that additionally has an associated name.
We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others.
"""
def __new__(self, url, obj):
ret = six.text_type.__new__(self, url)
ret = str.__new__(self, url)
ret.obj = obj
return ret
@ -65,13 +59,12 @@ class Hyperlink(six.text_type):
# This ensures that we only called `__str__` lazily,
# as in some cases calling __str__ on a model instances *might*
# involve a database lookup.
return six.text_type(self.obj)
return str(self.obj)
is_hyperlink = True
@python_2_unicode_compatible
class PKOnlyObject(object):
class PKOnlyObject:
"""
This is a mock object, used for when we only need the pk of the object
instance, but still want to return an object with a .pk attribute,
@ -121,14 +114,14 @@ class RelatedField(Field):
)
kwargs.pop('many', None)
kwargs.pop('allow_empty', None)
super(RelatedField, self).__init__(**kwargs)
super().__init__(**kwargs)
def __new__(cls, *args, **kwargs):
# We override this method in order to automagically create
# `ManyRelatedField` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super(RelatedField, cls).__new__(cls, *args, **kwargs)
return super().__new__(cls, *args, **kwargs)
@classmethod
def many_init(cls, *args, **kwargs):
@ -157,7 +150,7 @@ class RelatedField(Field):
# We force empty strings to None values for relational fields.
if data == '':
data = None
return super(RelatedField, self).run_validation(data)
return super().run_validation(data)
def get_queryset(self):
queryset = self.queryset
@ -189,7 +182,7 @@ class RelatedField(Field):
pass
# Standard case, return the object instance.
return super(RelatedField, self).get_attribute(instance)
return super().get_attribute(instance)
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
@ -225,7 +218,7 @@ class RelatedField(Field):
)
def display_value(self, instance):
return six.text_type(instance)
return str(instance)
class StringRelatedField(RelatedField):
@ -236,10 +229,10 @@ class StringRelatedField(RelatedField):
def __init__(self, **kwargs):
kwargs['read_only'] = True
super(StringRelatedField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_representation(self, value):
return six.text_type(value)
return str(value)
class PrimaryKeyRelatedField(RelatedField):
@ -251,7 +244,7 @@ class PrimaryKeyRelatedField(RelatedField):
def __init__(self, **kwargs):
self.pk_field = kwargs.pop('pk_field', None)
super(PrimaryKeyRelatedField, self).__init__(**kwargs)
super().__init__(**kwargs)
def use_pk_only_optimization(self):
return True
@ -297,7 +290,7 @@ class HyperlinkedRelatedField(RelatedField):
# implicit `self` argument to be passed.
self.reverse = reverse
super(HyperlinkedRelatedField, self).__init__(**kwargs)
super().__init__(**kwargs)
def use_pk_only_optimization(self):
return self.lookup_field == 'pk'
@ -317,10 +310,10 @@ class HyperlinkedRelatedField(RelatedField):
return queryset.get(**lookup_kwargs)
except ValueError:
exc = ObjectValueError(str(sys.exc_info()[1]))
six.reraise(type(exc), exc, sys.exc_info()[2])
raise exc.with_traceback(sys.exc_info()[2])
except TypeError:
exc = ObjectTypeError(str(sys.exc_info()[1]))
six.reraise(type(exc), exc, sys.exc_info()[2])
raise exc.with_traceback(sys.exc_info()[2])
def get_url(self, obj, view_name, request, format):
"""
@ -346,7 +339,7 @@ class HyperlinkedRelatedField(RelatedField):
if http_prefix:
# If needed convert absolute URLs to relative path
data = urlparse.urlparse(data).path
data = parse.urlparse(data).path
prefix = get_script_prefix()
if data.startswith(prefix):
data = '/' + data[len(prefix):]
@ -432,7 +425,7 @@ class HyperlinkedIdentityField(HyperlinkedRelatedField):
assert view_name is not None, 'The `view_name` argument is required.'
kwargs['read_only'] = True
kwargs['source'] = '*'
super(HyperlinkedIdentityField, self).__init__(view_name, **kwargs)
super().__init__(view_name, **kwargs)
def use_pk_only_optimization(self):
# We have the complete object instance already. We don't need
@ -453,7 +446,7 @@ class SlugRelatedField(RelatedField):
def __init__(self, slug_field=None, **kwargs):
assert slug_field is not None, 'The `slug_field` argument is required.'
self.slug_field = slug_field
super(SlugRelatedField, self).__init__(**kwargs)
super().__init__(**kwargs)
def to_internal_value(self, data):
try:
@ -502,7 +495,7 @@ class ManyRelatedField(Field):
self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT)
)
assert child_relation is not None, '`child_relation` is a required argument.'
super(ManyRelatedField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.child_relation.bind(field_name='', parent=self)
def get_value(self, dictionary):
@ -518,7 +511,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, str) 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')

View File

@ -6,10 +6,9 @@ on the response, such as JSON encoded data or HTML output.
REST framework also provides an HTML renderer that renders the browsable API.
"""
from __future__ import unicode_literals
import base64
from collections import OrderedDict
from urllib import parse
from django import forms
from django.conf import settings
@ -19,13 +18,12 @@ from django.http.multipartparser import parse_header
from django.template import engines, loader
from django.test.client import encode_multipart
from django.urls import NoReverseMatch
from django.utils import six
from django.utils.html import mark_safe
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi,
pygments_css
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
pygments_css, yaml
)
from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method
@ -39,7 +37,7 @@ def zero_as_none(value):
return None if value == 0 else value
class BaseRenderer(object):
class BaseRenderer:
"""
All renderers should extend this class, setting the `media_type`
and `format` attributes, and override the `.render()` method.
@ -106,18 +104,11 @@ class JSONRenderer(BaseRenderer):
allow_nan=not self.strict, separators=separators
)
# On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
# but if ensure_ascii=False, the return type is underspecified,
# and may (or may not) be unicode.
# On python 3.x json.dumps() returns unicode strings.
if isinstance(ret, six.text_type):
# We always fully escape \u2028 and \u2029 to ensure we output JSON
# that is a strict javascript subset. If bytes were returned
# by json.dumps() then we don't have these characters in any case.
# See: http://timelessrepo.com/json-isnt-a-javascript-subset
ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')
return bytes(ret.encode('utf-8'))
return ret
# We always fully escape \u2028 and \u2029 to ensure we output JSON
# that is a strict javascript subset.
# See: http://timelessrepo.com/json-isnt-a-javascript-subset
ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')
return ret.encode()
class TemplateHTMLRenderer(BaseRenderer):
@ -348,7 +339,7 @@ class HTMLFormRenderer(BaseRenderer):
# Get a clone of the field with text-only value representation.
field = field.as_form_field()
if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
if style.get('input_type') == 'datetime-local' and isinstance(field.value, str):
field.value = field.value.rstrip('Z')
if 'template' in style:
@ -576,7 +567,7 @@ class BrowsableAPIRenderer(BaseRenderer):
data.pop(name, None)
content = renderer.render(data, accepted, context)
# Renders returns bytes, but CharField expects a str.
content = content.decode('utf-8')
content = content.decode()
else:
content = None
@ -683,7 +674,7 @@ class BrowsableAPIRenderer(BaseRenderer):
csrf_header_name = csrf_header_name[5:]
csrf_header_name = csrf_header_name.replace('_', '-')
context = {
return {
'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
'code_style': pygments_css(self.code_style),
'view': view,
@ -719,7 +710,6 @@ class BrowsableAPIRenderer(BaseRenderer):
'csrf_cookie_name': csrf_cookie_name,
'csrf_header_name': csrf_header_name
}
return context
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
@ -790,7 +780,7 @@ class AdminRenderer(BrowsableAPIRenderer):
"""
Render the HTML for the browsable API representation.
"""
context = super(AdminRenderer, self).get_context(
context = super().get_context(
data, accepted_media_type, renderer_context
)
@ -932,3 +922,140 @@ class CoreJSONRenderer(BaseRenderer):
indent = bool(renderer_context.get('indent', 0))
codec = coreapi.codecs.CoreJSONCodec()
return codec.dump(data, indent=indent)
class _BaseOpenAPIRenderer:
def get_schema(self, instance):
CLASS_TO_TYPENAME = {
coreschema.Object: 'object',
coreschema.Array: 'array',
coreschema.Number: 'number',
coreschema.Integer: 'integer',
coreschema.String: 'string',
coreschema.Boolean: 'boolean',
}
schema = {}
if instance.__class__ in CLASS_TO_TYPENAME:
schema['type'] = CLASS_TO_TYPENAME[instance.__class__]
schema['title'] = instance.title
schema['description'] = instance.description
if hasattr(instance, 'enum'):
schema['enum'] = instance.enum
return schema
def get_parameters(self, link):
parameters = []
for field in link.fields:
if field.location not in ['path', 'query']:
continue
parameter = {
'name': field.name,
'in': field.location,
}
if field.required:
parameter['required'] = True
if field.description:
parameter['description'] = field.description
if field.schema:
parameter['schema'] = self.get_schema(field.schema)
parameters.append(parameter)
return parameters
def get_operation(self, link, name, tag):
operation_id = "%s_%s" % (tag, name) if tag else name
parameters = self.get_parameters(link)
operation = {
'operationId': operation_id,
}
if link.title:
operation['summary'] = link.title
if link.description:
operation['description'] = link.description
if parameters:
operation['parameters'] = parameters
if tag:
operation['tags'] = [tag]
return operation
def get_paths(self, document):
paths = {}
tag = None
for name, link in document.links.items():
path = parse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
for tag, section in document.data.items():
for name, link in section.links.items():
path = parse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
return paths
def get_structure(self, data):
return {
'openapi': '3.0.0',
'info': {
'version': '',
'title': data.title,
'description': data.description
},
'servers': [{
'url': data.url
}],
'paths': self.get_paths(data)
}
class CoreAPIOpenAPIRenderer(_BaseOpenAPIRenderer):
media_type = 'application/vnd.oai.openapi'
charset = None
format = 'openapi'
def __init__(self):
assert coreapi, 'Using CoreAPIOpenAPIRenderer, but `coreapi` is not installed.'
assert yaml, 'Using CoreAPIOpenAPIRenderer, but `pyyaml` is not installed.'
def render(self, data, media_type=None, renderer_context=None):
structure = self.get_structure(data)
return yaml.dump(structure, default_flow_style=False).encode()
class CoreAPIJSONOpenAPIRenderer(_BaseOpenAPIRenderer):
media_type = 'application/vnd.oai.openapi+json'
charset = None
format = 'openapi-json'
def __init__(self):
assert coreapi, 'Using CoreAPIJSONOpenAPIRenderer, but `coreapi` is not installed.'
def render(self, data, media_type=None, renderer_context=None):
structure = self.get_structure(data)
return json.dumps(structure, indent=4).encode('utf-8')
class OpenAPIRenderer(BaseRenderer):
media_type = 'application/vnd.oai.openapi'
charset = None
format = 'openapi'
def __init__(self):
assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
def render(self, data, media_type=None, renderer_context=None):
return yaml.dump(data, default_flow_style=False).encode('utf-8')
class JSONOpenAPIRenderer(BaseRenderer):
media_type = 'application/vnd.oai.openapi+json'
charset = None
format = 'openapi-json'
def render(self, data, media_type=None, renderer_context=None):
return json.dumps(data, indent=2).encode('utf-8')

View File

@ -8,8 +8,7 @@ The wrapped request then offers a richer API, in particular :
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
"""
from __future__ import unicode_literals
import io
import sys
from contextlib import contextmanager
@ -17,7 +16,6 @@ from django.conf import settings
from django.http import HttpRequest, QueryDict
from django.http.multipartparser import parse_header
from django.http.request import RawPostDataException
from django.utils import six
from django.utils.datastructures import MultiValueDict
from rest_framework import HTTP_HEADER_ENCODING, exceptions
@ -33,7 +31,7 @@ def is_form_media_type(media_type):
base_media_type == 'multipart/form-data')
class override_method(object):
class override_method:
"""
A context manager that temporarily overrides the method on a request,
additionally setting the `view.request` attribute.
@ -77,10 +75,10 @@ def wrap_attributeerrors():
except AttributeError:
info = sys.exc_info()
exc = WrappedAttributeError(str(info[1]))
six.reraise(type(exc), exc, info[2])
raise exc.with_traceback(info[2])
class Empty(object):
class Empty:
"""
Placeholder for unset attributes.
Cannot use `None`, as that may be a valid value.
@ -125,7 +123,7 @@ def clone_request(request, method):
return ret
class ForcedAuthentication(object):
class ForcedAuthentication:
"""
This authentication class is used if the test client or request factory
forcibly authenticated the request.
@ -139,7 +137,7 @@ class ForcedAuthentication(object):
return (self.force_user, self.force_token)
class Request(object):
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
@ -301,7 +299,7 @@ class Request(object):
elif not self._request._read_started:
self._stream = self._request
else:
self._stream = six.BytesIO(self.body)
self._stream = io.BytesIO(self.body)
def _supports_form_parsing(self):
"""

View File

@ -4,11 +4,9 @@ it is initialized with unrendered data, instead of a pre-rendered string.
The appropriate renderer is called during Django's template response rendering.
"""
from __future__ import unicode_literals
from http.client import responses
from django.template.response import SimpleTemplateResponse
from django.utils import six
from django.utils.six.moves.http_client import responses
from rest_framework.serializers import Serializer
@ -29,7 +27,7 @@ class Response(SimpleTemplateResponse):
Setting 'renderer' and 'media_type' will typically be deferred,
For example being set automatically by the `APIView`.
"""
super(Response, self).__init__(None, status=status)
super().__init__(None, status=status)
if isinstance(data, Serializer):
msg = (
@ -45,7 +43,7 @@ class Response(SimpleTemplateResponse):
self.content_type = content_type
if headers:
for name, value in six.iteritems(headers):
for name, value in headers.items():
self[name] = value
@property
@ -64,13 +62,13 @@ class Response(SimpleTemplateResponse):
content_type = self.content_type
if content_type is None and charset is not None:
content_type = "{0}; charset={1}".format(media_type, charset)
content_type = "{}; charset={}".format(media_type, charset)
elif content_type is None:
content_type = media_type
self['Content-Type'] = content_type
ret = renderer.render(self.data, accepted_media_type, context)
if isinstance(ret, six.text_type):
if isinstance(ret, str):
assert charset, (
'renderer returned unicode, and did not specify '
'a charset value.'
@ -94,7 +92,7 @@ class Response(SimpleTemplateResponse):
"""
Remove attributes from the response that shouldn't be cached.
"""
state = super(Response, self).__getstate__()
state = super().__getstate__()
for key in (
'accepted_renderer', 'renderer_context', 'resolver_match',
'client', 'request', 'json', 'wsgi_request'

Some files were not shown because too many files have changed in this diff Show More