Compare commits

..

No commits in common. "master" and "3.15.0" have entirely different histories.

131 changed files with 2324 additions and 2187 deletions

10
.github/ISSUE_TEMPLATE/1-issue.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Issue
about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏
---
## Checklist
- [ ] Raised initially as discussion #...
- [ ] 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.

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: Discussions
url: https://github.com/encode/django-rest-framework/discussions
about: >
The "Discussions" forum is where you want to start. 💖

View File

@ -9,16 +9,18 @@ on:
jobs:
tests:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
matrix:
python-version:
- '3.6'
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
steps:
- uses: actions/checkout@v4
@ -33,10 +35,19 @@ jobs:
run: python -m pip install --upgrade pip setuptools virtualenv wheel
- name: Install dependencies
run: python -m pip install --upgrade tox
run: python -m pip install --upgrade codecov tox
- name: Install tox-py
if: ${{ matrix.python-version == '3.6' }}
run: python -m pip install --upgrade tox-py
- name: Run tox targets for ${{ matrix.python-version }}
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d . | cut -f 1 -d '-')
if: ${{ matrix.python-version != '3.6' }}
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
- name: Run tox targets for ${{ matrix.python-version }}
if: ${{ matrix.python-version == '3.6' }}
run: tox --py current
- name: Run extra tox targets
if: ${{ matrix.python-version == '3.9' }}
@ -44,13 +55,12 @@ jobs:
tox -e base,dist,docs
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
env_vars: TOXENV,DJANGO
run: |
codecov -e TOXENV,DJANGO
test-docs:
name: Test documentation links
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

View File

@ -31,9 +31,3 @@ repos:
hooks:
- id: codespell
exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.1
hooks:
- id: pyupgrade
args: ["--py39-plus", "--keep-percent-format"]

View File

@ -2,4 +2,6 @@
At this point in its 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.

View File

@ -1,4 +1,4 @@
*Note*: Before submitting a code change, please review our [contributing guidelines](https://www.django-rest-framework.org/community/contributing/#pull-requests).
*Note*: Before submitting this pull request, please review our [contributing guidelines](https://www.django-rest-framework.org/community/contributing/#pull-requests).
## Description

View File

@ -28,9 +28,8 @@ The initial aim is to provide a single full-time position on REST framework.
[![][cryptapi-img]][cryptapi-url]
[![][fezto-img]][fezto-url]
[![][svix-img]][svix-url]
[![][zuplo-img]][zuplo-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], [FEZTO][fezto-url], [Svix][svix-url], and [Zuplo][zuplo-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], [FEZTO][fezto-url], and [Svix][svix-url].
---
@ -46,6 +45,8 @@ Some reasons you might want to use REST framework:
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
* [Extensive documentation][docs], and [great community support][group].
There is a live example API for testing purposes, [available here][sandbox].
**Below**: *Screenshot from the browsable API*
![Screenshot][image]
@ -54,8 +55,8 @@ Some reasons you might want to use REST framework:
# Requirements
* Python 3.9+
* Django 4.2, 5.0, 5.1, 5.2
* Python 3.6+
* Django 5.0, 4.2, 4.1, 4.0, 3.2, 3.1, 3.0
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
@ -173,6 +174,8 @@ Full documentation for the project is available at [https://www.django-rest-fram
For questions and support, use the [REST framework discussion group][group], or `#restframework` on libera.chat IRC.
You may also want to [follow the author on Twitter][twitter].
# Security
Please see the [security policy][security-policy].
@ -183,7 +186,9 @@ Please see the [security policy][security-policy].
[codecov]: https://codecov.io/github/encode/django-rest-framework?branch=master
[pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
[pypi]: https://pypi.org/project/djangorestframework/
[twitter]: https://twitter.com/starletdreaming
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[sandbox]: https://restframework.herokuapp.com/
[funding]: https://fund.django-rest-framework.org/topics/funding/
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
@ -197,7 +202,6 @@ Please see the [security policy][security-policy].
[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
[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-premium.png
[zuplo-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/zuplo-readme.png
[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
@ -208,7 +212,6 @@ Please see the [security policy][security-policy].
[cryptapi-url]: https://cryptapi.io
[fezto-url]: https://www.fezto.xyz/?utm_source=DjangoRESTFramework
[svix-url]: https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship
[zuplo-url]: https://zuplo.link/django-gh
[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

View File

@ -2,6 +2,8 @@
## Reporting a Vulnerability
**Please report security issues by emailing security@encode.io**.
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
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.

View File

@ -90,12 +90,6 @@ The kind of response that will be used depends on the authentication scheme. Al
Note that when a request may successfully authenticate, but still be denied permission to perform the request, in which case a `403 Permission Denied` response will always be used, regardless of the authentication scheme.
## Django 5.1+ `LoginRequiredMiddleware`
If you're running Django 5.1+ and use the [`LoginRequiredMiddleware`][login-required-middleware], please note that all views from DRF are opted-out of this middleware. This is because the authentication in DRF is based authentication and permissions classes, which may be determined after the middleware has been applied. Additionally, when the request is not authenticated, the middleware redirects the user to the login page, which is not suitable for API requests, where it's preferable to return a 401 status code.
REST framework offers an equivalent mechanism for DRF views via the global settings, `DEFAULT_AUTHENTICATION_CLASSES` and `DEFAULT_PERMISSION_CLASSES`. They should be changed accordingly if you need to enforce that API requests are logged in.
## Apache mod_wsgi specific configuration
Note that if deploying to [Apache using mod_wsgi][mod_wsgi_official], the authorization header is not passed through to a WSGI application by default, as it is assumed that authentication will be handled by Apache, rather than at an application level.
@ -454,12 +448,6 @@ There are currently two forks of this project.
More information can be found in the [Documentation](https://django-rest-durin.readthedocs.io/en/latest/index.html).
## django-pyoidc
[dango-pyoidc][django_pyoidc] adds support for OpenID Connect (OIDC) authentication. This allows you to delegate user management to an Identity Provider, which can be used to implement Single-Sign-On (SSO). It provides support for most uses-cases, such as customizing how token info are mapped to user models, using OIDC audiences for access control, etc.
More information can be found in the [Documentation](https://django-pyoidc.readthedocs.io/latest/index.html).
[cite]: https://jacobian.org/writing/rest-worst-practices/
[http401]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
[http403]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
@ -496,5 +484,3 @@ More information can be found in the [Documentation](https://django-pyoidc.readt
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
[django-rest-durin]: https://github.com/eshaan7/django-rest-durin
[login-required-middleware]: https://docs.djangoproject.com/en/stable/ref/middleware/#django.contrib.auth.middleware.LoginRequiredMiddleware
[django-pyoidc] : https://github.com/makinacorpus/django_pyoidc

View File

@ -59,29 +59,6 @@ class PostView(APIView):
return Response(content)
```
## Using cache with @api_view decorator
When using @api_view decorator, the Django-provided method-based cache decorators such as [`cache_page`][page],
[`vary_on_cookie`][cookie] and [`vary_on_headers`][headers] can be called directly.
```python
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie
from rest_framework.decorators import api_view
from rest_framework.response import Response
@cache_page(60 * 15)
@vary_on_cookie
@api_view(["GET"])
def get_user_list(request):
content = {"user_feed": request.user.get_user_feed()}
return Response(content)
```
**NOTE:** The [`cache_page`][page] decorator only caches the
`GET` and `HEAD` responses with status 200.

View File

@ -68,6 +68,14 @@ When serializing the instance, default will be used if the object attribute or d
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
Notes regarding default value propagation from model to serializer:
All the default values from model will pass as default to the serializer and the options method.
If the default is callable then it will be propagated to & evaluated every time in the serializer but not in options method.
If the value for given field is not given then default value will be present in the serializer and available in serializer's methods. Specified validation on given field will be evaluated on default value as that field will be present in the serializer.
### `allow_null`
Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value.
@ -291,8 +299,8 @@ Corresponds to `django.db.models.fields.DecimalField`.
* `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`.
* `decimal_places` The number of decimal places to store with the number.
* `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`.
* `max_value` Validate that the number provided is no greater than this value. Should be an integer or `Decimal` object.
* `min_value` Validate that the number provided is no less than this value. Should be an integer or `Decimal` object.
* `max_value` Validate that the number provided is no greater than this value.
* `min_value` Validate that the number provided is no less than this value.
* `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file.
* `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`.
* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`.
@ -552,7 +560,7 @@ For further examples on `HiddenField` see the [validators](validators.md) docume
---
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
---

View File

@ -173,11 +173,12 @@ This permission is suitable if you want to your API to allow read permissions to
This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property or `get_queryset()` method. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. The appropriate model is determined by checking `get_queryset().model` or `queryset.model`.
* `GET` requests require the user to have the `view` or `change` permission on the model
* `POST` requests require the user to have the `add` permission on the model.
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model.
* `DELETE` requests require the user to have the `delete` permission on the model.
The default behavior can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests.
The default behaviour can also be overridden to support custom model permissions.
To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details.

View File

@ -628,16 +628,12 @@ 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.
The [rest-framework-gm2m-relations][drf-gm2m-relations] library provides read/write serialization for [django-gm2m][django-gm2m-field].
[cite]: http://users.ece.utexas.edu/~adnan/pike.html
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[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
[drf-gm2m-relations]: https://github.com/mojtabaakbari221b/rest-framework-gm2m-relations
[django-gm2m-field]: https://github.com/tkhyn/django-gm2m
[django-intermediary-manytomany]: https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany
[dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
[to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data

View File

@ -283,7 +283,7 @@ By default this will include the following keys: `view`, `request`, `response`,
The following is an example plaintext renderer that will return a response with the `data` parameter as the content of the response.
from django.utils.encoding import smart_str
from django.utils.encoding import smart_text
from rest_framework import renderers
@ -292,7 +292,7 @@ The following is an example plaintext renderer that will return a response with
format = 'txt'
def render(self, data, accepted_media_type=None, renderer_context=None):
return smart_str(data, encoding=self.charset)
return smart_text(data, encoding=self.charset)
## Setting the character set
@ -525,7 +525,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
## LaTeX
[Rest Framework Latex] provides a renderer that outputs PDFs using Lualatex. It is maintained by [Pebble (S/F Software)][mypebble].
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process

View File

@ -142,24 +142,6 @@ The above example would now generate the following URL pattern:
* URL path: `^users/{pk}/change-password/$`
* URL name: `'user-change_password'`
### Using Django `path()` with routers
By default, the URLs created by routers use regular expressions. This behavior can be modified by setting the `use_regex_path` argument to `False` when instantiating the router, in this case [path converters][path-converters-topic-reference] are used. For example:
router = SimpleRouter(use_regex_path=False)
The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_value_regex` attribute on the viewset or `lookup_value_converter` if using path converters. For example, you can limit the lookup to valid UUIDs:
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'my_model_id'
lookup_value_regex = '[0-9a-f]{32}'
class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'my_model_uuid'
lookup_value_converter = 'uuid'
Note that path converters will be used on all URLs registered in the router, including viewset actions.
# API Guide
## SimpleRouter
@ -178,13 +160,30 @@ This router includes routes for the standard set of `list`, `create`, `retrieve`
<tr><td>{prefix}/{lookup}/{url_path}/</td><td>GET, or as specified by `methods` argument</td><td>`@action(detail=True)` decorated method</td><td>{basename}-{url_name}</td></tr>
</table>
By default, the URLs created by `SimpleRouter` are appended with a trailing slash.
By default the URLs created by `SimpleRouter` are appended with a trailing slash.
This behavior can be modified by setting the `trailing_slash` argument to `False` when instantiating the router. For example:
router = SimpleRouter(trailing_slash=False)
Trailing slashes are conventional in Django, but are not used by default in some other frameworks such as Rails. Which style you choose to use is largely a matter of preference, although some javascript frameworks may expect a particular routing style.
By default the URLs created by `SimpleRouter` use regular expressions. This behavior can be modified by setting the `use_regex_path` argument to `False` when instantiating the router, in this case [path converters][path-converters-topic-reference] are used. For example:
router = SimpleRouter(use_regex_path=False)
**Note**: `use_regex_path=False` only works with Django 2.x or above, since this feature was introduced in 2.0.0. See [release note][simplified-routing-release-note]
The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_value_regex` attribute on the viewset or `lookup_value_converter` if using path converters. For example, you can limit the lookup to valid UUIDs:
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'my_model_id'
lookup_value_regex = '[0-9a-f]{32}'
class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'my_model_uuid'
lookup_value_converter = 'uuid'
## DefaultRouter
This router is similar to `SimpleRouter` as above, but additionally includes a default API root view, that returns a response containing hyperlinks to all the list views. It also generates routes for optional `.json` style format suffixes.
@ -352,4 +351,5 @@ The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions
[drf-extensions-customizable-endpoint-names]: https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name
[url-namespace-docs]: https://docs.djangoproject.com/en/4.0/topics/http/urls/#url-namespaces
[include-api-reference]: https://docs.djangoproject.com/en/4.0/ref/urls/#include
[simplified-routing-release-note]: https://docs.djangoproject.com/en/2.0/releases/2.0/#simplified-url-routing-syntax
[path-converters-topic-reference]: https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters

View File

@ -56,11 +56,10 @@ The following sections explain more.
### Install dependencies
pip install pyyaml uritemplate inflection
pip install pyyaml uritemplate
* `pyyaml` is used to generate schema into YAML-based OpenAPI format.
* `uritemplate` is used internally to get parameters in path.
* `inflection` is used to pluralize operations more appropriately in the list endpoints.
### Generating a static schema with the `generateschema` management command

View File

@ -233,7 +233,7 @@ Serializer classes can also include reusable validators that are applied to the
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.ChoiceField(choices=[101, 102, 103, 201])
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
@ -845,6 +845,8 @@ Here's an example of how you might choose to implement multiple updates:
class Meta:
list_serializer_class = BookListSerializer
It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the `allow_add_remove` behavior that was present in REST framework 2.
#### Customizing ListSerializer initialization
When a serializer with `many=True` is instantiated, we need to determine which arguments and keyword arguments should be passed to the `.__init__()` method for both the child `Serializer` class, and for the parent `ListSerializer` class.

View File

@ -460,4 +460,4 @@ Default: `None`
[cite]: https://www.python.org/dev/peps/pep-0020/
[rfc4627]: https://www.ietf.org/rfc/rfc4627.txt
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
[strftime]: https://docs.python.org/3/library/time.html#time.strftime

View File

@ -25,12 +25,9 @@ The `APIRequestFactory` class supports an almost identical API to Django's stand
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})
# Using the standard RequestFactory API to encode JSON data
request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json')
#### Using the `format` argument
Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a wide set of request formats. When using this argument, the factory will select an appropriate renderer and its configured `content_type`. For example:
Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a content type other than multipart form data. For example:
# Create a JSON POST request
factory = APIRequestFactory()
@ -44,7 +41,7 @@ To support a wider set of request formats, or change the default format, [see th
If you need to explicitly encode the request body, you can do so by setting the `content_type` flag. For example:
request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml')
request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')
#### PUT and PATCH with form data

View File

@ -45,7 +45,7 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C
}
}
The rates used in `DEFAULT_THROTTLE_RATES` can be specified over a period of second, minute, hour or day. The period must be specified after the `/` separator using `s`, `m`, `h` or `d`, respectively. For increased clarity, extended units such as `second`, `minute`, `hour`, `day` or even abbreviations like `sec`, `min`, `hr` are allowed, as only the first character is relevant to identify the rate.
The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period.
You can also set the throttling policy on a per-view or per-viewset basis,
using the `APIView` class-based views.
@ -110,7 +110,7 @@ You'll need to remember to also set your custom throttle class in the `'DEFAULT_
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.
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.
---
@ -220,4 +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
[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
[gh5181]: https://github.com/encode/django-rest-framework/issues/5181
[race]: https://en.wikipedia.org/wiki/Race_condition#Data_race

View File

@ -48,7 +48,7 @@ If we open up the Django shell using `manage.py shell` we can now
CustomerReportSerializer():
id = IntegerField(label='ID', read_only=True)
time_raised = DateTimeField(read_only=True)
reference = CharField(max_length=20, validators=[UniqueValidator(queryset=CustomerReportRecord.objects.all())])
reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
description = CharField(style={'type': 'textarea'})
The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field.
@ -166,7 +166,7 @@ If you want the date field to be entirely hidden from the user, then use `Hidden
---
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
---

View File

@ -128,8 +128,6 @@ You may inspect these attributes to adjust behavior based on the current action.
permission_classes = [IsAdminUser]
return [permission() for permission in permission_classes]
**Note**: the `action` attribute is not available in the `get_parsers`, `get_authenticators` and `get_content_negotiator` methods, as it is set _after_ they are called in the framework lifecycle. If you override one of these methods and try to access the `action` attribute in them, you will get an `AttributeError` error.
## 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 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.

View File

@ -31,6 +31,10 @@ The current minimum versions of Django still is 3.0 and Python 3.6.
`ModelSerializer` generates validators for [UniqueConstraint](https://docs.djangoproject.com/en/4.0/ref/models/constraints/#uniqueconstraint) (both UniqueValidator and UniqueTogetherValidator)
## ValidationErrors improvements
The `ValidationError` has been aligned with Django's, currently supporting the same style (signature) and nesting.
## SimpleRouter non-regex matching support
By default the URLs created by `SimpleRouter` use regular expressions. This behavior can be modified by setting the `use_regex_path` argument to `False` when instantiating the router.
@ -43,6 +47,10 @@ Dependency on pytz has been removed and deprecation warnings have been added, Dj
Searches now may contain _quoted phrases_ with spaces, each phrase is considered as a single search term, and it will raise a validation error if any null-character is provided in search. See the [Filtering API guide](../api-guide/filtering.md) for more information.
## Default values propagation
Model fields' default values are now propagated to serializer fields, for more information see the [Serializer fields API guide](../api-guide/fields.md#default).
## Other fixes and improvements
There are a number of fixes and minor improvements in this release, ranging from documentation, internal infrastructure (typing, testing, requirements, deprecation, etc.), security and overall behaviour.

View File

@ -1,42 +0,0 @@
<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.16
At the Internet, on March 28th, 2025, we are happy to announce the release of Django REST framework 3.16.
## Updated Django and Python support
The latest release now fully supports Django 5.1 and the upcoming 5.2 LTS as well as Python 3.13.
The current minimum versions of Django is now 4.2 and Python 3.9.
## Django LoginRequiredMiddleware
The new `LoginRequiredMiddleware` introduced by Django 5.1 can now be used alongside Django REST Framework, however it is not honored for API views as an equivalent behaviour can be configured via `DEFAULT_AUTHENTICATION_CLASSES`. See [our dedicated section](../api-guide/authentication.md#django-51-loginrequiredmiddleware) in the docs for more information.
## Improved support for UniqueConstraint
The generation of validators for [UniqueConstraint](https://docs.djangoproject.com/en/stable/ref/models/constraints/#uniqueconstraint) has been improved to support better nullable fields and constraints with conditions.
## Other fixes and improvements
There are a number of fixes and minor improvements in this release, ranging from documentation, internal infrastructure (typing, testing, requirements, deprecation, etc.), security and overall behaviour.
See the [release notes](release-notes.md) page for a complete listing.

View File

@ -4,9 +4,13 @@
>
> &mdash; [Tim Berners-Lee][cite]
!!! note
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.
At this point in its lifespan we consider Django REST framework to be feature-complete. We focus on pull requests that track the continued development of Django versions, and generally do not accept new features or code formatting changes.
---
**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
@ -28,8 +32,24 @@ The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines f
# Issues
* Django REST framework is considered feature-complete. Please do not file requests to change behavior, unless it is required for security reasons or to maintain compatibility with upcoming Django or Python versions.
* Feature requests will typically be closed with a recommendation that they be implemented outside the core REST framework library (e.g. as third-party libraries). This approach allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability and great documentation.
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 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.
* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue.
* 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.
* 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
Getting involved in triaging incoming issues is a good way to start contributing. Every single ticket that comes into the ticket tracker needs to be reviewed in order to determine what the next steps should be. Anyone can help out with this, you just need to be willing to
* Read through the ticket - does it make sense, is it missing any context that would help explain it better?
* Is the ticket reported in the correct place, would it be better suited as a discussion on the discussion group?
* If the ticket is a bug report, can you reproduce it? Are you able to write a failing test case that demonstrates the issue and that can be submitted as a pull request?
* If the ticket is a feature request, do you agree with it, and could the feature request instead be implemented as a third party package?
* If a ticket hasn't had much activity and it addresses something you need, then comment on the ticket and try to find out what's needed to get it moving again.
# Development
@ -189,6 +209,7 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[code-of-conduct]: https://www.djangoproject.com/conduct/
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[so-filter]: https://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/encode/django-rest-framework/issues?state=open
[pep-8]: https://www.python.org/dev/peps/pep-0008/
[build-status]: ../img/build-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests

View File

@ -1,388 +1,398 @@
<script>
// Imperfect, but easier to fit in with the existing docs build.
// Hyperlinks should point directly to the "fund." subdomain, but this'll
// handle the nav bar links without requiring any docs build changes for the moment.
if (window.location.hostname == "www.django-rest-framework.org") {
window.location.replace("https://fund.django-rest-framework.org/topics/funding/");
}
</script>
<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;
}
.chart {
background-color: #e3e3e3;
background: -webkit-linear-gradient(top, #fff 0, #e3e3e3 100%);
border: 1px solid #E6E6E6;
border-radius: 5px;
box-shadow: 0px 0px 2px 0px rgba(181, 181, 181, 0.3);
padding: 40px 0px 5px;
position: relative;
text-align: center;
width: 97%;
min-height: 255px;
position: relative;
top: 37px;
margin-bottom: 20px
}
.quantity {
text-align: center
}
.dollar {
font-size: 19px;
position: relative;
top: -18px;
}
.price {
font-size: 49px;
}
.period {
font-size: 17px;
position: relative;
top: -8px;
margin-left: 4px;
}
.plan-name {
text-align: center;
font-size: 20px;
font-weight: 400;
color: #777;
border-bottom: 1px solid #d5d5d5;
padding-bottom: 15px;
width: 90%;
margin: 0 auto;
margin-top: 8px;
}
.specs {
margin-top: 20px;
min-height: 130px;
}
.specs.freelancer {
min-height: 0px;
}
.spec {
font-size: 15px;
color: #474747;
text-align: center;
font-weight: 300;
margin-bottom: 13px;
}
.variable {
color: #1FBEE7;
font-weight: 400;
}
form.signup {
margin-top: 35px
}
.clear-promo {
padding-top: 30px
}
#main-content h1:first-of-type {
margin: 0 0 50px;
font-size: 60px;
font-weight: 200;
text-align: center
}
#main-content {
padding-top: 10px; line-height: 23px
}
#main-content li {
line-height: 23px
}
</style>
# Funding
If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
Signing up for a paid plan will:
* Directly contribute to faster releases, more features, and higher quality software.
* Allow more time to be invested in keeping the package up to date.
* Safeguard the future development of REST framework.
REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
---
## What funding has enabled so far
* 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.
* Contracting development time for the work on the JavaScript client library and API documentation tooling.
---
## What our sponsors and users say
> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
>
> &mdash; José Padilla, Django REST framework contributor
&nbsp;
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
>
> &mdash; Filipe Ximenes, Vinta Software
&nbsp;
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
>
> &mdash; Andrew Conti, Django REST framework user
Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
---
## Individual plan
This subscription is recommended for individuals with an interest in seeing REST framework continue to&nbsp;improve.
If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate&nbsp;plan](#corporate-plans).
<div class="pricing">
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.personal1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Individual</div>
<div class="specs freelancer">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
Credited on the site
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.personal1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.personal1 }}"
data-name="Django REST framework"
data-description="Individual"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
</div>
<div style="clear: both; padding-top: 50px"></div>
*Billing is monthly and you can cancel at any time.*
---
## Corporate plans
These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
<div class="pricing">
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Basic</div>
<div class="specs startup">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Funding page</span> ad placement
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate1 }}"
data-name="Django REST framework"
data-description="Basic"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
<div class="span4">
<div class="chart">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate2 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Professional</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate2 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate2 }}"
data-name="Django REST framework"
data-description="Professional"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
<div class="span4">
<div class="chart last">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate3 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Premium</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Homepage</span> ad placement
</div>
<div class="spec">
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate3 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate3 }}"
data-name="Django REST framework"
data-description="Premium"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
</div>
<div style="clear: both; padding-top: 50px"></div>
*Billing is monthly and you can cancel at any time.*
Once you've signed up, we will contact you via email and arrange your ad placements on the site.
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
---
## Accountability
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">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&amp;id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<h2>Stay up to date, with our monthly progress reports...</h2>
<div class="mc-field-group">
<label for="mce-EMAIL">Email Address </label>
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
<!--End mc_embed_signup-->
---
## Frequently asked questions
**Q: Can you issue monthly invoices?**
A: Yes, we are happy to issue monthly invoices. Please just <a href=mailto:funding@django-rest-framework.org>email us</a> and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
**Q: Does sponsorship include VAT?**
A: Sponsorship is VAT exempt.
**Q: Do I have to sign up for a certain time period?**
A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
**Q: Are you only looking for corporate sponsors?**
A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
---
## Our sponsors
<div id="fundingInclude"></div>
<script src="https://fund.django-rest-framework.org/funding_include.js"></script>
<script>
// Imperfect, but easier to fit in with the existing docs build.
// Hyperlinks should point directly to the "fund." subdomain, but this'll
// handle the nav bar links without requiring any docs build changes for the moment.
if (window.location.hostname == "www.django-rest-framework.org") {
window.location.replace("https://fund.django-rest-framework.org/topics/funding/");
}
</script>
<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;
}
.chart {
background-color: #e3e3e3;
background: -webkit-linear-gradient(top, #fff 0, #e3e3e3 100%);
border: 1px solid #E6E6E6;
border-radius: 5px;
box-shadow: 0px 0px 2px 0px rgba(181, 181, 181, 0.3);
padding: 40px 0px 5px;
position: relative;
text-align: center;
width: 97%;
min-height: 255px;
position: relative;
top: 37px;
margin-bottom: 20px
}
.quantity {
text-align: center
}
.dollar {
font-size: 19px;
position: relative;
top: -18px;
}
.price {
font-size: 49px;
}
.period {
font-size: 17px;
position: relative;
top: -8px;
margin-left: 4px;
}
.plan-name {
text-align: center;
font-size: 20px;
font-weight: 400;
color: #777;
border-bottom: 1px solid #d5d5d5;
padding-bottom: 15px;
width: 90%;
margin: 0 auto;
margin-top: 8px;
}
.specs {
margin-top: 20px;
min-height: 130px;
}
.specs.freelancer {
min-height: 0px;
}
.spec {
font-size: 15px;
color: #474747;
text-align: center;
font-weight: 300;
margin-bottom: 13px;
}
.variable {
color: #1FBEE7;
font-weight: 400;
}
form.signup {
margin-top: 35px
}
.clear-promo {
padding-top: 30px
}
#main-content h1:first-of-type {
margin: 0 0 50px;
font-size: 60px;
font-weight: 200;
text-align: center
}
#main-content {
padding-top: 10px; line-height: 23px
}
#main-content li {
line-height: 23px
}
</style>
# Funding
If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
Signing up for a paid plan will:
* Directly contribute to faster releases, more features, and higher quality software.
* Allow more time to be invested in documentation, issue triage, and community support.
* Safeguard the future development of REST framework.
REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
---
## What funding has enabled so far
* 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.
* Contracting development time for the work on the JavaScript client library and API documentation tooling.
---
## What future funding will enable
* Realtime API support, using WebSockets. This will consist of documentation and support for using REST framework together with Django Channels, plus integrating WebSocket support into the client libraries.
* Better authentication defaults, possibly bringing JWT & CORS support into the core package.
* Securing the community & operations manager position long-term.
* Opening up and securing a part-time position to focus on ticket triage and resolution.
* Paying for development time on building API client libraries in a range of programming languages. These would be integrated directly into the upcoming API documentation.
Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
---
## What our sponsors and users say
> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
>
> &mdash; José Padilla, Django REST framework contributor
&nbsp;
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
>
> &mdash; Filipe Ximenes, Vinta Software
&nbsp;
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
>
> &mdash; Andrew Conti, Django REST framework user
---
## Individual plan
This subscription is recommended for individuals with an interest in seeing REST framework continue to&nbsp;improve.
If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate&nbsp;plan](#corporate-plans).
<div class="pricing">
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.personal1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Individual</div>
<div class="specs freelancer">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
Credited on the site
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.personal1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.personal1 }}"
data-name="Django REST framework"
data-description="Individual"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
</div>
<div style="clear: both; padding-top: 50px"></div>
*Billing is monthly and you can cancel at any time.*
---
## Corporate plans
These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
<div class="pricing">
<div class="span4">
<div class="chart first">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate1 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Basic</div>
<div class="specs startup">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Funding page</span> ad placement
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate1 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate1 }}"
data-name="Django REST framework"
data-description="Basic"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
<div class="span4">
<div class="chart">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate2 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Professional</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate2 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate2 }}"
data-name="Django REST framework"
data-description="Professional"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
<div class="span4">
<div class="chart last">
<div class="quantity">
<span class="dollar">{{ symbol }}</span>
<span class="price">{{ rates.corporate3 }}</span>
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
</div>
<div class="plan-name">Premium</div>
<div class="specs">
<div class="spec">
Support ongoing development
</div>
<div class="spec">
<span class="variable">Homepage</span> ad placement
</div>
<div class="spec">
<span class="variable">Sidebar</span> ad placement
</div>
<div class="spec">
<span class="variable">Priority support</span> for your engineers
</div>
</div>
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate3 }}/" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="{{ stripe_public }}"
data-amount="{{ stripe_amounts.corporate3 }}"
data-name="Django REST framework"
data-description="Premium"
data-currency="{{ currency }}"
data-allow-remember-me=false
data-billing-address=true
data-label='Sign up'
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
</script>
</form>
</div>
</div>
</div>
<div style="clear: both; padding-top: 50px"></div>
*Billing is monthly and you can cancel at any time.*
Once you've signed up, we will contact you via email and arrange your ad placements on the site.
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
---
## Accountability
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">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&amp;id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<h2>Stay up to date, with our monthly progress reports...</h2>
<div class="mc-field-group">
<label for="mce-EMAIL">Email Address </label>
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
<!--End mc_embed_signup-->
---
## Frequently asked questions
**Q: Can you issue monthly invoices?**
A: Yes, we are happy to issue monthly invoices. Please just <a href=mailto:funding@django-rest-framework.org>email us</a> and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
**Q: Does sponsorship include VAT?**
A: Sponsorship is VAT exempt.
**Q: Do I have to sign up for a certain time period?**
A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
**Q: Are you only looking for corporate sponsors?**
A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
---
## Our sponsors
<div id="fundingInclude"></div>
<script src="https://fund.django-rest-framework.org/funding_include.js"></script>

View File

@ -7,7 +7,6 @@ Looking for a new Django REST Framework related role? On this site we provide a
* [https://www.djangoproject.com/community/jobs/][djangoproject-website]
* [https://www.python.org/jobs/][python-org-jobs]
* [https://django.on-remote.com][django-on-remote]
* [https://djangogigs.com][django-gigs-com]
* [https://djangojobs.net/jobs/][django-jobs-net]
* [https://findwork.dev/django-rest-framework-jobs][findwork-dev]
@ -27,7 +26,6 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram
[djangoproject-website]: https://www.djangoproject.com/community/jobs/
[python-org-jobs]: https://www.python.org/jobs/
[django-on-remote]: https://django.on-remote.com/
[django-gigs-com]: https://djangogigs.com
[django-jobs-net]: https://djangojobs.net/jobs/
[findwork-dev]: https://findwork.dev/django-rest-framework-jobs

View File

@ -13,13 +13,55 @@ The aim is to ensure that the project has a high
## Maintenance team
[Participating actively in the REST framework project](contributing.md) **does not require being part of the maintenance team**. Almost every important part of issue triage and project improvement can be actively worked on regardless of your collaborator status on the repository.
We have a quarterly maintenance cycle where new members may join the maintenance team. We currently cap the size of the team at 5 members, and may encourage folks to step out of the team for a cycle to allow new members to participate.
#### Composition
#### Current team
The composition of the maintenance team is handled by [@tomchristie](https://github.com/encode/). Team members will be added as collaborators to the repository.
The [maintenance team for Q4 2015](https://github.com/encode/django-rest-framework/issues/2190):
#### Responsibilities
* [@tomchristie](https://github.com/encode/)
* [@xordoquy](https://github.com/xordoquy/) (Release manager.)
* [@carltongibson](https://github.com/carltongibson/)
* [@kevin-brown](https://github.com/kevin-brown/)
* [@jpadilla](https://github.com/jpadilla/)
#### Maintenance cycles
Each maintenance cycle is initiated by an issue being opened with the `Process` label.
* To be considered for a maintainer role simply comment against the issue.
* Existing members must explicitly opt-in to the next cycle by check-marking their name.
* The final decision on the incoming team will be made by `@tomchristie`.
Members of the maintenance team will be added as collaborators to the repository.
The following template should be used for the description of the issue, and serves as the formal process for selecting the team.
This issue is for determining the maintenance team for the *** period.
Please see the [Project management](https://www.django-rest-framework.org/topics/project-management/) section of our documentation for more details.
---
#### Renewing existing members.
The following people are the current maintenance team. Please checkmark your name if you wish to continue to have write permission on the repository for the *** period.
- [ ] @***
- [ ] @***
- [ ] @***
- [ ] @***
- [ ] @***
---
#### New members.
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](https://www.django-rest-framework.org/topics/project-management/) documentation.
#### Responsibilities of team members
Team members have the following responsibilities.
@ -34,13 +76,18 @@ Further notes for maintainers:
* Code changes should come in the form of a pull request - do not push directly to master.
* Maintainers should typically not merge their own pull requests.
* Each issue/pull request should have exactly one label once triaged.
* Search for un-triaged issues with [is:open no:label][un-triaged].
It should be noted that participating actively in the REST framework project clearly **does not require being part of the maintenance team**. Almost every import part of issue triage and project improvement can be actively worked on regardless of your collaborator status on the repository.
---
## Release process
* The release manager is selected by `@tomchristie`.
* The release manager will then have the maintainer role added to PyPI package.
The release manager is selected on every quarterly maintenance cycle.
* The manager should be selected by `@tomchristie`.
* The manager will then have the maintainer role added to PyPI package.
* The previous manager will then have the maintainer role removed from the PyPI package.
Our PyPI releases will be handled by either the current release manager, or by `@tomchristie`. Every release should have an open issue tagged with the `Release` label and marked against the appropriate milestone.
@ -151,12 +198,15 @@ If `@tomchristie` ceases to participate in the project then `@j4mie` has respons
The following issues still need to be addressed:
* Ensure `@j4mie` has back-up access to the `django-rest-framework.org` domain setup and admin.
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
* Document ownership of the [live example][sandbox] API.
* Document ownership of the [mailing list][mailing-list] and IRC channel.
* Document ownership and management of the security mailing list.
[bus-factor]: https://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/encode/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[transifex-client]: https://pypi.org/project/transifex-client/
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
[sandbox]: https://restframework.herokuapp.com/
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework

View File

@ -2,13 +2,11 @@
## Versioning
- **Minor** version numbers (0.0.x) are used for changes that are API compatible. You should be able to upgrade between minor point releases without any other code changes.
Minor version numbers (0.0.x) are used for changes that are API compatible. You should be able to upgrade between minor point releases without any other code changes.
- **Medium** version numbers (0.x.0) may include API changes, in line with the [deprecation policy][deprecation-policy]. You should read the release notes carefully before upgrading between medium point releases.
Medium version numbers (0.x.0) may include API changes, in line with the [deprecation policy][deprecation-policy]. You should read the release notes carefully before upgrading between medium point releases.
- **Major** version numbers (x.0.0) are reserved for substantial project milestones.
As REST Framework is considered feature-complete, most releases are expected to be minor releases.
Major version numbers (x.0.0) are reserved for substantial project milestones.
## Deprecation policy
@ -36,115 +34,14 @@ You can determine your currently installed version using `pip show`:
---
## 3.16.x series
### 3.16.0
**Date**: 28th March 2025
This release is considered a significant release to improve upstream support with Django and Python. Some of these may change the behaviour of existing features and pre-existing behaviour. Specifically, some fixes were added to around the support of `UniqueConstraint` with nullable fields which will improve built-in serializer validation.
## Features
* Add official support for Django 5.1 and its new `LoginRequiredMiddleware` in [#9514](https://github.com/encode/django-rest-framework/pull/9514) and [#9657](https://github.com/encode/django-rest-framework/pull/9657)
* Add official Django 5.2a1 support in [#9634](https://github.com/encode/django-rest-framework/pull/9634)
* Add support for Python 3.13 in [#9527](https://github.com/encode/django-rest-framework/pull/9527) and [#9556](https://github.com/encode/django-rest-framework/pull/9556)
* Support Django 2.1+ test client JSON data automatically serialized in [#6511](https://github.com/encode/django-rest-framework/pull/6511) and fix a regression in [#9615](https://github.com/encode/django-rest-framework/pull/9615)
## Bug fixes
* Fix unique together validator to respect condition's fields from `UniqueConstraint` in [#9360](https://github.com/encode/django-rest-framework/pull/9360)
* Fix raising on nullable fields part of `UniqueConstraint` in [#9531](https://github.com/encode/django-rest-framework/pull/9531)
* Fix `unique_together` validation with source in [#9482](https://github.com/encode/django-rest-framework/pull/9482)
* Added protections to `AttributeError` raised within properties in [#9455](https://github.com/encode/django-rest-framework/pull/9455)
* Fix `get_template_context` to handle also lists in [#9467](https://github.com/encode/django-rest-framework/pull/9467)
* Fix "Converter is already registered" deprecation warning. in [#9512](https://github.com/encode/django-rest-framework/pull/9512)
* Fix noisy warning and accept integers as min/max values of `DecimalField` in [#9515](https://github.com/encode/django-rest-framework/pull/9515)
* Fix usages of `open()` in `setup.py` in [#9661](https://github.com/encode/django-rest-framework/pull/9661)
## Translations
* Add some missing Chinese translations in [#9505](https://github.com/encode/django-rest-framework/pull/9505)
* Fix spelling mistakes in Farsi language were corrected in [#9521](https://github.com/encode/django-rest-framework/pull/9521)
* Fixing and adding missing Brazilian Portuguese translations in [#9535](https://github.com/encode/django-rest-framework/pull/9535)
## Removals
* Remove support for Python 3.8 in [#9670](https://github.com/encode/django-rest-framework/pull/9670)
* Remove long deprecated code from request wrapper in [#9441](https://github.com/encode/django-rest-framework/pull/9441)
* Remove deprecated `AutoSchema._get_reference` method in [#9525](https://github.com/encode/django-rest-framework/pull/9525)
## Documentation and internal changes
* Provide tests for hashing of `OperandHolder` in [#9437](https://github.com/encode/django-rest-framework/pull/9437)
* Update documentation: Add `adrf` third party package in [#9198](https://github.com/encode/django-rest-framework/pull/9198)
* Update tutorials links in Community contributions docs in [#9476](https://github.com/encode/django-rest-framework/pull/9476)
* Fix usage of deprecated Django function in example from docs in [#9509](https://github.com/encode/django-rest-framework/pull/9509)
* Move path converter docs into a separate section in [#9524](https://github.com/encode/django-rest-framework/pull/9524)
* Add test covering update view without `queryset` attribute in [#9528](https://github.com/encode/django-rest-framework/pull/9528)
* Fix Transifex link in [#9541](https://github.com/encode/django-rest-framework/pull/9541)
* Fix example `httpie` call in docs in [#9543](https://github.com/encode/django-rest-framework/pull/9543)
* Fix example for serializer field with choices in docs in [#9563](https://github.com/encode/django-rest-framework/pull/9563)
* Remove extra `<>` in validators example in [#9590](https://github.com/encode/django-rest-framework/pull/9590)
* Update `strftime` link in the docs in [#9624](https://github.com/encode/django-rest-framework/pull/9624)
* Switch to codecov GHA in [#9618](https://github.com/encode/django-rest-framework/pull/9618)
* Add note regarding availability of the `action` attribute in 'Introspecting ViewSet actions' docs section in [#9633](https://github.com/encode/django-rest-framework/pull/9633)
* Improved description of allowed throttling rates in documentation in [#9640](https://github.com/encode/django-rest-framework/pull/9640)
* Add `rest-framework-gm2m-relations` package to the list of 3rd party libraries in [#9063](https://github.com/encode/django-rest-framework/pull/9063)
* Fix a number of typos in the test suite in the docs in [#9662](https://github.com/encode/django-rest-framework/pull/9662)
* Add `django-pyoidc` as a third party authentication library in [#9667](https://github.com/encode/django-rest-framework/pull/9667)
## New Contributors
* [`@maerteijn`](https://github.com/maerteijn) made their first contribution in [#9198](https://github.com/encode/django-rest-framework/pull/9198)
* [`@FraCata00`](https://github.com/FraCata00) made their first contribution in [#9444](https://github.com/encode/django-rest-framework/pull/9444)
* [`@AlvaroVega`](https://github.com/AlvaroVega) made their first contribution in [#9451](https://github.com/encode/django-rest-framework/pull/9451)
* [`@james`](https://github.com/james)-mchugh made their first contribution in [#9455](https://github.com/encode/django-rest-framework/pull/9455)
* [`@ifeanyidavid`](https://github.com/ifeanyidavid) made their first contribution in [#9479](https://github.com/encode/django-rest-framework/pull/9479)
* [`@p`](https://github.com/p)-schlickmann made their first contribution in [#9480](https://github.com/encode/django-rest-framework/pull/9480)
* [`@akkuman`](https://github.com/akkuman) made their first contribution in [#9505](https://github.com/encode/django-rest-framework/pull/9505)
* [`@rafaelgramoschi`](https://github.com/rafaelgramoschi) made their first contribution in [#9509](https://github.com/encode/django-rest-framework/pull/9509)
* [`@Sinaatkd`](https://github.com/Sinaatkd) made their first contribution in [#9521](https://github.com/encode/django-rest-framework/pull/9521)
* [`@gtkacz`](https://github.com/gtkacz) made their first contribution in [#9535](https://github.com/encode/django-rest-framework/pull/9535)
* [`@sliverc`](https://github.com/sliverc) made their first contribution in [#9556](https://github.com/encode/django-rest-framework/pull/9556)
* [`@gabrielromagnoli1987`](https://github.com/gabrielromagnoli1987) made their first contribution in [#9543](https://github.com/encode/django-rest-framework/pull/9543)
* [`@cheehong1030`](https://github.com/cheehong1030) made their first contribution in [#9563](https://github.com/encode/django-rest-framework/pull/9563)
* [`@amansharma612`](https://github.com/amansharma612) made their first contribution in [#9590](https://github.com/encode/django-rest-framework/pull/9590)
* [`@Gluroda`](https://github.com/Gluroda) made their first contribution in [#9616](https://github.com/encode/django-rest-framework/pull/9616)
* [`@deepakangadi`](https://github.com/deepakangadi) made their first contribution in [#9624](https://github.com/encode/django-rest-framework/pull/9624)
* [`@EXG1O`](https://github.com/EXG1O) made their first contribution in [#9633](https://github.com/encode/django-rest-framework/pull/9633)
* [`@decadenza`](https://github.com/decadenza) made their first contribution in [#9640](https://github.com/encode/django-rest-framework/pull/9640)
* [`@mojtabaakbari221b`](https://github.com/mojtabaakbari221b) made their first contribution in [#9063](https://github.com/encode/django-rest-framework/pull/9063)
* [`@mikemanger`](https://github.com/mikemanger) made their first contribution in [#9661](https://github.com/encode/django-rest-framework/pull/9661)
* [`@gbip`](https://github.com/gbip) made their first contribution in [#9667](https://github.com/encode/django-rest-framework/pull/9667)
**Full Changelog**: https://github.com/encode/django-rest-framework/compare/3.15.2...3.16.0
## 3.15.x series
### 3.15.2
**Date**: 14th June 2024
* Fix potential XSS vulnerability in browsable API. [#9435](https://github.com/encode/django-rest-framework/pull/9435)
* Revert "Ensure CursorPagination respects nulls in the ordering field". [#9381](https://github.com/encode/django-rest-framework/pull/9381)
* Use warnings rather than logging a warning for DecimalField. [#9367](https://github.com/encode/django-rest-framework/pull/9367)
* Remove unused code. [#9393](https://github.com/encode/django-rest-framework/pull/9393)
* Django < 4.2 and Python < 3.8 no longer supported. [#9393](https://github.com/encode/django-rest-framework/pull/9393)
### 3.15.1
Date: 22nd March 2024
* Fix `SearchFilter` handling of quoted and comma separated strings, when `.get_search_terms` is being called into by a custom class. See [[#9338](https://github.com/encode/django-rest-framework/issues/9338)]
* Revert number of 3.15.0 issues which included unintended side-effects. See [[#9331](https://github.com/encode/django-rest-framework/issues/9331)]
### 3.15.0
Date: 15th March 2024
* Django 5.0 and Python 3.12 support [[#9157](https://github.com/encode/django-rest-framework/pull/9157)]
* Use POST method instead of GET to perform logout in browsable API [[9208](https://github.com/encode/django-rest-framework/pull/9208)]
* Django 5.0 and Python 3.12 support [[#9157] (https://github.com/encode/django-rest-framework/pull/9157)]
* Use POST method instead of GET to perform logout in browsable API [[9208] (https://github.com/encode/django-rest-framework/pull/9208)]
* Added jQuery 3.7.1 support & dropped previous version [[#9094](https://github.com/encode/django-rest-framework/pull/9094)]
* Use str as default path converter [[#9066](https://github.com/encode/django-rest-framework/pull/9066)]
* Document support for http.HTTPMethod in the @action decorator added in Python 3.11 [[#9067](https://github.com/encode/django-rest-framework/pull/9067)]
@ -195,7 +92,7 @@ Date: 15th March 2024
* Use autocomplete widget for user selection in Token admin [[#8534](https://github.com/encode/django-rest-framework/pull/8534)]
* Make browsable API compatible with strong CSP [[#8784](https://github.com/encode/django-rest-framework/pull/8784)]
* Avoid inline script execution for injecting CSRF token [[#7016](https://github.com/encode/django-rest-framework/pull/7016)]
* Mitigate global dependency on inflection [[#8017](https://github.com/encode/django-rest-framework/pull/8017)] [[#8781](https://github.com/encode/django-rest-framework/pull/8781)]
* Mitigate global dependency on inflection #8017 [[#8017](https://github.com/encode/django-rest-framework/pull/8017)] [[#8781](https://github.com/encode/django-rest-framework/pull/8781)]
* Register Django urls [[#8778](https://github.com/encode/django-rest-framework/pull/8778)]
* Implemented Verbose Name Translation for TokenProxy [[#8713](https://github.com/encode/django-rest-framework/pull/8713)]
* Properly handle OverflowError in DurationField deserialization [[#8042](https://github.com/encode/django-rest-framework/pull/8042)]
@ -205,7 +102,7 @@ Date: 15th March 2024
* Fix 404 when page query parameter is empty string [[#8578](https://github.com/encode/django-rest-framework/pull/8578)]
* Fixes instance check in ListSerializer.to_representation [[#8726](https://github.com/encode/django-rest-framework/pull/8726)] [[#8727](https://github.com/encode/django-rest-framework/pull/8727)]
* FloatField will crash if the input is a number that is too big [[#8725](https://github.com/encode/django-rest-framework/pull/8725)]
* Add missing DurationField to SimpleMetadata label_lookup [[#8702](https://github.com/encode/django-rest-framework/pull/8702)]
* Add missing DurationField to SimpleMetada label_lookup [[#8702](https://github.com/encode/django-rest-framework/pull/8702)]
* Add support for Python 3.11 [[#8752](https://github.com/encode/django-rest-framework/pull/8752)]
* Make request consistently available in pagination classes [[#8764](https://github.com/encode/django-rest-framework/pull/9764)]
* Possibility to remove trailing zeros on DecimalFields representation [[#6514](https://github.com/encode/django-rest-framework/pull/6514)]
@ -213,7 +110,7 @@ Date: 15th March 2024
* Add `__eq__` method for `OperandHolder` class [[#8710](https://github.com/encode/django-rest-framework/pull/8710)]
* Avoid importing `django.test` package when not testing [[#8699](https://github.com/encode/django-rest-framework/pull/8699)]
* Preserve exception messages for wrapped Django exceptions [[#8051](https://github.com/encode/django-rest-framework/pull/8051)]
* Include `examples` and `format` to OpenAPI schema of CursorPagination [[#8687](https://github.com/encode/django-rest-framework/pull/8687)] [[#8686](https://github.com/encode/django-rest-framework/pull/8686)]
* Include `examples` and `format` to OpenAPI schema of CursorPagination [[#8687] (https://github.com/encode/django-rest-framework/pull/8687)] [[#8686](https://github.com/encode/django-rest-framework/pull/8686)]
* Fix infinite recursion with deepcopy on Request [[#8684](https://github.com/encode/django-rest-framework/pull/8684)]
* Refactor: Replace try/except with contextlib.suppress() [[#8676](https://github.com/encode/django-rest-framework/pull/8676)]
* Minor fix to SerializeMethodField docstring [[#8629](https://github.com/encode/django-rest-framework/pull/8629)]
@ -512,7 +409,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
* 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 default_error_message [#5881][gh5881]
* 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]

View File

@ -32,7 +32,7 @@ We suggest adding your package to the [REST Framework][rest-framework-grid] grid
#### Adding to the Django REST framework docs
Create a [Pull Request][drf-create-pr] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Packages][third-party-packages] section.
Create a [Pull Request][drf-create-pr] or [Issue][drf-create-issue] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Packages][third-party-packages] section.
#### Announce on the discussion group.
@ -44,11 +44,7 @@ Django REST Framework has a growing community of developers, packages, and resou
Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages][rest-framework-grid].
To submit new content, [create a pull request][drf-create-pr].
## Async Support
* [adrf](https://github.com/em1208/adrf) - Async support, provides async Views, ViewSets, and Serializers.
To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].
### Authentication
@ -62,7 +58,6 @@ To submit new content, [create a pull request][drf-create-pr].
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
* [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers.
* [django-rest-authemail][django-rest-authemail] - Provides a RESTful API for user signup and authentication using email addresses.
* [dango-pyoidc][django-pyoidc] adds support for OpenID Connect (OIDC) authentication.
### Permissions
@ -130,7 +125,6 @@ To submit new content, [create a pull request][drf-create-pr].
### Misc
* [drf-sendables][drf-sendables] - User messages for Django REST Framework
* [cookiecutter-django-rest][cookiecutter-django-rest] - A cookiecutter template that takes care of the setup and configuration so you can focus on making your REST apis awesome.
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serializer that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
@ -160,11 +154,9 @@ To submit new content, [create a pull request][drf-create-pr].
### Customization
* [drf-restwind][drf-restwind] - a modern re-imagining of the Django REST Framework utilizes TailwindCSS and DaisyUI to provide flexible and customizable UI solutions with minimal coding effort.
* [drf-redesign][drf-redesign] - A project that gives a fresh look to the browse-able API using Bootstrap 5.
* [drf-material][drf-material] - A project that gives a sleek and elegant look to the browsable API using Material Design.
[drf-sendables]: https://github.com/amikrop/drf-sendables
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
[new-repo]: https://github.com/new
@ -175,6 +167,7 @@ To submit new content, [create a pull request][drf-create-pr].
[drf-compat]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/compat.py
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
[drf-create-pr]: https://github.com/encode/django-rest-framework/compare
[drf-create-issue]: https://github.com/encode/django-rest-framework/issues/new
[authentication]: ../api-guide/authentication.md
[permissions]: ../api-guide/permissions.md
[third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages
@ -255,7 +248,5 @@ To submit new content, [create a pull request][drf-create-pr].
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
[drf-api-action]: https://github.com/Ori-Roza/drf-api-action
[drf-restwind]: https://github.com/youzarsiph/drf-restwind
[drf-redesign]: https://github.com/youzarsiph/drf-redesign
[drf-material]: https://github.com/youzarsiph/drf-material
[django-pyoidc]: https://github.com/makinacorpus/django_pyoidc

View File

@ -12,7 +12,7 @@ There are a wide range of resources available for learning and using Django REST
<img src="../../img/books/tsd-cover.png"/>
</a>
<a class="book-cover" href="https://djangoforapis.com">
<img src="../../img/books/dfa-40-cover.jpg"/>
<img src="../../img/books/dfa-cover.jpg"/>
</a>
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
<img src="../../img/books/bda-cover.png"/>
@ -28,6 +28,7 @@ There are a wide range of resources available for learning and using Django REST
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
* [Django REST Framework - An Introduction][drf-an-intro]
* [Django REST Framework Tutorial][drf-tutorial]
* [Django REST Framework Course][django-rest-framework-course]
* [Building a RESTful API with Django REST Framework][building-a-restful-api-with-drf]
* [Getting Started with Django REST Framework and AngularJS][getting-started-with-django-rest-framework-and-angularjs]
* [End to End Web App with Django REST Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs]
@ -38,10 +39,8 @@ There are a wide range of resources available for learning and using Django REST
* [Check Credentials Using Django REST Framework][check-credentials-using-django-rest-framework]
* [Creating a Production Ready API with Python and Django REST Framework Part 1][creating-a-production-ready-api-with-python-and-drf-part1]
* [Creating a Production Ready API with Python and Django REST Framework Part 2][creating-a-production-ready-api-with-python-and-drf-part2]
* [Creating a Production Ready API with Python and Django REST Framework Part 3][creating-a-production-ready-api-with-python-and-drf-part3]
* [Creating a Production Ready API with Python and Django REST Framework Part 4][creating-a-production-ready-api-with-python-and-drf-part4]
* [Django Polls Tutorial API][django-polls-api]
* [Django REST Framework Tutorial: Todo API][django-rest-framework-todo-api]
* [Django REST Framework Tutorial - Build a Blog API][django-rest-framework-tutorial-build-a-blog]
* [Django REST Framework & React Tutorial - Build a Todo List API][django-rest-framework-react-tutorial-build-a-todo-list]
* [Tutorial: Django REST with React (Django 2.0)][django-rest-react-valentinog]
@ -50,11 +49,11 @@ There are a wide range of resources available for learning and using Django REST
### Talks
* [Level Up! Rethinking the Web API Framework][pycon-us-2017]
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-toolkit]
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-tookit]
* [Django REST API - So Easy You Can Learn It in 25 Minutes][django-rest-api-so-easy]
* [Tom Christie about Django Rest Framework at Django: Under The Hood][django-under-hood-2014]
* [Django REST Framework: Schemas, Hypermedia & Client Libraries][pycon-uk-2016]
* [Finally Understand Authentication in Django REST Framework][django-con-2018]
### Tutorials
@ -104,6 +103,7 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/news-and-insights/api-development-with-django-and-django-rest-framework/
[cdrf.co]:http://www.cdrf.co
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg
[django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E
[integrating-pandas-drf-and-bokeh]: https://web.archive.org/web/20180104205117/http://machinalis.com/blog/pandas-django-rest-framework-bokeh/
@ -115,14 +115,12 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[chatbot-using-drf-part1]: https://chatbotslife.com/chatbot-using-django-rest-framework-api-ai-slack-part-1-3-69c7e38b7b1e#.g2aceuncf
[new-django-admin-with-drf-and-emberjs]: https://blog.levit.be/new-django-admin-with-emberjs-what-are-the-news/
[drf-schema]: https://drf-schema-adapter.readthedocs.io/en/latest/
[creating-a-production-ready-api-with-python-and-drf-part1]: https://www.andreagrandi.it/posts/creating-production-ready-api-python-django-rest-framework-part-1/
[creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/
[creating-a-production-ready-api-with-python-and-drf-part3]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-3/
[creating-a-production-ready-api-with-python-and-drf-part4]: https://www.andreagrandi.it/posts/creating-a-production-ready-api-with-python-and-django-rest-framework-part-4/
[django-polls-api]: https://learndjango.com/tutorials/django-polls-tutorial-api
[django-rest-framework-todo-api]: https://learndjango.com/tutorials/django-rest-framework-tutorial-todo-api
[creating-a-production-ready-api-with-python-and-drf-part1]: https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/
[creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/
[django-rest-framework-tutorial-build-a-blog]: https://wsvincent.com/django-rest-framework-tutorial/
[django-rest-framework-react-tutorial-build-a-todo-list]: https://wsvincent.com/django-rest-framework-react-tutorial/
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
[full-fledged-rest-api-with-django-oauth-toolkit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww
[building-a-rest-api-using-django-and-drf]: https://www.youtube.com/watch?v=PwssEec3IRw
[drf-tutorials]: https://www.youtube.com/watch?v=axRCBgbOJp8&list=PLJtp8Jm8EDzjgVg9vVyIUMoGyqtegj7FH
@ -137,4 +135,3 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/
[doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/
[developing-restful-apis-with-django-rest-framework]: https://testdriven.io/courses/django-rest-framework/
[django-con-2018]: https://youtu.be/pY-oje5b5Qk?si=AOU6tLi0IL1_pVzq

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/img/rfm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
docs/img/rfr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -75,11 +75,10 @@ continued development by **[signing up for a paid plan][funding]**.
<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>
<li><a href="https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/svix.png)">Svix</a></li>
<li><a href="https://zuplo.link/django-web" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/zuplo.png)">Zuplo</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, [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), [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework), [Svix](https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship), , and [Zuplo](https://zuplo.link/django-web).*
*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), [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework), and [Svix](https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship).*
---
@ -87,8 +86,8 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Django (4.2, 5.0, 5.1, 5.2)
* Python (3.9, 3.10, 3.11, 3.12, 3.13)
* Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11)
* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
@ -96,8 +95,8 @@ each Python and Django series.
The following packages are optional:
* [PyYAML][pyyaml], [uritemplate][uriteemplate] (5.1+, 3.0.0+) - Schema generation support.
* [Markdown][markdown] (3.3.0+) - Markdown support for the browsable API.
* [Pygments][pygments] (2.7.0+) - Add syntax highlighting to Markdown processing.
* [Markdown][markdown] (3.0.0+) - Markdown support for the browsable API.
* [Pygments][pygments] (2.4.0+) - Add syntax highlighting to Markdown processing.
* [django-filter][django-filter] (1.0.1+) - Filtering support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
@ -185,7 +184,7 @@ Can't wait to get started? The [quickstart guide][quickstart] is the fastest way
## Development
See the [Contribution guidelines][contributing] for information on how to clone
the repository, run the test suite and help maintain the code base of REST
the repository, run the test suite and contribute changes back to REST
Framework.
## Support
@ -196,7 +195,9 @@ For priority support please sign up for a [professional or premium sponsorship p
## Security
**Please report security issues by emailing security@encode.io**.
Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team).
**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.
@ -246,6 +247,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[serializer-section]: api-guide/serializers#serializers
[modelserializer-section]: api-guide/serializers#modelserializer
[functionview-section]: api-guide/views#function-based-views
[sandbox]: https://restframework.herokuapp.com/
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[quickstart]: tutorial/quickstart.md

View File

@ -20,11 +20,9 @@ By default, the API will return the format specified by the headers, which in th
To quickly add authentication to the browesable api, add a routes named `"login"` and `"logout"` under the namespace `"rest_framework"`. DRF provides default routes for this which you can add to your urlconf:
```python
from django.urls import include, path
urlpatterns = [
# ...
path("api-auth/", include("rest_framework.urls", namespace="rest_framework"))
url(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework"))
]
```
@ -81,43 +79,22 @@ For more specific CSS tweaks than simply overriding the default bootstrap theme
### Third party packages for customization
You can use a third party package for customization, rather than doing it by yourself. Here is 3 packages for customizing the API:
You can use a third party package for customization, rather than doing it by yourself. Here is 2 packages for customizing the API:
* [drf-restwind][drf-restwind] - a modern re-imagining of the Django REST Framework utilizes TailwindCSS and DaisyUI to provide flexible and customizable UI solutions with minimal coding effort.
* [drf-redesign][drf-redesign] - A package for customizing the API using Bootstrap 5. Modern and sleek design, it comes with the support for dark mode.
* [drf-material][drf-material] - Material design for Django REST Framework.
* [rest-framework-redesign][rest-framework-redesign] - A package for customizing the API using Bootstrap 5. Modern and sleek design, it comes with the support for dark mode.
* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework.
---
![API Root][drf-rw-api-root]
![Django REST Framework Redesign][rfr]
![List View][drf-rw-list-view]
![Detail View][drf-rw-detail-view]
*Screenshots of the drf-restwind*
*Screenshot of the rest-framework-redesign*
---
---
![Django REST Framework Material][rfm]
![API Root][drf-r-api-root]
![List View][drf-r-list-view]
![Detail View][drf-r-detail-view]
*Screenshot of the drf-redesign*
---
![API Root][drf-m-api-root]
![List View][drf-m-api-root]
![Detail View][drf-m-api-root]
*Screenshot of the drf-material*
*Screenshot of the rest-framework-material*
---
@ -218,15 +195,7 @@ There are [a variety of packages for autocomplete widgets][autocomplete-packages
[bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
[drf-restwind]: https://github.com/youzarsiph/drf-restwind
[drf-rw-api-root]: ../img/drf-rw-api-root.png
[drf-rw-list-view]: ../img/drf-rw-list-view.png
[drf-rw-detail-view]: ../img/drf-rw-detail-view.png
[drf-redesign]: https://github.com/youzarsiph/drf-redesign
[drf-r-api-root]: ../img/drf-r-api-root.png
[drf-r-list-view]: ../img/drf-r-list-view.png
[drf-r-detail-view]: ../img/drf-r-detail-view.png
[drf-material]: https://github.com/youzarsiph/drf-material
[drf-m-api-root]: ../img/drf-m-api-root.png
[drf-m-list-view]: ../img/drf-m-list-view.png
[drf-m-detail-view]: ../img/drf-m-detail-view.png
[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign
[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material
[rfr]: ../img/rfr.png
[rfm]: ../img/rfm.png

View File

@ -1,220 +1,220 @@
# HTML & Forms
REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can be used as HTML forms and rendered in templates.
## Rendering HTML
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.
The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
**views.py**:
from my_project.example.models import Profile
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
class ProfileList(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'profile_list.html'
def get(self, request):
queryset = Profile.objects.all()
return Response({'profiles': queryset})
**profile_list.html**:
<html><body>
<h1>Profiles</h1>
<ul>
{% for profile in profiles %}
<li>{{ profile.name }}</li>
{% endfor %}
</ul>
</body></html>
## Rendering Forms
Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
**views.py**:
from django.shortcuts import get_object_or_404
from my_project.example.models import Profile
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.views import APIView
class ProfileDetail(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'profile_detail.html'
def get(self, request, pk):
profile = get_object_or_404(Profile, pk=pk)
serializer = ProfileSerializer(profile)
return Response({'serializer': serializer, 'profile': profile})
def post(self, request, pk):
profile = get_object_or_404(Profile, pk=pk)
serializer = ProfileSerializer(profile, data=request.data)
if not serializer.is_valid():
return Response({'serializer': serializer, 'profile': profile})
serializer.save()
return redirect('profile-list')
**profile_detail.html**:
{% load rest_framework %}
<html><body>
<h1>Profile - {{ profile.name }}</h1>
<form action="{% url 'profile-detail' pk=profile.pk %}" method="POST">
{% csrf_token %}
{% render_form serializer %}
<input type="submit" value="Save">
</form>
</body></html>
### Using template packs
The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(
max_length=100,
style={'placeholder': 'Email', 'autofocus': True}
)
password = serializers.CharField(
max_length=100,
style={'input_type': 'password', 'placeholder': 'Password'}
)
remember_me = serializers.BooleanField()
---
#### `rest_framework/vertical`
Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
*This is the default template pack.*
{% load rest_framework %}
...
<form action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer template_pack='rest_framework/vertical' %}
<button type="submit" class="btn btn-default">Sign in</button>
</form>
![Vertical form example](../img/vertical.png)
---
#### `rest_framework/horizontal`
Presents labels and controls alongside each other, using a 2/10 column split.
*This is the form style used in the browsable API and admin renderers.*
{% load rest_framework %}
...
<form class="form-horizontal" action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
![Horizontal form example](../img/horizontal.png)
---
#### `rest_framework/inline`
A compact form style that presents all the controls inline.
{% load rest_framework %}
...
<form class="form-inline" action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer template_pack='rest_framework/inline' %}
<button type="submit" class="btn btn-default">Sign in</button>
</form>
![Inline form example](../img/inline.png)
## Field styles
Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
details = serializers.CharField(
max_length=1000,
style={'base_template': 'textarea.html'}
)
If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
details = serializers.CharField(
max_length=1000,
style={'template': 'my-field-templates/custom-input.html'}
)
Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
details = serializers.CharField(
max_length=1000,
style={'base_template': 'textarea.html', 'rows': 10}
)
The complete list of `base_template` options and their associated style options is listed below.
base_template | Valid field types | Additional style options
-----------------------|-------------------------------------------------------------|-----------------------------------------------
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
textarea.html | `CharField` | rows, placeholder, hide_label
select.html | `ChoiceField` or relational field types | hide_label
radio.html | `ChoiceField` or relational field types | inline, hide_label
select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
checkbox.html | `BooleanField` | hide_label
fieldset.html | Nested serializer | hide_label
list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
# HTML & Forms
REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can be used as HTML forms and rendered in templates.
## Rendering HTML
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.
The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
**views.py**:
from my_project.example.models import Profile
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
class ProfileList(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'profile_list.html'
def get(self, request):
queryset = Profile.objects.all()
return Response({'profiles': queryset})
**profile_list.html**:
<html><body>
<h1>Profiles</h1>
<ul>
{% for profile in profiles %}
<li>{{ profile.name }}</li>
{% endfor %}
</ul>
</body></html>
## Rendering Forms
Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
**views.py**:
from django.shortcuts import get_object_or_404
from my_project.example.models import Profile
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.views import APIView
class ProfileDetail(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'profile_detail.html'
def get(self, request, pk):
profile = get_object_or_404(Profile, pk=pk)
serializer = ProfileSerializer(profile)
return Response({'serializer': serializer, 'profile': profile})
def post(self, request, pk):
profile = get_object_or_404(Profile, pk=pk)
serializer = ProfileSerializer(profile, data=request.data)
if not serializer.is_valid():
return Response({'serializer': serializer, 'profile': profile})
serializer.save()
return redirect('profile-list')
**profile_detail.html**:
{% load rest_framework %}
<html><body>
<h1>Profile - {{ profile.name }}</h1>
<form action="{% url 'profile-detail' pk=profile.pk %}" method="POST">
{% csrf_token %}
{% render_form serializer %}
<input type="submit" value="Save">
</form>
</body></html>
### Using template packs
The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(
max_length=100,
style={'placeholder': 'Email', 'autofocus': True}
)
password = serializers.CharField(
max_length=100,
style={'input_type': 'password', 'placeholder': 'Password'}
)
remember_me = serializers.BooleanField()
---
#### `rest_framework/vertical`
Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
*This is the default template pack.*
{% load rest_framework %}
...
<form action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer template_pack='rest_framework/vertical' %}
<button type="submit" class="btn btn-default">Sign in</button>
</form>
![Vertical form example](../img/vertical.png)
---
#### `rest_framework/horizontal`
Presents labels and controls alongside each other, using a 2/10 column split.
*This is the form style used in the browsable API and admin renderers.*
{% load rest_framework %}
...
<form class="form-horizontal" action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Sign in</button>
</div>
</div>
</form>
![Horizontal form example](../img/horizontal.png)
---
#### `rest_framework/inline`
A compact form style that presents all the controls inline.
{% load rest_framework %}
...
<form class="form-inline" action="{% url 'login' %}" method="post" novalidate>
{% csrf_token %}
{% render_form serializer template_pack='rest_framework/inline' %}
<button type="submit" class="btn btn-default">Sign in</button>
</form>
![Inline form example](../img/inline.png)
## Field styles
Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
details = serializers.CharField(
max_length=1000,
style={'base_template': 'textarea.html'}
)
If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
details = serializers.CharField(
max_length=1000,
style={'template': 'my-field-templates/custom-input.html'}
)
Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
details = serializers.CharField(
max_length=1000,
style={'base_template': 'textarea.html', 'rows': 10}
)
The complete list of `base_template` options and their associated style options is listed below.
base_template | Valid field types | Additional style options
-----------------------|-------------------------------------------------------------|-----------------------------------------------
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
textarea.html | `CharField` | rows, placeholder, hide_label
select.html | `ChoiceField` or relational field types | hide_label
radio.html | `ChoiceField` or relational field types | inline, hide_label
select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
checkbox.html | `BooleanField` | hide_label
fieldset.html | Nested serializer | hide_label
list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label

View File

@ -105,7 +105,7 @@ For API clients the most appropriate of these will typically be to use the `Acce
[cite]: https://youtu.be/Wa0VfS2q94Y
[django-translation]: https://docs.djangoproject.com/en/stable/topics/i18n/translation
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
[transifex-project]: https://explore.transifex.com/django-rest-framework-1/django-rest-framework/
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[django-po-source]: https://raw.githubusercontent.com/encode/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
[django-language-preference]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#how-django-discovers-language-preference
[django-locale-paths]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOCALE_PATHS

View File

@ -8,7 +8,7 @@ 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 [encode/rest-framework-tutorial][repo] repository on GitHub. Feel free to clone the repository and see the code in action.
**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].
---
@ -150,7 +150,7 @@ At this point we've translated the model instance into Python native datatypes.
content = JSONRenderer().render(serializer.data)
content
# b'{"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...
@ -165,7 +165,7 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer.is_valid()
# True
serializer.validated_data
# {'title': '', 'code': 'print("hello, world")', '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
# [{'id': 1, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 3, 'title': '', 'code': '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
@ -307,7 +307,7 @@ Quit out of the shell...
Validating models...
0 errors found
Django version 5.0, using settings 'tutorial.settings'
Django version 4.0, using settings 'tutorial.settings'
Starting Development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
@ -321,50 +321,42 @@ You can install httpie using pip:
Finally, we can get a list of all of the snippets:
http GET http://127.0.0.1:8000/snippets/ --unsorted
http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 3,
"title": "",
"code": "print(\"hello, world\")",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
Or we can get a particular snippet by referencing its id:
http GET http://127.0.0.1:8000/snippets/2/ --unsorted
HTTP/1.1 200 OK
...
{
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
Or we can get a particular snippet by referencing its id:
http http://127.0.0.1:8000/snippets/2/
HTTP/1.1 200 OK
...
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
@ -379,6 +371,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/
[venv]: https://docs.python.org/3/library/venv.html
[tut-2]: 2-requests-and-responses.md
[httpie]: https://github.com/httpie/httpie#installation

View File

@ -15,6 +15,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick
source env/bin/activate # On Windows use `env\Scripts\activate`
# 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

View File

@ -439,17 +439,3 @@ ul.sponsor {
display: inline-block !important;
}
/* admonition */
.admonition {
border: .075rem solid #448aff;
border-radius: .2rem;
margin: 1.5625em 0;
padding: 0 .6rem;
}
.admonition-title {
background: #448aff1a;
font-weight: 700;
margin: 0 -.6rem 1em;
padding: 0.4rem 0.6rem;
}

View File

@ -9,7 +9,6 @@ theme:
custom_dir: docs_theme
markdown_extensions:
- admonition
- toc:
anchorlink: True
@ -66,7 +65,6 @@ nav:
- 'Contributing to REST framework': 'community/contributing.md'
- 'Project management': 'community/project-management.md'
- 'Release Notes': 'community/release-notes.md'
- '3.16 Announcement': 'community/3.16-announcement.md'
- '3.15 Announcement': 'community/3.15-announcement.md'
- '3.14 Announcement': 'community/3.14-announcement.md'
- '3.13 Announcement': 'community/3.13-announcement.md'

View File

@ -1,5 +1,6 @@
# MkDocs to build our documentation.
mkdocs==1.6.0
mkdocs==1.2.4
jinja2>=2.10,<3.1.0 # contextfilter has been renamed
# pylinkvalidator to check for broken links in documentation.
pylinkvalidator==0.3

View File

@ -6,5 +6,5 @@ django-guardian>=2.4.0,<2.5
inflection==0.5.1
markdown>=3.3.7
psycopg2-binary>=2.9.5,<2.10
pygments~=2.17.0
pygments>=2.12.0,<2.14.0
pyyaml>=5.3.1,<5.4

View File

@ -7,8 +7,10 @@ ______ _____ _____ _____ __
\_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
"""
import django
__title__ = 'Django REST framework'
__version__ = '3.16.0'
__version__ = '3.15.0'
__author__ = 'Tom Christie'
__license__ = 'BSD 3-Clause'
__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'
@ -23,5 +25,13 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
ISO_8601 = 'iso-8601'
if django.VERSION < (3, 2):
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
class RemovedInDRF315Warning(DeprecationWarning):
pass
class RemovedInDRF317Warning(PendingDeprecationWarning):
pass

View File

@ -0,0 +1,4 @@
import django
if django.VERSION < (3, 2):
default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'

View File

@ -28,6 +28,7 @@ class TokenAdmin(admin.ModelAdmin):
search_help_text = _('Username')
ordering = ('-created',)
actions = None # Actions not compatible with mapped IDs.
autocomplete_fields = ("user",)
def get_changelist(self, request, **kwargs):
return TokenChangeList

View File

@ -42,4 +42,4 @@ class Command(BaseCommand):
username)
)
self.stdout.write(
f'Generated token {token.key} for user {username}')
'Generated token {} for user {}'.format(token.key, username))

View File

@ -3,9 +3,6 @@ The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
import django
from django.db import models
from django.db.models.constants import LOOKUP_SEP
from django.db.models.sql.query import Node
from django.views.generic import View
@ -49,12 +46,6 @@ try:
except ImportError:
yaml = None
# inflection is optional
try:
import inflection
except ImportError:
inflection = None
# requests is optional
try:
@ -154,16 +145,36 @@ else:
return False
if django.VERSION >= (4, 2):
# Django 4.2+: use the stock parse_header_parameters function
# Note: Django 4.1 also has an implementation of parse_header_parameters
# which is slightly different from the one in 4.2, it needs
# the compatibility shim as well.
from django.utils.http import parse_header_parameters
else:
# Django <= 4.1: create a compatibility shim for parse_header_parameters
from django.http.multipartparser import parse_header
def parse_header_parameters(line):
# parse_header works with bytes, but parse_header_parameters
# works with strings. Call encode to convert the line to bytes.
main_value_pair, params = parse_header(line.encode())
return main_value_pair, {
# parse_header will convert *some* values to string.
# parse_header_parameters converts *all* values to string.
# Make sure all values are converted by calling decode on
# any remaining non-string values.
k: v if isinstance(v, str) else v.decode()
for k, v in params.items()
}
if django.VERSION >= (5, 1):
# Django 5.1+: use the stock ip_address_validators function
# Note: Before Django 5.1, ip_address_validators returns a tuple containing
# 1) the list of validators and 2) the error message. Starting from
# Django 5.1 ip_address_validators only returns the list of validators
from django.core.validators import ip_address_validators
def get_referenced_base_fields_from_q(q):
return q.referenced_base_fields
else:
# Django <= 5.1: create a compatibility shim for ip_address_validators
from django.core.validators import \
@ -172,35 +183,6 @@ else:
def ip_address_validators(protocol, unpack_ipv4):
return _ip_address_validators(protocol, unpack_ipv4)[0]
# Django < 5.1: create a compatibility shim for Q.referenced_base_fields
# https://github.com/django/django/blob/5.1a1/django/db/models/query_utils.py#L179
def _get_paths_from_expression(expr):
if isinstance(expr, models.F):
yield expr.name
elif hasattr(expr, 'flatten'):
for child in expr.flatten():
if isinstance(child, models.F):
yield child.name
elif isinstance(child, models.Q):
yield from _get_children_from_q(child)
def _get_children_from_q(q):
for child in q.children:
if isinstance(child, Node):
yield from _get_children_from_q(child)
elif isinstance(child, tuple):
lhs, rhs = child
yield lhs
if hasattr(rhs, 'resolve_expression'):
yield from _get_paths_from_expression(rhs)
elif hasattr(child, 'resolve_expression'):
yield from _get_paths_from_expression(child)
def get_referenced_base_fields_from_q(q):
return {
child.split(LOOKUP_SEP, 1)[0] for child in _get_children_from_q(q)
}
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: https://bugs.python.org/issue22767

View File

@ -144,17 +144,30 @@ class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Invalid input.')
default_code = 'invalid'
default_params = {}
def __init__(self, detail=None, code=None):
def __init__(self, detail=None, code=None, params=None):
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
if params is None:
params = self.default_params
# For validation failures, we may collect many errors together,
# so the details should always be coerced to a list if not already.
if isinstance(detail, tuple):
detail = list(detail)
if isinstance(detail, str):
detail = [detail % params]
elif isinstance(detail, ValidationError):
detail = detail.detail
elif isinstance(detail, (list, tuple)):
final_detail = []
for detail_item in detail:
if isinstance(detail_item, ValidationError):
final_detail += detail_item.detail
else:
final_detail += [detail_item % params if isinstance(detail_item, str) else detail_item]
detail = final_detail
elif not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail]

View File

@ -4,9 +4,9 @@ import datetime
import decimal
import functools
import inspect
import logging
import re
import uuid
import warnings
from collections.abc import Mapping
from enum import Enum
@ -44,6 +44,8 @@ from rest_framework.utils.formatting import lazy_format
from rest_framework.utils.timezone import valid_datetime
from rest_framework.validators import ProhibitSurrogateCharactersValidator
logger = logging.getLogger("rest_framework.fields")
class empty:
"""
@ -111,7 +113,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(f'Exception raised in callable attribute "{attr}"; original exception was: {exc}')
raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))
return instance
@ -986,10 +988,10 @@ class DecimalField(Field):
self.max_value = max_value
self.min_value = min_value
if self.max_value is not None and not isinstance(self.max_value, (int, decimal.Decimal)):
warnings.warn("max_value should be an integer or Decimal instance.")
if self.min_value is not None and not isinstance(self.min_value, (int, decimal.Decimal)):
warnings.warn("min_value should be an integer or Decimal instance.")
if self.max_value is not None and not isinstance(self.max_value, decimal.Decimal):
logger.warning("max_value in DecimalField should be Decimal type.")
if self.min_value is not None and not isinstance(self.min_value, decimal.Decimal):
logger.warning("min_value in DecimalField should be Decimal type.")
if self.max_digits is not None and self.decimal_places is not None:
self.max_whole_digits = self.max_digits - self.decimal_places
@ -1103,7 +1105,7 @@ class DecimalField(Field):
if self.localize:
return localize_input(quantized)
return f'{quantized:f}'
return '{:f}'.format(quantized)
def quantize(self, value):
"""
@ -1861,7 +1863,7 @@ class SerializerMethodField(Field):
def bind(self, field_name, parent):
# The method name defaults to `get_{field_name}`.
if self.method_name is None:
self.method_name = f'get_{field_name}'
self.method_name = 'get_{field_name}'.format(field_name=field_name)
super().bind(field_name, parent)

View File

@ -21,20 +21,18 @@ from rest_framework.settings import api_settings
def search_smart_split(search_terms):
"""Returns sanitized search terms as a list."""
split_terms = []
"""generator that first splits string by spaces, leaving quoted phrases together,
then it splits non-quoted phrases by commas.
"""
for term in smart_split(search_terms):
# trim commas to avoid bad matching for quoted phrases
term = term.strip(',')
if term.startswith(('"', "'")) and term[0] == term[-1]:
# quoted phrases are kept together without any other split
split_terms.append(unescape_string_literal(term))
yield unescape_string_literal(term)
else:
# non-quoted tokens are split by comma, keeping only non-empty ones
for sub_term in term.split(','):
if sub_term:
split_terms.append(sub_term.strip())
return split_terms
yield from (sub_term.strip() for sub_term in term.split(',') if sub_term)
class BaseFilterBackend:
@ -87,8 +85,7 @@ class SearchFilter(BaseFilterBackend):
"""
value = request.query_params.get(self.search_param, '')
field = CharField(trim_whitespace=False, allow_blank=True)
cleaned_value = field.run_validation(value)
return search_smart_split(cleaned_value)
return field.run_validation(value)
def construct_search(self, field_name, queryset):
lookup = self.lookup_prefixes.get(field_name[0])
@ -114,6 +111,10 @@ class SearchFilter(BaseFilterBackend):
if hasattr(field, "path_infos"):
# Update opts to follow the relation.
opts = field.path_infos[-1].to_opts
# django < 4.1
elif hasattr(field, 'get_path_info'):
# Update opts to follow the relation.
opts = field.get_path_info()[-1].to_opts
# Otherwise, use the field with icontains.
lookup = 'icontains'
return LOOKUP_SEP.join([field_name, lookup])
@ -162,7 +163,7 @@ class SearchFilter(BaseFilterBackend):
reduce(
operator.or_,
(models.Q(**{orm_lookup: term}) for orm_lookup in orm_lookups)
) for term in search_terms
) for term in search_smart_split(search_terms)
)
queryset = queryset.filter(reduce(operator.and_, conditions))

View File

@ -11,7 +11,6 @@
# Thomas Tanner, 2015
# Tom Jaster <futur3.tom@googlemail.com>, 2015
# Xavier Ordoquy <xordoquy@linovia.com>, 2015
# stefan6419846, 2025
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
@ -28,19 +27,19 @@ msgstr ""
#: authentication.py:70
msgid "Invalid basic header. No credentials provided."
msgstr "Ungültiger Basic Header. Keine Zugangsdaten angegeben."
msgstr "Ungültiger basic header. Keine Zugangsdaten angegeben."
#: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "Ungültiger Basic Header. Zugangsdaten sollen keine Leerzeichen enthalten."
msgstr "Ungültiger basic header. Zugangsdaten sollen keine Leerzeichen enthalten."
#: authentication.py:83
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "Ungültiger Basic Header. Zugangsdaten sind nicht korrekt mit base64 kodiert."
msgstr "Ungültiger basic header. Zugangsdaten sind nicht korrekt mit base64 kodiert."
#: authentication.py:101
msgid "Invalid username/password."
msgstr "Ungültiger Benutzername/Passwort."
msgstr "Ungültiger Benutzername/Passwort"
#: authentication.py:104 authentication.py:206
msgid "User inactive or deleted."
@ -48,16 +47,16 @@ msgstr "Benutzer inaktiv oder gelöscht."
#: authentication.py:184
msgid "Invalid token header. No credentials provided."
msgstr "Ungültiger Token Header. Keine Zugangsdaten angegeben."
msgstr "Ungültiger token header. Keine Zugangsdaten angegeben."
#: authentication.py:187
msgid "Invalid token header. Token string should not contain spaces."
msgstr "Ungültiger Token Header. Zugangsdaten sollen keine Leerzeichen enthalten."
msgstr "Ungültiger token header. Zugangsdaten sollen keine Leerzeichen enthalten."
#: authentication.py:193
msgid ""
"Invalid token header. Token string should not contain invalid characters."
msgstr "Ungültiger Token Header. Zugangsdaten dürfen keine ungültigen Zeichen enthalten."
msgstr "Ungültiger Token Header. Tokens dürfen keine ungültigen Zeichen enthalten."
#: authentication.py:203
msgid "Invalid token."
@ -109,7 +108,7 @@ msgstr "Ein Serverfehler ist aufgetreten."
#: exceptions.py:142
msgid "Invalid input."
msgstr "Ungültige Eingabe."
msgstr ""
#: exceptions.py:161
msgid "Malformed request."
@ -125,7 +124,7 @@ msgstr "Anmeldedaten fehlen."
#: exceptions.py:179
msgid "You do not have permission to perform this action."
msgstr "Sie sind nicht berechtigt, diese Aktion durchzuführen."
msgstr "Sie sind nicht berechtigt diese Aktion durchzuführen."
#: exceptions.py:185
msgid "Not found."
@ -152,17 +151,17 @@ msgstr "Die Anfrage wurde gedrosselt."
#: exceptions.py:224
#, python-brace-format
msgid "Expected available in {wait} second."
msgstr "Erwarte Verfügbarkeit in {wait} Sekunde."
msgstr ""
#: exceptions.py:225
#, python-brace-format
msgid "Expected available in {wait} seconds."
msgstr "Erwarte Verfügbarkeit in {wait} Sekunden."
msgstr ""
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
#: validators.py:183
msgid "This field is required."
msgstr "Dieses Feld ist zwingend erforderlich."
msgstr "Dieses Feld ist erforderlich."
#: fields.py:317
msgid "This field may not be null."
@ -170,11 +169,11 @@ msgstr "Dieses Feld darf nicht null sein."
#: fields.py:701
msgid "Must be a valid boolean."
msgstr "Muss ein gültiger Wahrheitswert sein."
msgstr ""
#: fields.py:766
msgid "Not a valid string."
msgstr "Kein gültiger String."
msgstr ""
#: fields.py:767
msgid "This field may not be blank."
@ -208,7 +207,7 @@ msgstr "Gib ein gültiges \"slug\" aus Buchstaben, Ziffern, Unterstrichen und Mi
msgid ""
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
"or hyphens."
msgstr "Gib ein gültiges \"slug\" aus Unicode-Buchstaben, Ziffern, Unterstrichen und Minuszeichen ein."
msgstr ""
#: fields.py:854
msgid "Enter a valid URL."
@ -216,11 +215,11 @@ msgstr "Gib eine gültige URL ein."
#: fields.py:867
msgid "Must be a valid UUID."
msgstr "Muss eine gültige UUID sein."
msgstr ""
#: fields.py:903
msgid "Enter a valid IPv4 or IPv6 address."
msgstr "Geben Sie eine gültige IPv4 oder IPv6 Adresse an."
msgstr "Geben Sie eine gültige IPv4 oder IPv6 Adresse an"
#: fields.py:931
msgid "A valid integer is required."
@ -274,11 +273,11 @@ msgstr "Erwarte eine Datums- und Zeitangabe, erhielt aber ein Datum."
#: fields.py:1150
#, python-brace-format
msgid "Invalid datetime for the timezone \"{timezone}\"."
msgstr "Ungültige Datumsangabe für die Zeitzone \"{timezone}\"."
msgstr ""
#: fields.py:1151
msgid "Datetime value out of range."
msgstr "Datumsangabe außerhalb des Bereichs."
msgstr ""
#: fields.py:1236
#, python-brace-format
@ -359,12 +358,12 @@ msgstr "Diese Liste darf nicht leer sein."
#: fields.py:1605
#, python-brace-format
msgid "Ensure this field has at least {min_length} elements."
msgstr "Dieses Feld muss mindestens {min_length} Einträge enthalten."
msgstr ""
#: fields.py:1606
#, python-brace-format
msgid "Ensure this field has no more than {max_length} elements."
msgstr "Dieses Feld darf nicht mehr als {max_length} Einträge enthalten."
msgstr ""
#: fields.py:1682
#, python-brace-format
@ -373,7 +372,7 @@ msgstr "Erwartete ein Dictionary mit Elementen, erhielt aber den Typ \"{input_ty
#: fields.py:1683
msgid "This dictionary may not be empty."
msgstr "Dieses Dictionary darf nicht leer sein."
msgstr ""
#: fields.py:1755
msgid "Value must be valid JSON."
@ -385,7 +384,7 @@ msgstr "Suche"
#: filters.py:50
msgid "A search term."
msgstr "Ein Suchbegriff."
msgstr ""
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
@ -393,7 +392,7 @@ msgstr "Sortierung"
#: filters.py:181
msgid "Which field to use when ordering the results."
msgstr "Feld, das zum Sortieren der Ergebnisse verwendet werden soll."
msgstr ""
#: filters.py:287
msgid "ascending"
@ -405,11 +404,11 @@ msgstr "Absteigend"
#: pagination.py:174
msgid "A page number within the paginated result set."
msgstr "Eine Seitenzahl in der paginierten Ergebnismenge."
msgstr ""
#: pagination.py:179 pagination.py:372 pagination.py:590
msgid "Number of results to return per page."
msgstr "Anzahl der pro Seite zurückzugebenden Ergebnisse."
msgstr ""
#: pagination.py:189
msgid "Invalid page."
@ -417,11 +416,11 @@ msgstr "Ungültige Seite."
#: pagination.py:374
msgid "The initial index from which to return the results."
msgstr "Der initiale Index, von dem die Ergebnisse zurückgegeben werden sollen."
msgstr ""
#: pagination.py:581
msgid "The pagination cursor value."
msgstr "Der Zeigerwert für die Paginierung"
msgstr ""
#: pagination.py:583
msgid "Invalid cursor"
@ -435,7 +434,7 @@ msgstr "Ungültiger pk \"{pk_value}\" - Object existiert nicht."
#: relations.py:247
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "Falscher Typ. Erwarte pk-Wert, erhielt aber {data_type}."
msgstr "Falscher Typ. Erwarte pk Wert, erhielt aber {data_type}."
#: relations.py:280
msgid "Invalid hyperlink - No URL match."
@ -452,7 +451,7 @@ msgstr "Ungültiger Hyperlink - Objekt existiert nicht."
#: relations.py:283
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "Falscher Typ. Erwarte URL-Zeichenkette, erhielt aber {data_type}."
msgstr "Falscher Typ. Erwarte URL Zeichenkette, erhielt aber {data_type}."
#: relations.py:448
#, python-brace-format
@ -465,20 +464,20 @@ msgstr "Ungültiger Wert."
#: schemas/utils.py:32
msgid "unique integer value"
msgstr "eindeutiger Ganzzahl-Wert"
msgstr ""
#: schemas/utils.py:34
msgid "UUID string"
msgstr "UUID-String"
msgstr ""
#: schemas/utils.py:36
msgid "unique value"
msgstr "eindeutiger Wert"
msgstr ""
#: schemas/utils.py:38
#, python-brace-format
msgid "A {value_type} identifying this {name}."
msgstr "Ein {value_type}, der {name} identifiziert."
msgstr ""
#: serializers.py:337
#, python-brace-format
@ -488,7 +487,7 @@ msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten."
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:136
msgid "Extra Actions"
msgstr "Zusätzliche Aktionen"
msgstr ""
#: templates/rest_framework/admin.html:130
#: templates/rest_framework/base.html:150
@ -497,27 +496,27 @@ msgstr "Filter"
#: templates/rest_framework/base.html:37
msgid "navbar"
msgstr "Navigation"
msgstr ""
#: templates/rest_framework/base.html:75
msgid "content"
msgstr "Inhalt"
msgstr ""
#: templates/rest_framework/base.html:78
msgid "request form"
msgstr "Anfrage-Formular"
msgstr ""
#: templates/rest_framework/base.html:157
msgid "main content"
msgstr "Hauptteil"
msgstr ""
#: templates/rest_framework/base.html:173
msgid "request info"
msgstr "Anfrage-Informationen"
msgstr ""
#: templates/rest_framework/base.html:177
msgid "response info"
msgstr "Antwort-Informationen"
msgstr ""
#: templates/rest_framework/horizontal/radio.html:4
#: templates/rest_framework/inline/radio.html:3
@ -543,7 +542,7 @@ msgstr "Die Felder {field_names} müssen eine eindeutige Menge bilden."
#: validators.py:171
#, python-brace-format
msgid "Surrogate characters are not allowed: U+{code_point:X}."
msgstr "Ersatzzeichen sind nicht erlaubt: U+{code_point:X}."
msgstr ""
#: validators.py:243
#, python-brace-format
@ -566,7 +565,7 @@ msgstr "Ungültige Version in der \"Accept\" Kopfzeile."
#: versioning.py:71
msgid "Invalid version in URL path."
msgstr "Ungültige Version im URL-Pfad."
msgstr "Ungültige Version im URL Pfad."
#: versioning.py:116
msgid "Invalid version in URL path. Does not match any version namespace."

View File

@ -7,7 +7,6 @@
# Aryan Baghi <ar.baghi.ce@gmail.com>, 2020
# Omid Zarin <zarinpy@gmail.com>, 2019
# Xavier Ordoquy <xordoquy@linovia.com>, 2020
# Sina Amini <general.sina.amini.20@gmail.com>, 2024
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
@ -188,7 +187,7 @@ msgstr "مطمعن شوید طول این مقدار حداقل {min_length} ا
#: fields.py:816
msgid "Enter a valid email address."
msgstr "پست الکترونیکی صحیح وارد کنید."
msgstr "پست الکترونیکی صحبح وارد کنید."
#: fields.py:827
msgid "This value does not match the required pattern."

View File

@ -7,7 +7,6 @@
# Aryan Baghi <ar.baghi.ce@gmail.com>, 2020
# Omid Zarin <zarinpy@gmail.com>, 2019
# Xavier Ordoquy <xordoquy@linovia.com>, 2020
# Sina Amini <general.sina.amini.20@gmail.com>, 2024
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
@ -188,7 +187,7 @@ msgstr "مطمعن شوید طول این مقدار حداقل {min_length} ا
#: fields.py:816
msgid "Enter a valid email address."
msgstr "پست الکترونیکی صحیح وارد کنید."
msgstr "پست الکترونیکی صحبح وارد کنید."
#: fields.py:827
msgid "This value does not match the required pattern."

View File

@ -1,37 +1,37 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# JAEGYUN JUNG <twicegoddessana1229@gmail.com>, 2024
# Hochul Kwak <rhkrghcjf@gmail.com>, 2018
# GarakdongBigBoy <novelview9@gmail.com>, 2017
# Hochul Kwak <rhkrghcjf@gmail.com>, 2018
# Joon Hwan 김준환 <xncbf12@gmail.com>, 2017
# SUN CHOI <best2378@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-22 16:13+0900\n"
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
"Last-Translator: JAEGYUN JUNG <twicegoddessana1229@gmail.com>\n"
"Language: ko_KR\n"
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
"Language-Team: Korean (Korea) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ko_KR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ko_KR\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: authentication.py:70
msgid "Invalid basic header. No credentials provided."
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터가 제공되지 않았습니다."
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
#: authentication.py:73
msgid "Invalid basic header. Credentials string should not contain spaces."
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터 문자열은 공백을 포함하지 않아야 합니다."
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials) 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
#: authentication.py:84
#: authentication.py:83
msgid "Invalid basic header. Credentials not correctly base64 encoded."
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터가 올바르게 base64 인코딩되지 않았습니다."
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 base64로 적절히 부호화(encode)되지 않았습니다."
#: authentication.py:101
msgid "Invalid username/password."
@ -43,11 +43,11 @@ msgstr "계정이 중지되었거나 삭제되었습니다."
#: authentication.py:184
msgid "Invalid token header. No credentials provided."
msgstr "토큰 헤더가 유효하지 않습니다. 인증 데이터가 제공되지 않았습니다."
msgstr "토큰 헤더가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
#: authentication.py:187
msgid "Invalid token header. Token string should not contain spaces."
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 공백을 포함하지 않아야 합니다."
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
#: authentication.py:193
msgid ""
@ -58,10 +58,6 @@ msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 유효
msgid "Invalid token."
msgstr "토큰이 유효하지 않습니다."
#: authtoken/admin.py:28 authtoken/serializers.py:9
msgid "Username"
msgstr "사용자 이름"
#: authtoken/apps.py:7
msgid "Auth Token"
msgstr "인증 토큰"
@ -72,19 +68,23 @@ msgstr "키"
#: authtoken/models.py:16
msgid "User"
msgstr "사용자"
msgstr "유저"
#: authtoken/models.py:18
msgid "Created"
msgstr "생성일시"
msgstr "생성"
#: authtoken/models.py:27 authtoken/models.py:54 authtoken/serializers.py:19
#: authtoken/models.py:27 authtoken/serializers.py:19
msgid "Token"
msgstr "토큰"
#: authtoken/models.py:28 authtoken/models.py:55
#: authtoken/models.py:28
msgid "Tokens"
msgstr "토큰(들)"
msgstr ""
#: authtoken/serializers.py:9
msgid "Username"
msgstr "유저이름"
#: authtoken/serializers.py:13
msgid "Password"
@ -92,398 +92,390 @@ msgstr "비밀번호"
#: authtoken/serializers.py:35
msgid "Unable to log in with provided credentials."
msgstr "제공된 인증 데이터로는 로그인할 수 없습니다."
msgstr "제공된 인증데이터(credentials)로는 로그인할 수 없습니다."
#: authtoken/serializers.py:38
msgid "Must include \"username\" and \"password\"."
msgstr "\"아이디\"와 \"비밀번호\"를 포함해야 합니다."
#: exceptions.py:105
#: exceptions.py:102
msgid "A server error occurred."
msgstr "서버 장애가 발생했습니다."
#: exceptions.py:145
#: exceptions.py:142
msgid "Invalid input."
msgstr "유효하지 않은 입력입니다."
msgstr ""
#: exceptions.py:166
#: exceptions.py:161
msgid "Malformed request."
msgstr "잘못된 요청입니다."
#: exceptions.py:172
#: exceptions.py:167
msgid "Incorrect authentication credentials."
msgstr "자격 인증 데이터가 올바르지 않습니다."
msgstr "자격 인증데이터(authentication credentials)가 정확하지 않습니다."
#: exceptions.py:178
#: exceptions.py:173
msgid "Authentication credentials were not provided."
msgstr "자격 인증 데이터가 제공되지 않았습니다."
msgstr "자격 인증데이터(authentication credentials)가 제공되지 않았습니다."
#: exceptions.py:184
#: exceptions.py:179
msgid "You do not have permission to perform this action."
msgstr "이 작업을 수행할 권한이 없습니다."
msgstr "이 작업을 수행할 권한(permission)이 없습니다."
#: exceptions.py:190
#: exceptions.py:185
msgid "Not found."
msgstr "찾을 수 없습니다."
#: exceptions.py:196
#: exceptions.py:191
#, python-brace-format
msgid "Method \"{method}\" not allowed."
msgstr "메서드 \"{method}\"는 허용되지 않습니다."
msgstr "메소드(Method) \"{method}\"는 허용되지 않습니다."
#: exceptions.py:207
#: exceptions.py:202
msgid "Could not satisfy the request Accept header."
msgstr "요청 Accept 헤더를 만족시킬 수 없습니다."
msgstr "Accept header 요청을 만족할 수 없습니다."
#: exceptions.py:217
#: exceptions.py:212
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "요청된 \"{media_type}\"가 지원되지 않는 미디어 형태입니다."
#: exceptions.py:228
#: exceptions.py:223
msgid "Request was throttled."
msgstr "요청이 제한되었습니다."
msgstr "요청이 지연(throttled)되었습니다."
#: exceptions.py:229
#: exceptions.py:224
#, python-brace-format
msgid "Expected available in {wait} second."
msgstr "{wait} 초 후에 사용 가능합니다."
msgstr ""
#: exceptions.py:230
#: exceptions.py:225
#, python-brace-format
msgid "Expected available in {wait} seconds."
msgstr "{wait} 초 후에 사용 가능합니다."
msgstr ""
#: fields.py:292 relations.py:240 relations.py:276 validators.py:99
#: validators.py:219
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
#: validators.py:183
msgid "This field is required."
msgstr "이 필드는 필수 항목입니다."
#: fields.py:293
#: fields.py:317
msgid "This field may not be null."
msgstr "이 필드는 null일 수 없습니다."
#: fields.py:661
#: fields.py:701
msgid "Must be a valid boolean."
msgstr "유효한 불리언이어야 합니다."
msgstr ""
#: fields.py:724
#: fields.py:766
msgid "Not a valid string."
msgstr "유효한 문자열이 아닙니다."
msgstr ""
#: fields.py:725
#: fields.py:767
msgid "This field may not be blank."
msgstr "이 필드는 blank일 수 없습니다."
#: fields.py:726 fields.py:1881
#: fields.py:768 fields.py:1881
#, python-brace-format
msgid "Ensure this field has no more than {max_length} characters."
msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하세요."
msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하십시오."
#: fields.py:727
#: fields.py:769
#, python-brace-format
msgid "Ensure this field has at least {min_length} characters."
msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하세요."
msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하십시오."
#: fields.py:774
#: fields.py:816
msgid "Enter a valid email address."
msgstr "유효한 이메일 주소를 입력하세요."
msgstr "유효한 이메일 주소를 입력하십시오."
#: fields.py:785
#: fields.py:827
msgid "This value does not match the required pattern."
msgstr "이 값은 요구되는 패턴과 일치하지 않습니다."
msgstr "형식에 맞지 않는 값입니다."
#: fields.py:796
#: fields.py:838
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하세요."
msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하십시오."
#: fields.py:797
#: fields.py:839
msgid ""
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
"or hyphens."
msgstr "유니코드 문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하세요."
msgstr ""
#: fields.py:812
#: fields.py:854
msgid "Enter a valid URL."
msgstr "유효한 URL을 입력하세요."
msgstr "유효한 URL을 입력하십시오."
#: fields.py:825
#: fields.py:867
msgid "Must be a valid UUID."
msgstr "유효한 UUID 이어야 합니다."
msgstr ""
#: fields.py:861
#: fields.py:903
msgid "Enter a valid IPv4 or IPv6 address."
msgstr "유효한 IPv4 또는 IPv6 주소를 입력하세요."
msgstr "유효한 IPv4 또는 IPv6 주소를 입력하십시오."
#: fields.py:889
#: fields.py:931
msgid "A valid integer is required."
msgstr "유효한 정수를 입력하세요."
msgstr "유효한 정수(integer)를 넣어주세요."
#: fields.py:890 fields.py:927 fields.py:966 fields.py:1349
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
#, python-brace-format
msgid "Ensure this value is less than or equal to {max_value}."
msgstr "이 값이 {max_value}보다 작거나 같은지 확인하세요."
msgstr "이 값이 {max_value}보다 작거나 같은지 확인하십시오."
#: fields.py:891 fields.py:928 fields.py:967 fields.py:1350
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
#, python-brace-format
msgid "Ensure this value is greater than or equal to {min_value}."
msgstr "이 값이 {min_value}보다 크거나 같은지 확인하세요."
msgstr "이 값이 {min_value}보다 크거나 같은지 확인하십시오."
#: fields.py:892 fields.py:929 fields.py:971
#: fields.py:934 fields.py:971 fields.py:1010
msgid "String value too large."
msgstr "문자열 값이 너무 니다."
msgstr "문자열 값이 너무 니다."
#: fields.py:926 fields.py:965
#: fields.py:968 fields.py:1004
msgid "A valid number is required."
msgstr "유효한 숫자를 입력하세요."
msgstr "유효한 숫자를 넣어주세요."
#: fields.py:930
msgid "Integer value too large to convert to float"
msgstr "정수 값이 너무 커서 부동 소수점으로 변환할 수 없습니다."
#: fields.py:968
#: fields.py:1007
#, python-brace-format
msgid "Ensure that there are no more than {max_digits} digits in total."
msgstr "총 자릿수가 {max_digits}을(를) 초과하지 않는지 확인하세요."
msgstr "전체 숫자(digits)가 {max_digits} 이하인지 확인하십시오."
#: fields.py:969
#: fields.py:1008
#, python-brace-format
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "소수점 이하 자릿수가 {max_decimal_places}을(를) 초과하지 않는지 확인하세요."
msgid ""
"Ensure that there are no more than {max_decimal_places} decimal places."
msgstr "소수점 자릿수가 {max_decimal_places} 이하인지 확인하십시오."
#: fields.py:970
#: fields.py:1009
#, python-brace-format
msgid ""
"Ensure that there are no more than {max_whole_digits} digits before the "
"decimal point."
msgstr "소수점 앞 자릿수가 {max_whole_digits}을(를) 초과하지 않는지 확인하세요."
msgstr "소수점 자리 앞에 숫자(digits)가 {max_whole_digits} 이하인지 확인하십시오."
#: fields.py:1129
#: fields.py:1148
#, python-brace-format
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
msgstr "Datetime의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
#: fields.py:1130
#: fields.py:1149
msgid "Expected a datetime but got a date."
msgstr "datatime이 예상되었지만 date를 받았습니다."
msgstr "예상된 datatime 대신 date를 받았습니다."
#: fields.py:1131
#: fields.py:1150
#, python-brace-format
msgid "Invalid datetime for the timezone \"{timezone}\"."
msgstr "\"{timezone}\" 시간대에 대한 유효하지 않은 datetime 입니다."
msgstr ""
#: fields.py:1132
#: fields.py:1151
msgid "Datetime value out of range."
msgstr "Datetime 값이 범위를 벗어났습니다."
msgstr ""
#: fields.py:1219
#: fields.py:1236
#, python-brace-format
msgid "Date has wrong format. Use one of these formats instead: {format}."
msgstr "Date의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
#: fields.py:1220
#: fields.py:1237
msgid "Expected a date but got a datetime."
msgstr "예상된 date 대신 datetime을 받았습니다."
#: fields.py:1286
#: fields.py:1303
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
#: fields.py:1348
#: fields.py:1365
#, python-brace-format
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr "Duration의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
#: fields.py:1351
#, python-brace-format
msgid "The number of days must be between {min_days} and {max_days}."
msgstr "일수는 {min_days} 이상 {max_days} 이하이어야 합니다."
#: fields.py:1386 fields.py:1446
#: fields.py:1399 fields.py:1456
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\"은 유효하지 않은 선택입니다."
msgstr "\"{input}\"이 유효하지 않은 선택(choice)입니다."
#: fields.py:1389
#: fields.py:1402
#, python-brace-format
msgid "More than {count} items..."
msgstr "{count}개 이상의 아이템이 있습니다..."
msgstr ""
#: fields.py:1447 fields.py:1596 relations.py:486 serializers.py:593
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습니다."
#: fields.py:1448
#: fields.py:1458
msgid "This selection may not be empty."
msgstr "이 선택 항목은 비워 둘 수 없습니다."
#: fields.py:1487
#: fields.py:1495
#, python-brace-format
msgid "\"{input}\" is not a valid path choice."
msgstr "\"{input}\" 유효하지 않은 경로 선택입니다."
msgstr "\"{input}\" 유효하지 않은 경로 선택입니다."
#: fields.py:1507
#: fields.py:1514
msgid "No file was submitted."
msgstr "파일이 제출되지 않았습니다."
#: fields.py:1508
msgid "The submitted data was not a file. Check the encoding type on the form."
#: fields.py:1515
msgid ""
"The submitted data was not a file. Check the encoding type on the form."
msgstr "제출된 데이터는 파일이 아닙니다. 제출된 서식의 인코딩 형식을 확인하세요."
#: fields.py:1509
#: fields.py:1516
msgid "No filename could be determined."
msgstr "파일명을 알 수 없습니다."
#: fields.py:1510
#: fields.py:1517
msgid "The submitted file is empty."
msgstr "제출한 파일이 비어있습니다."
#: fields.py:1511
#: fields.py:1518
#, python-brace-format
msgid ""
"Ensure this filename has at most {max_length} characters (it has {length})."
msgstr "이 파일명의 글자수가 최대 {max_length}자를 넘지 않는지 확인하세요. (현재 {length}자입니다)."
msgstr "이 파일명의 글자수가 최대 {max_length}를 넘지 않는지 확인하십시오. (이것은 {length}가 있습니다)."
#: fields.py:1559
#: fields.py:1566
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "유효한 이미지 파일을 업로드하세요. 업로드하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다."
msgstr "유효한 이미지 파일을 업로드 하십시오. 업로드 하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다."
#: fields.py:1597 relations.py:487 serializers.py:594
#: fields.py:1604 relations.py:486 serializers.py:571
msgid "This list may not be empty."
msgstr "이 리스트는 비워 둘 수 없습니다."
#: fields.py:1598 serializers.py:596
#: fields.py:1605
#, python-brace-format
msgid "Ensure this field has at least {min_length} elements."
msgstr "이 필드가 최소 {min_length} 개의 요소를 가지는지 확인하세요."
msgstr ""
#: fields.py:1599 serializers.py:595
#: fields.py:1606
#, python-brace-format
msgid "Ensure this field has no more than {max_length} elements."
msgstr "이 필드가 최대 {max_length} 개의 요소를 가지는지 확인하세요."
msgstr ""
#: fields.py:1677
#: fields.py:1682
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "아이템 딕셔너리가 예상되었으나 \"{input_type}\" 타입을 받았습니다."
#: fields.py:1678
#: fields.py:1683
msgid "This dictionary may not be empty."
msgstr "이 딕셔너리는 비어있을 수 없습니다."
msgstr ""
#: fields.py:1750
#: fields.py:1755
msgid "Value must be valid JSON."
msgstr "유효한 JSON 값이어야 합니다."
msgstr "Value 는 유효한 JSON형식이어야 합니다."
#: filters.py:72 templates/rest_framework/filters/search.html:2
#: templates/rest_framework/filters/search.html:8
#: filters.py:49 templates/rest_framework/filters/search.html:2
msgid "Search"
msgstr "검색"
#: filters.py:73
#: filters.py:50
msgid "A search term."
msgstr "검색어."
msgstr ""
#: filters.py:224 templates/rest_framework/filters/ordering.html:3
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
msgstr "순서"
#: filters.py:225
#: filters.py:181
msgid "Which field to use when ordering the results."
msgstr "결과 정렬 시 사용할 필드."
msgstr ""
#: filters.py:341
#: filters.py:287
msgid "ascending"
msgstr "오름차순"
#: filters.py:342
#: filters.py:288
msgid "descending"
msgstr "내림차순"
#: pagination.py:180
#: pagination.py:174
msgid "A page number within the paginated result set."
msgstr "페이지네이션된 결과 집합 내의 페이지 번호."
msgstr ""
#: pagination.py:185 pagination.py:382 pagination.py:599
#: pagination.py:179 pagination.py:372 pagination.py:590
msgid "Number of results to return per page."
msgstr "페이지당 반환할 결과 수."
msgstr ""
#: pagination.py:195
#: pagination.py:189
msgid "Invalid page."
msgstr "페이지가 유효하지 않습니다."
#: pagination.py:384
#: pagination.py:374
msgid "The initial index from which to return the results."
msgstr "결과를 반환할 초기 인덱스."
msgstr ""
#: pagination.py:590
#: pagination.py:581
msgid "The pagination cursor value."
msgstr "페이지네이션 커서 값."
msgstr ""
#: pagination.py:592
#: pagination.py:583
msgid "Invalid cursor"
msgstr "커서가 유효하지 않습니다."
msgstr "커서(cursor)가 유효하지 않습니다."
#: relations.py:241
#: relations.py:246
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "유효하지 않은 pk \"{pk_value}\" - 객체가 존재하지 않습니다."
#: relations.py:242
#: relations.py:247
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "잘못된 형식입니다. pk 값이 예상되었지만, {data_type}을(를) 받았습니다."
msgstr "잘못된 형식입니다. pk 값 대신 {data_type}를 받았습니다."
#: relations.py:277
#: relations.py:280
msgid "Invalid hyperlink - No URL match."
msgstr "유효하지 않은 하이퍼링크 - 일치하는 URL이 없습니다."
#: relations.py:278
#: relations.py:281
msgid "Invalid hyperlink - Incorrect URL match."
msgstr "유효하지 않은 하이퍼링크 - URL이 일치하지 않습니다."
#: relations.py:279
#: relations.py:282
msgid "Invalid hyperlink - Object does not exist."
msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않습니다."
#: relations.py:280
#: relations.py:283
#, python-brace-format
msgid "Incorrect type. Expected URL string, received {data_type}."
msgstr "잘못된 형식입니다. URL 문자열을 예상했으나 {data_type}을 받았습니다."
#: relations.py:445
#: relations.py:448
#, python-brace-format
msgid "Object with {slug_name}={value} does not exist."
msgstr "{slug_name}={value} 객체가 존재하지 않습니다."
#: relations.py:446
#: relations.py:449
msgid "Invalid value."
msgstr "값이 유효하지 않습니다."
#: schemas/utils.py:32
msgid "unique integer value"
msgstr "고유한 정수 값"
msgstr ""
#: schemas/utils.py:34
msgid "UUID string"
msgstr "UUID 문자열"
msgstr ""
#: schemas/utils.py:36
msgid "unique value"
msgstr "고유한 값"
msgstr ""
#: schemas/utils.py:38
#, python-brace-format
msgid "A {value_type} identifying this {name}."
msgstr "{name}을 식별하는 {value_type}."
msgstr ""
#: serializers.py:340
#: serializers.py:337
#, python-brace-format
msgid "Invalid data. Expected a dictionary, but got {datatype}."
msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}를 받았습니다."
@ -491,7 +483,7 @@ msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:136
msgid "Extra Actions"
msgstr "추가 Action들"
msgstr ""
#: templates/rest_framework/admin.html:130
#: templates/rest_framework/base.html:150
@ -500,33 +492,33 @@ msgstr "필터"
#: templates/rest_framework/base.html:37
msgid "navbar"
msgstr "네비게이션 바"
msgstr ""
#: templates/rest_framework/base.html:75
msgid "content"
msgstr "콘텐츠"
msgstr ""
#: templates/rest_framework/base.html:78
msgid "request form"
msgstr "요청 폼"
msgstr ""
#: templates/rest_framework/base.html:157
msgid "main content"
msgstr "메인 콘텐츠"
msgstr ""
#: templates/rest_framework/base.html:173
msgid "request info"
msgstr "요청 정보"
msgstr ""
#: templates/rest_framework/base.html:177
msgid "response info"
msgstr "응답 정보"
msgstr ""
#: templates/rest_framework/horizontal/radio.html:4
#: templates/rest_framework/inline/radio.html:3
#: templates/rest_framework/vertical/radio.html:3
msgid "None"
msgstr "없음"
msgstr ""
#: templates/rest_framework/horizontal/select_multiple.html:4
#: templates/rest_framework/inline/select_multiple.html:3
@ -536,49 +528,49 @@ msgstr "선택할 아이템이 없습니다."
#: validators.py:39
msgid "This field must be unique."
msgstr "이 필드는 반드시 고유해야 합니다."
msgstr "이 필드는 반드시 고유(unique)해야 합니다."
#: validators.py:98
#: validators.py:89
#, python-brace-format
msgid "The fields {field_names} must make a unique set."
msgstr "필드 {field_names} 는 반드시 고유해야 합니다."
msgstr "필드 {field_names} 는 반드시 고유(unique)해야 합니다."
#: validators.py:200
#: validators.py:171
#, python-brace-format
msgid "Surrogate characters are not allowed: U+{code_point:X}."
msgstr "대체(surrogate) 문자는 허용되지 않습니다: U+{code_point:X}."
msgstr ""
#: validators.py:290
#: validators.py:243
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" date."
msgstr "이 필드는 \"{date_field}\" 날짜에 대해 고유해야 합니다."
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 날짜를 갖습니다."
#: validators.py:305
#: validators.py:258
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" month."
msgstr "이 필드는 \"{date_field}\" 월에 대해 고유해야 합니다."
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 월을 갖습니다. "
#: validators.py:318
#: validators.py:271
#, python-brace-format
msgid "This field must be unique for the \"{date_field}\" year."
msgstr "이 필드는 \"{date_field}\" 연도에 대해 고유해야 합니다."
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 년을 갖습니다. "
#: versioning.py:40
msgid "Invalid version in \"Accept\" header."
msgstr "\"Accept\" 헤더의 버전이 유효하지 않습니다."
msgstr "\"Accept\" 헤더(header)의 버전이 유효하지 않습니다."
#: versioning.py:71
msgid "Invalid version in URL path."
msgstr "URL 경로의 버전이 유효하지 않습니다."
msgstr "URL path의 버전이 유효하지 않습니다."
#: versioning.py:118
#: versioning.py:116
msgid "Invalid version in URL path. Does not match any version namespace."
msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임스페이스와 일치하지 않습니다."
msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임 스페이스와 일치하지 않습니다."
#: versioning.py:150
#: versioning.py:148
msgid "Invalid version in hostname."
msgstr "hostname 내 버전이 유효하지 않습니다."
msgstr "hostname내 버전이 유효하지 않습니다."
#: versioning.py:172
#: versioning.py:170
msgid "Invalid version in query parameter."
msgstr "쿼리 파라메터 내 버전이 유효하지 않습니다."
msgstr "쿼리 파라메터내 버전이 유효하지 않습니다."

View File

@ -1,7 +1,7 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
#
# Translators:
# Cloves Oliveira <clovesolivier23@gmail.com>, 2020
# Craig Blaszczyk <masterjakul@gmail.com>, 2015
@ -9,8 +9,6 @@
# Filipe Rinaldi <filipe.rinaldi@gmail.com>, 2015
# Hugo Leonardo Chalhoub Mendonça <hugoleonardocm@live.com>, 2015
# Jonatas Baldin <jonatas.baldin@gmail.com>, 2017
# Gabriel Mitelman Tkacz <gmtkacz@proton.me>, 2024
# Matheus Oliveira <moliveiracdev@gmail.com>, 2025
msgid ""
msgstr ""
"Project-Id-Version: Django REST framework\n"
@ -108,11 +106,11 @@ msgstr "Ocorreu um erro de servidor."
#: exceptions.py:142
msgid "Invalid input."
msgstr "Entrada inválida"
msgstr ""
#: exceptions.py:161
msgid "Malformed request."
msgstr "Requisição malformada."
msgstr "Pedido malformado."
#: exceptions.py:167
msgid "Incorrect authentication credentials."
@ -142,7 +140,7 @@ msgstr "Não foi possível satisfazer a requisição do cabeçalho Accept."
#: exceptions.py:212
#, python-brace-format
msgid "Unsupported media type \"{media_type}\" in request."
msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado."
msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado."
#: exceptions.py:223
msgid "Request was throttled."
@ -151,12 +149,12 @@ msgstr "Pedido foi limitado."
#: exceptions.py:224
#, python-brace-format
msgid "Expected available in {wait} second."
msgstr "Disponível em {wait} segundo."
msgstr ""
#: exceptions.py:225
#, python-brace-format
msgid "Expected available in {wait} seconds."
msgstr "Disponível em {wait} segundos."
msgstr ""
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
#: validators.py:183
@ -169,15 +167,15 @@ msgstr "Este campo não pode ser nulo."
#: fields.py:701
msgid "Must be a valid boolean."
msgstr "Deve ser um valor booleano válido."
msgstr ""
#: fields.py:766
msgid "Not a valid string."
msgstr "Não é uma string válida."
msgstr ""
#: fields.py:767
msgid "This field may not be blank."
msgstr "Este campo não pode estar em branco."
msgstr "Este campo não pode ser em branco."
#: fields.py:768 fields.py:1881
#, python-brace-format
@ -201,21 +199,21 @@ msgstr "Este valor não corresponde ao padrão exigido."
msgid ""
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
"hyphens."
msgstr "Insira um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
#: fields.py:839
msgid ""
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
"or hyphens."
msgstr "Insira um \"slug\" válido que consista de letras Unicode, números, sublinhados ou hífens."
msgstr ""
#: fields.py:854
msgid "Enter a valid URL."
msgstr "Insira um URL válido."
msgstr "Entrar um URL válido."
#: fields.py:867
msgid "Must be a valid UUID."
msgstr "Deve ser um UUID válido."
msgstr ""
#: fields.py:903
msgid "Enter a valid IPv4 or IPv6 address."
@ -273,11 +271,11 @@ msgstr "Necessário uma data e hora mas recebeu uma data."
#: fields.py:1150
#, python-brace-format
msgid "Invalid datetime for the timezone \"{timezone}\"."
msgstr "Data e hora inválidas para o fuso horário \"{timezone}\"."
msgstr ""
#: fields.py:1151
msgid "Datetime value out of range."
msgstr "Valor de data e hora fora do intervalo."
msgstr ""
#: fields.py:1236
#, python-brace-format
@ -291,17 +289,17 @@ msgstr "Necessário uma data mas recebeu uma data e hora."
#: fields.py:1303
#, python-brace-format
msgid "Time has wrong format. Use one of these formats instead: {format}."
msgstr "Formato inválido para tempo. Use um dos formatos a seguir: {format}."
msgstr "Formato inválido para Tempo. Use um dos formatos a seguir: {format}."
#: fields.py:1365
#, python-brace-format
msgid "Duration has wrong format. Use one of these formats instead: {format}."
msgstr "Formato inválido para duração. Use um dos formatos a seguir: {format}."
msgstr "Formato inválido para Duração. Use um dos formatos a seguir: {format}."
#: fields.py:1399 fields.py:1456
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" não é um escolha válida."
msgstr "\"{input}\" não é um escolha válido."
#: fields.py:1402
#, python-brace-format
@ -311,7 +309,7 @@ msgstr "Mais de {count} itens..."
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
#, python-brace-format
msgid "Expected a list of items but got type \"{input_type}\"."
msgstr "Esperava uma lista de itens, mas recebeu tipo \"{input_type}\"."
msgstr "Necessário uma lista de itens, mas recebeu tipo \"{input_type}\"."
#: fields.py:1458
msgid "This selection may not be empty."
@ -349,7 +347,7 @@ msgstr "Certifique-se de que o nome do arquivo tem menos de {max_length} caracte
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr "Faça upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido."
msgstr "Fazer upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido."
#: fields.py:1604 relations.py:486 serializers.py:571
msgid "This list may not be empty."
@ -358,25 +356,25 @@ msgstr "Esta lista não pode estar vazia."
#: fields.py:1605
#, python-brace-format
msgid "Ensure this field has at least {min_length} elements."
msgstr "Certifique-se de que este campo tenha pelo menos {min_length} elementos."
msgstr ""
#: fields.py:1606
#, python-brace-format
msgid "Ensure this field has no more than {max_length} elements."
msgstr "Certifique-se de que este campo não tenha mais que {max_length} elementos."
msgstr ""
#: fields.py:1682
#, python-brace-format
msgid "Expected a dictionary of items but got type \"{input_type}\"."
msgstr "Esperava um dicionário de itens mas recebeu tipo \"{input_type}\"."
msgstr "Esperado um dicionário de itens mas recebeu tipo \"{input_type}\"."
#: fields.py:1683
msgid "This dictionary may not be empty."
msgstr "Este dicionário não pode estar vazio."
msgstr ""
#: fields.py:1755
msgid "Value must be valid JSON."
msgstr "Valor deve ser JSON válido."
msgstr "Valor devo ser JSON válido."
#: filters.py:49 templates/rest_framework/filters/search.html:2
msgid "Search"
@ -384,7 +382,7 @@ msgstr "Buscar"
#: filters.py:50
msgid "A search term."
msgstr "Um termo de busca."
msgstr ""
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
@ -392,23 +390,23 @@ msgstr "Ordenando"
#: filters.py:181
msgid "Which field to use when ordering the results."
msgstr "Qual campo usar ao ordenar os resultados."
msgstr ""
#: filters.py:287
msgid "ascending"
msgstr "crescente"
msgstr "ascendente"
#: filters.py:288
msgid "descending"
msgstr "decrescente"
msgstr "descendente"
#: pagination.py:174
msgid "A page number within the paginated result set."
msgstr "Um número de página dentro do conjunto de resultados paginado."
msgstr ""
#: pagination.py:179 pagination.py:372 pagination.py:590
msgid "Number of results to return per page."
msgstr "Número de resultados a serem retornados por página."
msgstr ""
#: pagination.py:189
msgid "Invalid page."
@ -416,11 +414,11 @@ msgstr "Página inválida."
#: pagination.py:374
msgid "The initial index from which to return the results."
msgstr "O índice inicial a partir do qual retornar os resultados."
msgstr ""
#: pagination.py:581
msgid "The pagination cursor value."
msgstr "O valor do cursor de paginação."
msgstr ""
#: pagination.py:583
msgid "Invalid cursor"
@ -434,7 +432,7 @@ msgstr "Pk inválido \"{pk_value}\" - objeto não existe."
#: relations.py:247
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr "Tipo incorreto. Esperava valor pk, recebeu {data_type}."
msgstr "Tipo incorreto. Esperado valor pk, recebeu {data_type}."
#: relations.py:280
msgid "Invalid hyperlink - No URL match."
@ -464,20 +462,20 @@ msgstr "Valor inválido."
#: schemas/utils.py:32
msgid "unique integer value"
msgstr "valor inteiro único"
msgstr ""
#: schemas/utils.py:34
msgid "UUID string"
msgstr "string UUID"
msgstr ""
#: schemas/utils.py:36
msgid "unique value"
msgstr "valor único"
msgstr ""
#: schemas/utils.py:38
#, python-brace-format
msgid "A {value_type} identifying this {name}."
msgstr "Um {value_type} que identifica este {name}."
msgstr ""
#: serializers.py:337
#, python-brace-format
@ -487,7 +485,7 @@ msgstr "Dado inválido. Necessário um dicionário mas recebeu {datatype}."
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:136
msgid "Extra Actions"
msgstr "Ações Extras"
msgstr ""
#: templates/rest_framework/admin.html:130
#: templates/rest_framework/base.html:150
@ -532,7 +530,7 @@ msgstr "Nenhum item para escholher."
#: validators.py:39
msgid "This field must be unique."
msgstr "Esse campo deve ser único."
msgstr "Esse campo deve ser único."
#: validators.py:89
#, python-brace-format
@ -542,7 +540,7 @@ msgstr "Os campos {field_names} devem criar um set único."
#: validators.py:171
#, python-brace-format
msgid "Surrogate characters are not allowed: U+{code_point:X}."
msgstr "Caracteres substitutos não são permitidos: U+{code_point:X}."
msgstr ""
#: validators.py:243
#, python-brace-format

View File

@ -353,12 +353,12 @@ msgstr "列表字段不能为空值。"
#: fields.py:1605
#, python-brace-format
msgid "Ensure this field has at least {min_length} elements."
msgstr "请确保这个字段至少包含 {min_length} 个元素。"
msgstr ""
#: fields.py:1606
#, python-brace-format
msgid "Ensure this field has no more than {max_length} elements."
msgstr "请确保这个字段不能超过 {max_length} 个元素。"
msgstr ""
#: fields.py:1682
#, python-brace-format
@ -367,7 +367,7 @@ msgstr "期望是包含类目的字典,得到类型为 “{input_type}”。"
#: fields.py:1683
msgid "This dictionary may not be empty."
msgstr "这个字典可能不是空的。"
msgstr ""
#: fields.py:1755
msgid "Value must be valid JSON."

View File

@ -104,7 +104,7 @@ msgstr "服务器出现了错误。"
#: exceptions.py:142
msgid "Invalid input."
msgstr "无效的输入。"
msgstr ""
#: exceptions.py:161
msgid "Malformed request."
@ -142,17 +142,17 @@ msgstr "不支持请求中的媒体类型 “{media_type}”。"
#: exceptions.py:223
msgid "Request was throttled."
msgstr "请求已被限流。"
msgstr "请求超过了限速。"
#: exceptions.py:224
#, python-brace-format
msgid "Expected available in {wait} second."
msgstr "预计 {wait} 秒后可用。"
msgstr ""
#: exceptions.py:225
#, python-brace-format
msgid "Expected available in {wait} seconds."
msgstr "预计 {wait} 秒后可用。"
msgstr ""
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
#: validators.py:183
@ -165,11 +165,11 @@ msgstr "该字段不能为 null。"
#: fields.py:701
msgid "Must be a valid boolean."
msgstr "必须是有效的布尔值。"
msgstr ""
#: fields.py:766
msgid "Not a valid string."
msgstr "不是有效的字符串。"
msgstr ""
#: fields.py:767
msgid "This field may not be blank."
@ -203,7 +203,7 @@ msgstr "请输入合法的“短语“,只能包含字母,数字,下划线
msgid ""
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
"or hyphens."
msgstr "请输入有效的“slug”由 Unicode 字母、数字、下划线或连字符组成。"
msgstr ""
#: fields.py:854
msgid "Enter a valid URL."
@ -211,7 +211,7 @@ msgstr "请输入合法的URL。"
#: fields.py:867
msgid "Must be a valid UUID."
msgstr "必须是有效的 UUID。"
msgstr ""
#: fields.py:903
msgid "Enter a valid IPv4 or IPv6 address."
@ -269,11 +269,11 @@ msgstr "期望为日期时间,获得的是日期。"
#: fields.py:1150
#, python-brace-format
msgid "Invalid datetime for the timezone \"{timezone}\"."
msgstr "时区“{timezone}”的时间格式无效。"
msgstr ""
#: fields.py:1151
msgid "Datetime value out of range."
msgstr "时间数值超出有效范围。"
msgstr ""
#: fields.py:1236
#, python-brace-format
@ -354,12 +354,12 @@ msgstr "列表不能为空。"
#: fields.py:1605
#, python-brace-format
msgid "Ensure this field has at least {min_length} elements."
msgstr "该字段必须包含至少 {min_length} 个元素。"
msgstr ""
#: fields.py:1606
#, python-brace-format
msgid "Ensure this field has no more than {max_length} elements."
msgstr "该字段不能超过 {max_length} 个元素。"
msgstr ""
#: fields.py:1682
#, python-brace-format
@ -368,7 +368,7 @@ msgstr "期望是包含类目的字典,得到类型为 “{input_type}”。"
#: fields.py:1683
msgid "This dictionary may not be empty."
msgstr "该字典不能为空。"
msgstr ""
#: fields.py:1755
msgid "Value must be valid JSON."
@ -380,7 +380,7 @@ msgstr " 搜索"
#: filters.py:50
msgid "A search term."
msgstr "搜索关键词。"
msgstr ""
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
msgid "Ordering"
@ -388,7 +388,7 @@ msgstr "排序"
#: filters.py:181
msgid "Which field to use when ordering the results."
msgstr "用于排序结果的字段。"
msgstr ""
#: filters.py:287
msgid "ascending"
@ -400,11 +400,11 @@ msgstr "倒排序"
#: pagination.py:174
msgid "A page number within the paginated result set."
msgstr "分页结果集中的页码。"
msgstr ""
#: pagination.py:179 pagination.py:372 pagination.py:590
msgid "Number of results to return per page."
msgstr "每页返回的结果数量。"
msgstr ""
#: pagination.py:189
msgid "Invalid page."
@ -412,11 +412,11 @@ msgstr "无效页面。"
#: pagination.py:374
msgid "The initial index from which to return the results."
msgstr "返回结果的起始索引位置。"
msgstr ""
#: pagination.py:581
msgid "The pagination cursor value."
msgstr "分页游标值"
msgstr ""
#: pagination.py:583
msgid "Invalid cursor"
@ -460,20 +460,20 @@ msgstr "无效值。"
#: schemas/utils.py:32
msgid "unique integer value"
msgstr "唯一整数值"
msgstr ""
#: schemas/utils.py:34
msgid "UUID string"
msgstr "UUID 字符串"
msgstr ""
#: schemas/utils.py:36
msgid "unique value"
msgstr "唯一值"
msgstr ""
#: schemas/utils.py:38
#, python-brace-format
msgid "A {value_type} identifying this {name}."
msgstr "标识此 {name} 的 {value_type}。"
msgstr ""
#: serializers.py:337
#, python-brace-format
@ -483,7 +483,7 @@ msgstr "无效数据。期待为字典类型,得到的是 {datatype} 。"
#: templates/rest_framework/admin.html:116
#: templates/rest_framework/base.html:136
msgid "Extra Actions"
msgstr "扩展操作"
msgstr ""
#: templates/rest_framework/admin.html:130
#: templates/rest_framework/base.html:150
@ -492,27 +492,27 @@ msgstr "过滤器"
#: templates/rest_framework/base.html:37
msgid "navbar"
msgstr "导航栏"
msgstr ""
#: templates/rest_framework/base.html:75
msgid "content"
msgstr "内容主体"
msgstr ""
#: templates/rest_framework/base.html:78
msgid "request form"
msgstr "请求表单"
msgstr ""
#: templates/rest_framework/base.html:157
msgid "main content"
msgstr "主要内容区"
msgstr ""
#: templates/rest_framework/base.html:173
msgid "request info"
msgstr "请求信息"
msgstr ""
#: templates/rest_framework/base.html:177
msgid "response info"
msgstr "响应信息"
msgstr ""
#: templates/rest_framework/horizontal/radio.html:4
#: templates/rest_framework/inline/radio.html:3
@ -538,7 +538,7 @@ msgstr "字段 {field_names} 必须能构成唯一集合。"
#: validators.py:171
#, python-brace-format
msgid "Surrogate characters are not allowed: U+{code_point:X}."
msgstr "不允许使用代理字符: U+{code_point:X}。"
msgstr ""
#: validators.py:243
#, python-brace-format

View File

@ -11,6 +11,7 @@ from django.http import Http404
from django.utils.encoding import force_str
from rest_framework import exceptions, serializers
from rest_framework.fields import empty
from rest_framework.request import clone_request
from rest_framework.utils.field_mapping import ClassLookupDict
@ -149,4 +150,7 @@ class SimpleMetadata(BaseMetadata):
for choice_value, choice_name in field.choices.items()
]
if getattr(field, 'default', None) and field.default != empty and not callable(field.default):
field_info['default'] = field.default
return field_info

View File

@ -4,6 +4,8 @@ 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 django.db.models.query import prefetch_related_objects
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
@ -67,10 +69,13 @@ class UpdateModelMixin:
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
queryset = self.filter_queryset(self.get_queryset())
if queryset._prefetch_related_lookups:
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
# forcibly invalidate the prefetch cache on the instance,
# and then re-prefetch related objects
instance._prefetched_objects_cache = {}
prefetch_related_objects([instance], *queryset._prefetch_related_lookups)
return Response(serializer.data)

View File

@ -65,7 +65,7 @@ class DefaultContentNegotiation(BaseContentNegotiation):
full_media_type = ';'.join(
(renderer.media_type,) +
tuple(
f'{key}={value}'
'{}={}'.format(key, value)
for key, value in media_type_wrapper.params.items()
)
)

View File

@ -11,6 +11,7 @@ from urllib import parse
from django.core.paginator import InvalidPage
from django.core.paginator import Paginator as DjangoPaginator
from django.db.models import Q
from django.template import loader
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
@ -630,7 +631,7 @@ class CursorPagination(BasePagination):
queryset = queryset.order_by(*self.ordering)
# If we have a cursor with a fixed position then filter by that.
if current_position is not None:
if str(current_position) != 'None':
order = self.ordering[0]
is_reversed = order.startswith('-')
order_attr = order.lstrip('-')
@ -641,7 +642,12 @@ class CursorPagination(BasePagination):
else:
kwargs = {order_attr + '__gt': current_position}
queryset = queryset.filter(**kwargs)
filter_query = Q(**kwargs)
# If some records contain a null for the ordering field, don't lose them.
# When reverse ordering, nulls will come last and need to be included.
if (reverse and not is_reversed) or is_reversed:
filter_query |= Q(**{order_attr + '__isnull': True})
queryset = queryset.filter(filter_query)
# If we have an offset cursor then offset the entire page by that amount.
# We also always fetch an extra item in order to determine if there is a
@ -714,7 +720,7 @@ class CursorPagination(BasePagination):
# The item in this position and the item following it
# have different positions. We can use this position as
# our marker.
has_item_with_unique_position = True
has_item_with_unique_position = position is not None
break
# The item in this position has the same position as the item
@ -767,7 +773,7 @@ class CursorPagination(BasePagination):
# The item in this position and the item following it
# have different positions. We can use this position as
# our marker.
has_item_with_unique_position = True
has_item_with_unique_position = position is not None
break
# The item in this position has the same position as the item
@ -890,7 +896,7 @@ class CursorPagination(BasePagination):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
return str(attr)
return None if attr is None else str(attr)
def get_paginated_response(self, data):
return Response({

View File

@ -15,9 +15,9 @@ from django.http.multipartparser import ChunkIter
from django.http.multipartparser import \
MultiPartParser as DjangoMultiPartParser
from django.http.multipartparser import MultiPartParserError
from django.utils.http import parse_header_parameters
from rest_framework import renderers
from rest_framework.compat import parse_header_parameters
from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings
from rest_framework.utils import json

View File

@ -54,9 +54,6 @@ class OperandHolder(OperationHolderMixin):
self.op2_class == other.op2_class
)
def __hash__(self):
return hash((self.operator_class, self.op1_class, self.op2_class))
class AND:
def __init__(self, op1, op2):
@ -189,9 +186,9 @@ class DjangoModelPermissions(BasePermission):
# Override this if you need to also provide 'view' permissions,
# or if you want to provide custom permission codes.
perms_map = {
'GET': [],
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': [],
'HEAD': [],
'HEAD': ['%(app_label)s.view_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
@ -225,7 +222,7 @@ class DjangoModelPermissions(BasePermission):
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
assert queryset is not None, (
f'{view.__class__.__name__}.get_queryset() returned None'
'{}.get_queryset() returned None'.format(view.__class__.__name__)
)
return queryset
return view.queryset
@ -242,8 +239,13 @@ class DjangoModelPermissions(BasePermission):
queryset = self._queryset(view)
perms = self.get_required_permissions(request.method, queryset.model)
change_perm = self.get_required_permissions('PUT', queryset.model)
return request.user.has_perms(perms)
user = request.user
if request.method == 'GET':
return user.has_perms(perms) or user.has_perms(change_perm)
return user.has_perms(perms)
class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):

View File

@ -19,13 +19,12 @@ from django.core.paginator import Page
from django.template import engines, loader
from django.urls import NoReverseMatch
from django.utils.html import mark_safe
from django.utils.http import parse_header_parameters
from django.utils.safestring import SafeString
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
pygments_css, yaml
parse_header_parameters, pygments_css, yaml
)
from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method
@ -171,10 +170,6 @@ class TemplateHTMLRenderer(BaseRenderer):
def get_template_context(self, data, renderer_context):
response = renderer_context['response']
# in case a ValidationError is caught the data parameter may be a list
# see rest_framework.views.exception_handler
if isinstance(data, list):
return {'details': data, 'status_code': response.status_code}
if response.exception:
data['status_code'] = response.status_code
return data

View File

@ -16,9 +16,9 @@ from django.conf import settings
from django.http import HttpRequest, QueryDict
from django.http.request import RawPostDataException
from django.utils.datastructures import MultiValueDict
from django.utils.http import parse_header_parameters
from rest_framework import exceptions
from rest_framework.compat import parse_header_parameters
from rest_framework.settings import api_settings
@ -217,8 +217,7 @@ class Request:
@property
def data(self):
if not _hasattr(self, '_full_data'):
with wrap_attributeerrors():
self._load_data_and_files()
self._load_data_and_files()
return self._full_data
@property
@ -421,14 +420,20 @@ class Request:
_request = self.__getattribute__("_request")
return getattr(_request, attr)
except AttributeError:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")
return self.__getattribute__(attr)
@property
def DATA(self):
raise NotImplementedError(
'`request.DATA` has been deprecated in favor of `request.data` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
@property
def POST(self):
# Ensure that request.POST uses our request parsing.
if not _hasattr(self, '_data'):
with wrap_attributeerrors():
self._load_data_and_files()
self._load_data_and_files()
if is_form_media_type(self.content_type):
return self._data
return QueryDict('', encoding=self._request._encoding)
@ -439,10 +444,16 @@ class Request:
# Different from the other two cases, which are not valid property
# names on the WSGIRequest class.
if not _hasattr(self, '_files'):
with wrap_attributeerrors():
self._load_data_and_files()
self._load_data_and_files()
return self._files
@property
def QUERY_PARAMS(self):
raise NotImplementedError(
'`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
'since version 3.0, and has been fully removed as of version 3.2.'
)
def force_plaintext_errors(self, value):
# Hack to allow our exception handler to force choice of
# plaintext or html error responses.

View File

@ -66,7 +66,7 @@ class Response(SimpleTemplateResponse):
content_type = self.content_type
if content_type is None and charset is not None:
content_type = f"{media_type}; charset={charset}"
content_type = "{}; charset={}".format(media_type, charset)
elif content_type is None:
content_type = media_type
self['Content-Type'] = content_type

View File

@ -68,7 +68,7 @@ class LinkNode(dict):
current_val = self.methods_counter[preferred_key]
self.methods_counter[preferred_key] += 1
key = f'{preferred_key}_{current_val}'
key = '{}_{}'.format(preferred_key, current_val)
if key not in self:
return key

View File

@ -79,9 +79,8 @@ class ViewInspector:
view = self.view
method_name = getattr(view, 'action', method.lower())
method_func = getattr(view, method_name, None)
method_docstring = method_func.__doc__
if method_func and method_docstring:
method_docstring = getattr(view, method_name, None).__doc__
if method_docstring:
# An explicit docstring on the method or action.
return self._get_description_section(view, method.lower(), formatting.dedent(smart_str(method_docstring)))
else:

View File

@ -11,8 +11,10 @@ from django.core.validators import (
from django.db import models
from django.utils.encoding import force_str
from rest_framework import exceptions, renderers, serializers
from rest_framework.compat import inflection, uritemplate
from rest_framework import (
RemovedInDRF315Warning, exceptions, renderers, serializers
)
from rest_framework.compat import uritemplate
from rest_framework.fields import _UnvalidatedField, empty
from rest_framework.settings import api_settings
@ -82,7 +84,7 @@ class SchemaGenerator(BaseSchemaGenerator):
continue
if components_schemas[k] == components[k]:
continue
warnings.warn(f'Schema component "{k}" has been overridden with a different value.')
warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k))
components_schemas.update(components)
@ -245,8 +247,9 @@ class AutoSchema(ViewInspector):
name = name[:-len(action)]
if action == 'list':
assert inflection, '`inflection` must be installed for OpenAPI schema support.'
name = inflection.pluralize(name)
from inflection import pluralize
name = pluralize(name)
return name
@ -644,7 +647,7 @@ class AutoSchema(ViewInspector):
return self.get_serializer(path, method)
def get_reference(self, serializer):
return {'$ref': f'#/components/schemas/{self.get_component_name(serializer)}'}
return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))}
def get_request_body(self, path, method):
if method not in ('PUT', 'PATCH', 'POST'):
@ -719,3 +722,11 @@ class AutoSchema(ViewInspector):
path = path[1:]
return [path.split('/')[0].replace('_', '-')]
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.15.",
RemovedInDRF315Warning, stacklevel=2
)
return self.get_reference(serializer)

View File

@ -26,9 +26,7 @@ from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from rest_framework.compat import (
get_referenced_base_fields_from_q, postgres_fields
)
from rest_framework.compat import postgres_fields
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail
from rest_framework.settings import api_settings
@ -1427,20 +1425,20 @@ class ModelSerializer(Serializer):
def get_unique_together_constraints(self, model):
"""
Returns iterator of (fields, queryset, condition_fields, condition),
each entry describes an unique together constraint on `fields` in `queryset`
with respect of constraint's `condition`.
Returns iterator of (fields, queryset), each entry describes an unique together
constraint on `fields` in `queryset`.
"""
for parent_class in [model] + list(model._meta.parents):
for unique_together in parent_class._meta.unique_together:
yield unique_together, model._default_manager, [], None
yield unique_together, model._default_manager
for constraint in parent_class._meta.constraints:
if isinstance(constraint, models.UniqueConstraint) and len(constraint.fields) > 1:
if constraint.condition is None:
condition_fields = []
else:
condition_fields = list(get_referenced_base_fields_from_q(constraint.condition))
yield (constraint.fields, model._default_manager, condition_fields, constraint.condition)
yield (
constraint.fields,
model._default_manager
if constraint.condition is None
else model._default_manager.filter(constraint.condition)
)
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
"""
@ -1472,10 +1470,9 @@ class ModelSerializer(Serializer):
# Include each of the `unique_together` and `UniqueConstraint` field names,
# so long as all the field names are included on the serializer.
for unique_together_list, queryset, condition_fields, condition in self.get_unique_together_constraints(model):
unique_together_list_and_condition_fields = set(unique_together_list) | set(condition_fields)
if set(field_names).issuperset(unique_together_list_and_condition_fields):
unique_constraint_names |= unique_together_list_and_condition_fields
for unique_together_list, queryset in self.get_unique_together_constraints(model):
if set(field_names).issuperset(unique_together_list):
unique_constraint_names |= set(unique_together_list)
# Now we have all the field names that have uniqueness constraints
# applied, we can add the extra 'required=...' or 'default=...'
@ -1493,8 +1490,6 @@ class ModelSerializer(Serializer):
default = timezone.now
elif unique_constraint_field.has_default():
default = unique_constraint_field.default
elif unique_constraint_field.null:
default = None
else:
default = empty
@ -1597,13 +1592,12 @@ class ModelSerializer(Serializer):
# Note that we make sure to check `unique_together` both on the
# base model class, but also on any parent classes.
validators = []
for unique_together, queryset, condition_fields, condition in self.get_unique_together_constraints(self.Meta.model):
for unique_together, queryset in self.get_unique_together_constraints(self.Meta.model):
# Skip if serializer does not map to all unique together sources
unique_together_and_condition_fields = set(unique_together) | set(condition_fields)
if not set(source_map).issuperset(unique_together_and_condition_fields):
if not set(source_map).issuperset(unique_together):
continue
for source in unique_together_and_condition_fields:
for source in unique_together:
assert len(source_map[source]) == 1, (
"Unable to create `UniqueTogetherValidator` for "
"`{model}.{field}` as `{serializer}` has multiple "
@ -1622,9 +1616,7 @@ class ModelSerializer(Serializer):
field_names = tuple(source_map[f][0] for f in unique_together)
validator = UniqueTogetherValidator(
queryset=queryset,
fields=field_names,
condition_fields=tuple(source_map[f][0] for f in condition_fields),
condition=condition,
fields=field_names
)
validators.append(validator)
return validators

View File

@ -322,5 +322,5 @@ def break_long_headers(header):
when possible (are comma separated)
"""
if len(header) > 160 and ',' in header:
header = mark_safe('<br> ' + ', <br>'.join(escape(header).split(',')))
header = mark_safe('<br> ' + ', <br>'.join(header.split(',')))
return header

View File

@ -3,6 +3,7 @@
import io
from importlib import import_module
import django
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIHandler
@ -150,19 +151,15 @@ class APIRequestFactory(DjangoRequestFactory):
"""
Encode the data returning a two tuple of (bytes, content_type)
"""
if data is None:
return (b'', content_type)
return ('', content_type)
assert format is None or content_type is None, (
'You may not set both `format` and `content_type`.'
)
if content_type:
try:
data = self._encode_json(data, content_type)
except AttributeError:
pass
# Content type specified explicitly, treat data as a raw bytestring
ret = force_bytes(data, settings.DEFAULT_CHARSET)
@ -397,7 +394,19 @@ class URLPatternsTestCase(testcases.SimpleTestCase):
cls._override.enable()
cls.addClassCleanup(cls._override.disable)
cls.addClassCleanup(cleanup_url_patterns, cls)
if django.VERSION > (4, 0):
cls.addClassCleanup(cls._override.disable)
cls.addClassCleanup(cleanup_url_patterns, cls)
super().setUpClass()
if django.VERSION < (4, 0):
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls._override.disable()
if hasattr(cls, '_module_urlpatterns'):
cls._module.urlpatterns = cls._module_urlpatterns
else:
del cls._module.urlpatterns

View File

@ -1,11 +1,10 @@
from django.urls import URLResolver, include, path, re_path, register_converter
from django.urls.converters import get_converters
from django.urls.resolvers import RoutePattern
from rest_framework.settings import api_settings
def _get_format_path_converter(allowed):
def _get_format_path_converter(suffix_kwarg, allowed):
if allowed:
if len(allowed) == 1:
allowed_pattern = allowed[0]
@ -24,14 +23,11 @@ def _get_format_path_converter(allowed):
def to_url(self, value):
return '.' + value + '/'
return FormatSuffixConverter
def _generate_converter_name(allowed):
converter_name = 'drf_format_suffix'
if allowed:
converter_name += '_' + '_'.join(allowed)
return converter_name
return converter_name, FormatSuffixConverter
def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route=None):
@ -108,10 +104,8 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None):
else:
suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg
converter_name = _generate_converter_name(allowed)
if converter_name not in get_converters():
suffix_converter = _get_format_path_converter(allowed)
register_converter(suffix_converter, converter_name)
converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed)
register_converter(suffix_converter, converter_name)
suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg)

View File

@ -9,6 +9,7 @@ from django.db import models
from django.utils.text import capfirst
from rest_framework.compat import postgres_fields
from rest_framework.fields import empty
from rest_framework.validators import UniqueValidator
NUMERIC_FIELD_TYPES = (
@ -66,7 +67,7 @@ def get_unique_validators(field_name, model_field):
"""
Returns a list of UniqueValidators that should be applied to the field.
"""
field_set = {field_name}
field_set = set([field_name])
conditions = {
c.condition
for c in model_field.model._meta.constraints
@ -127,6 +128,9 @@ def get_field_kwargs(field_name, model_field):
kwargs['read_only'] = True
return kwargs
if model_field.default is not None and model_field.default != empty and not callable(model_field.default):
kwargs['default'] = model_field.default
if model_field.has_default() or model_field.blank or model_field.null:
kwargs['required'] = False

View File

@ -3,7 +3,7 @@ Handling of media types, as found in HTTP Content-Type and Accept headers.
See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
"""
from django.utils.http import parse_header_parameters
from rest_framework.compat import parse_header_parameters
def media_type_matches(lhs, rhs):

View File

@ -1,4 +1,5 @@
import contextlib
import sys
from collections.abc import Mapping, MutableMapping
from django.utils.encoding import force_str
@ -28,20 +29,21 @@ class ReturnDict(dict):
# but preserve the raw data.
return (dict, (dict(self),))
# These are basically copied from OrderedDict, with `serializer` added.
def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(self, serializer=self.serializer)
new.update(other)
return new
if sys.version_info >= (3, 9):
# These are basically copied from OrderedDict, with `serializer` added.
def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(self, serializer=self.serializer)
new.update(other)
return new
def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(other, serializer=self.serializer)
new.update(self)
return new
def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(other, serializer=self.serializer)
new.update(self)
return new
class ReturnList(list):

View File

@ -6,9 +6,7 @@ This gives us better separation of concerns, allows us to use single-step
object creation, and makes it possible to switch between using the implicit
`ModelSerializer` class and an equivalent explicit `Serializer` class.
"""
from django.core.exceptions import FieldError
from django.db import DataError
from django.db.models import Exists
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
@ -25,17 +23,6 @@ def qs_exists(queryset):
return False
def qs_exists_with_condition(queryset, condition, against):
if condition is None:
return qs_exists(queryset)
try:
# use the same query as UniqueConstraint.validate
# https://github.com/django/django/blob/7ba2a0db20c37a5b1500434ca4ed48022311c171/django/db/models/constraints.py#L672
return (condition & Exists(queryset.filter(condition))).check(against)
except (TypeError, ValueError, DataError, FieldError):
return False
def qs_filter(queryset, **kwargs):
try:
return queryset.filter(**kwargs)
@ -112,12 +99,10 @@ class UniqueTogetherValidator:
missing_message = _('This field is required.')
requires_context = True
def __init__(self, queryset, fields, message=None, condition_fields=None, condition=None):
def __init__(self, queryset, fields, message=None):
self.queryset = queryset
self.fields = fields
self.message = message or self.message
self.condition_fields = [] if condition_fields is None else condition_fields
self.condition = condition
def enforce_required_fields(self, attrs, serializer):
"""
@ -129,7 +114,7 @@ class UniqueTogetherValidator:
missing_items = {
field_name: self.missing_message
for field_name in (*self.fields, *self.condition_fields)
for field_name in self.fields
if serializer.fields[field_name].source not in attrs
}
if missing_items:
@ -174,33 +159,29 @@ class UniqueTogetherValidator:
queryset = self.filter_queryset(attrs, queryset, serializer)
queryset = self.exclude_current_instance(attrs, queryset, serializer.instance)
checked_names = [
serializer.fields[field_name].source for field_name in self.fields
]
# Ignore validation if any field is None
if serializer.instance is None:
checked_values = [attrs[field_name] for field_name in checked_names]
checked_values = [
value for field, value in attrs.items() if field in self.fields
]
else:
# Ignore validation if all field values are unchanged
checked_values = [
attrs[field_name]
for field_name in checked_names
if attrs[field_name] != getattr(serializer.instance, field_name)
value
for field, value in attrs.items()
if field in self.fields and value != getattr(serializer.instance, field)
]
condition_kwargs = {source: attrs[source] for source in self.condition_fields}
if checked_values and None not in checked_values and qs_exists_with_condition(queryset, self.condition, condition_kwargs):
if checked_values and None not in checked_values and qs_exists(queryset):
field_names = ', '.join(self.fields)
message = self.message.format(field_names=field_names)
raise ValidationError(message, code='unique')
def __repr__(self):
return '<{}({})>'.format(
return '<%s(queryset=%s, fields=%s)>' % (
self.__class__.__name__,
', '.join(
f'{attr}={smart_repr(getattr(self, attr))}'
for attr in ('queryset', 'fields', 'condition')
if getattr(self, attr) is not None)
smart_repr(self.queryset),
smart_repr(self.fields)
)
def __eq__(self, other):

View File

@ -119,15 +119,16 @@ class NamespaceVersioning(BaseVersioning):
def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None)
if resolver_match is None or not resolver_match.namespace:
return self.default_version
if resolver_match is not None and resolver_match.namespace:
# Allow for possibly nested namespaces.
possible_versions = resolver_match.namespace.split(':')
for version in possible_versions:
if self.is_allowed_version(version):
return version
# Allow for possibly nested namespaces.
possible_versions = resolver_match.namespace.split(':')
for version in possible_versions:
if self.is_allowed_version(version):
return version
raise exceptions.NotFound(self.invalid_version_message)
if not self.is_allowed_version(self.default_version):
raise exceptions.NotFound(self.invalid_version_message)
return self.default_version
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:

View File

@ -1,7 +1,6 @@
"""
Provides an APIView class that is the base of all views in REST framework.
"""
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.db import connections, models
@ -140,11 +139,6 @@ class APIView(View):
view.cls = cls
view.initkwargs = initkwargs
# Exempt all DRF views from Django's LoginRequiredMiddleware. Users should set
# DEFAULT_PERMISSION_CLASSES to 'rest_framework.permissions.IsAuthenticated' instead
if DJANGO_VERSION >= (5, 1):
view.login_required = False
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
return csrf_exempt(view)
@ -427,7 +421,7 @@ class APIView(View):
"""
# Make the error obvious if a proper response is not returned
assert isinstance(response, HttpResponseBase), (
'Expected a `Response`, `HttpResponse` or `StreamingHttpResponse` '
'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
'to be returned from the view, but received a `%s`'
% type(response)
)

View File

@ -19,7 +19,6 @@ automatically.
from functools import update_wrapper
from inspect import getmembers
from django import VERSION as DJANGO_VERSION
from django.urls import NoReverseMatch
from django.utils.decorators import classonlymethod
from django.views.decorators.csrf import csrf_exempt
@ -137,12 +136,6 @@ class ViewSetMixin:
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
# Exempt from Django's LoginRequiredMiddleware. Users should set
# DEFAULT_PERMISSION_CLASSES to 'rest_framework.permissions.IsAuthenticated' instead
if DJANGO_VERSION >= (5, 1):
view.login_required = False
return csrf_exempt(view)
def initialize_request(self, request, *args, **kwargs):

View File

@ -3,7 +3,7 @@ license_files = LICENSE.md
[tool:pytest]
addopts=--tb=short --strict-markers -ra
testpaths = tests
testspath = tests
filterwarnings = ignore:CoreAPI compatibility is deprecated*:rest_framework.RemovedInDRF317Warning
[flake8]

View File

@ -1,12 +1,14 @@
#!/usr/bin/env python3
import os
import re
import shutil
import sys
from io import open
from setuptools import find_packages, setup
CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 9)
REQUIRED_PYTHON = (3, 6)
# This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON:
@ -35,7 +37,7 @@ an older version of Django REST Framework:
def read(f):
with open(f, encoding='utf-8') as file:
with open(f, 'r', encoding='utf-8') as file:
return file.read()
@ -81,27 +83,32 @@ setup(
author_email='tom@tomchristie.com', # SEE NOTE BELOW (*)
packages=find_packages(exclude=['tests*']),
include_package_data=True,
install_requires=["django>=4.2"],
python_requires=">=3.9",
install_requires=["django>=3.0", 'backports.zoneinfo;python_version<"3.9"'],
python_requires=">=3.6",
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 3.0',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Framework :: Django :: 5.1',
'Framework :: Django :: 5.2',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet :: WWW/HTTP',
],

View File

@ -1,5 +1,6 @@
import base64
import django
import pytest
from django.conf import settings
from django.contrib.auth.models import User
@ -234,13 +235,21 @@ class SessionAuthTests(TestCase):
Ensure POSTing form over session authentication with CSRF token succeeds.
Regression test for #6088
"""
# Remove this shim when dropping support for Django 3.0.
if django.VERSION < (3, 1):
from django.middleware.csrf import _get_new_csrf_token
else:
from django.middleware.csrf import (
_get_new_csrf_string, _mask_cipher_secret
)
def _get_new_csrf_token():
return _mask_cipher_secret(_get_new_csrf_string())
self.csrf_client.login(username=self.username, password=self.password)
# Set the csrf_token cookie so that CsrfViewMiddleware._get_token() works
from django.middleware.csrf import (
_get_new_csrf_string, _mask_cipher_secret
)
token = _mask_cipher_secret(_get_new_csrf_string())
token = _get_new_csrf_token()
self.csrf_client.cookies[settings.CSRF_COOKIE_NAME] = token
# Post the token matching the cookie value

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