Merge branch 'master' into bump-mkdocs
22
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Documentation: https://github.com/probot/stale
|
||||||
|
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 1
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: stale
|
13
.github/workflows/main.yml
vendored
|
@ -21,18 +21,13 @@ jobs:
|
||||||
- '3.10'
|
- '3.10'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'pip'
|
||||||
- uses: actions/cache@v2
|
cache-dependency-path: 'requirements/*.txt'
|
||||||
with:
|
|
||||||
path: ~/.cache/pip
|
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*.txt') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pip-
|
|
||||||
|
|
||||||
- name: Upgrade packaging tools
|
- name: Upgrade packaging tools
|
||||||
run: python -m pip install --upgrade pip setuptools virtualenv wheel
|
run: python -m pip install --upgrade pip setuptools virtualenv wheel
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
# Contributing to REST framework
|
# Contributing to REST framework
|
||||||
|
|
||||||
See the [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/).
|
At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes.
|
||||||
|
|
||||||
|
Apart from minor documentation changes, the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.
|
||||||
|
|
||||||
|
The [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/) gives some more information on our process and code of conduct.
|
||||||
|
|
19
README.md
|
@ -21,14 +21,14 @@ The initial aim is to provide a single full-time position on REST framework.
|
||||||
|
|
||||||
[![][sentry-img]][sentry-url]
|
[![][sentry-img]][sentry-url]
|
||||||
[![][stream-img]][stream-url]
|
[![][stream-img]][stream-url]
|
||||||
[![][rollbar-img]][rollbar-url]
|
[![][spacinov-img]][spacinov-url]
|
||||||
[![][esg-img]][esg-url]
|
|
||||||
[![][retool-img]][retool-url]
|
[![][retool-img]][retool-url]
|
||||||
[![][bitio-img]][bitio-url]
|
[![][bitio-img]][bitio-url]
|
||||||
[![][posthog-img]][posthog-url]
|
[![][posthog-img]][posthog-url]
|
||||||
[![][cryptapi-img]][cryptapi-url]
|
[![][cryptapi-img]][cryptapi-url]
|
||||||
|
[![][fezto-img]][fezto-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], [ESG][esg-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], and [CryptAPI][cryptapi-url].
|
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url].
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox].
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
* Python (3.6, 3.7, 3.8, 3.9, 3.10)
|
* Python (3.6, 3.7, 3.8, 3.9, 3.10)
|
||||||
* Django (2.2, 3.0, 3.1, 3.2, 4.0)
|
* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)
|
||||||
|
|
||||||
We **highly recommend** and only officially support the latest patch release of
|
We **highly recommend** and only officially support the latest patch release of
|
||||||
each Python and Django series.
|
each Python and Django series.
|
||||||
|
@ -67,11 +67,12 @@ Install using `pip`...
|
||||||
pip install djangorestframework
|
pip install djangorestframework
|
||||||
|
|
||||||
Add `'rest_framework'` to your `INSTALLED_APPS` setting.
|
Add `'rest_framework'` to your `INSTALLED_APPS` setting.
|
||||||
|
```python
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
...
|
...
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
# Example
|
# Example
|
||||||
|
|
||||||
|
@ -193,21 +194,21 @@ Please see the [security policy][security-policy].
|
||||||
|
|
||||||
[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-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
|
[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
|
||||||
[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png
|
[spacinov-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/spacinov-readme.png
|
||||||
[esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png
|
|
||||||
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png
|
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png
|
||||||
[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png
|
[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png
|
||||||
[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png
|
[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png
|
||||||
[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png
|
[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png
|
||||||
|
[fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png
|
||||||
|
|
||||||
[sentry-url]: https://getsentry.com/welcome/
|
[sentry-url]: https://getsentry.com/welcome/
|
||||||
[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage
|
[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage
|
||||||
[rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial
|
[spacinov-url]: https://www.spacinov.com/
|
||||||
[esg-url]: https://software.esg-usa.com/
|
|
||||||
[retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship
|
[retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship
|
||||||
[bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship
|
[bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship
|
||||||
[posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship
|
[posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship
|
||||||
[cryptapi-url]: https://cryptapi.io
|
[cryptapi-url]: https://cryptapi.io
|
||||||
|
[fezto-url]: https://www.fezto.xyz/?utm_source=DjangoRESTFramework
|
||||||
|
|
||||||
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||||
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
|
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
|
||||||
|
|
||||||
Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
**Please report security issues by emailing security@djangoproject.com**.
|
||||||
|
|
||||||
[security-mail]: mailto:rest-framework-security@googlegroups.com
|
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
||||||
|
|
|
@ -120,6 +120,14 @@ Unauthenticated responses that are denied permission will result in an `HTTP 401
|
||||||
|
|
||||||
## TokenAuthentication
|
## TokenAuthentication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note:** The token authentication provided by Django REST framework is a fairly simple implementation.
|
||||||
|
|
||||||
|
For an implementation which allows more than one token per user, has some tighter security implementation details, and supports token expiry, please see the [Django REST Knox][django-rest-knox] third party package.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
|
This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
|
||||||
|
|
||||||
To use the `TokenAuthentication` scheme you'll need to [configure the authentication classes](#setting-the-authentication-scheme) to include `TokenAuthentication`, and additionally include `rest_framework.authtoken` in your `INSTALLED_APPS` setting:
|
To use the `TokenAuthentication` scheme you'll need to [configure the authentication classes](#setting-the-authentication-scheme) to include `TokenAuthentication`, and additionally include `rest_framework.authtoken` in your `INSTALLED_APPS` setting:
|
||||||
|
@ -129,11 +137,9 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica
|
||||||
'rest_framework.authtoken'
|
'rest_framework.authtoken'
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
Make sure to run `manage.py migrate` after changing your settings.
|
||||||
|
|
||||||
**Note:** Make sure to run `manage.py migrate` after changing your settings. The `rest_framework.authtoken` app provides Django database migrations.
|
The `rest_framework.authtoken` app provides Django database migrations.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
You'll also need to create tokens for your users.
|
You'll also need to create tokens for your users.
|
||||||
|
|
||||||
|
@ -146,7 +152,7 @@ For clients to authenticate, the token key should be included in the `Authorizat
|
||||||
|
|
||||||
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
|
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
|
||||||
|
|
||||||
**Note:** If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable.
|
*If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable.*
|
||||||
|
|
||||||
If successfully authenticated, `TokenAuthentication` provides the following credentials.
|
If successfully authenticated, `TokenAuthentication` provides the following credentials.
|
||||||
|
|
||||||
|
@ -355,6 +361,10 @@ The following example will authenticate any incoming request as the user given b
|
||||||
|
|
||||||
The following third-party packages are also available.
|
The following third-party packages are also available.
|
||||||
|
|
||||||
|
## django-rest-knox
|
||||||
|
|
||||||
|
[Django-rest-knox][django-rest-knox] library provides models and views to handle token-based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).
|
||||||
|
|
||||||
## Django OAuth Toolkit
|
## Django OAuth Toolkit
|
||||||
|
|
||||||
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [jazzband][jazzband] 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 [jazzband][jazzband] 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**.
|
||||||
|
@ -420,13 +430,9 @@ There are currently two forks of this project.
|
||||||
* [Django-rest-auth][django-rest-auth] is the original project, [but is not currently receiving updates](https://github.com/Tivix/django-rest-auth/issues/568).
|
* [Django-rest-auth][django-rest-auth] is the original project, [but is not currently receiving updates](https://github.com/Tivix/django-rest-auth/issues/568).
|
||||||
* [Dj-rest-auth][dj-rest-auth] is a newer fork of the project.
|
* [Dj-rest-auth][dj-rest-auth] is a newer fork of the project.
|
||||||
|
|
||||||
## django-rest-framework-social-oauth2
|
## drf-social-oauth2
|
||||||
|
|
||||||
[Django-rest-framework-social-oauth2][django-rest-framework-social-oauth2] library provides an easy way to integrate social plugins (facebook, twitter, google, etc.) to your authentication system and an easy oauth2 setup. With this library, you will be able to authenticate users based on external tokens (e.g. facebook access token), convert these tokens to "in-house" oauth2 tokens and use and generate oauth2 tokens to authenticate your users.
|
[Drf-social-oauth2][drf-social-oauth2] is a framework that helps you authenticate with major social oauth2 vendors, such as Facebook, Google, Twitter, Orcid, etc. It generates tokens in a JWTed way with an easy setup.
|
||||||
|
|
||||||
## django-rest-knox
|
|
||||||
|
|
||||||
[Django-rest-knox][django-rest-knox] library provides models and views to handle token-based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).
|
|
||||||
|
|
||||||
## drfpasswordless
|
## drfpasswordless
|
||||||
|
|
||||||
|
@ -473,7 +479,7 @@ More information can be found in the [Documentation](https://django-rest-durin.r
|
||||||
[djoser]: https://github.com/sunscrapers/djoser
|
[djoser]: https://github.com/sunscrapers/djoser
|
||||||
[django-rest-auth]: https://github.com/Tivix/django-rest-auth
|
[django-rest-auth]: https://github.com/Tivix/django-rest-auth
|
||||||
[dj-rest-auth]: https://github.com/jazzband/dj-rest-auth
|
[dj-rest-auth]: https://github.com/jazzband/dj-rest-auth
|
||||||
[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2
|
[drf-social-oauth2]: https://github.com/wagnerdelima/drf-social-oauth2
|
||||||
[django-rest-knox]: https://github.com/James1345/django-rest-knox
|
[django-rest-knox]: https://github.com/James1345/django-rest-knox
|
||||||
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
||||||
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
|
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
|
||||||
|
|
|
@ -260,6 +260,15 @@ Set as `handler400`:
|
||||||
|
|
||||||
handler400 = 'rest_framework.exceptions.bad_request'
|
handler400 = 'rest_framework.exceptions.bad_request'
|
||||||
|
|
||||||
|
# Third party packages
|
||||||
|
|
||||||
|
The following third-party packages are also available.
|
||||||
|
|
||||||
|
## DRF Standardized Errors
|
||||||
|
|
||||||
|
The [drf-standardized-errors][drf-standardized-errors] package provides an exception handler that generates the same format for all 4xx and 5xx responses. It is a drop-in replacement for the default exception handler and allows customizing the error response format without rewriting the whole exception handler. The standardized error response format is easier to document and easier to handle by API consumers.
|
||||||
|
|
||||||
[cite]: https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/
|
[cite]: https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/
|
||||||
[authentication]: authentication.md
|
[authentication]: authentication.md
|
||||||
[django-custom-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
|
[django-custom-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
|
||||||
|
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
||||||
|
|
|
@ -42,7 +42,7 @@ Set to false if this field is not required to be present during deserialization.
|
||||||
|
|
||||||
Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
|
Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
|
||||||
|
|
||||||
Defaults to `True`.
|
Defaults to `True`. If you're using [Model Serializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) default value will be `False` if you have specified `blank=True` or `default` or `null=True` at your field in your `Model`.
|
||||||
|
|
||||||
### `default`
|
### `default`
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ Relational fields are used to represent model relationships. They can be applie
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Note:** REST Framework does not attempt to automatically optimize querysets passed to serializers in terms of `select_related` and `prefetch_related` since it would be too much magic. A serializer with a field spanning an orm relation through its source attribute could require an additional database hit to fetch related object from the database. It is the programmer's responsibility to optimize queries to avoid additional database hits which could occur while using such a serializer.
|
**Note:** REST Framework does not attempt to automatically optimize querysets passed to serializers in terms of `select_related` and `prefetch_related` since it would be too much magic. A serializer with a field spanning an orm relation through its source attribute could require an additional database hit to fetch related objects from the database. It is the programmer's responsibility to optimize queries to avoid additional database hits which could occur while using such a serializer.
|
||||||
|
|
||||||
For example, the following serializer would lead to a database hit each time evaluating the tracks field if it is not prefetched:
|
For example, the following serializer would lead to a database hit each time evaluating the tracks field if it is not prefetched:
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ In order to customize the top-level schema, subclass
|
||||||
as an argument to the `generateschema` command or `get_schema_view()` helper
|
as an argument to the `generateschema` command or `get_schema_view()` helper
|
||||||
function.
|
function.
|
||||||
|
|
||||||
### get_schema(self, request)
|
### get_schema(self, request=None, public=False)
|
||||||
|
|
||||||
Returns a dictionary that represents the OpenAPI schema:
|
Returns a dictionary that represents the OpenAPI schema:
|
||||||
|
|
||||||
|
@ -313,6 +313,11 @@ Computes the component's name from the serializer.
|
||||||
|
|
||||||
You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names.
|
You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names.
|
||||||
|
|
||||||
|
#### `get_reference()`
|
||||||
|
|
||||||
|
Returns a reference to the serializer component. This may be useful if you override `get_schema()`.
|
||||||
|
|
||||||
|
|
||||||
#### `map_serializer()`
|
#### `map_serializer()`
|
||||||
|
|
||||||
Maps serializers to their OpenAPI representations.
|
Maps serializers to their OpenAPI representations.
|
||||||
|
|
|
@ -299,7 +299,7 @@ similar way as with `RequestsClient`.
|
||||||
|
|
||||||
# API Test cases
|
# API Test cases
|
||||||
|
|
||||||
REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`.
|
REST framework includes the following test case classes, that mirror the existing [Django's test case classes][provided_test_case_classes], but use `APIClient` instead of Django's default `Client`.
|
||||||
|
|
||||||
* `APISimpleTestCase`
|
* `APISimpleTestCase`
|
||||||
* `APITransactionTestCase`
|
* `APITransactionTestCase`
|
||||||
|
@ -413,5 +413,6 @@ For example, to add support for using `format='html'` in test requests, you migh
|
||||||
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
|
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
|
||||||
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
|
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
|
||||||
[configuration]: #configuration
|
[configuration]: #configuration
|
||||||
[refresh_from_db_docs]: https://docs.djangoproject.com/en/1.11/ref/models/instances/#django.db.models.Model.refresh_from_db
|
[refresh_from_db_docs]: https://docs.djangoproject.com/en/stable/ref/models/instances/#django.db.models.Model.refresh_from_db
|
||||||
[session_objects]: https://requests.readthedocs.io/en/master/user/advanced/#session-objects
|
[session_objects]: https://requests.readthedocs.io/en/master/user/advanced/#session-objects
|
||||||
|
[provided_test_case_classes]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes
|
||||||
|
|
|
@ -19,6 +19,10 @@ Multiple throttles can also be used if you want to impose both burst throttling
|
||||||
|
|
||||||
Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed.
|
Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed.
|
||||||
|
|
||||||
|
**The application-level throttling that REST framework provides should not be considered a security measure or protection against brute forcing or denial-of-service attacks. Deliberately malicious actors will always be able to spoof IP origins. In addition to this, the built-in throttling implementations are implemented using Django's cache framework, and use non-atomic operations to determine the request rate, which may sometimes result in some fuzziness.
|
||||||
|
|
||||||
|
The application-level throttling provided by REST framework is intended for implementing policies such as different business tiers and basic protections against service over-use.**
|
||||||
|
|
||||||
## How throttling is determined
|
## How throttling is determined
|
||||||
|
|
||||||
As with permissions and authentication, throttling in REST framework is always defined as a list of classes.
|
As with permissions and authentication, throttling in REST framework is always defined as a list of classes.
|
||||||
|
@ -79,7 +83,7 @@ Throttle classes set in this way will override any viewset level class settings.
|
||||||
}
|
}
|
||||||
return Response(content)
|
return Response(content)
|
||||||
|
|
||||||
## How clients are identified
|
## How clients are identified
|
||||||
|
|
||||||
The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used.
|
The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used.
|
||||||
|
|
||||||
|
@ -102,6 +106,12 @@ If you need to use a cache other than `'default'`, you can do so by creating a c
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
## A note on concurrency
|
||||||
|
|
||||||
|
The built-in throttle implementations are open to [race conditions][race], so under high concurrency they may allow a few extra requests through.
|
||||||
|
|
||||||
|
If your project relies on guaranteeing the number of requests during concurrent requests, you will need to implement your own throttle class. See [issue #5181][gh5181] for more details.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# API Reference
|
# API Reference
|
||||||
|
@ -210,3 +220,5 @@ The following is an example of a rate throttle, that will randomly throttle 1 in
|
||||||
[identifying-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
|
[identifying-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
|
||||||
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
|
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
|
||||||
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache
|
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache
|
||||||
|
[gh5181]: https://github.com/encode/django-rest-framework/issues/5181
|
||||||
|
[race]: https://en.wikipedia.org/wiki/Race_condition#Data_race
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
There are many ways you can contribute to Django REST framework. We'd like it to be a community-led project, so please get involved and help shape the future of the project.
|
There are many ways you can contribute to Django REST framework. We'd like it to be a community-led project, so please get involved and help shape the future of the project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case.
|
The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case.
|
||||||
|
@ -26,14 +32,13 @@ The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines f
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
It's really helpful if you can make sure to address issues on the correct channel. Usage questions should be directed to the [discussion group][google-group]. Feature requests, bug reports and other issues should be raised on the GitHub [issue tracker][issues].
|
Our contribution process is that the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.
|
||||||
|
|
||||||
Some tips on good issue reporting:
|
Some tips on good potential issue reporting:
|
||||||
|
|
||||||
* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
|
* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
|
||||||
* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.
|
* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue.
|
||||||
* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
|
* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation. At this point in it's lifespan we consider Django REST framework to be essentially feature-complete.
|
||||||
* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
|
|
||||||
* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.
|
* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.
|
||||||
|
|
||||||
## Triaging issues
|
## Triaging issues
|
||||||
|
|
|
@ -148,6 +148,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons.
|
* [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons.
|
||||||
* [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models..
|
* [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models..
|
||||||
* [fast-drf] - A model based library for making API development faster and easier.
|
* [fast-drf] - A model based library for making API development faster and easier.
|
||||||
|
* [django-requestlogs] - Providing middleware and other helpers for audit logging for REST framework.
|
||||||
|
* [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints.
|
||||||
|
|
||||||
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
|
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
|
||||||
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
|
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
|
||||||
|
@ -237,3 +239,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[graphwrap]: https://github.com/PaulGilmartin/graph_wrap
|
[graphwrap]: https://github.com/PaulGilmartin/graph_wrap
|
||||||
[rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions
|
[rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions
|
||||||
[fast-drf]: https://github.com/iashraful/fast-drf
|
[fast-drf]: https://github.com/iashraful/fast-drf
|
||||||
|
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
|
||||||
|
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
||||||
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
BIN
docs/img/premium/fezto-readme.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
docs/img/premium/spacinov-readme.png
Normal file
After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -68,16 +68,16 @@ continued development by **[signing up for a paid plan][funding]**.
|
||||||
<ul class="premium-promo promo">
|
<ul class="premium-promo promo">
|
||||||
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
||||||
<li><a href="https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
<li><a href="https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||||
<li><a href="https://software.esg-usa.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/esg-new-logo.png)">ESG</a></li>
|
<li><a href="https://www.spacinov.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/spacinov.png)">Spacinov</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://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
|
<li><a href="https://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
|
||||||
<li><a href="https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/bitio_logo_gold_background.png)">bit.io</a></li>
|
<li><a href="https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/bitio_logo_gold_background.png)">bit.io</a></li>
|
||||||
<li><a href="https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/135996800-d49fe024-32d9-441a-98d9-4c7596287a67.png)">PostHog</a></li>
|
<li><a href="https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/135996800-d49fe024-32d9-441a-98d9-4c7596287a67.png)">PostHog</a></li>
|
||||||
<li><a href="https://cryptapi.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cryptapi.png)">CryptAPI</a></li>
|
<li><a href="https://cryptapi.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cryptapi.png)">CryptAPI</a></li>
|
||||||
|
<li><a href="https://www.fezto.xyz/?utm_source=DjangoRESTFramework" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/fezto.png)">FEZTO</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||||
|
|
||||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [CryptAPI](https://cryptapi.io).*
|
*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=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**.
|
||||||
REST framework requires the following:
|
REST framework requires the following:
|
||||||
|
|
||||||
* Python (3.6, 3.7, 3.8, 3.9, 3.10)
|
* Python (3.6, 3.7, 3.8, 3.9, 3.10)
|
||||||
* Django (2.2, 3.0, 3.1, 3.2, 4.0)
|
* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)
|
||||||
|
|
||||||
We **highly recommend** and only officially support the latest patch release of
|
We **highly recommend** and only officially support the latest patch release of
|
||||||
each Python and Django series.
|
each Python and Django series.
|
||||||
|
@ -194,9 +194,11 @@ For priority support please sign up for a [professional or premium sponsorship p
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
|
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
|
||||||
|
|
||||||
Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
**Please report security issues by emailing security@djangoproject.com**.
|
||||||
|
|
||||||
|
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ from django.utils.duration import duration_string
|
||||||
from django.utils.encoding import is_protected_type, smart_str
|
from django.utils.encoding import is_protected_type, smart_str
|
||||||
from django.utils.formats import localize_input, sanitize_separators
|
from django.utils.formats import localize_input, sanitize_separators
|
||||||
from django.utils.ipv6 import clean_ipv6_address
|
from django.utils.ipv6 import clean_ipv6_address
|
||||||
from django.utils.timezone import utc
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from pytz.exceptions import InvalidTimeError
|
from pytz.exceptions import InvalidTimeError
|
||||||
|
|
||||||
|
@ -63,6 +62,9 @@ def is_simple_callable(obj):
|
||||||
"""
|
"""
|
||||||
True if the object is a callable that takes no arguments.
|
True if the object is a callable that takes no arguments.
|
||||||
"""
|
"""
|
||||||
|
if not callable(obj):
|
||||||
|
return False
|
||||||
|
|
||||||
# Bail early since we cannot inspect built-in function signatures.
|
# Bail early since we cannot inspect built-in function signatures.
|
||||||
if inspect.isbuiltin(obj):
|
if inspect.isbuiltin(obj):
|
||||||
raise BuiltinSignatureError(
|
raise BuiltinSignatureError(
|
||||||
|
@ -1190,7 +1192,7 @@ class DateTimeField(Field):
|
||||||
except InvalidTimeError:
|
except InvalidTimeError:
|
||||||
self.fail('make_aware', timezone=field_timezone)
|
self.fail('make_aware', timezone=field_timezone)
|
||||||
elif (field_timezone is None) and timezone.is_aware(value):
|
elif (field_timezone is None) and timezone.is_aware(value):
|
||||||
return timezone.make_naive(value, utc)
|
return timezone.make_naive(value, datetime.timezone.utc)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def default_timezone(self):
|
def default_timezone(self):
|
||||||
|
|
|
@ -636,7 +636,7 @@ class AutoSchema(ViewInspector):
|
||||||
"""
|
"""
|
||||||
return self.get_serializer(path, method)
|
return self.get_serializer(path, method)
|
||||||
|
|
||||||
def _get_reference(self, serializer):
|
def get_reference(self, serializer):
|
||||||
return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))}
|
return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))}
|
||||||
|
|
||||||
def get_request_body(self, path, method):
|
def get_request_body(self, path, method):
|
||||||
|
@ -650,7 +650,7 @@ class AutoSchema(ViewInspector):
|
||||||
if not isinstance(serializer, serializers.Serializer):
|
if not isinstance(serializer, serializers.Serializer):
|
||||||
item_schema = {}
|
item_schema = {}
|
||||||
else:
|
else:
|
||||||
item_schema = self._get_reference(serializer)
|
item_schema = self.get_reference(serializer)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'content': {
|
'content': {
|
||||||
|
@ -674,7 +674,7 @@ class AutoSchema(ViewInspector):
|
||||||
if not isinstance(serializer, serializers.Serializer):
|
if not isinstance(serializer, serializers.Serializer):
|
||||||
item_schema = {}
|
item_schema = {}
|
||||||
else:
|
else:
|
||||||
item_schema = self._get_reference(serializer)
|
item_schema = self.get_reference(serializer)
|
||||||
|
|
||||||
if is_list_view(path, method, self.view):
|
if is_list_view(path, method, self.view):
|
||||||
response_schema = {
|
response_schema = {
|
||||||
|
@ -808,3 +808,11 @@ class AutoSchema(ViewInspector):
|
||||||
RemovedInDRF314Warning, stacklevel=2
|
RemovedInDRF314Warning, stacklevel=2
|
||||||
)
|
)
|
||||||
return self.allows_filters(path, method)
|
return self.allows_filters(path, method)
|
||||||
|
|
||||||
|
def _get_reference(self, serializer):
|
||||||
|
warnings.warn(
|
||||||
|
"Method `_get_reference()` has been renamed to `get_reference()`. "
|
||||||
|
"The old name will be removed in DRF v3.14.",
|
||||||
|
RemovedInDRF314Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
return self.get_reference(serializer)
|
||||||
|
|
|
@ -288,7 +288,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
def get(self, path, data=None, follow=False, **extra):
|
def get(self, path, data=None, follow=False, **extra):
|
||||||
response = super().get(path, data=data, **extra)
|
response = super().get(path, data=data, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def post(self, path, data=None, format=None, content_type=None,
|
def post(self, path, data=None, format=None, content_type=None,
|
||||||
|
@ -296,7 +296,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
response = super().post(
|
response = super().post(
|
||||||
path, data=data, format=format, content_type=content_type, **extra)
|
path, data=data, format=format, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def put(self, path, data=None, format=None, content_type=None,
|
def put(self, path, data=None, format=None, content_type=None,
|
||||||
|
@ -304,7 +304,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
response = super().put(
|
response = super().put(
|
||||||
path, data=data, format=format, content_type=content_type, **extra)
|
path, data=data, format=format, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def patch(self, path, data=None, format=None, content_type=None,
|
def patch(self, path, data=None, format=None, content_type=None,
|
||||||
|
@ -312,7 +312,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
response = super().patch(
|
response = super().patch(
|
||||||
path, data=data, format=format, content_type=content_type, **extra)
|
path, data=data, format=format, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, path, data=None, format=None, content_type=None,
|
def delete(self, path, data=None, format=None, content_type=None,
|
||||||
|
@ -320,7 +320,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
response = super().delete(
|
response = super().delete(
|
||||||
path, data=data, format=format, content_type=content_type, **extra)
|
path, data=data, format=format, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def options(self, path, data=None, format=None, content_type=None,
|
def options(self, path, data=None, format=None, content_type=None,
|
||||||
|
@ -328,7 +328,7 @@ class APIClient(APIRequestFactory, DjangoClient):
|
||||||
response = super().options(
|
response = super().options(
|
||||||
path, data=data, format=format, content_type=content_type, **extra)
|
path, data=data, format=format, content_type=content_type, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
|
|
1
setup.py
|
@ -94,6 +94,7 @@ setup(
|
||||||
'Framework :: Django :: 3.1',
|
'Framework :: Django :: 3.1',
|
||||||
'Framework :: Django :: 3.2',
|
'Framework :: Django :: 3.2',
|
||||||
'Framework :: Django :: 4.0',
|
'Framework :: Django :: 4.0',
|
||||||
|
'Framework :: Django :: 4.1',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.timezone import utc
|
|
||||||
|
|
||||||
from rest_framework.compat import coreapi
|
from rest_framework.compat import coreapi
|
||||||
from rest_framework.utils.encoders import JSONEncoder
|
from rest_framework.utils.encoders import JSONEncoder
|
||||||
from rest_framework.utils.serializer_helpers import ReturnList
|
from rest_framework.utils.serializer_helpers import ReturnList
|
||||||
|
|
||||||
|
utc = timezone.utc
|
||||||
|
|
||||||
|
|
||||||
class MockList:
|
class MockList:
|
||||||
def tolist(self):
|
def tolist(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ import pytz
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.utils.timezone import activate, deactivate, override, utc
|
from django.utils.timezone import activate, deactivate, override
|
||||||
|
|
||||||
import rest_framework
|
import rest_framework
|
||||||
from rest_framework import exceptions, serializers
|
from rest_framework import exceptions, serializers
|
||||||
|
@ -17,6 +17,8 @@ from rest_framework.fields import (
|
||||||
BuiltinSignatureError, DjangoImageField, is_simple_callable
|
BuiltinSignatureError, DjangoImageField, is_simple_callable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
utc = datetime.timezone.utc
|
||||||
|
|
||||||
# Tests for helper functions.
|
# Tests for helper functions.
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
||||||
|
@ -73,6 +75,10 @@ class TestIsSimpleCallable:
|
||||||
assert is_simple_callable(valid_vargs_kwargs)
|
assert is_simple_callable(valid_vargs_kwargs)
|
||||||
assert not is_simple_callable(invalid)
|
assert not is_simple_callable(invalid)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('obj', (True, None, "str", b'bytes', 123, 1.23))
|
||||||
|
def test_not_callable(self, obj):
|
||||||
|
assert not is_simple_callable(obj)
|
||||||
|
|
||||||
def test_4602_regression(self):
|
def test_4602_regression(self):
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import django
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
@ -452,11 +453,14 @@ class TestPosgresFieldsMapping(TestCase):
|
||||||
model = ArrayFieldModel
|
model = ArrayFieldModel
|
||||||
fields = ['array_field', 'array_field_with_blank']
|
fields = ['array_field', 'array_field_with_blank']
|
||||||
|
|
||||||
|
validators = ""
|
||||||
|
if django.VERSION < (4, 1):
|
||||||
|
validators = ", validators=[<django.core.validators.MaxLengthValidator object>]"
|
||||||
expected = dedent("""
|
expected = dedent("""
|
||||||
TestSerializer():
|
TestSerializer():
|
||||||
array_field = ListField(allow_empty=False, child=CharField(label='Array field', validators=[<django.core.validators.MaxLengthValidator object>]))
|
array_field = ListField(allow_empty=False, child=CharField(label='Array field'%s))
|
||||||
array_field_with_blank = ListField(child=CharField(label='Array field with blank', validators=[<django.core.validators.MaxLengthValidator object>]), required=False)
|
array_field_with_blank = ListField(child=CharField(label='Array field with blank'%s), required=False)
|
||||||
""")
|
""" % (validators, validators))
|
||||||
self.assertEqual(repr(TestSerializer()), expected)
|
self.assertEqual(repr(TestSerializer()), expected)
|
||||||
|
|
||||||
@pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField')
|
@pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField')
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import itertools
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
@ -14,7 +17,7 @@ from rest_framework.test import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET', 'POST'])
|
@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
|
||||||
def view(request):
|
def view(request):
|
||||||
return Response({
|
return Response({
|
||||||
'auth': request.META.get('HTTP_AUTHORIZATION', b''),
|
'auth': request.META.get('HTTP_AUTHORIZATION', b''),
|
||||||
|
@ -36,6 +39,11 @@ def redirect_view(request):
|
||||||
return redirect('/view/')
|
return redirect('/view/')
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
|
||||||
|
def redirect_307_308_view(request, code):
|
||||||
|
return HttpResponseRedirect('/view/', status=code)
|
||||||
|
|
||||||
|
|
||||||
class BasicSerializer(serializers.Serializer):
|
class BasicSerializer(serializers.Serializer):
|
||||||
flag = fields.BooleanField(default=lambda: True)
|
flag = fields.BooleanField(default=lambda: True)
|
||||||
|
|
||||||
|
@ -51,6 +59,7 @@ urlpatterns = [
|
||||||
path('view/', view),
|
path('view/', view),
|
||||||
path('session-view/', session_view),
|
path('session-view/', session_view),
|
||||||
path('redirect-view/', redirect_view),
|
path('redirect-view/', redirect_view),
|
||||||
|
path('redirect-view/<int:code>/', redirect_307_308_view),
|
||||||
path('post-view/', post_view)
|
path('post-view/', post_view)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -146,41 +155,32 @@ class TestAPITestClient(TestCase):
|
||||||
"""
|
"""
|
||||||
Follow redirect by setting follow argument.
|
Follow redirect by setting follow argument.
|
||||||
"""
|
"""
|
||||||
response = self.client.get('/redirect-view/')
|
for method in ('get', 'post', 'put', 'patch', 'delete', 'options'):
|
||||||
|
with self.subTest(method=method):
|
||||||
|
req_method = getattr(self.client, method)
|
||||||
|
response = req_method('/redirect-view/')
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
response = self.client.get('/redirect-view/', follow=True)
|
response = req_method('/redirect-view/', follow=True)
|
||||||
assert response.redirect_chain is not None
|
assert response.redirect_chain is not None
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = self.client.post('/redirect-view/')
|
def test_follow_307_308_preserve_kwargs(self, *mocked_methods):
|
||||||
assert response.status_code == 302
|
"""
|
||||||
response = self.client.post('/redirect-view/', follow=True)
|
Follow redirect by setting follow argument, and make sure the following
|
||||||
assert response.redirect_chain is not None
|
method called with appropriate kwargs.
|
||||||
assert response.status_code == 200
|
"""
|
||||||
|
methods = ('get', 'post', 'put', 'patch', 'delete', 'options')
|
||||||
response = self.client.put('/redirect-view/')
|
codes = (307, 308)
|
||||||
assert response.status_code == 302
|
for method, code in itertools.product(methods, codes):
|
||||||
response = self.client.put('/redirect-view/', follow=True)
|
subtest_ctx = self.subTest(method=method, code=code)
|
||||||
assert response.redirect_chain is not None
|
patch_ctx = patch.object(self.client, method, side_effect=getattr(self.client, method))
|
||||||
assert response.status_code == 200
|
with subtest_ctx, patch_ctx as req_method:
|
||||||
|
kwargs = {'data': {'example': 'test'}, 'format': 'json'}
|
||||||
response = self.client.patch('/redirect-view/')
|
response = req_method('/redirect-view/%s/' % code, follow=True, **kwargs)
|
||||||
assert response.status_code == 302
|
|
||||||
response = self.client.patch('/redirect-view/', follow=True)
|
|
||||||
assert response.redirect_chain is not None
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
response = self.client.delete('/redirect-view/')
|
|
||||||
assert response.status_code == 302
|
|
||||||
response = self.client.delete('/redirect-view/', follow=True)
|
|
||||||
assert response.redirect_chain is not None
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
response = self.client.options('/redirect-view/')
|
|
||||||
assert response.status_code == 302
|
|
||||||
response = self.client.options('/redirect-view/', follow=True)
|
|
||||||
assert response.redirect_chain is not None
|
assert response.redirect_chain is not None
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
for _, call_args, call_kwargs in req_method.mock_calls:
|
||||||
|
assert all(call_kwargs[k] == kwargs[k] for k in kwargs if k in call_kwargs)
|
||||||
|
|
||||||
def test_invalid_multipart_data(self):
|
def test_invalid_multipart_data(self):
|
||||||
"""
|
"""
|
||||||
|
|
6
tox.ini
|
@ -3,7 +3,7 @@ envlist =
|
||||||
{py36,py37,py38,py39}-django22,
|
{py36,py37,py38,py39}-django22,
|
||||||
{py36,py37,py38,py39}-django31,
|
{py36,py37,py38,py39}-django31,
|
||||||
{py36,py37,py38,py39,py310}-django32,
|
{py36,py37,py38,py39,py310}-django32,
|
||||||
{py38,py39,py310}-{django40,djangomain},
|
{py38,py39,py310}-{django40,django41,djangomain},
|
||||||
base,dist,docs,
|
base,dist,docs,
|
||||||
|
|
||||||
[travis:env]
|
[travis:env]
|
||||||
|
@ -12,6 +12,7 @@ DJANGO =
|
||||||
3.1: django31
|
3.1: django31
|
||||||
3.2: django32
|
3.2: django32
|
||||||
4.0: django40
|
4.0: django40
|
||||||
|
4.1: django41
|
||||||
main: djangomain
|
main: djangomain
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
@ -24,7 +25,8 @@ deps =
|
||||||
django22: Django>=2.2,<3.0
|
django22: Django>=2.2,<3.0
|
||||||
django31: Django>=3.1,<3.2
|
django31: Django>=3.1,<3.2
|
||||||
django32: Django>=3.2,<4.0
|
django32: Django>=3.2,<4.0
|
||||||
django40: Django>=4.0,<5.0
|
django40: Django>=4.0,<4.1
|
||||||
|
django41: Django>=4.1a1,<4.2
|
||||||
djangomain: https://github.com/django/django/archive/main.tar.gz
|
djangomain: https://github.com/django/django/archive/main.tar.gz
|
||||||
-rrequirements/requirements-testing.txt
|
-rrequirements/requirements-testing.txt
|
||||||
-rrequirements/requirements-optionals.txt
|
-rrequirements/requirements-optionals.txt
|
||||||
|
|