Merge branch 'main' into remove-transifex-in-docs
17
.github/ISSUE_TEMPLATE/1-issue.md
vendored
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
name: Issue
|
|
||||||
about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Note: REST framework is considered feature-complete. New functionality should be implemented outside the core REST framework. For details, please check the docs: https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages
|
|
||||||
-->
|
|
||||||
|
|
||||||
- [ ] Raised initially as discussion #...
|
|
||||||
- [ ] This is not a feature request suitable for implementation outside this project. Please elaborate what it is:
|
|
||||||
- [ ] compatibility fix for new Django/Python version ...
|
|
||||||
- [ ] other type of bug fix
|
|
||||||
- [ ] other type of improvement that does not touch existing code or change existing behavior (e.g. wrapper for new Django field)
|
|
||||||
- [ ] I have reduced the issue to the simplest possible case.
|
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -4,3 +4,4 @@ contact_links:
|
||||||
url: https://github.com/encode/django-rest-framework/discussions
|
url: https://github.com/encode/django-rest-framework/discussions
|
||||||
about: >
|
about: >
|
||||||
The "Discussions" forum is where you want to start. 💖
|
The "Discussions" forum is where you want to start. 💖
|
||||||
|
Please note that at this point in its lifespan, we consider Django REST framework to be feature-complete.
|
||||||
|
|
29
.github/workflows/main.yml
vendored
|
@ -3,30 +3,30 @@ name: CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Python ${{ matrix.python-version }}
|
name: Python ${{ matrix.python-version }}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version:
|
python-version:
|
||||||
- '3.8'
|
|
||||||
- '3.9'
|
|
||||||
- '3.10'
|
- '3.10'
|
||||||
- '3.11'
|
- '3.11'
|
||||||
- '3.12'
|
- '3.12'
|
||||||
- '3.13'
|
- '3.13'
|
||||||
|
- '3.14'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
cache-dependency-path: 'requirements/*.txt'
|
cache-dependency-path: 'requirements/*.txt'
|
||||||
|
|
||||||
|
@ -34,29 +34,30 @@ jobs:
|
||||||
run: python -m pip install --upgrade pip setuptools virtualenv wheel
|
run: python -m pip install --upgrade pip setuptools virtualenv wheel
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: python -m pip install --upgrade codecov tox
|
run: python -m pip install --upgrade tox
|
||||||
|
|
||||||
- name: Run tox targets for ${{ matrix.python-version }}
|
- name: Run tox targets for ${{ matrix.python-version }}
|
||||||
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d . | cut -f 1 -d '-')
|
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d . | cut -f 1 -d '-')
|
||||||
|
|
||||||
- name: Run extra tox targets
|
- name: Run extra tox targets
|
||||||
if: ${{ matrix.python-version == '3.9' }}
|
if: ${{ matrix.python-version == '3.13' }}
|
||||||
run: |
|
run: |
|
||||||
tox -e base,dist,docs
|
tox -e base,dist,docs
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
run: |
|
uses: codecov/codecov-action@v5
|
||||||
codecov -e TOXENV,DJANGO
|
with:
|
||||||
|
env_vars: TOXENV,DJANGO
|
||||||
|
|
||||||
test-docs:
|
test-docs:
|
||||||
name: Test documentation links
|
name: Test documentation links
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.13'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install -r requirements/requirements-documentation.txt
|
run: pip install -r requirements/requirements-documentation.txt
|
||||||
|
|
29
.github/workflows/mkdocs-deploy.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
name: mkdocs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- docs/**
|
||||||
|
- docs_theme/**
|
||||||
|
- requirements/requirements-documentation.txt
|
||||||
|
- mkdocs.yml
|
||||||
|
- .github/workflows/mkdocs-deploy.yml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: github-pages
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- run: git fetch --no-tags --prune --depth=1 origin gh-pages
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- run: pip install -r requirements/requirements-documentation.txt
|
||||||
|
- run: mkdocs gh-deploy
|
6
.github/workflows/pre-commit.yml
vendored
|
@ -3,7 +3,7 @@ name: pre-commit
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -11,11 +11,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
|
|
@ -31,3 +31,17 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js
|
exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js
|
||||||
|
additional_dependencies:
|
||||||
|
# python doesn't come with a toml parser prior to 3.11
|
||||||
|
- "tomli; python_version < '3.11'"
|
||||||
|
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.19.1
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: ["--py39-plus", "--keep-percent-format"]
|
||||||
|
|
||||||
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
|
rev: v2.6.0
|
||||||
|
hooks:
|
||||||
|
- id: pyproject-fmt
|
||||||
|
|
|
@ -2,6 +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.
|
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.
|
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 open a 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.
|
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.
|
||||||
|
|
28
README.md
|
@ -54,8 +54,8 @@ Some reasons you might want to use REST framework:
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
* Python 3.8+
|
* Python 3.10+
|
||||||
* Django 4.2, 5.0, 5.1
|
* Django 4.2, 5.0, 5.1, 5.2
|
||||||
|
|
||||||
We **highly recommend** and only officially support the latest patch release of
|
We **highly recommend** and only officially support the latest patch release of
|
||||||
each Python and Django series.
|
each Python and Django series.
|
||||||
|
@ -179,8 +179,8 @@ Please see the [security policy][security-policy].
|
||||||
|
|
||||||
[build-status-image]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml/badge.svg
|
[build-status-image]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml/badge.svg
|
||||||
[build-status]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml
|
[build-status]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml
|
||||||
[coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/master.svg
|
[coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/main.svg
|
||||||
[codecov]: https://codecov.io/github/encode/django-rest-framework?branch=master
|
[codecov]: https://codecov.io/github/encode/django-rest-framework?branch=main
|
||||||
[pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
|
[pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
|
||||||
[pypi]: https://pypi.org/project/djangorestframework/
|
[pypi]: https://pypi.org/project/djangorestframework/
|
||||||
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||||
|
@ -188,16 +188,16 @@ Please see the [security policy][security-policy].
|
||||||
[funding]: https://fund.django-rest-framework.org/topics/funding/
|
[funding]: https://fund.django-rest-framework.org/topics/funding/
|
||||||
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
||||||
|
|
||||||
[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png
|
[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/sentry-readme.png
|
||||||
[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
|
[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/stream-readme.png
|
||||||
[spacinov-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/spacinov-readme.png
|
[spacinov-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/spacinov-readme.png
|
||||||
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png
|
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/retool-readme.png
|
||||||
[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png
|
[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/bitio-readme.png
|
||||||
[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png
|
[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/posthog-readme.png
|
||||||
[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png
|
[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/cryptapi-readme.png
|
||||||
[fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png
|
[fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/fezto-readme.png
|
||||||
[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-premium.png
|
[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/svix-premium.png
|
||||||
[zuplo-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/zuplo-readme.png
|
[zuplo-img]: https://raw.githubusercontent.com/encode/django-rest-framework/main/docs/img/premium/zuplo-readme.png
|
||||||
|
|
||||||
[sentry-url]: https://getsentry.com/welcome/
|
[sentry-url]: https://getsentry.com/welcome/
|
||||||
[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage
|
[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
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@encode.io**.
|
||||||
|
|
||||||
**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.
|
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
||||||
|
|
|
@ -454,6 +454,12 @@ 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).
|
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/
|
[cite]: https://jacobian.org/writing/rest-worst-practices/
|
||||||
[http401]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
[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
|
[http403]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
|
||||||
|
@ -491,3 +497,4 @@ More information can be found in the [Documentation](https://django-rest-durin.r
|
||||||
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
|
[django-rest-authemail]: https://github.com/celiao/django-rest-authemail
|
||||||
[django-rest-durin]: https://github.com/eshaan7/django-rest-durin
|
[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
|
[login-required-middleware]: https://docs.djangoproject.com/en/stable/ref/middleware/#django.contrib.auth.middleware.LoginRequiredMiddleware
|
||||||
|
[django-pyoidc] : https://github.com/makinacorpus/django_pyoidc
|
||||||
|
|
|
@ -85,7 +85,7 @@ def get_user_list(request):
|
||||||
**NOTE:** The [`cache_page`][page] decorator only caches the
|
**NOTE:** The [`cache_page`][page] decorator only caches the
|
||||||
`GET` and `HEAD` responses with status 200.
|
`GET` and `HEAD` responses with status 200.
|
||||||
|
|
||||||
[page]: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-view-cache
|
[page]: https://docs.djangoproject.com/en/stable/topics/cache/#the-per-view-cache
|
||||||
[cookie]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie
|
[cookie]: https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie
|
||||||
[headers]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_headers
|
[headers]: https://docs.djangoproject.com/en/stable/topics/http/decorators/#django.views.decorators.vary.vary_on_headers
|
||||||
[decorator]: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class
|
[decorator]: https://docs.djangoproject.com/en/stable/topics/class-based-views/intro/#decorating-the-class
|
||||||
|
|
|
@ -269,5 +269,5 @@ The [drf-standardized-errors][drf-standardized-errors] package provides an excep
|
||||||
|
|
||||||
[cite]: https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/
|
[cite]: https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/
|
||||||
[authentication]: authentication.md
|
[authentication]: authentication.md
|
||||||
[django-custom-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
|
[django-custom-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
|
||||||
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
||||||
|
|
|
@ -42,7 +42,7 @@ Set to false if this field is not required to be present during deserialization.
|
||||||
|
|
||||||
Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
|
Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation.
|
||||||
|
|
||||||
Defaults to `True`. If you're using [Model Serializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) default value will be `False` if you have specified `blank=True` or `default` or `null=True` at your field in your `Model`.
|
Defaults to `True`. If you're using [Model Serializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer), the default value will be `False` when you have specified a `default`, or when the corresponding `Model` field has `blank=True` or `null=True` and is not part of a unique constraint at the same time. (Note that without a `default` value, [unique constraints will cause the field to be required](https://www.django-rest-framework.org/api-guide/validators/#optional-fields).)
|
||||||
|
|
||||||
### `default`
|
### `default`
|
||||||
|
|
||||||
|
@ -377,13 +377,16 @@ A Duration representation.
|
||||||
Corresponds to `django.db.models.fields.DurationField`
|
Corresponds to `django.db.models.fields.DurationField`
|
||||||
|
|
||||||
The `validated_data` for these fields will contain a `datetime.timedelta` instance.
|
The `validated_data` for these fields will contain a `datetime.timedelta` instance.
|
||||||
The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu]'`.
|
|
||||||
|
|
||||||
**Signature:** `DurationField(max_value=None, min_value=None)`
|
**Signature:** `DurationField(format=api_settings.DURATION_FORMAT, max_value=None, min_value=None)`
|
||||||
|
|
||||||
|
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `DURATION_FORMAT` settings key, which will be `'django'` unless set. Formats are described below. Setting this value to `None` indicates that Python `timedelta` objects should be returned by `to_representation`. In this case the date encoding will be determined by the renderer.
|
||||||
* `max_value` Validate that the duration provided is no greater than this value.
|
* `max_value` Validate that the duration provided is no greater than this value.
|
||||||
* `min_value` Validate that the duration provided is no less than this value.
|
* `min_value` Validate that the duration provided is no less than this value.
|
||||||
|
|
||||||
|
#### `DurationField` formats
|
||||||
|
Format may either be the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style intervals should be used (eg `'P4DT1H15M20S'`), or `'django'` which indicates that Django interval format `'[DD] [HH:[MM:]]ss[.uuuuuu]'` should be used (eg: `'4 1:15:20'`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Choice selection fields
|
# Choice selection fields
|
||||||
|
@ -552,7 +555,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). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
|
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -857,4 +860,4 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
|
||||||
[django-hstore]: https://github.com/djangonauts/django-hstore
|
[django-hstore]: https://github.com/djangonauts/django-hstore
|
||||||
[python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes
|
[python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes
|
||||||
[django-current-timezone]: https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone
|
[django-current-timezone]: https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone
|
||||||
[django-docs-select-related]: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#django.db.models.query.QuerySet.select_related
|
[django-docs-select-related]: https://docs.djangoproject.com/en/stable/ref/models/querysets/#django.db.models.query.QuerySet.select_related
|
||||||
|
|
|
@ -235,7 +235,7 @@ For example:
|
||||||
|
|
||||||
search_fields = ['=username', '=email']
|
search_fields = ['=username', '=email']
|
||||||
|
|
||||||
By default, the search parameter is named `'search'`, but this may be overridden with the `SEARCH_PARAM` setting.
|
By default, the search parameter is named `'search'`, but this may be overridden with the `SEARCH_PARAM` setting in the `REST_FRAMEWORK` configuration.
|
||||||
|
|
||||||
To dynamically change search fields based on request content, it's possible to subclass the `SearchFilter` and override the `get_search_fields()` function. For example, the following subclass will only search on `title` if the query parameter `title_only` is in the request:
|
To dynamically change search fields based on request content, it's possible to subclass the `SearchFilter` and override the `get_search_fields()` function. For example, the following subclass will only search on `title` if the query parameter `title_only` is in the request:
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ The `OrderingFilter` class supports simple query parameter controlled ordering o
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
By default, the query parameter is named `'ordering'`, but this may be overridden with the `ORDERING_PARAM` setting.
|
By default, the query parameter is named `'ordering'`, but this may be overridden with the `ORDERING_PARAM` setting in the `REST_FRAMEWORK` configuration.
|
||||||
|
|
||||||
For example, to order users by username:
|
For example, to order users by username:
|
||||||
|
|
||||||
|
@ -367,6 +367,6 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
|
||||||
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
|
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
|
||||||
[django-url-filter]: https://github.com/miki725/django-url-filter
|
[django-url-filter]: https://github.com/miki725/django-url-filter
|
||||||
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
|
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
|
||||||
[HStoreField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#hstorefield
|
[HStoreField]: https://docs.djangoproject.com/en/stable/ref/contrib/postgres/fields/#hstorefield
|
||||||
[JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield
|
[JSONField]: https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.JSONField
|
||||||
[postgres-search]: https://docs.djangoproject.com/en/stable/ref/contrib/postgres/search/
|
[postgres-search]: https://docs.djangoproject.com/en/stable/ref/contrib/postgres/search/
|
||||||
|
|
|
@ -374,8 +374,6 @@ Allowing `PUT` as create operations is problematic, as it necessarily exposes in
|
||||||
|
|
||||||
Both styles "`PUT` as 404" and "`PUT` as create" can be valid in different circumstances, but from version 3.0 onwards we now use 404 behavior as the default, due to it being simpler and more obvious.
|
Both styles "`PUT` as 404" and "`PUT` as create" can be valid in different circumstances, but from version 3.0 onwards we now use 404 behavior as the default, due to it being simpler and more obvious.
|
||||||
|
|
||||||
If you need to generic PUT-as-create behavior you may want to include something like [this `AllowPUTAsCreateMixin` class](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b) as a mixin to your views.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Third party packages
|
# Third party packages
|
||||||
|
@ -395,4 +393,4 @@ The following third party packages provide additional generic view implementatio
|
||||||
[UpdateModelMixin]: #updatemodelmixin
|
[UpdateModelMixin]: #updatemodelmixin
|
||||||
[DestroyModelMixin]: #destroymodelmixin
|
[DestroyModelMixin]: #destroymodelmixin
|
||||||
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
||||||
[django-docs-select-related]: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#django.db.models.query.QuerySet.select_related
|
[django-docs-select-related]: https://docs.djangoproject.com/en/stable/ref/models/querysets/#django.db.models.query.QuerySet.select_related
|
||||||
|
|
|
@ -201,7 +201,7 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests and are using django-guardian for your object-level permissions backend, you'll want to consider using the `DjangoObjectPermissionsFilter` class provided by the [`djangorestframework-guardian2` package][django-rest-framework-guardian2]. It ensures that list endpoints only return results including objects for which the user has appropriate view permissions.
|
**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests and are using django-guardian for your object-level permissions backend, you'll want to consider using the `DjangoObjectPermissionsFilter` class provided by the [`djangorestframework-guardian` package][django-rest-framework-guardian]. It ensures that list endpoints only return results including objects for which the user has appropriate view permissions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -356,6 +356,6 @@ The [Django Rest Framework PSQ][drf-psq] package is an extension that gives supp
|
||||||
[rest-framework-roles]: https://github.com/Pithikos/rest-framework-roles
|
[rest-framework-roles]: https://github.com/Pithikos/rest-framework-roles
|
||||||
[djangorestframework-api-key]: https://florimondmanca.github.io/djangorestframework-api-key/
|
[djangorestframework-api-key]: https://florimondmanca.github.io/djangorestframework-api-key/
|
||||||
[django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters
|
[django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters
|
||||||
[django-rest-framework-guardian2]: https://github.com/johnthagen/django-rest-framework-guardian2
|
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
|
||||||
[drf-access-policy]: https://github.com/rsinger86/drf-access-policy
|
[drf-access-policy]: https://github.com/rsinger86/drf-access-policy
|
||||||
[drf-psq]: https://github.com/drf-psq/drf-psq
|
[drf-psq]: https://github.com/drf-psq/drf-psq
|
||||||
|
|
|
@ -628,12 +628,16 @@ 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-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
|
[cite]: http://users.ece.utexas.edu/~adnan/pike.html
|
||||||
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
|
[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
|
[routers]: https://www.django-rest-framework.org/api-guide/routers#defaultrouter
|
||||||
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
|
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
|
||||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||||
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations
|
[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
|
[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
|
[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
|
[to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data
|
||||||
|
|
|
@ -525,7 +525,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
|
||||||
|
|
||||||
## LaTeX
|
## LaTeX
|
||||||
|
|
||||||
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
|
[Rest Framework Latex] provides a renderer that outputs PDFs using Lualatex. It is maintained by [Pebble (S/F Software)][mypebble].
|
||||||
|
|
||||||
|
|
||||||
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process
|
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process
|
||||||
|
|
|
@ -350,6 +350,6 @@ The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions
|
||||||
[drf-extensions-nested-viewsets]: https://chibisov.github.io/drf-extensions/docs/#nested-routes
|
[drf-extensions-nested-viewsets]: https://chibisov.github.io/drf-extensions/docs/#nested-routes
|
||||||
[drf-extensions-collection-level-controllers]: https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers
|
[drf-extensions-collection-level-controllers]: https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers
|
||||||
[drf-extensions-customizable-endpoint-names]: https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name
|
[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
|
[url-namespace-docs]: https://docs.djangoproject.com/en/stable/topics/http/urls/#url-namespaces
|
||||||
[include-api-reference]: https://docs.djangoproject.com/en/4.0/ref/urls/#include
|
[include-api-reference]: https://docs.djangoproject.com/en/stable/ref/urls/#include
|
||||||
[path-converters-topic-reference]: https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters
|
[path-converters-topic-reference]: https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters
|
||||||
|
|
|
@ -392,7 +392,7 @@ introspection.
|
||||||
|
|
||||||
#### `get_operation_id()`
|
#### `get_operation_id()`
|
||||||
|
|
||||||
There must be a unique [operationid](openapi-operationid) for each operation.
|
There must be a unique [operationid][openapi-operationid] for each operation.
|
||||||
By default the `operationId` is deduced from the model name, serializer name or
|
By default the `operationId` is deduced from the model name, serializer name or
|
||||||
view name. The operationId looks like "listItems", "retrieveItem",
|
view name. The operationId looks like "listItems", "retrieveItem",
|
||||||
"updateItem", etc. The `operationId` is camelCase by convention.
|
"updateItem", etc. The `operationId` is camelCase by convention.
|
||||||
|
@ -451,14 +451,14 @@ If your views have related customizations that are needed frequently, you can
|
||||||
create a base `AutoSchema` subclass for your project that takes additional
|
create a base `AutoSchema` subclass for your project that takes additional
|
||||||
`__init__()` kwargs to save subclassing `AutoSchema` for each view.
|
`__init__()` kwargs to save subclassing `AutoSchema` for each view.
|
||||||
|
|
||||||
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
|
[cite]: https://www.heroku.com/blog/json_schema_for_heroku_platform_api/
|
||||||
[openapi]: https://github.com/OAI/OpenAPI-Specification
|
[openapi]: https://github.com/OAI/OpenAPI-Specification
|
||||||
[openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions
|
[openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#specification-extensions
|
||||||
[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#operationObject
|
||||||
[openapi-tags]: https://swagger.io/specification/#tagObject
|
[openapi-tags]: https://swagger.io/specification/#tagObject
|
||||||
[openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-17
|
[openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#fixed-fields-17
|
||||||
[openapi-components]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
|
[openapi-components]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#componentsObject
|
||||||
[openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject
|
[openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#referenceObject
|
||||||
[openapi-generator]: https://github.com/OpenAPITools/openapi-generator
|
[openapi-generator]: https://github.com/OpenAPITools/openapi-generator
|
||||||
[swagger-codegen]: https://github.com/swagger-api/swagger-codegen
|
[swagger-codegen]: https://github.com/swagger-api/swagger-codegen
|
||||||
[info-object]: https://swagger.io/specification/#infoObject
|
[info-object]: https://swagger.io/specification/#infoObject
|
||||||
|
|
|
@ -233,7 +233,7 @@ Serializer classes can also include reusable validators that are applied to the
|
||||||
|
|
||||||
class EventSerializer(serializers.Serializer):
|
class EventSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.CharField()
|
||||||
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
|
room_number = serializers.ChoiceField(choices=[101, 102, 103, 201])
|
||||||
date = serializers.DateField()
|
date = serializers.DateField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1189,6 +1189,10 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested
|
||||||
|
|
||||||
The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data.
|
The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data.
|
||||||
|
|
||||||
|
## Shapeless Serializers
|
||||||
|
|
||||||
|
The [drf-shapeless-serializers][drf-shapeless-serializers] package provides dynamic serializer configuration capabilities, allowing runtime field selection, renaming, attribute modification, and nested relationship configuration without creating multiple serializer classes. It helps eliminate serializer boilerplate while providing flexible API responses.
|
||||||
|
|
||||||
|
|
||||||
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
|
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
|
||||||
[relations]: relations.md
|
[relations]: relations.md
|
||||||
|
@ -1212,3 +1216,4 @@ The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your da
|
||||||
[djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/
|
[djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/
|
||||||
[drf-writable-nested]: https://github.com/beda-software/drf-writable-nested
|
[drf-writable-nested]: https://github.com/beda-software/drf-writable-nested
|
||||||
[drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content
|
[drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content
|
||||||
|
[drf-shapeless-serializers]: https://github.com/khaledsukkar2/drf-shapeless-serializers
|
||||||
|
|
|
@ -314,6 +314,15 @@ May be a list including the string `'iso-8601'` or Python [strftime format][strf
|
||||||
|
|
||||||
Default: `['iso-8601']`
|
Default: `['iso-8601']`
|
||||||
|
|
||||||
|
|
||||||
|
#### DURATION_FORMAT
|
||||||
|
|
||||||
|
Indicates the default format that should be used for rendering the output of `DurationField` serializer fields. If `None`, then `DurationField` serializer fields will return Python `timedelta` objects, and the duration encoding will be determined by the renderer.
|
||||||
|
|
||||||
|
May be any of `None`, `'iso-8601'` or `'django'` (the format accepted by `django.utils.dateparse.parse_duration`).
|
||||||
|
|
||||||
|
Default: `'django'`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Encodings
|
## Encodings
|
||||||
|
@ -460,4 +469,4 @@ Default: `None`
|
||||||
[cite]: https://www.python.org/dev/peps/pep-0020/
|
[cite]: https://www.python.org/dev/peps/pep-0020/
|
||||||
[rfc4627]: https://www.ietf.org/rfc/rfc4627.txt
|
[rfc4627]: https://www.ietf.org/rfc/rfc4627.txt
|
||||||
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
|
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
|
||||||
[strftime]: https://docs.python.org/3/library/time.html#time.strftime
|
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
|
||||||
|
|
|
@ -25,9 +25,12 @@ The `APIRequestFactory` class supports an almost identical API to Django's stand
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
request = factory.post('/notes/', {'title': 'new idea'})
|
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
|
#### 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 content type other than multipart form data. 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 wide set of request formats. When using this argument, the factory will select an appropriate renderer and its configured `content_type`. For example:
|
||||||
|
|
||||||
# Create a JSON POST request
|
# Create a JSON POST request
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
@ -41,7 +44,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:
|
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/', json.dumps({'title': 'new idea'}), content_type='application/json')
|
request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml')
|
||||||
|
|
||||||
#### PUT and PATCH with form data
|
#### PUT and PATCH with form data
|
||||||
|
|
||||||
|
@ -102,6 +105,20 @@ This means that setting attributes directly on the request object may not always
|
||||||
request.user = user
|
request.user = user
|
||||||
response = view(request)
|
response = view(request)
|
||||||
|
|
||||||
|
If you want to test a request involving the REST framework’s 'Request' object, you’ll need to manually transform it first:
|
||||||
|
|
||||||
|
class DummyView(APIView):
|
||||||
|
...
|
||||||
|
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/', {'demo': 'test'})
|
||||||
|
drf_request = DummyView().initialize_request(request)
|
||||||
|
assert drf_request.query_params == {'demo': ['test']}
|
||||||
|
|
||||||
|
request = factory.post('/', {'example': 'test'})
|
||||||
|
drf_request = DummyView().initialize_request(request)
|
||||||
|
assert drf_request.data.get('example') == 'test'
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Forcing CSRF validation
|
## Forcing CSRF validation
|
||||||
|
@ -414,5 +431,5 @@ For example, to add support for using `format='html'` in test requests, you migh
|
||||||
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
|
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
|
||||||
[configuration]: #configuration
|
[configuration]: #configuration
|
||||||
[refresh_from_db_docs]: https://docs.djangoproject.com/en/stable/ref/models/instances/#django.db.models.Model.refresh_from_db
|
[refresh_from_db_docs]: https://docs.djangoproject.com/en/stable/ref/models/instances/#django.db.models.Model.refresh_from_db
|
||||||
[session_objects]: https://requests.readthedocs.io/en/master/user/advanced/#session-objects
|
[session_objects]: https://requests.readthedocs.io/en/latest/user/advanced/#session-objects
|
||||||
[provided_test_case_classes]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes
|
[provided_test_case_classes]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes
|
||||||
|
|
|
@ -45,7 +45,7 @@ The default throttling policy may be set globally, using the `DEFAULT_THROTTLE_C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `minute`, `hour` or `day` as the throttle period.
|
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.
|
||||||
|
|
||||||
You can also set the throttling policy on a per-view or per-viewset basis,
|
You can also set the throttling policy on a per-view or per-viewset basis,
|
||||||
using the `APIView` class-based views.
|
using the `APIView` class-based views.
|
||||||
|
|
|
@ -13,7 +13,7 @@ Most of the time you're dealing with validation in REST framework you'll simply
|
||||||
|
|
||||||
However, sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes.
|
However, sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes.
|
||||||
|
|
||||||
## Validation in REST framework
|
## Validation in REST framework
|
||||||
|
|
||||||
Validation in Django REST framework serializers is handled a little differently to how validation works in Django's `ModelForm` class.
|
Validation in Django REST framework serializers is handled a little differently to how validation works in Django's `ModelForm` class.
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ If we open up the Django shell using `manage.py shell` we can now
|
||||||
CustomerReportSerializer():
|
CustomerReportSerializer():
|
||||||
id = IntegerField(label='ID', read_only=True)
|
id = IntegerField(label='ID', read_only=True)
|
||||||
time_raised = DateTimeField(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'})
|
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.
|
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.
|
||||||
|
@ -75,7 +75,7 @@ This validator should be applied to *serializer fields*, like so:
|
||||||
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
|
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
|
||||||
)
|
)
|
||||||
|
|
||||||
## UniqueTogetherValidator
|
## UniqueTogetherValidator
|
||||||
|
|
||||||
This validator can be used to enforce `unique_together` constraints on model instances.
|
This validator can be used to enforce `unique_together` constraints on model instances.
|
||||||
It has two required arguments, and a single optional `messages` argument:
|
It has two required arguments, and a single optional `messages` argument:
|
||||||
|
@ -92,7 +92,7 @@ The validator should be applied to *serializer classes*, like so:
|
||||||
# ...
|
# ...
|
||||||
class Meta:
|
class Meta:
|
||||||
# ToDo items belong to a parent list, and have an ordering defined
|
# ToDo items belong to a parent list, and have an ordering defined
|
||||||
# by the 'position' field. No two items in a given list may share
|
# by the 'position' field. No two items in a given list may share
|
||||||
# the same position.
|
# the same position.
|
||||||
validators = [
|
validators = [
|
||||||
UniqueTogetherValidator(
|
UniqueTogetherValidator(
|
||||||
|
@ -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). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259).
|
**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -186,8 +186,13 @@ The available decorators are:
|
||||||
* `@authentication_classes(...)`
|
* `@authentication_classes(...)`
|
||||||
* `@throttle_classes(...)`
|
* `@throttle_classes(...)`
|
||||||
* `@permission_classes(...)`
|
* `@permission_classes(...)`
|
||||||
|
* `@content_negotiation_class(...)`
|
||||||
|
* `@metadata_class(...)`
|
||||||
|
* `@versioning_class(...)`
|
||||||
|
|
||||||
Each of these decorators takes a single argument which must be a list or tuple of classes.
|
Each of these decorators is equivalent to setting their respective [api policy attributes][api-policy-attributes].
|
||||||
|
|
||||||
|
All decorators take a single argument. The ones that end with `_class` expect a single class while the ones ending in `_classes` expect a list or tuple of classes.
|
||||||
|
|
||||||
|
|
||||||
## View schema decorator
|
## View schema decorator
|
||||||
|
@ -224,4 +229,5 @@ You may pass `None` in order to exclude the view from schema generation.
|
||||||
[throttling]: throttling.md
|
[throttling]: throttling.md
|
||||||
[schemas]: schemas.md
|
[schemas]: schemas.md
|
||||||
[classy-drf]: http://www.cdrf.co
|
[classy-drf]: http://www.cdrf.co
|
||||||
|
[api-policy-attributes]: views.md#api-policy-attributes
|
||||||
|
|
||||||
|
|
|
@ -128,6 +128,8 @@ You may inspect these attributes to adjust behavior based on the current action.
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
return [permission() for permission in permission_classes]
|
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
|
## 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.
|
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.
|
||||||
|
|
|
@ -961,5 +961,5 @@ You can follow development on the GitHub site, where we use [milestones to indic
|
||||||
|
|
||||||
[kickstarter]: https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3
|
[kickstarter]: https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3
|
||||||
[sponsors]: https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors
|
[sponsors]: https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors
|
||||||
[mixins.py]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py
|
[mixins.py]: https://github.com/encode/django-rest-framework/blob/main/rest_framework/mixins.py
|
||||||
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files
|
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files
|
||||||
|
|
|
@ -46,7 +46,7 @@ The cursor based pagination renders a more simple style of control:
|
||||||
|
|
||||||
The pagination API was previously only able to alter the pagination style in the body of the response. The API now supports being able to write pagination information in response headers, making it possible to use pagination schemes that use the `Link` or `Content-Range` headers.
|
The pagination API was previously only able to alter the pagination style in the body of the response. The API now supports being able to write pagination information in response headers, making it possible to use pagination schemes that use the `Link` or `Content-Range` headers.
|
||||||
|
|
||||||
For more information, see the [custom pagination styles](../api-guide/pagination/#custom-pagination-styles) documentation.
|
For more information, see the [custom pagination styles](../api-guide/pagination.md#custom-pagination-styles) documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
42
docs/community/3.16-announcement.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<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.
|
|
@ -54,7 +54,7 @@ The `ModelSerializer` and `HyperlinkedModelSerializer` classes should now includ
|
||||||
|
|
||||||
[forms-api]: ../topics/html-and-forms.md
|
[forms-api]: ../topics/html-and-forms.md
|
||||||
[ajax-form]: https://github.com/encode/ajax-form
|
[ajax-form]: https://github.com/encode/ajax-form
|
||||||
[jsonfield]: ../api-guide/fields#jsonfield
|
[jsonfield]: ../api-guide/fields.md#jsonfield
|
||||||
[accept-headers]: ../topics/browser-enhancements.md#url-based-accept-headers
|
[accept-headers]: ../topics/browser-enhancements.md#url-based-accept-headers
|
||||||
[method-override]: ../topics/browser-enhancements.md#http-header-based-method-overriding
|
[method-override]: ../topics/browser-enhancements.md#http-header-based-method-overriding
|
||||||
[django-supported-versions]: https://www.djangoproject.com/download/#supported-versions
|
[django-supported-versions]: https://www.djangoproject.com/download/#supported-versions
|
||||||
|
|
|
@ -179,16 +179,16 @@ The full set of itemized release notes [are available here][release-notes].
|
||||||
[moss]: mozilla-grant.md
|
[moss]: mozilla-grant.md
|
||||||
[funding]: funding.md
|
[funding]: funding.md
|
||||||
[core-api]: https://www.coreapi.org/
|
[core-api]: https://www.coreapi.org/
|
||||||
[command-line-client]: api-clients#command-line-client
|
[command-line-client]: https://github.com/encode/django-rest-framework/blob/3.4.7/docs/topics/api-clients.md#command-line-client
|
||||||
[client-library]: api-clients#python-client-library
|
[client-library]: https://github.com/encode/django-rest-framework/blob/3.4.7/docs/topics/api-clients.md#python-client-library
|
||||||
[core-json]: https://www.coreapi.org/specification/encoding/#core-json-encoding
|
[core-json]: https://www.coreapi.org/specification/encoding/#core-json-encoding
|
||||||
[swagger]: https://openapis.org/specification
|
[swagger]: https://openapis.org/specification
|
||||||
[hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
|
[hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
|
||||||
[api-blueprint]: https://apiblueprint.org/
|
[api-blueprint]: https://apiblueprint.org/
|
||||||
[tut-7]: ../tutorial/7-schemas-and-client-libraries/
|
[tut-7]: https://github.com/encode/django-rest-framework/blob/3.4.7/docs/tutorial/7-schemas-and-client-libraries.md
|
||||||
[schema-generation]: ../api-guide/schemas/
|
[schema-generation]: ../api-guide/schemas.md
|
||||||
[api-clients]: https://github.com/encode/django-rest-framework/blob/3.14.0/docs/topics/api-clients.md
|
[api-clients]: https://github.com/encode/django-rest-framework/blob/3.14.0/docs/topics/api-clients.md
|
||||||
[milestone]: https://github.com/encode/django-rest-framework/milestone/35
|
[milestone]: https://github.com/encode/django-rest-framework/milestone/35
|
||||||
[release-notes]: release-notes#34
|
[release-notes]: ./release-notes.md#34x-series
|
||||||
[metadata]: ../api-guide/metadata/#custom-metadata-classes
|
[metadata]: ../api-guide/metadata.md#custom-metadata-classes
|
||||||
[gh3751]: https://github.com/encode/django-rest-framework/issues/3751
|
[gh3751]: https://github.com/encode/django-rest-framework/issues/3751
|
||||||
|
|
|
@ -254,9 +254,9 @@ in version 3.3 and raised a deprecation warning in 3.4. Its usage is now mandato
|
||||||
[funding]: funding.md
|
[funding]: funding.md
|
||||||
[uploads]: https://core-api.github.io/python-client/api-guide/utils/#file
|
[uploads]: https://core-api.github.io/python-client/api-guide/utils/#file
|
||||||
[downloads]: https://core-api.github.io/python-client/api-guide/codecs/#downloadcodec
|
[downloads]: https://core-api.github.io/python-client/api-guide/codecs/#downloadcodec
|
||||||
[schema-generation-api]: ../api-guide/schemas/#schemagenerator
|
[schema-generation-api]: ../api-guide/schemas.md#schemagenerator
|
||||||
[schema-docs]: ../api-guide/schemas/#schemas-as-documentation
|
[schema-docs]: ../api-guide/schemas.md#schemas-as-documentation
|
||||||
[schema-view]: ../api-guide/schemas/#the-get_schema_view-shortcut
|
[schema-view]: ../api-guide/schemas.md#get_schema_view
|
||||||
[django-rest-raml]: https://github.com/encode/django-rest-raml
|
[django-rest-raml]: https://github.com/encode/django-rest-raml
|
||||||
[raml-image]: ../img/raml.png
|
[raml-image]: ../img/raml.png
|
||||||
[raml-codec]: https://github.com/core-api/python-raml-codec
|
[raml-codec]: https://github.com/core-api/python-raml-codec
|
||||||
|
|
|
@ -30,9 +30,7 @@ The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines f
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
|
|
||||||
Our contribution process is that the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion.
|
Our contribution process is that the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Some tips on good potential issue reporting:
|
||||||
|
|
||||||
Some tips on good potential issue reporting:
|
|
||||||
|
|
||||||
* 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.
|
* 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.
|
||||||
* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue.
|
* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue.
|
||||||
|
@ -83,12 +81,45 @@ To run the tests, clone the repository, and then:
|
||||||
# Run the tests
|
# Run the tests
|
||||||
./runtests.py
|
./runtests.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note:** if your tests require access to the database, do not forget to inherit from `django.test.TestCase` or use the `@pytest.mark.django_db()` decorator.
|
||||||
|
|
||||||
|
For example, with TestCase:
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class MyDatabaseTest(TestCase):
|
||||||
|
def test_something(self):
|
||||||
|
# Your test code here
|
||||||
|
pass
|
||||||
|
|
||||||
|
Or with decorator:
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.django_db()
|
||||||
|
class MyDatabaseTest:
|
||||||
|
def test_something(self):
|
||||||
|
# Your test code here
|
||||||
|
pass
|
||||||
|
|
||||||
|
You can reuse existing models defined in `tests/models.py` for your tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Test options
|
### Test options
|
||||||
|
|
||||||
Run using a more concise output style.
|
Run using a more concise output style.
|
||||||
|
|
||||||
./runtests.py -q
|
./runtests.py -q
|
||||||
|
|
||||||
|
|
||||||
|
If you do not want the output to be captured (for example, to see print statements directly), you can use the `-s` flag.
|
||||||
|
|
||||||
|
./runtests.py -s
|
||||||
|
|
||||||
|
|
||||||
Run the tests for a given test case.
|
Run the tests for a given test case.
|
||||||
|
|
||||||
./runtests.py MyTestCase
|
./runtests.py MyTestCase
|
||||||
|
@ -101,6 +132,7 @@ Shorter form to run the tests for a given test method.
|
||||||
|
|
||||||
./runtests.py test_this_method
|
./runtests.py test_this_method
|
||||||
|
|
||||||
|
|
||||||
Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
|
Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
|
||||||
|
|
||||||
### Running against multiple environments
|
### Running against multiple environments
|
||||||
|
@ -206,13 +238,12 @@ 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/
|
[code-of-conduct]: https://www.djangoproject.com/conduct/
|
||||||
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
|
||||||
[so-filter]: https://stackexchange.com/filters/66475/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/
|
[pep-8]: https://www.python.org/dev/peps/pep-0008/
|
||||||
[build-status]: ../img/build-status.png
|
[build-status]: ../img/build-status.png
|
||||||
[pull-requests]: https://help.github.com/articles/using-pull-requests
|
[pull-requests]: https://help.github.com/articles/using-pull-requests
|
||||||
[tox]: https://tox.readthedocs.io/en/latest/
|
[tox]: https://tox.readthedocs.io/en/latest/
|
||||||
[markdown]: https://daringfireball.net/projects/markdown/basics
|
[markdown]: https://daringfireball.net/projects/markdown/basics
|
||||||
[docs]: https://github.com/encode/django-rest-framework/tree/master/docs
|
[docs]: https://github.com/encode/django-rest-framework/tree/main/docs
|
||||||
[mou]: http://mouapp.com/
|
[mou]: http://mouapp.com/
|
||||||
[repo]: https://github.com/encode/django-rest-framework
|
[repo]: https://github.com/encode/django-rest-framework
|
||||||
[how-to-fork]: https://help.github.com/articles/fork-a-repo/
|
[how-to-fork]: https://help.github.com/articles/fork-a-repo/
|
||||||
|
|
|
@ -114,7 +114,7 @@ If you use REST framework commercially we strongly encourage you to invest in it
|
||||||
Signing up for a paid plan will:
|
Signing up for a paid plan will:
|
||||||
|
|
||||||
* Directly contribute to faster releases, more features, and higher quality software.
|
* Directly contribute to faster releases, more features, and higher quality software.
|
||||||
* Allow more time to be invested in documentation, issue triage, and community support.
|
* Allow more time to be invested in keeping the package up to date.
|
||||||
* Safeguard the future development of REST framework.
|
* 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.
|
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.
|
||||||
|
@ -134,18 +134,6 @@ REST framework continues to be open-source and permissively licensed, but we fir
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 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
|
## 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.
|
> 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.
|
||||||
|
@ -165,6 +153,8 @@ DRF is one of the core reasons why Django is top choice among web frameworks tod
|
||||||
>
|
>
|
||||||
> — Andrew Conti, Django REST framework user
|
> — 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
|
## Individual plan
|
||||||
|
|
|
@ -31,7 +31,7 @@ Team members have the following responsibilities.
|
||||||
|
|
||||||
Further notes for maintainers:
|
Further notes for maintainers:
|
||||||
|
|
||||||
* Code changes should come in the form of a pull request - do not push directly to master.
|
* Code changes should come in the form of a pull request - do not push directly to main.
|
||||||
* Maintainers should typically not merge their own pull requests.
|
* Maintainers should typically not merge their own pull requests.
|
||||||
* Each issue/pull request should have exactly one label once triaged.
|
* Each issue/pull request should have exactly one label once triaged.
|
||||||
* Search for un-triaged issues with [is:open no:label][un-triaged].
|
* Search for un-triaged issues with [is:open no:label][un-triaged].
|
||||||
|
@ -53,12 +53,13 @@ The following template should be used for the description of the issue, and serv
|
||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
- [ ] Create pull request for [release notes](https://github.com/encode/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/encode/django-rest-framework/milestones/***).
|
- [ ] Create pull request for [release notes](https://github.com/encode/django-rest-framework/blob/mains/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/encode/django-rest-framework/milestones/***).
|
||||||
- [ ] Update supported versions:
|
- [ ] Update supported versions:
|
||||||
- [ ] `setup.py` `python_requires` list
|
- [ ] `pyproject.toml` `python_requires` list
|
||||||
- [ ] `setup.py` Python & Django version trove classifiers
|
- [ ] `pyproject.toml` Python & Django version trove classifiers
|
||||||
- [ ] `README` Python & Django versions
|
- [ ] `README` Python & Django versions
|
||||||
- [ ] `docs` Python & Django versions
|
- [ ] `docs` Python & Django versions
|
||||||
|
|
||||||
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py).
|
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py).
|
||||||
- [ ] Ensure documentation validates
|
- [ ] Ensure documentation validates
|
||||||
- Build and serve docs `mkdocs serve`
|
- Build and serve docs `mkdocs serve`
|
||||||
|
@ -66,7 +67,9 @@ The following template should be used for the description of the issue, and serv
|
||||||
- [ ] Confirm with @tomchristie that release is finalized and ready to go.
|
- [ ] Confirm with @tomchristie that release is finalized and ready to go.
|
||||||
- [ ] Ensure that release date is included in pull request.
|
- [ ] Ensure that release date is included in pull request.
|
||||||
- [ ] Merge the release pull request.
|
- [ ] Merge the release pull request.
|
||||||
- [ ] Push the package to PyPI with `./setup.py publish`.
|
- [ ] Install the release tools: `pip install build twine`
|
||||||
|
- [ ] Build the package: `python -m build`
|
||||||
|
- [ ] Push the package to PyPI with `twine upload dist/*`
|
||||||
- [ ] Tag the release, with `git tag -a *.*.* -m 'version *.*.*'; git push --tags`.
|
- [ ] Tag the release, with `git tag -a *.*.* -m 'version *.*.*'; git push --tags`.
|
||||||
- [ ] Deploy the documentation with `mkdocs gh-deploy`.
|
- [ ] Deploy the documentation with `mkdocs gh-deploy`.
|
||||||
- [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
|
- [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
|
||||||
|
|
|
@ -36,6 +36,153 @@ You can determine your currently installed version using `pip show`:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 3.16.x series
|
||||||
|
|
||||||
|
### 3.16.1
|
||||||
|
|
||||||
|
**Date**: 6th August 2025
|
||||||
|
|
||||||
|
This release fixes a few bugs, clean-up some old code paths for unsupported Python versions and improve translations.
|
||||||
|
|
||||||
|
#### Minor changes
|
||||||
|
|
||||||
|
* Cleanup optional `backports.zoneinfo` dependency and conditions on unsupported Python 3.8 and lower in [#9681](https://github.com/encode/django-rest-framework/pull/9681). Python versions prior to 3.9 were already unsupported so this shouldn't be a breaking change.
|
||||||
|
|
||||||
|
#### Bug fixes
|
||||||
|
|
||||||
|
* Fix regression in `unique_together` validation with `SerializerMethodField` in [#9712](https://github.com/encode/django-rest-framework/pull/9712)
|
||||||
|
* Fix `UniqueTogetherValidator` to handle fields with `source` attribute in [#9688](https://github.com/encode/django-rest-framework/pull/9688)
|
||||||
|
* Drop HTML line breaks on long headers in browsable API in [#9438](https://github.com/encode/django-rest-framework/pull/9438)
|
||||||
|
|
||||||
|
#### Translations
|
||||||
|
|
||||||
|
* Add Kazakh locale support in [#9713](https://github.com/encode/django-rest-framework/pull/9713)
|
||||||
|
* Update translations for Korean translations in [#9571](https://github.com/encode/django-rest-framework/pull/9571)
|
||||||
|
* Update German translations in [#9676](https://github.com/encode/django-rest-framework/pull/9676)
|
||||||
|
* Update Chinese translations in [#9675](https://github.com/encode/django-rest-framework/pull/9675)
|
||||||
|
* Update Arabic translations-sal in [#9595](https://github.com/encode/django-rest-framework/pull/9595)
|
||||||
|
* Update Persian translations in [#9576](https://github.com/encode/django-rest-framework/pull/9576)
|
||||||
|
* Update Spanish translations in [#9701](https://github.com/encode/django-rest-framework/pull/9701)
|
||||||
|
* Update Turkish Translations in [#9749](https://github.com/encode/django-rest-framework/pull/9749)
|
||||||
|
* Fix some typos in Brazilian Portuguese translations in [#9673](https://github.com/encode/django-rest-framework/pull/9673)
|
||||||
|
|
||||||
|
#### Documentation
|
||||||
|
|
||||||
|
* Removed reference to GitHub Issues and Discussions in [#9660](https://github.com/encode/django-rest-framework/pull/9660)
|
||||||
|
* Add `drf-restwind` and update outdated images in `browsable-api.md` in [#9680](https://github.com/encode/django-rest-framework/pull/9680)
|
||||||
|
* Updated funding page to represent current scope in [#9686](https://github.com/encode/django-rest-framework/pull/9686)
|
||||||
|
* Fix broken Heroku JSON Schema link in [#9693](https://github.com/encode/django-rest-framework/pull/9693)
|
||||||
|
* Update Django documentation links to use stable version in [#9698](https://github.com/encode/django-rest-framework/pull/9698)
|
||||||
|
* Expand docs on unique constraints cause 'required=True' in [#9725](https://github.com/encode/django-rest-framework/pull/9725)
|
||||||
|
* Revert extension back from `djangorestframework-guardian2` to `djangorestframework-guardian` in [#9734](https://github.com/encode/django-rest-framework/pull/9734)
|
||||||
|
* Add note to tutorial about required `request` in serializer context when using `HyperlinkedModelSerializer` in [#9732](https://github.com/encode/django-rest-framework/pull/9732)
|
||||||
|
|
||||||
|
#### Internal changes
|
||||||
|
|
||||||
|
* Update GitHub Actions to use Ubuntu 24.04 for testing in [#9677](https://github.com/encode/django-rest-framework/pull/9677)
|
||||||
|
* Update test matrix to use Django 5.2 stable version in [#9679](https://github.com/encode/django-rest-framework/pull/9679)
|
||||||
|
* Add `pyupgrade` to `pre-commit` hooks in [#9682](https://github.com/encode/django-rest-framework/pull/9682)
|
||||||
|
* Fix test with Django 5 when `pytz` is available in [#9715](https://github.com/encode/django-rest-framework/pull/9715)
|
||||||
|
|
||||||
|
#### New Contributors
|
||||||
|
|
||||||
|
* [`@araggohnxd`](https://github.com/araggohnxd) made their first contribution in [#9673](https://github.com/encode/django-rest-framework/pull/9673)
|
||||||
|
* [`@mbeijen`](https://github.com/mbeijen) made their first contribution in [#9660](https://github.com/encode/django-rest-framework/pull/9660)
|
||||||
|
* [`@stefan6419846`](https://github.com/stefan6419846) made their first contribution in [#9676](https://github.com/encode/django-rest-framework/pull/9676)
|
||||||
|
* [`@ren000thomas`](https://github.com/ren000thomas) made their first contribution in [#9675](https://github.com/encode/django-rest-framework/pull/9675)
|
||||||
|
* [`@ulgens`](https://github.com/ulgens) made their first contribution in [#9682](https://github.com/encode/django-rest-framework/pull/9682)
|
||||||
|
* [`@bukh-sal`](https://github.com/bukh-sal) made their first contribution in [#9595](https://github.com/encode/django-rest-framework/pull/9595)
|
||||||
|
* [`@rezatn0934`](https://github.com/rezatn0934) made their first contribution in [#9576](https://github.com/encode/django-rest-framework/pull/9576)
|
||||||
|
* [`@Rohit10jr`](https://github.com/Rohit10jr) made their first contribution in [#9693](https://github.com/encode/django-rest-framework/pull/9693)
|
||||||
|
* [`@kushibayev`](https://github.com/kushibayev) made their first contribution in [#9713](https://github.com/encode/django-rest-framework/pull/9713)
|
||||||
|
* [`@alihassancods`](https://github.com/alihassancods) made their first contribution in [#9732](https://github.com/encode/django-rest-framework/pull/9732)
|
||||||
|
* [`@kulikjak`](https://github.com/kulikjak) made their first contribution in [#9715](https://github.com/encode/django-rest-framework/pull/9715)
|
||||||
|
* [`@Natgho`](https://github.com/Natgho) made their first contribution in [#9749](https://github.com/encode/django-rest-framework/pull/9749)
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/encode/django-rest-framework/compare/3.16.0...3.16.1
|
||||||
|
|
||||||
|
### 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.x series
|
||||||
|
|
||||||
### 3.15.2
|
### 3.15.2
|
||||||
|
@ -121,7 +268,7 @@ Date: 15th March 2024
|
||||||
* Fix 404 when page query parameter is empty string [[#8578](https://github.com/encode/django-rest-framework/pull/8578)]
|
* 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)]
|
* 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)]
|
* 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 SimpleMetada label_lookup [[#8702](https://github.com/encode/django-rest-framework/pull/8702)]
|
* Add missing DurationField to SimpleMetadata 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)]
|
* 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)]
|
* 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)]
|
* Possibility to remove trailing zeros on DecimalFields representation [[#6514](https://github.com/encode/django-rest-framework/pull/6514)]
|
||||||
|
@ -428,7 +575,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
||||||
* Allow hashing of ErrorDetail. [#5932][gh5932]
|
* Allow hashing of ErrorDetail. [#5932][gh5932]
|
||||||
* Correct schema parsing for JSONField [#5878][gh5878]
|
* Correct schema parsing for JSONField [#5878][gh5878]
|
||||||
* Render descriptions (from help_text) using safe [#5869][gh5869]
|
* Render descriptions (from help_text) using safe [#5869][gh5869]
|
||||||
* Removed input value from deault_error_message [#5881][gh5881]
|
* Removed input value from default_error_message [#5881][gh5881]
|
||||||
* Added min_value/max_value support in DurationField [#5643][gh5643]
|
* Added min_value/max_value support in DurationField [#5643][gh5643]
|
||||||
* Fixed instance being overwritten in pk-only optimization try/except block [#5747][gh5747]
|
* Fixed instance being overwritten in pk-only optimization try/except block [#5747][gh5747]
|
||||||
* Fixed AttributeError from items filter when value is None [#5981][gh5981]
|
* Fixed AttributeError from items filter when value is None [#5981][gh5981]
|
||||||
|
|
|
@ -32,7 +32,7 @@ We suggest adding your package to the [REST Framework][rest-framework-grid] grid
|
||||||
|
|
||||||
#### Adding to the Django REST framework docs
|
#### Adding to the Django REST framework docs
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
#### Announce on the discussion group.
|
#### Announce on the discussion group.
|
||||||
|
|
||||||
|
@ -44,7 +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].
|
Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages][rest-framework-grid].
|
||||||
|
|
||||||
To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].
|
To submit new content, [create a pull request][drf-create-pr].
|
||||||
|
|
||||||
## Async Support
|
## Async Support
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
|
* [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.
|
* [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.
|
* [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
|
### Permissions
|
||||||
|
|
||||||
|
@ -87,6 +88,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models.
|
* [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models.
|
||||||
* [django-restql][django-restql] - Turn your REST API into a GraphQL like API(It allows clients to control which fields will be sent in a response, uses GraphQL like syntax, supports read and write on both flat and nested fields).
|
* [django-restql][django-restql] - Turn your REST API into a GraphQL like API(It allows clients to control which fields will be sent in a response, uses GraphQL like syntax, supports read and write on both flat and nested fields).
|
||||||
* [graphwrap][graphwrap] - Transform your REST API into a fully compliant GraphQL API with just two lines of code. Leverages [Graphene-Django](https://docs.graphene-python.org/projects/django/en/latest/) to dynamically build, at runtime, a GraphQL ObjectType for each view in your API.
|
* [graphwrap][graphwrap] - Transform your REST API into a fully compliant GraphQL API with just two lines of code. Leverages [Graphene-Django](https://docs.graphene-python.org/projects/django/en/latest/) to dynamically build, at runtime, a GraphQL ObjectType for each view in your API.
|
||||||
|
* [drf-shapeless-serializers][drf-shapeless-serializers] - Dynamically assemble, configure, and shape your Django Rest Framework serializers at runtime, much like connecting Lego bricks.
|
||||||
|
|
||||||
### Serializer fields
|
### Serializer fields
|
||||||
|
|
||||||
|
@ -125,7 +127,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
* [djangorestframework-chain][djangorestframework-chain] - Allows arbitrary chaining of both relations and lookup filters.
|
* [djangorestframework-chain][djangorestframework-chain] - Allows arbitrary chaining of both relations and lookup filters.
|
||||||
* [django-url-filter][django-url-filter] - Allows a safe way to filter data via human-friendly URLs. It is a generic library which is not tied to DRF but it provides easy integration with DRF.
|
* [django-url-filter][django-url-filter] - Allows a safe way to filter data via human-friendly URLs. It is a generic library which is not tied to DRF but it provides easy integration with DRF.
|
||||||
* [drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values.
|
* [drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values.
|
||||||
* [django-rest-framework-guardian2][django-rest-framework-guardian2] - Provides integration with django-guardian, including the `DjangoObjectPermissionsFilter` previously found in DRF.
|
* [django-rest-framework-guardian][django-rest-framework-guardian] - Provides integration with django-guardian, including the `DjangoObjectPermissionsFilter` previously found in DRF.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
|
@ -159,6 +161,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
|
|
||||||
### Customization
|
### 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-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-material][drf-material] - A project that gives a sleek and elegant look to the browsable API using Material Design.
|
||||||
|
|
||||||
|
@ -170,13 +173,12 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[pypi-register]: https://pypi.org/account/register/
|
[pypi-register]: https://pypi.org/account/register/
|
||||||
[semver]: https://semver.org/
|
[semver]: https://semver.org/
|
||||||
[tox-docs]: https://tox.readthedocs.io/en/latest/
|
[tox-docs]: https://tox.readthedocs.io/en/latest/
|
||||||
[drf-compat]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/compat.py
|
[drf-compat]: https://github.com/encode/django-rest-framework/blob/main/rest_framework/compat.py
|
||||||
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
|
[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-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
|
[authentication]: ../api-guide/authentication.md
|
||||||
[permissions]: ../api-guide/permissions.md
|
[permissions]: ../api-guide/permissions.md
|
||||||
[third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages
|
[third-party-packages]: #existing-third-party-packages
|
||||||
[discussion-group]: https://groups.google.com/forum/#!forum/django-rest-framework
|
[discussion-group]: https://groups.google.com/forum/#!forum/django-rest-framework
|
||||||
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
|
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
|
||||||
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
|
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
|
||||||
|
@ -241,7 +243,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[djangorestframework-dataclasses]: https://github.com/oxan/djangorestframework-dataclasses
|
[djangorestframework-dataclasses]: https://github.com/oxan/djangorestframework-dataclasses
|
||||||
[django-restql]: https://github.com/yezyilomo/django-restql
|
[django-restql]: https://github.com/yezyilomo/django-restql
|
||||||
[djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt
|
[djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt
|
||||||
[django-rest-framework-guardian2]: https://github.com/johnthagen/django-rest-framework-guardian2
|
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
|
||||||
[drf-viewset-profiler]: https://github.com/fvlima/drf-viewset-profiler
|
[drf-viewset-profiler]: https://github.com/fvlima/drf-viewset-profiler
|
||||||
[djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/
|
[djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/
|
||||||
[django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf
|
[django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf
|
||||||
|
@ -254,5 +256,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
|
[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs
|
||||||
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors
|
||||||
[drf-api-action]: https://github.com/Ori-Roza/drf-api-action
|
[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-redesign]: https://github.com/youzarsiph/drf-redesign
|
||||||
[drf-material]: https://github.com/youzarsiph/drf-material
|
[drf-material]: https://github.com/youzarsiph/drf-material
|
||||||
|
[django-pyoidc]: https://github.com/makinacorpus/django_pyoidc
|
||||||
|
[drf-shapeless-serializers]: https://github.com/khaledsukkar2/drf-shapeless-serializers
|
||||||
|
|
|
@ -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"/>
|
<img src="../../img/books/tsd-cover.png"/>
|
||||||
</a>
|
</a>
|
||||||
<a class="book-cover" href="https://djangoforapis.com">
|
<a class="book-cover" href="https://djangoforapis.com">
|
||||||
<img src="../../img/books/dfa-cover.jpg"/>
|
<img src="../../img/books/dfa-40-cover.jpg"/>
|
||||||
</a>
|
</a>
|
||||||
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
|
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
|
||||||
<img src="../../img/books/bda-cover.png"/>
|
<img src="../../img/books/bda-cover.png"/>
|
||||||
|
@ -28,7 +28,6 @@ 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]
|
* [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 - An Introduction][drf-an-intro]
|
||||||
* [Django REST Framework Tutorial][drf-tutorial]
|
* [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]
|
* [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]
|
* [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]
|
* [End to End Web App with Django REST Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs]
|
||||||
|
@ -41,8 +40,8 @@ There are a wide range of resources available for learning and using Django REST
|
||||||
* [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 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 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]
|
* [Creating a Production Ready API with Python and Django REST Framework – Part 4][creating-a-production-ready-api-with-python-and-drf-part4]
|
||||||
* [Django REST Framework Tutorial - Build a Blog API][django-rest-framework-tutorial-build-a-blog]
|
* [Django Polls Tutorial API][django-polls-api]
|
||||||
* [Django REST Framework & React Tutorial - Build a Todo List API][django-rest-framework-react-tutorial-build-a-todo-list]
|
* [Django REST Framework Tutorial: Todo API][django-rest-framework-todo-api]
|
||||||
* [Tutorial: Django REST with React (Django 2.0)][django-rest-react-valentinog]
|
* [Tutorial: Django REST with React (Django 2.0)][django-rest-react-valentinog]
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,11 +50,11 @@ There are a wide range of resources available for learning and using Django REST
|
||||||
### Talks
|
### Talks
|
||||||
|
|
||||||
* [Level Up! Rethinking the Web API Framework][pycon-us-2017]
|
* [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-tookit]
|
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-toolkit]
|
||||||
* [Django REST API - So Easy You Can Learn It in 25 Minutes][django-rest-api-so-easy]
|
* [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]
|
* [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]
|
* [Django REST Framework: Schemas, Hypermedia & Client Libraries][pycon-uk-2016]
|
||||||
|
* [Finally Understand Authentication in Django REST Framework][django-con-2018]
|
||||||
|
|
||||||
### Tutorials
|
### Tutorials
|
||||||
|
|
||||||
|
@ -105,7 +104,6 @@ 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/
|
[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
|
[cdrf.co]:http://www.cdrf.co
|
||||||
[medium-django-rest-framework]: https://medium.com/django-rest-framework
|
[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
|
[pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg
|
||||||
[django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E
|
[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/
|
[integrating-pandas-drf-and-bokeh]: https://web.archive.org/web/20180104205117/http://machinalis.com/blog/pandas-django-rest-framework-bokeh/
|
||||||
|
@ -121,10 +119,10 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
|
||||||
[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-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-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/
|
[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-rest-framework-tutorial-build-a-blog]: https://wsvincent.com/django-rest-framework-tutorial/
|
[django-polls-api]: https://learndjango.com/tutorials/django-polls-tutorial-api
|
||||||
[django-rest-framework-react-tutorial-build-a-todo-list]: https://wsvincent.com/django-rest-framework-react-tutorial/
|
[django-rest-framework-todo-api]: https://learndjango.com/tutorials/django-rest-framework-tutorial-todo-api
|
||||||
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
|
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
|
||||||
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
|
[full-fledged-rest-api-with-django-oauth-toolkit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
|
||||||
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww
|
[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
|
[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
|
[drf-tutorials]: https://www.youtube.com/watch?v=axRCBgbOJp8&list=PLJtp8Jm8EDzjgVg9vVyIUMoGyqtegj7FH
|
||||||
|
@ -139,3 +137,4 @@ 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/
|
[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/
|
[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/
|
[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
|
BIN
docs/img/books/dfa-40-cover.jpg
Normal file
After Width: | Height: | Size: 755 KiB |
BIN
docs/img/drf-m-api-root.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
docs/img/drf-m-detail-view.png
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
docs/img/drf-m-list-view.png
Normal file
After Width: | Height: | Size: 123 KiB |
BIN
docs/img/drf-r-api-root.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
docs/img/drf-r-detail-view.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
docs/img/drf-r-list-view.png
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
docs/img/drf-rw-api-root.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
docs/img/drf-rw-detail-view.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
docs/img/drf-rw-list-view.png
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
docs/img/rfm.png
Before Width: | Height: | Size: 134 KiB |
BIN
docs/img/rfr.png
Before Width: | Height: | Size: 142 KiB |
|
@ -87,8 +87,8 @@ continued development by **[signing up for a paid plan][funding]**.
|
||||||
|
|
||||||
REST framework requires the following:
|
REST framework requires the following:
|
||||||
|
|
||||||
* Django (4.2, 5.0, 5.1)
|
* Django (4.2, 5.0, 5.1, 5.2)
|
||||||
* Python (3.8, 3.9, 3.10, 3.11, 3.12, 3.13)
|
* Python (3.10, 3.11, 3.12, 3.13, 3.14)
|
||||||
|
|
||||||
We **highly recommend** and only officially support the latest patch release of
|
We **highly recommend** and only officially support the latest patch release of
|
||||||
each Python and Django series.
|
each Python and Django series.
|
||||||
|
@ -196,9 +196,7 @@ For priority support please sign up for a [professional or premium sponsorship p
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
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@encode.io**.
|
||||||
|
|
||||||
**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.
|
The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
|
||||||
|
|
||||||
|
|
|
@ -81,22 +81,43 @@ For more specific CSS tweaks than simply overriding the default bootstrap theme
|
||||||
|
|
||||||
### Third party packages for customization
|
### Third party packages for customization
|
||||||
|
|
||||||
You can use a third party package for customization, rather than doing it by yourself. Here is 2 packages for customizing the API:
|
You can use a third party package for customization, rather than doing it by yourself. Here is 3 packages for customizing the API:
|
||||||
|
|
||||||
* [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.
|
* [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.
|
||||||
* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework.
|
* [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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
![Django REST Framework Redesign][rfr]
|
![API Root][drf-rw-api-root]
|
||||||
|
|
||||||
*Screenshot of the rest-framework-redesign*
|
![List View][drf-rw-list-view]
|
||||||
|
|
||||||
|
![Detail View][drf-rw-detail-view]
|
||||||
|
|
||||||
|
*Screenshots of the drf-restwind*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
![Django REST Framework Material][rfm]
|
---
|
||||||
|
|
||||||
*Screenshot of the rest-framework-material*
|
![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*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -197,7 +218,15 @@ There are [a variety of packages for autocomplete widgets][autocomplete-packages
|
||||||
[bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar
|
[bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar
|
||||||
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
|
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
|
||||||
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
|
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
|
||||||
[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign
|
[drf-restwind]: https://github.com/youzarsiph/drf-restwind
|
||||||
[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material
|
[drf-rw-api-root]: ../img/drf-rw-api-root.png
|
||||||
[rfr]: ../img/rfr.png
|
[drf-rw-list-view]: ../img/drf-rw-list-view.png
|
||||||
[rfm]: ../img/rfm.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
|
||||||
|
|
|
@ -16,14 +16,18 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
|
||||||
|
|
||||||
Before we do anything else we'll create a new virtual environment, using [venv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
|
Before we do anything else we'll create a new virtual environment, using [venv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
|
||||||
|
|
||||||
|
```bash
|
||||||
python3 -m venv env
|
python3 -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
Now that we're inside a virtual environment, we can install our package requirements.
|
Now that we're inside a virtual environment, we can install our package requirements.
|
||||||
|
|
||||||
|
```bash
|
||||||
pip install django
|
pip install django
|
||||||
pip install djangorestframework
|
pip install djangorestframework
|
||||||
pip install pygments # We'll be using this for the code highlighting
|
pip install pygments # We'll be using this for the code highlighting
|
||||||
|
```
|
||||||
|
|
||||||
**Note:** To exit the virtual environment at any time, just type `deactivate`. For more information see the [venv documentation][venv].
|
**Note:** To exit the virtual environment at any time, just type `deactivate`. For more information see the [venv documentation][venv].
|
||||||
|
|
||||||
|
@ -32,21 +36,27 @@ Now that we're inside a virtual environment, we can install our package requirem
|
||||||
Okay, we're ready to get coding.
|
Okay, we're ready to get coding.
|
||||||
To get started, let's create a new project to work with.
|
To get started, let's create a new project to work with.
|
||||||
|
|
||||||
|
```bash
|
||||||
cd ~
|
cd ~
|
||||||
django-admin startproject tutorial
|
django-admin startproject tutorial
|
||||||
cd tutorial
|
cd tutorial
|
||||||
|
```
|
||||||
|
|
||||||
Once that's done we can create an app that we'll use to create a simple Web API.
|
Once that's done we can create an app that we'll use to create a simple Web API.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py startapp snippets
|
python manage.py startapp snippets
|
||||||
|
```
|
||||||
|
|
||||||
We'll need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. Let's edit the `tutorial/settings.py` file:
|
We'll need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. Let's edit the `tutorial/settings.py` file:
|
||||||
|
|
||||||
|
```text
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
...
|
...
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'snippets',
|
'snippets',
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Okay, we're ready to roll.
|
Okay, we're ready to roll.
|
||||||
|
|
||||||
|
@ -54,6 +64,7 @@ Okay, we're ready to roll.
|
||||||
|
|
||||||
For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets/models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.
|
For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets/models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from pygments.lexers import get_all_lexers
|
from pygments.lexers import get_all_lexers
|
||||||
from pygments.styles import get_all_styles
|
from pygments.styles import get_all_styles
|
||||||
|
@ -65,24 +76,30 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
|
||||||
|
|
||||||
class Snippet(models.Model):
|
class Snippet(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
title = models.CharField(max_length=100, blank=True, default='')
|
title = models.CharField(max_length=100, blank=True, default="")
|
||||||
code = models.TextField()
|
code = models.TextField()
|
||||||
linenos = models.BooleanField(default=False)
|
linenos = models.BooleanField(default=False)
|
||||||
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
|
language = models.CharField(
|
||||||
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
|
choices=LANGUAGE_CHOICES, default="python", max_length=100
|
||||||
|
)
|
||||||
|
style = models.CharField(choices=STYLE_CHOICES, default="friendly", max_length=100)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['created']
|
ordering = ["created"]
|
||||||
|
```
|
||||||
|
|
||||||
We'll also need to create an initial migration for our snippet model, and sync the database for the first time.
|
We'll also need to create an initial migration for our snippet model, and sync the database for the first time.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py makemigrations snippets
|
python manage.py makemigrations snippets
|
||||||
python manage.py migrate snippets
|
python manage.py migrate snippets
|
||||||
|
```
|
||||||
|
|
||||||
## Creating a Serializer class
|
## Creating a Serializer class
|
||||||
|
|
||||||
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following.
|
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following.
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
|
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
|
||||||
|
|
||||||
|
@ -90,10 +107,10 @@ The first thing we need to get started on our Web API is to provide a way of ser
|
||||||
class SnippetSerializer(serializers.Serializer):
|
class SnippetSerializer(serializers.Serializer):
|
||||||
id = serializers.IntegerField(read_only=True)
|
id = serializers.IntegerField(read_only=True)
|
||||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
code = serializers.CharField(style={"base_template": "textarea.html"})
|
||||||
linenos = serializers.BooleanField(required=False)
|
linenos = serializers.BooleanField(required=False)
|
||||||
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
|
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default="python")
|
||||||
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
|
style = serializers.ChoiceField(choices=STYLE_CHOICES, default="friendly")
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""
|
"""
|
||||||
|
@ -105,13 +122,14 @@ The first thing we need to get started on our Web API is to provide a way of ser
|
||||||
"""
|
"""
|
||||||
Update and return an existing `Snippet` instance, given the validated data.
|
Update and return an existing `Snippet` instance, given the validated data.
|
||||||
"""
|
"""
|
||||||
instance.title = validated_data.get('title', instance.title)
|
instance.title = validated_data.get("title", instance.title)
|
||||||
instance.code = validated_data.get('code', instance.code)
|
instance.code = validated_data.get("code", instance.code)
|
||||||
instance.linenos = validated_data.get('linenos', instance.linenos)
|
instance.linenos = validated_data.get("linenos", instance.linenos)
|
||||||
instance.language = validated_data.get('language', instance.language)
|
instance.language = validated_data.get("language", instance.language)
|
||||||
instance.style = validated_data.get('style', instance.style)
|
instance.style = validated_data.get("style", instance.style)
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
```
|
||||||
|
|
||||||
The first part of the serializer class defines the fields that get serialized/deserialized. The `create()` and `update()` methods define how fully fledged instances are created or modified when calling `serializer.save()`
|
The first part of the serializer class defines the fields that get serialized/deserialized. The `create()` and `update()` methods define how fully fledged instances are created or modified when calling `serializer.save()`
|
||||||
|
|
||||||
|
@ -125,57 +143,71 @@ We can actually also save ourselves some time by using the `ModelSerializer` cla
|
||||||
|
|
||||||
Before we go any further we'll familiarize ourselves with using our new Serializer class. Let's drop into the Django shell.
|
Before we go any further we'll familiarize ourselves with using our new Serializer class. Let's drop into the Django shell.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py shell
|
python manage.py shell
|
||||||
|
```
|
||||||
|
|
||||||
Okay, once we've got a few imports out of the way, let's create a couple of code snippets to work with.
|
Okay, once we've got a few imports out of the way, let's create a couple of code snippets to work with.
|
||||||
|
|
||||||
from snippets.models import Snippet
|
```pycon
|
||||||
from snippets.serializers import SnippetSerializer
|
>>> from snippets.models import Snippet
|
||||||
from rest_framework.renderers import JSONRenderer
|
>>> from snippets.serializers import SnippetSerializer
|
||||||
from rest_framework.parsers import JSONParser
|
>>> from rest_framework.renderers import JSONRenderer
|
||||||
|
>>> from rest_framework.parsers import JSONParser
|
||||||
|
|
||||||
snippet = Snippet(code='foo = "bar"\n')
|
>>> snippet = Snippet(code='foo = "bar"\n')
|
||||||
snippet.save()
|
>>> snippet.save()
|
||||||
|
|
||||||
snippet = Snippet(code='print("hello, world")\n')
|
>>> snippet = Snippet(code='print("hello, world")\n')
|
||||||
snippet.save()
|
>>> snippet.save()
|
||||||
|
```
|
||||||
|
|
||||||
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
|
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
|
||||||
|
|
||||||
serializer = SnippetSerializer(snippet)
|
```pycon
|
||||||
serializer.data
|
>>> serializer = SnippetSerializer(snippet)
|
||||||
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
|
>>> serializer.data
|
||||||
|
{'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
|
||||||
|
```
|
||||||
|
|
||||||
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
|
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
|
||||||
|
|
||||||
content = JSONRenderer().render(serializer.data)
|
```pycon
|
||||||
content
|
>>> content = JSONRenderer().render(serializer.data)
|
||||||
# b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'
|
>>> content
|
||||||
|
b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'
|
||||||
|
```
|
||||||
|
|
||||||
Deserialization is similar. First we parse a stream into Python native datatypes...
|
Deserialization is similar. First we parse a stream into Python native datatypes...
|
||||||
|
|
||||||
import io
|
```pycon
|
||||||
|
>>> import io
|
||||||
|
|
||||||
stream = io.BytesIO(content)
|
>>> stream = io.BytesIO(content)
|
||||||
data = JSONParser().parse(stream)
|
>>> data = JSONParser().parse(stream)
|
||||||
|
```
|
||||||
|
|
||||||
...then we restore those native datatypes into a fully populated object instance.
|
...then we restore those native datatypes into a fully populated object instance.
|
||||||
|
|
||||||
serializer = SnippetSerializer(data=data)
|
```pycon
|
||||||
serializer.is_valid()
|
>>> serializer = SnippetSerializer(data=data)
|
||||||
# True
|
>>> serializer.is_valid()
|
||||||
serializer.validated_data
|
True
|
||||||
# {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
|
>>> serializer.validated_data
|
||||||
serializer.save()
|
{'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
|
||||||
# <Snippet: Snippet object>
|
>>> serializer.save()
|
||||||
|
<Snippet: Snippet object>
|
||||||
|
```
|
||||||
|
|
||||||
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer.
|
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer.
|
||||||
|
|
||||||
We can also serialize querysets instead of model instances. To do so we simply add a `many=True` flag to the serializer arguments.
|
We can also serialize querysets instead of model instances. To do so we simply add a `many=True` flag to the serializer arguments.
|
||||||
|
|
||||||
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
|
```pycon
|
||||||
serializer.data
|
>>> serializer = SnippetSerializer(Snippet.objects.all(), many=True)
|
||||||
# [{'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'}]
|
>>> 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'}]
|
||||||
|
```
|
||||||
|
|
||||||
## Using ModelSerializers
|
## Using ModelSerializers
|
||||||
|
|
||||||
|
@ -186,23 +218,28 @@ In the same way that Django provides both `Form` classes and `ModelForm` classes
|
||||||
Let's look at refactoring our serializer using the `ModelSerializer` class.
|
Let's look at refactoring our serializer using the `ModelSerializer` class.
|
||||||
Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
|
Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
|
||||||
|
|
||||||
|
```python
|
||||||
class SnippetSerializer(serializers.ModelSerializer):
|
class SnippetSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Snippet
|
model = Snippet
|
||||||
fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
|
fields = ["id", "title", "code", "linenos", "language", "style"]
|
||||||
|
```
|
||||||
|
|
||||||
One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing its representation. Open the Django shell with `python manage.py shell`, then try the following:
|
One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing its representation. Open the Django shell with `python manage.py shell`, then try the following:
|
||||||
|
|
||||||
from snippets.serializers import SnippetSerializer
|
```pycon
|
||||||
serializer = SnippetSerializer()
|
>>> from snippets.serializers import SnippetSerializer
|
||||||
print(repr(serializer))
|
|
||||||
# SnippetSerializer():
|
>>> serializer = SnippetSerializer()
|
||||||
# id = IntegerField(label='ID', read_only=True)
|
>>> print(repr(serializer))
|
||||||
# title = CharField(allow_blank=True, max_length=100, required=False)
|
SnippetSerializer():
|
||||||
# code = CharField(style={'base_template': 'textarea.html'})
|
id = IntegerField(label='ID', read_only=True)
|
||||||
# linenos = BooleanField(required=False)
|
title = CharField(allow_blank=True, max_length=100, required=False)
|
||||||
# language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
|
code = CharField(style={'base_template': 'textarea.html'})
|
||||||
# style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
|
linenos = BooleanField(required=False)
|
||||||
|
language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
|
||||||
|
style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
|
||||||
|
```
|
||||||
|
|
||||||
It's important to remember that `ModelSerializer` classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes:
|
It's important to remember that `ModelSerializer` classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes:
|
||||||
|
|
||||||
|
@ -216,36 +253,41 @@ For the moment we won't use any of REST framework's other features, we'll just w
|
||||||
|
|
||||||
Edit the `snippets/views.py` file, and add the following.
|
Edit the `snippets/views.py` file, and add the following.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippets.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
|
```
|
||||||
|
|
||||||
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
|
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
|
||||||
|
|
||||||
|
```python
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def snippet_list(request):
|
def snippet_list(request):
|
||||||
"""
|
"""
|
||||||
List all code snippets, or create a new snippet.
|
List all code snippets, or create a new snippet.
|
||||||
"""
|
"""
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
snippets = Snippet.objects.all()
|
snippets = Snippet.objects.all()
|
||||||
serializer = SnippetSerializer(snippets, many=True)
|
serializer = SnippetSerializer(snippets, many=True)
|
||||||
return JsonResponse(serializer.data, safe=False)
|
return JsonResponse(serializer.data, safe=False)
|
||||||
|
|
||||||
elif request.method == 'POST':
|
elif request.method == "POST":
|
||||||
data = JSONParser().parse(request)
|
data = JSONParser().parse(request)
|
||||||
serializer = SnippetSerializer(data=data)
|
serializer = SnippetSerializer(data=data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return JsonResponse(serializer.data, status=201)
|
return JsonResponse(serializer.data, status=201)
|
||||||
return JsonResponse(serializer.errors, status=400)
|
return JsonResponse(serializer.errors, status=400)
|
||||||
|
```
|
||||||
|
|
||||||
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
|
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
|
||||||
|
|
||||||
We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
|
We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
|
||||||
|
|
||||||
|
```python
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def snippet_detail(request, pk):
|
def snippet_detail(request, pk):
|
||||||
"""
|
"""
|
||||||
|
@ -256,11 +298,11 @@ We'll also need a view which corresponds to an individual snippet, and can be us
|
||||||
except Snippet.DoesNotExist:
|
except Snippet.DoesNotExist:
|
||||||
return HttpResponse(status=404)
|
return HttpResponse(status=404)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
serializer = SnippetSerializer(snippet)
|
serializer = SnippetSerializer(snippet)
|
||||||
return JsonResponse(serializer.data)
|
return JsonResponse(serializer.data)
|
||||||
|
|
||||||
elif request.method == 'PUT':
|
elif request.method == "PUT":
|
||||||
data = JSONParser().parse(request)
|
data = JSONParser().parse(request)
|
||||||
serializer = SnippetSerializer(snippet, data=data)
|
serializer = SnippetSerializer(snippet, data=data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
|
@ -268,27 +310,32 @@ We'll also need a view which corresponds to an individual snippet, and can be us
|
||||||
return JsonResponse(serializer.data)
|
return JsonResponse(serializer.data)
|
||||||
return JsonResponse(serializer.errors, status=400)
|
return JsonResponse(serializer.errors, status=400)
|
||||||
|
|
||||||
elif request.method == 'DELETE':
|
elif request.method == "DELETE":
|
||||||
snippet.delete()
|
snippet.delete()
|
||||||
return HttpResponse(status=204)
|
return HttpResponse(status=204)
|
||||||
|
```
|
||||||
|
|
||||||
Finally we need to wire these views up. Create the `snippets/urls.py` file:
|
Finally we need to wire these views up. Create the `snippets/urls.py` file:
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from snippets import views
|
from snippets import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('snippets/', views.snippet_list),
|
path("snippets/", views.snippet_list),
|
||||||
path('snippets/<int:pk>/', views.snippet_detail),
|
path("snippets/<int:pk>/", views.snippet_detail),
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
|
We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include('snippets.urls')),
|
path("", include("snippets.urls")),
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now.
|
It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now.
|
||||||
|
|
||||||
|
@ -298,10 +345,13 @@ Now we can start up a sample server that serves our snippets.
|
||||||
|
|
||||||
Quit out of the shell...
|
Quit out of the shell...
|
||||||
|
|
||||||
quit()
|
```pycon
|
||||||
|
>>> quit()
|
||||||
|
```
|
||||||
|
|
||||||
...and start up Django's development server.
|
...and start up Django's development server.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
|
|
||||||
Validating models...
|
Validating models...
|
||||||
|
@ -310,6 +360,7 @@ Quit out of the shell...
|
||||||
Django version 5.0, using settings 'tutorial.settings'
|
Django version 5.0, using settings 'tutorial.settings'
|
||||||
Starting Development server at http://127.0.0.1:8000/
|
Starting Development server at http://127.0.0.1:8000/
|
||||||
Quit the server with CONTROL-C.
|
Quit the server with CONTROL-C.
|
||||||
|
```
|
||||||
|
|
||||||
In another terminal window, we can test the server.
|
In another terminal window, we can test the server.
|
||||||
|
|
||||||
|
@ -317,10 +368,13 @@ We can test our API using [curl][curl] or [httpie][httpie]. Httpie is a user fri
|
||||||
|
|
||||||
You can install httpie using pip:
|
You can install httpie using pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
pip install httpie
|
pip install httpie
|
||||||
|
```
|
||||||
|
|
||||||
Finally, we can get a list of all of the snippets:
|
Finally, we can get a list of all of the snippets:
|
||||||
|
|
||||||
|
```bash
|
||||||
http GET http://127.0.0.1:8000/snippets/ --unsorted
|
http GET http://127.0.0.1:8000/snippets/ --unsorted
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
|
@ -351,9 +405,11 @@ Finally, we can get a list of all of the snippets:
|
||||||
"style": "friendly"
|
"style": "friendly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Or we can get a particular snippet by referencing its id:
|
Or we can get a particular snippet by referencing its id:
|
||||||
|
|
||||||
|
```bash
|
||||||
http GET http://127.0.0.1:8000/snippets/2/ --unsorted
|
http GET http://127.0.0.1:8000/snippets/2/ --unsorted
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
|
@ -366,6 +422,7 @@ Or we can get a particular snippet by referencing its id:
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"style": "friendly"
|
"style": "friendly"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
|
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,18 @@ Let's introduce a couple of essential building blocks.
|
||||||
|
|
||||||
REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.data` attribute, which is similar to `request.POST`, but more useful for working with Web APIs.
|
REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.data` attribute, which is similar to `request.POST`, but more useful for working with Web APIs.
|
||||||
|
|
||||||
|
```python
|
||||||
request.POST # Only handles form data. Only works for 'POST' method.
|
request.POST # Only handles form data. Only works for 'POST' method.
|
||||||
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
|
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
|
||||||
|
```
|
||||||
|
|
||||||
## Response objects
|
## Response objects
|
||||||
|
|
||||||
REST framework also introduces a `Response` object, which is a type of `TemplateResponse` that takes unrendered content and uses content negotiation to determine the correct content type to return to the client.
|
REST framework also introduces a `Response` object, which is a type of `TemplateResponse` that takes unrendered content and uses content negotiation to determine the correct content type to return to the client.
|
||||||
|
|
||||||
|
```python
|
||||||
return Response(data) # Renders to content type as requested by the client.
|
return Response(data) # Renders to content type as requested by the client.
|
||||||
|
```
|
||||||
|
|
||||||
## Status codes
|
## Status codes
|
||||||
|
|
||||||
|
@ -35,6 +39,7 @@ The wrappers also provide behavior such as returning `405 Method Not Allowed` re
|
||||||
|
|
||||||
Okay, let's go ahead and start using these new components to refactor our views slightly.
|
Okay, let's go ahead and start using these new components to refactor our views slightly.
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -42,28 +47,30 @@ Okay, let's go ahead and start using these new components to refactor our views
|
||||||
from snippets.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET', 'POST'])
|
@api_view(["GET", "POST"])
|
||||||
def snippet_list(request):
|
def snippet_list(request):
|
||||||
"""
|
"""
|
||||||
List all code snippets, or create a new snippet.
|
List all code snippets, or create a new snippet.
|
||||||
"""
|
"""
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
snippets = Snippet.objects.all()
|
snippets = Snippet.objects.all()
|
||||||
serializer = SnippetSerializer(snippets, many=True)
|
serializer = SnippetSerializer(snippets, many=True)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
elif request.method == 'POST':
|
elif request.method == "POST":
|
||||||
serializer = SnippetSerializer(data=request.data)
|
serializer = SnippetSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
```
|
||||||
|
|
||||||
Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.
|
Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.
|
||||||
|
|
||||||
Here is the view for an individual snippet, in the `views.py` module.
|
Here is the view for an individual snippet, in the `views.py` module.
|
||||||
|
|
||||||
@api_view(['GET', 'PUT', 'DELETE'])
|
```python
|
||||||
|
@api_view(["GET", "PUT", "DELETE"])
|
||||||
def snippet_detail(request, pk):
|
def snippet_detail(request, pk):
|
||||||
"""
|
"""
|
||||||
Retrieve, update or delete a code snippet.
|
Retrieve, update or delete a code snippet.
|
||||||
|
@ -73,20 +80,21 @@ Here is the view for an individual snippet, in the `views.py` module.
|
||||||
except Snippet.DoesNotExist:
|
except Snippet.DoesNotExist:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == "GET":
|
||||||
serializer = SnippetSerializer(snippet)
|
serializer = SnippetSerializer(snippet)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
elif request.method == 'PUT':
|
elif request.method == "PUT":
|
||||||
serializer = SnippetSerializer(snippet, data=request.data)
|
serializer = SnippetSerializer(snippet, data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
elif request.method == 'DELETE':
|
elif request.method == "DELETE":
|
||||||
snippet.delete()
|
snippet.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
```
|
||||||
|
|
||||||
This should all feel very familiar - it is not a lot different from working with regular Django views.
|
This should all feel very familiar - it is not a lot different from working with regular Django views.
|
||||||
|
|
||||||
|
@ -94,28 +102,27 @@ Notice that we're no longer explicitly tying our requests or responses to a give
|
||||||
|
|
||||||
## Adding optional format suffixes to our URLs
|
## Adding optional format suffixes to our URLs
|
||||||
|
|
||||||
To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url].
|
To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [<http://example.com/api/items/4.json>][json-url].
|
||||||
|
|
||||||
Start by adding a `format` keyword argument to both of the views, like so.
|
Start by adding a `format` keyword argument to both of the views, like so.
|
||||||
|
`def snippet_list(request, format=None):`
|
||||||
def snippet_list(request, format=None):
|
|
||||||
|
|
||||||
and
|
and
|
||||||
|
`def snippet_detail(request, pk, format=None):`
|
||||||
def snippet_detail(request, pk, format=None):
|
|
||||||
|
|
||||||
Now update the `snippets/urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
Now update the `snippets/urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
from snippets import views
|
from snippets import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('snippets/', views.snippet_list),
|
path("snippets/", views.snippet_list),
|
||||||
path('snippets/<int:pk>/', views.snippet_detail),
|
path("snippets/<int:pk>/", views.snippet_detail),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
```
|
||||||
|
|
||||||
We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of referring to a specific format.
|
We don't necessarily need to add these extra url patterns in, but it gives us a simple, clean way of referring to a specific format.
|
||||||
|
|
||||||
|
@ -125,6 +132,7 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][
|
||||||
|
|
||||||
We can get a list of all of the snippets, as before.
|
We can get a list of all of the snippets, as before.
|
||||||
|
|
||||||
|
```bash
|
||||||
http http://127.0.0.1:8000/snippets/
|
http http://127.0.0.1:8000/snippets/
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
|
@ -147,19 +155,25 @@ We can get a list of all of the snippets, as before.
|
||||||
"style": "friendly"
|
"style": "friendly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
We can control the format of the response that we get back, either by using the `Accept` header:
|
We can control the format of the response that we get back, either by using the `Accept` header:
|
||||||
|
|
||||||
|
```bash
|
||||||
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
|
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
|
||||||
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
|
http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
|
||||||
|
```
|
||||||
|
|
||||||
Or by appending a format suffix:
|
Or by appending a format suffix:
|
||||||
|
|
||||||
|
```bash
|
||||||
http http://127.0.0.1:8000/snippets.json # JSON suffix
|
http http://127.0.0.1:8000/snippets.json # JSON suffix
|
||||||
http http://127.0.0.1:8000/snippets.api # Browsable API suffix
|
http http://127.0.0.1:8000/snippets.api # Browsable API suffix
|
||||||
|
```
|
||||||
|
|
||||||
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
|
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
|
||||||
|
|
||||||
|
```bash
|
||||||
# POST using form data
|
# POST using form data
|
||||||
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
|
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
|
||||||
|
|
||||||
|
@ -183,10 +197,11 @@ Similarly, we can control the format of the request that we send, using the `Con
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"style": "friendly"
|
"style": "friendly"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
If you add a `--debug` switch to the `http` requests above, you will be able to see the request type in request headers.
|
If you add a `--debug` switch to the `http` requests above, you will be able to see the request type in request headers.
|
||||||
|
|
||||||
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
|
Now go and open the API in a web browser, by visiting [<http://127.0.0.1:8000/snippets/>][devserver].
|
||||||
|
|
||||||
### Browsability
|
### Browsability
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ We can also write our API views using class-based views, rather than function ba
|
||||||
|
|
||||||
We'll start by rewriting the root view as a class-based view. All this involves is a little bit of refactoring of `views.py`.
|
We'll start by rewriting the root view as a class-based view. All this involves is a little bit of refactoring of `views.py`.
|
||||||
|
|
||||||
|
```python
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippets.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
@ -18,6 +19,7 @@ We'll start by rewriting the root view as a class-based view. All this involves
|
||||||
"""
|
"""
|
||||||
List all snippets, or create a new snippet.
|
List all snippets, or create a new snippet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
snippets = Snippet.objects.all()
|
snippets = Snippet.objects.all()
|
||||||
serializer = SnippetSerializer(snippets, many=True)
|
serializer = SnippetSerializer(snippets, many=True)
|
||||||
|
@ -29,13 +31,16 @@ We'll start by rewriting the root view as a class-based view. All this involves
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
```
|
||||||
|
|
||||||
So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view in `views.py`.
|
So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view in `views.py`.
|
||||||
|
|
||||||
|
```python
|
||||||
class SnippetDetail(APIView):
|
class SnippetDetail(APIView):
|
||||||
"""
|
"""
|
||||||
Retrieve, update or delete a snippet instance.
|
Retrieve, update or delete a snippet instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_object(self, pk):
|
def get_object(self, pk):
|
||||||
try:
|
try:
|
||||||
return Snippet.objects.get(pk=pk)
|
return Snippet.objects.get(pk=pk)
|
||||||
|
@ -59,21 +64,24 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
|
||||||
snippet = self.get_object(pk)
|
snippet = self.get_object(pk)
|
||||||
snippet.delete()
|
snippet.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
```
|
||||||
|
|
||||||
That's looking good. Again, it's still pretty similar to the function based view right now.
|
That's looking good. Again, it's still pretty similar to the function based view right now.
|
||||||
|
|
||||||
We'll also need to refactor our `snippets/urls.py` slightly now that we're using class-based views.
|
We'll also need to refactor our `snippets/urls.py` slightly now that we're using class-based views.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
from snippets import views
|
from snippets import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('snippets/', views.SnippetList.as_view()),
|
path("snippets/", views.SnippetList.as_view()),
|
||||||
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
|
path("snippets/<int:pk>/", views.SnippetDetail.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
```
|
||||||
|
|
||||||
Okay, we're done. If you run the development server everything should be working just as before.
|
Okay, we're done. If you run the development server everything should be working just as before.
|
||||||
|
|
||||||
|
@ -85,14 +93,16 @@ The create/retrieve/update/delete operations that we've been using so far are go
|
||||||
|
|
||||||
Let's take a look at how we can compose the views by using the mixin classes. Here's our `views.py` module again.
|
Let's take a look at how we can compose the views by using the mixin classes. Here's our `views.py` module again.
|
||||||
|
|
||||||
|
```python
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippets.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
class SnippetList(mixins.ListModelMixin,
|
|
||||||
mixins.CreateModelMixin,
|
class SnippetList(
|
||||||
generics.GenericAPIView):
|
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
|
||||||
|
):
|
||||||
queryset = Snippet.objects.all()
|
queryset = Snippet.objects.all()
|
||||||
serializer_class = SnippetSerializer
|
serializer_class = SnippetSerializer
|
||||||
|
|
||||||
|
@ -101,15 +111,19 @@ Let's take a look at how we can compose the views by using the mixin classes. H
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
return self.create(request, *args, **kwargs)
|
return self.create(request, *args, **kwargs)
|
||||||
|
```
|
||||||
|
|
||||||
We'll take a moment to examine exactly what's happening here. We're building our view using `GenericAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
|
We'll take a moment to examine exactly what's happening here. We're building our view using `GenericAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
|
||||||
|
|
||||||
The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far.
|
The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions. We're then explicitly binding the `get` and `post` methods to the appropriate actions. Simple enough stuff so far.
|
||||||
|
|
||||||
class SnippetDetail(mixins.RetrieveModelMixin,
|
```python
|
||||||
|
class SnippetDetail(
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.DestroyModelMixin,
|
mixins.DestroyModelMixin,
|
||||||
generics.GenericAPIView):
|
generics.GenericAPIView,
|
||||||
|
):
|
||||||
queryset = Snippet.objects.all()
|
queryset = Snippet.objects.all()
|
||||||
serializer_class = SnippetSerializer
|
serializer_class = SnippetSerializer
|
||||||
|
|
||||||
|
@ -121,6 +135,7 @@ The base class provides the core functionality, and the mixin classes provide th
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
return self.destroy(request, *args, **kwargs)
|
return self.destroy(request, *args, **kwargs)
|
||||||
|
```
|
||||||
|
|
||||||
Pretty similar. Again we're using the `GenericAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
|
Pretty similar. Again we're using the `GenericAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
|
||||||
|
|
||||||
|
@ -128,6 +143,7 @@ Pretty similar. Again we're using the `GenericAPIView` class to provide the cor
|
||||||
|
|
||||||
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our `views.py` module even more.
|
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our `views.py` module even more.
|
||||||
|
|
||||||
|
```python
|
||||||
from snippets.models import Snippet
|
from snippets.models import Snippet
|
||||||
from snippets.serializers import SnippetSerializer
|
from snippets.serializers import SnippetSerializer
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
@ -141,6 +157,7 @@ Using the mixin classes we've rewritten the views to use slightly less code than
|
||||||
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
queryset = Snippet.objects.all()
|
queryset = Snippet.objects.all()
|
||||||
serializer_class = SnippetSerializer
|
serializer_class = SnippetSerializer
|
||||||
|
```
|
||||||
|
|
||||||
Wow, that's pretty concise. We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django.
|
Wow, that's pretty concise. We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django.
|
||||||
|
|
||||||
|
|
|
@ -14,61 +14,78 @@ First, let's add a couple of fields. One of those fields will be used to repres
|
||||||
|
|
||||||
Add the following two fields to the `Snippet` model in `models.py`.
|
Add the following two fields to the `Snippet` model in `models.py`.
|
||||||
|
|
||||||
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
|
```python
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
"auth.User", related_name="snippets", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
highlighted = models.TextField()
|
highlighted = models.TextField()
|
||||||
|
```
|
||||||
|
|
||||||
We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code highlighting library.
|
We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code highlighting library.
|
||||||
|
|
||||||
We'll need some extra imports:
|
We'll need some extra imports:
|
||||||
|
|
||||||
|
```python
|
||||||
from pygments.lexers import get_lexer_by_name
|
from pygments.lexers import get_lexer_by_name
|
||||||
from pygments.formatters.html import HtmlFormatter
|
from pygments.formatters.html import HtmlFormatter
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
|
```
|
||||||
|
|
||||||
And now we can add a `.save()` method to our model class:
|
And now we can add a `.save()` method to our model class:
|
||||||
|
|
||||||
|
```python
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Use the `pygments` library to create a highlighted HTML
|
Use the `pygments` library to create a highlighted HTML
|
||||||
representation of the code snippet.
|
representation of the code snippet.
|
||||||
"""
|
"""
|
||||||
lexer = get_lexer_by_name(self.language)
|
lexer = get_lexer_by_name(self.language)
|
||||||
linenos = 'table' if self.linenos else False
|
linenos = "table" if self.linenos else False
|
||||||
options = {'title': self.title} if self.title else {}
|
options = {"title": self.title} if self.title else {}
|
||||||
formatter = HtmlFormatter(style=self.style, linenos=linenos,
|
formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options)
|
||||||
full=True, **options)
|
|
||||||
self.highlighted = highlight(self.code, lexer, formatter)
|
self.highlighted = highlight(self.code, lexer, formatter)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
```
|
||||||
|
|
||||||
When that's all done we'll need to update our database tables.
|
When that's all done we'll need to update our database tables.
|
||||||
Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
|
Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
|
||||||
|
|
||||||
|
```bash
|
||||||
rm -f db.sqlite3
|
rm -f db.sqlite3
|
||||||
rm -r snippets/migrations
|
rm -r snippets/migrations
|
||||||
python manage.py makemigrations snippets
|
python manage.py makemigrations snippets
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command.
|
You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py createsuperuser
|
python manage.py createsuperuser
|
||||||
|
```
|
||||||
|
|
||||||
## Adding endpoints for our User models
|
## Adding endpoints for our User models
|
||||||
|
|
||||||
Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy. In `serializers.py` add:
|
Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy. In `serializers.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
|
snippets = serializers.PrimaryKeyRelatedField(
|
||||||
|
many=True, queryset=Snippet.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', 'username', 'snippets']
|
fields = ["id", "username", "snippets"]
|
||||||
|
```
|
||||||
|
|
||||||
Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it.
|
Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it.
|
||||||
|
|
||||||
We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class-based views.
|
We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class-based views.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,15 +97,20 @@ We'll also add a couple of views to `views.py`. We'd like to just use read-only
|
||||||
class UserDetail(generics.RetrieveAPIView):
|
class UserDetail(generics.RetrieveAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
```
|
||||||
|
|
||||||
Make sure to also import the `UserSerializer` class
|
Make sure to also import the `UserSerializer` class
|
||||||
|
|
||||||
|
```python
|
||||||
from snippets.serializers import UserSerializer
|
from snippets.serializers import UserSerializer
|
||||||
|
```
|
||||||
|
|
||||||
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `snippets/urls.py`.
|
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `snippets/urls.py`.
|
||||||
|
|
||||||
path('users/', views.UserList.as_view()),
|
```python
|
||||||
path('users/<int:pk>/', views.UserDetail.as_view()),
|
path("users/", views.UserList.as_view()),
|
||||||
|
path("users/<int:pk>/", views.UserDetail.as_view()),
|
||||||
|
```
|
||||||
|
|
||||||
## Associating Snippets with Users
|
## Associating Snippets with Users
|
||||||
|
|
||||||
|
@ -98,8 +120,10 @@ The way we deal with that is by overriding a `.perform_create()` method on our s
|
||||||
|
|
||||||
On the `SnippetList` view class, add the following method:
|
On the `SnippetList` view class, add the following method:
|
||||||
|
|
||||||
|
```python
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(owner=self.request.user)
|
serializer.save(owner=self.request.user)
|
||||||
|
```
|
||||||
|
|
||||||
The `create()` method of our serializer will now be passed an additional `'owner'` field, along with the validated data from the request.
|
The `create()` method of our serializer will now be passed an additional `'owner'` field, along with the validated data from the request.
|
||||||
|
|
||||||
|
@ -107,7 +131,9 @@ The `create()` method of our serializer will now be passed an additional `'owner
|
||||||
|
|
||||||
Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition in `serializers.py`:
|
Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition in `serializers.py`:
|
||||||
|
|
||||||
owner = serializers.ReadOnlyField(source='owner.username')
|
```python
|
||||||
|
owner = serializers.ReadOnlyField(source="owner.username")
|
||||||
|
```
|
||||||
|
|
||||||
**Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class.
|
**Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class.
|
||||||
|
|
||||||
|
@ -123,11 +149,15 @@ REST framework includes a number of permission classes that we can use to restri
|
||||||
|
|
||||||
First add the following import in the views module
|
First add the following import in the views module
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
```
|
||||||
|
|
||||||
Then, add the following property to **both** the `SnippetList` and `SnippetDetail` view classes.
|
Then, add the following property to **both** the `SnippetList` and `SnippetDetail` view classes.
|
||||||
|
|
||||||
|
```python
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||||
|
```
|
||||||
|
|
||||||
## Adding login to the Browsable API
|
## Adding login to the Browsable API
|
||||||
|
|
||||||
|
@ -137,13 +167,17 @@ We can add a login view for use with the browsable API, by editing the URLconf i
|
||||||
|
|
||||||
Add the following import at the top of the file:
|
Add the following import at the top of the file:
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
```
|
||||||
|
|
||||||
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
|
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
|
||||||
|
|
||||||
|
```python
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path('api-auth/', include('rest_framework.urls')),
|
path("api-auth/", include("rest_framework.urls")),
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
The `'api-auth/'` part of pattern can actually be whatever URL you want to use.
|
The `'api-auth/'` part of pattern can actually be whatever URL you want to use.
|
||||||
|
|
||||||
|
@ -159,6 +193,7 @@ To do that we're going to need to create a custom permission.
|
||||||
|
|
||||||
In the snippets app, create a new file, `permissions.py`
|
In the snippets app, create a new file, `permissions.py`
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,15 +210,19 @@ In the snippets app, create a new file, `permissions.py`
|
||||||
|
|
||||||
# Write permissions are only allowed to the owner of the snippet.
|
# Write permissions are only allowed to the owner of the snippet.
|
||||||
return obj.owner == request.user
|
return obj.owner == request.user
|
||||||
|
```
|
||||||
|
|
||||||
Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
|
Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
|
||||||
|
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
|
```python
|
||||||
IsOwnerOrReadOnly]
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
|
||||||
|
```
|
||||||
|
|
||||||
Make sure to also import the `IsOwnerOrReadOnly` class.
|
Make sure to also import the `IsOwnerOrReadOnly` class.
|
||||||
|
|
||||||
|
```python
|
||||||
from snippets.permissions import IsOwnerOrReadOnly
|
from snippets.permissions import IsOwnerOrReadOnly
|
||||||
|
```
|
||||||
|
|
||||||
Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.
|
Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.
|
||||||
|
|
||||||
|
@ -197,14 +236,17 @@ If we're interacting with the API programmatically we need to explicitly provide
|
||||||
|
|
||||||
If we try to create a snippet without authenticating, we'll get an error:
|
If we try to create a snippet without authenticating, we'll get an error:
|
||||||
|
|
||||||
|
```bash
|
||||||
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
|
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
|
||||||
|
|
||||||
{
|
{
|
||||||
"detail": "Authentication credentials were not provided."
|
"detail": "Authentication credentials were not provided."
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
We can make a successful request by including the username and password of one of the users we created earlier.
|
We can make a successful request by including the username and password of one of the users we created earlier.
|
||||||
|
|
||||||
|
```bash
|
||||||
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
|
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -216,6 +258,7 @@ We can make a successful request by including the username and password of one o
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"style": "friendly"
|
"style": "friendly"
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,21 @@ At the moment relationships within our API are represented by using primary keys
|
||||||
|
|
||||||
Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. In your `snippets/views.py` add:
|
Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. In your `snippets/views.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(["GET"])
|
||||||
def api_root(request, format=None):
|
def api_root(request, format=None):
|
||||||
return Response({
|
return Response(
|
||||||
'users': reverse('user-list', request=request, format=format),
|
{
|
||||||
'snippets': reverse('snippet-list', request=request, format=format)
|
"users": reverse("user-list", request=request, format=format),
|
||||||
})
|
"snippets": reverse("snippet-list", request=request, format=format),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
|
Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
|
||||||
|
|
||||||
|
@ -30,8 +34,10 @@ The other thing we need to consider when creating the code highlight view is tha
|
||||||
|
|
||||||
Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets/views.py` add:
|
Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets/views.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import renderers
|
from rest_framework import renderers
|
||||||
|
|
||||||
|
|
||||||
class SnippetHighlight(generics.GenericAPIView):
|
class SnippetHighlight(generics.GenericAPIView):
|
||||||
queryset = Snippet.objects.all()
|
queryset = Snippet.objects.all()
|
||||||
renderer_classes = [renderers.StaticHTMLRenderer]
|
renderer_classes = [renderers.StaticHTMLRenderer]
|
||||||
|
@ -39,15 +45,20 @@ Instead of using a concrete generic view, we'll use the base class for represent
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
snippet = self.get_object()
|
snippet = self.get_object()
|
||||||
return Response(snippet.highlighted)
|
return Response(snippet.highlighted)
|
||||||
|
```
|
||||||
|
|
||||||
As usual we need to add the new views that we've created in to our URLconf.
|
As usual we need to add the new views that we've created in to our URLconf.
|
||||||
We'll add a url pattern for our new API root in `snippets/urls.py`:
|
We'll add a url pattern for our new API root in `snippets/urls.py`:
|
||||||
|
|
||||||
path('', views.api_root),
|
```python
|
||||||
|
path("", views.api_root),
|
||||||
|
```
|
||||||
|
|
||||||
And then add a url pattern for the snippet highlights:
|
And then add a url pattern for the snippet highlights:
|
||||||
|
|
||||||
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),
|
```python
|
||||||
|
path("snippets/<int:pk>/highlight/", views.SnippetHighlight.as_view()),
|
||||||
|
```
|
||||||
|
|
||||||
## Hyperlinking our API
|
## Hyperlinking our API
|
||||||
|
|
||||||
|
@ -73,27 +84,62 @@ The `HyperlinkedModelSerializer` has the following differences from `ModelSerial
|
||||||
|
|
||||||
We can easily re-write our existing serializers to use hyperlinking. In your `snippets/serializers.py` add:
|
We can easily re-write our existing serializers to use hyperlinking. In your `snippets/serializers.py` add:
|
||||||
|
|
||||||
|
```python
|
||||||
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
|
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
owner = serializers.ReadOnlyField(source='owner.username')
|
owner = serializers.ReadOnlyField(source="owner.username")
|
||||||
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
|
highlight = serializers.HyperlinkedIdentityField(
|
||||||
|
view_name="snippet-highlight", format="html"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Snippet
|
model = Snippet
|
||||||
fields = ['url', 'id', 'highlight', 'owner',
|
fields = [
|
||||||
'title', 'code', 'linenos', 'language', 'style']
|
"url",
|
||||||
|
"id",
|
||||||
|
"highlight",
|
||||||
|
"owner",
|
||||||
|
"title",
|
||||||
|
"code",
|
||||||
|
"linenos",
|
||||||
|
"language",
|
||||||
|
"style",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
|
snippets = serializers.HyperlinkedRelatedField(
|
||||||
|
many=True, view_name="snippet-detail", read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['url', 'id', 'username', 'snippets']
|
fields = ["url", "id", "username", "snippets"]
|
||||||
|
```
|
||||||
|
|
||||||
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
|
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
|
||||||
|
|
||||||
Because we've included format suffixed URLs such as `'.json'`, we also need to indicate on the `highlight` field that any format suffixed hyperlinks it returns should use the `'.html'` suffix.
|
Because we've included format suffixed URLs such as `'.json'`, we also need to indicate on the `highlight` field that any format suffixed hyperlinks it returns should use the `'.html'` suffix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
|
||||||
|
When you are manually instantiating these serializers inside your views (e.g., in `SnippetDetail` or `SnippetList`), you **must** pass `context={'request': request}` so the serializer knows how to build absolute URLs. For example, instead of:
|
||||||
|
|
||||||
|
```python
|
||||||
|
serializer = SnippetSerializer(snippet)
|
||||||
|
```
|
||||||
|
|
||||||
|
You must write:
|
||||||
|
|
||||||
|
```python
|
||||||
|
serializer = SnippetSerializer(snippet, context={"request": request})
|
||||||
|
```
|
||||||
|
|
||||||
|
If your view is a subclass of `GenericAPIView`, you may use the `get_serializer_context()` as a convenience method.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Making sure our URL patterns are named
|
## Making sure our URL patterns are named
|
||||||
|
|
||||||
If we're going to have a hyperlinked API, we need to make sure we name our URL patterns. Let's take a look at which URL patterns we need to name.
|
If we're going to have a hyperlinked API, we need to make sure we name our URL patterns. Let's take a look at which URL patterns we need to name.
|
||||||
|
@ -105,29 +151,29 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
|
||||||
|
|
||||||
After adding all those names into our URLconf, our final `snippets/urls.py` file should look like this:
|
After adding all those names into our URLconf, our final `snippets/urls.py` file should look like this:
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
from snippets import views
|
from snippets import views
|
||||||
|
|
||||||
# API endpoints
|
# API endpoints
|
||||||
urlpatterns = format_suffix_patterns([
|
urlpatterns = format_suffix_patterns(
|
||||||
path('', views.api_root),
|
[
|
||||||
path('snippets/',
|
path("", views.api_root),
|
||||||
views.SnippetList.as_view(),
|
path("snippets/", views.SnippetList.as_view(), name="snippet-list"),
|
||||||
name='snippet-list'),
|
path(
|
||||||
path('snippets/<int:pk>/',
|
"snippets/<int:pk>/", views.SnippetDetail.as_view(), name="snippet-detail"
|
||||||
views.SnippetDetail.as_view(),
|
),
|
||||||
name='snippet-detail'),
|
path(
|
||||||
path('snippets/<int:pk>/highlight/',
|
"snippets/<int:pk>/highlight/",
|
||||||
views.SnippetHighlight.as_view(),
|
views.SnippetHighlight.as_view(),
|
||||||
name='snippet-highlight'),
|
name="snippet-highlight",
|
||||||
path('users/',
|
),
|
||||||
views.UserList.as_view(),
|
path("users/", views.UserList.as_view(), name="user-list"),
|
||||||
name='user-list'),
|
path("users/<int:pk>/", views.UserDetail.as_view(), name="user-detail"),
|
||||||
path('users/<int:pk>/',
|
]
|
||||||
views.UserDetail.as_view(),
|
)
|
||||||
name='user-detail')
|
```
|
||||||
])
|
|
||||||
|
|
||||||
## Adding pagination
|
## Adding pagination
|
||||||
|
|
||||||
|
@ -135,10 +181,12 @@ The list views for users and code snippets could end up returning quite a lot of
|
||||||
|
|
||||||
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
|
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
|
||||||
|
|
||||||
|
```python
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
||||||
'PAGE_SIZE': 10
|
"PAGE_SIZE": 10,
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Note that settings in REST framework are all namespaced into a single dictionary setting, named `REST_FRAMEWORK`, which helps keep them well separated from your other project settings.
|
Note that settings in REST framework are all namespaced into a single dictionary setting, named `REST_FRAMEWORK`, which helps keep them well separated from your other project settings.
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ Let's take our current set of views, and refactor them into view sets.
|
||||||
|
|
||||||
First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class:
|
First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class:
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,13 +20,16 @@ First of all let's refactor our `UserList` and `UserDetail` classes into a singl
|
||||||
"""
|
"""
|
||||||
This viewset automatically provides `list` and `retrieve` actions.
|
This viewset automatically provides `list` and `retrieve` actions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
```
|
||||||
|
|
||||||
Here we've used the `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two separate classes.
|
Here we've used the `ReadOnlyModelViewSet` class to automatically provide the default 'read-only' operations. We're still setting the `queryset` and `serializer_class` attributes exactly as we did when we were using regular views, but we no longer need to provide the same information to two separate classes.
|
||||||
|
|
||||||
Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class.
|
Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class.
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework import renderers
|
from rest_framework import renderers
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -39,10 +43,10 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
|
||||||
|
|
||||||
Additionally we also provide an extra `highlight` action.
|
Additionally we also provide an extra `highlight` action.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = Snippet.objects.all()
|
queryset = Snippet.objects.all()
|
||||||
serializer_class = SnippetSerializer
|
serializer_class = SnippetSerializer
|
||||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
|
||||||
IsOwnerOrReadOnly]
|
|
||||||
|
|
||||||
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
|
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
|
||||||
def highlight(self, request, *args, **kwargs):
|
def highlight(self, request, *args, **kwargs):
|
||||||
|
@ -51,6 +55,7 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(owner=self.request.user)
|
serializer.save(owner=self.request.user)
|
||||||
|
```
|
||||||
|
|
||||||
This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
|
This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
|
||||||
|
|
||||||
|
@ -67,42 +72,40 @@ To see what's going on under the hood let's first explicitly create a set of vie
|
||||||
|
|
||||||
In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concrete views.
|
In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concrete views.
|
||||||
|
|
||||||
|
```python
|
||||||
from rest_framework import renderers
|
from rest_framework import renderers
|
||||||
|
|
||||||
from snippets.views import api_root, SnippetViewSet, UserViewSet
|
from snippets.views import api_root, SnippetViewSet, UserViewSet
|
||||||
|
|
||||||
snippet_list = SnippetViewSet.as_view({
|
snippet_list = SnippetViewSet.as_view({"get": "list", "post": "create"})
|
||||||
'get': 'list',
|
snippet_detail = SnippetViewSet.as_view(
|
||||||
'post': 'create'
|
{"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}
|
||||||
})
|
)
|
||||||
snippet_detail = SnippetViewSet.as_view({
|
snippet_highlight = SnippetViewSet.as_view(
|
||||||
'get': 'retrieve',
|
{"get": "highlight"}, renderer_classes=[renderers.StaticHTMLRenderer]
|
||||||
'put': 'update',
|
)
|
||||||
'patch': 'partial_update',
|
user_list = UserViewSet.as_view({"get": "list"})
|
||||||
'delete': 'destroy'
|
user_detail = UserViewSet.as_view({"get": "retrieve"})
|
||||||
})
|
```
|
||||||
snippet_highlight = SnippetViewSet.as_view({
|
|
||||||
'get': 'highlight'
|
|
||||||
}, renderer_classes=[renderers.StaticHTMLRenderer])
|
|
||||||
user_list = UserViewSet.as_view({
|
|
||||||
'get': 'list'
|
|
||||||
})
|
|
||||||
user_detail = UserViewSet.as_view({
|
|
||||||
'get': 'retrieve'
|
|
||||||
})
|
|
||||||
|
|
||||||
Notice how we're creating multiple views from each `ViewSet` class, by binding the HTTP methods to the required action for each view.
|
Notice how we're creating multiple views from each `ViewSet` class, by binding the HTTP methods to the required action for each view.
|
||||||
|
|
||||||
Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual.
|
Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual.
|
||||||
|
|
||||||
urlpatterns = format_suffix_patterns([
|
```python
|
||||||
path('', api_root),
|
urlpatterns = format_suffix_patterns(
|
||||||
path('snippets/', snippet_list, name='snippet-list'),
|
[
|
||||||
path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
|
path("", api_root),
|
||||||
path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
|
path("snippets/", snippet_list, name="snippet-list"),
|
||||||
path('users/', user_list, name='user-list'),
|
path("snippets/<int:pk>/", snippet_detail, name="snippet-detail"),
|
||||||
path('users/<int:pk>/', user_detail, name='user-detail')
|
path(
|
||||||
])
|
"snippets/<int:pk>/highlight/", snippet_highlight, name="snippet-highlight"
|
||||||
|
),
|
||||||
|
path("users/", user_list, name="user-list"),
|
||||||
|
path("users/<int:pk>/", user_detail, name="user-detail"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
## Using Routers
|
## Using Routers
|
||||||
|
|
||||||
|
@ -110,6 +113,7 @@ Because we're using `ViewSet` classes rather than `View` classes, we actually do
|
||||||
|
|
||||||
Here's our re-wired `snippets/urls.py` file.
|
Here's our re-wired `snippets/urls.py` file.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
@ -117,13 +121,14 @@ Here's our re-wired `snippets/urls.py` file.
|
||||||
|
|
||||||
# Create a router and register our ViewSets with it.
|
# Create a router and register our ViewSets with it.
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'snippets', views.SnippetViewSet, basename='snippet')
|
router.register(r"snippets", views.SnippetViewSet, basename="snippet")
|
||||||
router.register(r'users', views.UserViewSet, basename='user')
|
router.register(r"users", views.UserViewSet, basename="user")
|
||||||
|
|
||||||
# The API URLs are now determined automatically by the router.
|
# The API URLs are now determined automatically by the router.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path("", include(router.urls)),
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Registering the ViewSets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the view set itself.
|
Registering the ViewSets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the view set itself.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ We're going to create a simple API to allow admin users to view and edit the use
|
||||||
|
|
||||||
Create a new Django project named `tutorial`, then start a new app called `quickstart`.
|
Create a new Django project named `tutorial`, then start a new app called `quickstart`.
|
||||||
|
|
||||||
|
```bash
|
||||||
# Create the project directory
|
# Create the project directory
|
||||||
mkdir tutorial
|
mkdir tutorial
|
||||||
cd tutorial
|
cd tutorial
|
||||||
|
@ -22,9 +23,11 @@ Create a new Django project named `tutorial`, then start a new app called `quick
|
||||||
cd tutorial
|
cd tutorial
|
||||||
django-admin startapp quickstart
|
django-admin startapp quickstart
|
||||||
cd ..
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
The project layout should look like:
|
The project layout should look like:
|
||||||
|
|
||||||
|
```bash
|
||||||
$ pwd
|
$ pwd
|
||||||
<some path>/tutorial
|
<some path>/tutorial
|
||||||
$ find .
|
$ find .
|
||||||
|
@ -47,16 +50,21 @@ The project layout should look like:
|
||||||
./env
|
./env
|
||||||
./env/...
|
./env/...
|
||||||
./manage.py
|
./manage.py
|
||||||
|
```
|
||||||
|
|
||||||
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart).
|
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart).
|
||||||
|
|
||||||
Now sync your database for the first time:
|
Now sync your database for the first time:
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
We'll also create an initial user named `admin` with a password. We'll authenticate as that user later in our example.
|
We'll also create an initial user named `admin` with a password. We'll authenticate as that user later in our example.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py createsuperuser --username admin --email admin@example.com
|
python manage.py createsuperuser --username admin --email admin@example.com
|
||||||
|
```
|
||||||
|
|
||||||
Once you've set up a database and the initial user is created and ready to go, open up the app's directory and we'll get coding...
|
Once you've set up a database and the initial user is created and ready to go, open up the app's directory and we'll get coding...
|
||||||
|
|
||||||
|
@ -64,6 +72,7 @@ Once you've set up a database and the initial user is created and ready to go, o
|
||||||
|
|
||||||
First up we're going to define some serializers. Let's create a new module named `tutorial/quickstart/serializers.py` that we'll use for our data representations.
|
First up we're going to define some serializers. Let's create a new module named `tutorial/quickstart/serializers.py` that we'll use for our data representations.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
@ -71,13 +80,14 @@ First up we're going to define some serializers. Let's create a new module named
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['url', 'username', 'email', 'groups']
|
fields = ["url", "username", "email", "groups"]
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['url', 'name']
|
fields = ["url", "name"]
|
||||||
|
```
|
||||||
|
|
||||||
Notice that we're using hyperlinked relations in this case with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
|
Notice that we're using hyperlinked relations in this case with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
|
||||||
|
|
||||||
|
@ -85,6 +95,7 @@ Notice that we're using hyperlinked relations in this case with `HyperlinkedMode
|
||||||
|
|
||||||
Right, we'd better write some views then. Open `tutorial/quickstart/views.py` and get typing.
|
Right, we'd better write some views then. Open `tutorial/quickstart/views.py` and get typing.
|
||||||
|
|
||||||
|
```python
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from rest_framework import permissions, viewsets
|
from rest_framework import permissions, viewsets
|
||||||
|
|
||||||
|
@ -95,7 +106,8 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
||||||
"""
|
"""
|
||||||
API endpoint that allows users to be viewed or edited.
|
API endpoint that allows users to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
queryset = User.objects.all().order_by('-date_joined')
|
|
||||||
|
queryset = User.objects.all().order_by("-date_joined")
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
@ -104,9 +116,11 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
||||||
"""
|
"""
|
||||||
API endpoint that allows groups to be viewed or edited.
|
API endpoint that allows groups to be viewed or edited.
|
||||||
"""
|
"""
|
||||||
queryset = Group.objects.all().order_by('name')
|
|
||||||
|
queryset = Group.objects.all().order_by("name")
|
||||||
serializer_class = GroupSerializer
|
serializer_class = GroupSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
```
|
||||||
|
|
||||||
Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`.
|
Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`.
|
||||||
|
|
||||||
|
@ -116,21 +130,23 @@ We can easily break these down into individual views if we need to, but using vi
|
||||||
|
|
||||||
Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
|
Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
|
||||||
|
|
||||||
|
```python
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from tutorial.quickstart import views
|
from tutorial.quickstart import views
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'users', views.UserViewSet)
|
router.register(r"users", views.UserViewSet)
|
||||||
router.register(r'groups', views.GroupViewSet)
|
router.register(r"groups", views.GroupViewSet)
|
||||||
|
|
||||||
# Wire up our API using automatic URL routing.
|
# Wire up our API using automatic URL routing.
|
||||||
# Additionally, we include login URLs for the browsable API.
|
# Additionally, we include login URLs for the browsable API.
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path("", include(router.urls)),
|
||||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
|
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
|
||||||
|
|
||||||
|
@ -139,21 +155,26 @@ Again, if we need more control over the API URLs we can simply drop down to usin
|
||||||
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
|
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
|
||||||
|
|
||||||
## Pagination
|
## Pagination
|
||||||
|
|
||||||
Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
|
Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
|
||||||
|
|
||||||
|
```python
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
||||||
'PAGE_SIZE': 10
|
"PAGE_SIZE": 10,
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py`
|
Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py`
|
||||||
|
|
||||||
|
```text
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
...
|
...
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
]
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Okay, we're done.
|
Okay, we're done.
|
||||||
|
|
||||||
|
@ -163,10 +184,13 @@ Okay, we're done.
|
||||||
|
|
||||||
We're now ready to test the API we've built. Let's fire up the server from the command line.
|
We're now ready to test the API we've built. Let's fire up the server from the command line.
|
||||||
|
|
||||||
|
```bash
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
We can now access our API, both from the command-line, using tools like `curl`...
|
We can now access our API, both from the command-line, using tools like `curl`...
|
||||||
|
|
||||||
|
```bash
|
||||||
bash: curl -u admin -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/users/
|
bash: curl -u admin -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/users/
|
||||||
Enter host password for user 'admin':
|
Enter host password for user 'admin':
|
||||||
{
|
{
|
||||||
|
@ -182,9 +206,11 @@ We can now access our API, both from the command-line, using tools like `curl`..
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Or using the [httpie][httpie], command line tool...
|
Or using the [httpie][httpie], command line tool...
|
||||||
|
|
||||||
|
```bash
|
||||||
bash: http -a admin http://127.0.0.1:8000/users/
|
bash: http -a admin http://127.0.0.1:8000/users/
|
||||||
http: password for admin@127.0.0.1:8000::
|
http: password for admin@127.0.0.1:8000::
|
||||||
$HTTP/1.1 200 OK
|
$HTTP/1.1 200 OK
|
||||||
|
@ -202,7 +228,7 @@ Or using the [httpie][httpie], command line tool...
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Or directly through the browser, by going to the URL `http://127.0.0.1:8000/users/`...
|
Or directly through the browser, by going to the URL `http://127.0.0.1:8000/users/`...
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if page.meta.source %}
|
{% if page.meta.source %}
|
||||||
{% for filename in page.meta.source %}
|
{% for filename in page.meta.source %}
|
||||||
<a class="github" href="https://github.com/encode/django-rest-framework/tree/master/rest_framework/{{ filename }}">
|
<a class="github" href="https://github.com/encode/django-rest-framework/tree/main/rest_framework/{{ filename }}">
|
||||||
<span class="label label-info">{{ filename }}</span>
|
<span class="label label-info">{{ filename }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="repo-link btn btn-primary btn-small" href="https://github.com/encode/django-rest-framework/tree/master">GitHub</a>
|
<a class="repo-link btn btn-primary btn-small" href="https://github.com/encode/django-rest-framework">GitHub</a>
|
||||||
<a class="repo-link btn btn-inverse btn-small {% if not page.next_page %}disabled{% endif %}" rel="next" {% if page.next_page %}href="{{ page.next_page.url|url }}"{% endif %}>
|
<a class="repo-link btn btn-inverse btn-small {% if not page.next_page %}disabled{% endif %}" rel="next" {% if page.next_page %}href="{{ page.next_page.url|url }}"{% endif %}>
|
||||||
Next <i class="icon-arrow-right icon-white"></i>
|
Next <i class="icon-arrow-right icon-white"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -66,6 +66,7 @@ nav:
|
||||||
- 'Contributing to REST framework': 'community/contributing.md'
|
- 'Contributing to REST framework': 'community/contributing.md'
|
||||||
- 'Project management': 'community/project-management.md'
|
- 'Project management': 'community/project-management.md'
|
||||||
- 'Release Notes': 'community/release-notes.md'
|
- 'Release Notes': 'community/release-notes.md'
|
||||||
|
- '3.16 Announcement': 'community/3.16-announcement.md'
|
||||||
- '3.15 Announcement': 'community/3.15-announcement.md'
|
- '3.15 Announcement': 'community/3.15-announcement.md'
|
||||||
- '3.14 Announcement': 'community/3.14-announcement.md'
|
- '3.14 Announcement': 'community/3.14-announcement.md'
|
||||||
- '3.13 Announcement': 'community/3.13-announcement.md'
|
- '3.13 Announcement': 'community/3.13-announcement.md'
|
||||||
|
|
79
pyproject.toml
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
[build-system]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
requires = [ "setuptools>=77.0.3" ]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "djangorestframework"
|
||||||
|
description = "Web APIs for Django, made easy."
|
||||||
|
readme = "README.md"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
authors = [ { name = "Tom Christie", email = "tom@tomchristie.com" } ]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Web Environment",
|
||||||
|
"Framework :: Django",
|
||||||
|
"Framework :: Django :: 4.2",
|
||||||
|
"Framework :: Django :: 5.0",
|
||||||
|
"Framework :: Django :: 5.1",
|
||||||
|
"Framework :: Django :: 5.2",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
|
]
|
||||||
|
dynamic = [ "version" ]
|
||||||
|
|
||||||
|
dependencies = [ "django>=4.2" ]
|
||||||
|
urls.Changelog = "https://www.django-rest-framework.org/community/release-notes/"
|
||||||
|
urls.Funding = "https://fund.django-rest-framework.org/topics/funding/"
|
||||||
|
urls.Homepage = "https://www.django-rest-framework.org"
|
||||||
|
urls.Source = "https://github.com/encode/django-rest-framework"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = { attr = "rest_framework.__version__" }
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = [ "rest_framework*" ]
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
skip = [ ".tox" ]
|
||||||
|
atomic = true
|
||||||
|
multi_line_output = 5
|
||||||
|
extra_standard_library = [ "types" ]
|
||||||
|
known_third_party = [ "pytest", "_pytest", "django", "pytz", "uritemplate" ]
|
||||||
|
known_first_party = [ "rest_framework", "tests" ]
|
||||||
|
|
||||||
|
[tool.codespell]
|
||||||
|
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
|
||||||
|
skip = "*/kickstarter-announcement.md,*.js,*.map,*.po"
|
||||||
|
ignore-words-list = "fo,malcom,ser"
|
||||||
|
|
||||||
|
[tool.pyproject-fmt]
|
||||||
|
max_supported_python = "3.14"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--tb=short --strict-markers -ra"
|
||||||
|
testpaths = [ "tests" ]
|
||||||
|
filterwarnings = [ "ignore:CoreAPI compatibility is deprecated*:rest_framework.RemovedInDRF317Warning" ]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
# NOTE: source is ignored with pytest-cov (but uses the same).
|
||||||
|
source = [ "." ]
|
||||||
|
include = [ "rest_framework/*", "tests/*" ]
|
||||||
|
branch = true
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
include = [ "rest_framework/*", "tests/*" ]
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
]
|
|
@ -5,3 +5,4 @@ pytest-django>=4.5.2,<5.0
|
||||||
importlib-metadata<5.0
|
importlib-metadata<5.0
|
||||||
# temporary pin of attrs
|
# temporary pin of attrs
|
||||||
attrs==22.1.0
|
attrs==22.1.0
|
||||||
|
pytz # Remove when dropping support for Django<5.0
|
||||||
|
|
|
@ -8,9 +8,9 @@ ______ _____ _____ _____ __
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__title__ = 'Django REST framework'
|
__title__ = 'Django REST framework'
|
||||||
__version__ = '3.15.2'
|
__version__ = '3.16.1'
|
||||||
__author__ = 'Tom Christie'
|
__author__ = 'Tom Christie'
|
||||||
__license__ = 'BSD 3-Clause'
|
__license__ = 'BSD-3-Clause'
|
||||||
__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'
|
__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'
|
||||||
|
|
||||||
# Version synonym
|
# Version synonym
|
||||||
|
@ -21,10 +21,7 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
|
||||||
|
|
||||||
# Default datetime input and output formats
|
# Default datetime input and output formats
|
||||||
ISO_8601 = 'iso-8601'
|
ISO_8601 = 'iso-8601'
|
||||||
|
DJANGO_DURATION_FORMAT = 'django'
|
||||||
|
|
||||||
class RemovedInDRF316Warning(DeprecationWarning):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RemovedInDRF317Warning(PendingDeprecationWarning):
|
class RemovedInDRF317Warning(PendingDeprecationWarning):
|
||||||
|
|
|
@ -42,4 +42,4 @@ class Command(BaseCommand):
|
||||||
username)
|
username)
|
||||||
)
|
)
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
'Generated token {} for user {}'.format(token.key, username))
|
f'Generated token {token.key} for user {username}')
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import binascii
|
import secrets
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -28,13 +27,22 @@ class Token(models.Model):
|
||||||
verbose_name_plural = _("Tokens")
|
verbose_name_plural = _("Tokens")
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Save the token instance.
|
||||||
|
|
||||||
|
If no key is provided, generates a cryptographically secure key.
|
||||||
|
For new tokens, ensures they are inserted as new (not updated).
|
||||||
|
"""
|
||||||
if not self.key:
|
if not self.key:
|
||||||
self.key = self.generate_key()
|
self.key = self.generate_key()
|
||||||
|
# For new objects, force INSERT to prevent overwriting existing tokens
|
||||||
|
if self._state.adding:
|
||||||
|
kwargs['force_insert'] = True
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_key(cls):
|
def generate_key(cls):
|
||||||
return binascii.hexlify(os.urandom(20)).decode()
|
return secrets.token_hex(20)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.key
|
return self.key
|
||||||
|
|
|
@ -3,6 +3,9 @@ The `compat` module provides support for backwards compatibility with older
|
||||||
versions of Django/Python, and compatibility wrappers around optional packages.
|
versions of Django/Python, and compatibility wrappers around optional packages.
|
||||||
"""
|
"""
|
||||||
import django
|
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
|
from django.views.generic import View
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,6 +160,10 @@ if django.VERSION >= (5, 1):
|
||||||
# 1) the list of validators and 2) the error message. Starting from
|
# 1) the list of validators and 2) the error message. Starting from
|
||||||
# Django 5.1 ip_address_validators only returns the list of validators
|
# Django 5.1 ip_address_validators only returns the list of validators
|
||||||
from django.core.validators import ip_address_validators
|
from django.core.validators import ip_address_validators
|
||||||
|
|
||||||
|
def get_referenced_base_fields_from_q(q):
|
||||||
|
return q.referenced_base_fields
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Django <= 5.1: create a compatibility shim for ip_address_validators
|
# Django <= 5.1: create a compatibility shim for ip_address_validators
|
||||||
from django.core.validators import \
|
from django.core.validators import \
|
||||||
|
@ -165,6 +172,35 @@ else:
|
||||||
def ip_address_validators(protocol, unpack_ipv4):
|
def ip_address_validators(protocol, unpack_ipv4):
|
||||||
return _ip_address_validators(protocol, unpack_ipv4)[0]
|
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
|
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
||||||
# See: https://bugs.python.org/issue22767
|
# See: https://bugs.python.org/issue22767
|
||||||
|
|
|
@ -70,6 +70,15 @@ def api_view(http_method_names=None):
|
||||||
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
|
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
|
||||||
APIView.permission_classes)
|
APIView.permission_classes)
|
||||||
|
|
||||||
|
WrappedAPIView.content_negotiation_class = getattr(func, 'content_negotiation_class',
|
||||||
|
APIView.content_negotiation_class)
|
||||||
|
|
||||||
|
WrappedAPIView.metadata_class = getattr(func, 'metadata_class',
|
||||||
|
APIView.metadata_class)
|
||||||
|
|
||||||
|
WrappedAPIView.versioning_class = getattr(func, "versioning_class",
|
||||||
|
APIView.versioning_class)
|
||||||
|
|
||||||
WrappedAPIView.schema = getattr(func, 'schema',
|
WrappedAPIView.schema = getattr(func, 'schema',
|
||||||
APIView.schema)
|
APIView.schema)
|
||||||
|
|
||||||
|
@ -113,6 +122,27 @@ def permission_classes(permission_classes):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def content_negotiation_class(content_negotiation_class):
|
||||||
|
def decorator(func):
|
||||||
|
func.content_negotiation_class = content_negotiation_class
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def metadata_class(metadata_class):
|
||||||
|
def decorator(func):
|
||||||
|
func.metadata_class = metadata_class
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def versioning_class(versioning_class):
|
||||||
|
def decorator(func):
|
||||||
|
func.versioning_class = versioning_class
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def schema(view_inspector):
|
def schema(view_inspector):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
func.schema = view_inspector
|
func.schema = view_inspector
|
||||||
|
|
|
@ -24,7 +24,7 @@ from django.utils import timezone
|
||||||
from django.utils.dateparse import (
|
from django.utils.dateparse import (
|
||||||
parse_date, parse_datetime, parse_duration, parse_time
|
parse_date, parse_datetime, parse_duration, parse_time
|
||||||
)
|
)
|
||||||
from django.utils.duration import duration_string
|
from django.utils.duration import duration_iso_string, duration_string
|
||||||
from django.utils.encoding import is_protected_type, smart_str
|
from django.utils.encoding import is_protected_type, smart_str
|
||||||
from django.utils.formats import localize_input, sanitize_separators
|
from django.utils.formats import localize_input, sanitize_separators
|
||||||
from django.utils.ipv6 import clean_ipv6_address
|
from django.utils.ipv6 import clean_ipv6_address
|
||||||
|
@ -35,7 +35,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pytz = None
|
pytz = None
|
||||||
|
|
||||||
from rest_framework import ISO_8601
|
from rest_framework import DJANGO_DURATION_FORMAT, ISO_8601
|
||||||
from rest_framework.compat import ip_address_validators
|
from rest_framework.compat import ip_address_validators
|
||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -111,7 +111,7 @@ def get_attribute(instance, attrs):
|
||||||
# If we raised an Attribute or KeyError here it'd get treated
|
# If we raised an Attribute or KeyError here it'd get treated
|
||||||
# as an omitted field in `Field.get_attribute()`. Instead we
|
# as an omitted field in `Field.get_attribute()`. Instead we
|
||||||
# raise a ValueError to ensure the exception is not masked.
|
# raise a ValueError to ensure the exception is not masked.
|
||||||
raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc))
|
raise ValueError(f'Exception raised in callable attribute "{attr}"; original exception was: {exc}')
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -1103,7 +1103,7 @@ class DecimalField(Field):
|
||||||
if self.localize:
|
if self.localize:
|
||||||
return localize_input(quantized)
|
return localize_input(quantized)
|
||||||
|
|
||||||
return '{:f}'.format(quantized)
|
return f'{quantized:f}'
|
||||||
|
|
||||||
def quantize(self, value):
|
def quantize(self, value):
|
||||||
"""
|
"""
|
||||||
|
@ -1351,9 +1351,22 @@ class DurationField(Field):
|
||||||
'overflow': _('The number of days must be between {min_days} and {max_days}.'),
|
'overflow': _('The number of days must be between {min_days} and {max_days}.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, *, format=empty, **kwargs):
|
||||||
self.max_value = kwargs.pop('max_value', None)
|
self.max_value = kwargs.pop('max_value', None)
|
||||||
self.min_value = kwargs.pop('min_value', None)
|
self.min_value = kwargs.pop('min_value', None)
|
||||||
|
if format is not empty:
|
||||||
|
if format is None or (isinstance(format, str) and format.lower() in (ISO_8601, DJANGO_DURATION_FORMAT)):
|
||||||
|
self.format = format
|
||||||
|
elif isinstance(format, str):
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown duration format provided, got '{format}'"
|
||||||
|
" while expecting 'django', 'iso-8601' or `None`."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
"duration format must be either str or `None`,"
|
||||||
|
f" not {type(format).__name__}"
|
||||||
|
)
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
if self.max_value is not None:
|
if self.max_value is not None:
|
||||||
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
|
message = lazy_format(self.error_messages['max_value'], max_value=self.max_value)
|
||||||
|
@ -1376,8 +1389,27 @@ class DurationField(Field):
|
||||||
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
|
self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]')
|
||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
|
output_format = getattr(self, 'format', api_settings.DURATION_FORMAT)
|
||||||
|
|
||||||
|
if output_format is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
if isinstance(output_format, str):
|
||||||
|
if output_format.lower() == ISO_8601:
|
||||||
|
return duration_iso_string(value)
|
||||||
|
|
||||||
|
if output_format.lower() == DJANGO_DURATION_FORMAT:
|
||||||
return duration_string(value)
|
return duration_string(value)
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown duration format provided, got '{output_format}'"
|
||||||
|
" while expecting 'django', 'iso-8601' or `None`."
|
||||||
|
)
|
||||||
|
raise TypeError(
|
||||||
|
"duration format must be either str or `None`,"
|
||||||
|
f" not {type(output_format).__name__}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Choice types...
|
# Choice types...
|
||||||
|
|
||||||
|
@ -1861,7 +1893,7 @@ class SerializerMethodField(Field):
|
||||||
def bind(self, field_name, parent):
|
def bind(self, field_name, parent):
|
||||||
# The method name defaults to `get_{field_name}`.
|
# The method name defaults to `get_{field_name}`.
|
||||||
if self.method_name is None:
|
if self.method_name is None:
|
||||||
self.method_name = 'get_{field_name}'.format(field_name=field_name)
|
self.method_name = f'get_{field_name}'
|
||||||
|
|
||||||
super().bind(field_name, parent)
|
super().bind(field_name, parent)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
# Bashar Al-Abdulhadi, 2016-2017
|
# Bashar Al-Abdulhadi, 2016-2017
|
||||||
# Eyad Toma <d.eyad.t@gmail.com>, 2015,2017
|
# Eyad Toma <d.eyad.t@gmail.com>, 2015,2017
|
||||||
# zak zak <zakaria.bendifallah@gmail.com>, 2020
|
# zak zak <zakaria.bendifallah@gmail.com>, 2020
|
||||||
|
# Salman Saeed Albukhaitan <ssyb2014@gmail.com>, 2024
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
|
@ -24,19 +25,19 @@ msgstr ""
|
||||||
|
|
||||||
#: authentication.py:70
|
#: authentication.py:70
|
||||||
msgid "Invalid basic header. No credentials provided."
|
msgid "Invalid basic header. No credentials provided."
|
||||||
msgstr "رأس أساسي غير صالح, لم تقدم اي بيانات."
|
msgstr "ترويسة أساسية غير صالحة. لم تقدم أي بيانات تفويض."
|
||||||
|
|
||||||
#: authentication.py:73
|
#: authentication.py:73
|
||||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||||
msgstr "رأس أساسي غير صالح, سلسلة البيانات لا يجب أن تحتوي على أي أحرف مسافات"
|
msgstr "ترويسة أساسية غير صالحة. يجب أن لا تحتوي سلسلة بيانات التفويض على مسافات."
|
||||||
|
|
||||||
#: authentication.py:83
|
#: authentication.py:83
|
||||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||||
msgstr "رأس أساسي غير صالح, البيانات ليست مرمّزة بصحة على أساس64."
|
msgstr "ترويسة أساسية غير صالحة. بيانات التفويض لم تُشفر بشكل صحيح بنظام أساس64."
|
||||||
|
|
||||||
#: authentication.py:101
|
#: authentication.py:101
|
||||||
msgid "Invalid username/password."
|
msgid "Invalid username/password."
|
||||||
msgstr "اسم المستخدم/كلمة السر غير صحيحين."
|
msgstr "اسم المستخدم/كلمة المرور غير صحيحة."
|
||||||
|
|
||||||
#: authentication.py:104 authentication.py:206
|
#: authentication.py:104 authentication.py:206
|
||||||
msgid "User inactive or deleted."
|
msgid "User inactive or deleted."
|
||||||
|
@ -93,7 +94,7 @@ msgstr "كلمة المرور"
|
||||||
|
|
||||||
#: authtoken/serializers.py:35
|
#: authtoken/serializers.py:35
|
||||||
msgid "Unable to log in with provided credentials."
|
msgid "Unable to log in with provided credentials."
|
||||||
msgstr "تعذر تسجيل الدخول بالبيانات التي ادخلتها."
|
msgstr "تعذر تسجيل الدخول بالبيانات المدخلة."
|
||||||
|
|
||||||
#: authtoken/serializers.py:38
|
#: authtoken/serializers.py:38
|
||||||
msgid "Must include \"username\" and \"password\"."
|
msgid "Must include \"username\" and \"password\"."
|
||||||
|
@ -101,11 +102,11 @@ msgstr "يجب أن تتضمن \"اسم المستخدم\" و \"كلمة الم
|
||||||
|
|
||||||
#: exceptions.py:102
|
#: exceptions.py:102
|
||||||
msgid "A server error occurred."
|
msgid "A server error occurred."
|
||||||
msgstr "حدث خطأ في المخدم."
|
msgstr "حدث خطأ في الخادم."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "مدخل غير صالح."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -130,11 +131,11 @@ msgstr "غير موجود."
|
||||||
#: exceptions.py:191
|
#: exceptions.py:191
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Method \"{method}\" not allowed."
|
msgid "Method \"{method}\" not allowed."
|
||||||
msgstr "الطريقة \"{method}\" غير مسموح بها."
|
msgstr "طريقة \"{method}\" غير مسموح بها."
|
||||||
|
|
||||||
#: exceptions.py:202
|
#: exceptions.py:202
|
||||||
msgid "Could not satisfy the request Accept header."
|
msgid "Could not satisfy the request Accept header."
|
||||||
msgstr "لم نتمكن من تلبية الرٱس Accept في الطلب."
|
msgstr "تعذر تلبية ترويسة Accept في الطلب."
|
||||||
|
|
||||||
#: exceptions.py:212
|
#: exceptions.py:212
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -143,17 +144,17 @@ msgstr "الوسيط \"{media_type}\" الموجود في الطلب غير مع
|
||||||
|
|
||||||
#: exceptions.py:223
|
#: exceptions.py:223
|
||||||
msgid "Request was throttled."
|
msgid "Request was throttled."
|
||||||
msgstr "تم تقييد الطلب."
|
msgstr "تم حد الطلب."
|
||||||
|
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "متوقع التوفر خلال {wait} ثانية."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "متوقع التوفر خلال {wait} ثواني."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -166,11 +167,11 @@ msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "يجب أن يكون قيمة منطقية صالحة."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "ليس نصاً صالحاً."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -179,16 +180,16 @@ msgstr "لا يمكن لهذا الحقل ان يكون فارغاً."
|
||||||
#: fields.py:768 fields.py:1881
|
#: fields.py:768 fields.py:1881
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} characters."
|
msgid "Ensure this field has no more than {max_length} characters."
|
||||||
msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف."
|
msgstr "تأكد ان عدد الحروف في هذا الحقل لا تتجاوز {max_length}."
|
||||||
|
|
||||||
#: fields.py:769
|
#: fields.py:769
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} characters."
|
msgid "Ensure this field has at least {min_length} characters."
|
||||||
msgstr "تأكد ان الحقل {min_length} محرف على الاقل."
|
msgstr "تأكد ان عدد الحروف في هذا الحقل لا يقل عن {min_length}."
|
||||||
|
|
||||||
#: fields.py:816
|
#: fields.py:816
|
||||||
msgid "Enter a valid email address."
|
msgid "Enter a valid email address."
|
||||||
msgstr "عليك ان تدخل بريد إلكتروني صالح."
|
msgstr "يرجى إدخال عنوان بريد إلكتروني صحيح."
|
||||||
|
|
||||||
#: fields.py:827
|
#: fields.py:827
|
||||||
msgid "This value does not match the required pattern."
|
msgid "This value does not match the required pattern."
|
||||||
|
@ -204,7 +205,7 @@ msgstr "أدخل \"slug\" صالح يحتوي على حروف، أرقام، ش
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
"or hyphens."
|
"or hyphens."
|
||||||
msgstr ""
|
msgstr "أدخل \"slug\" صالح يحتوي على حروف يونيكود، أرقام، شُرط سفلية أو واصلات."
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
|
@ -212,7 +213,7 @@ msgstr "الرجاء إدخال رابط إلكتروني صالح."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "يجب أن يكون معرف UUID صالح."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -225,16 +226,16 @@ msgstr "الرجاء إدخال رقم صحيح صالح."
|
||||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this value is less than or equal to {max_value}."
|
msgid "Ensure this value is less than or equal to {max_value}."
|
||||||
msgstr "تأكد ان القيمة أقل أو تساوي {max_value}."
|
msgstr "تأكد ان القيمة أقل من أو تساوي {max_value}."
|
||||||
|
|
||||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this value is greater than or equal to {min_value}."
|
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||||
msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
|
msgstr "تأكد ان القيمة أكبر من أو تساوي {min_value}."
|
||||||
|
|
||||||
#: fields.py:934 fields.py:971 fields.py:1010
|
#: fields.py:934 fields.py:971 fields.py:1010
|
||||||
msgid "String value too large."
|
msgid "String value too large."
|
||||||
msgstr "السلسلة اطول من القيمة المسموح بها."
|
msgstr "النص طويل جداً."
|
||||||
|
|
||||||
#: fields.py:968 fields.py:1004
|
#: fields.py:968 fields.py:1004
|
||||||
msgid "A valid number is required."
|
msgid "A valid number is required."
|
||||||
|
@ -249,7 +250,7 @@ msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رق
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
"Ensure that there are no more than {max_decimal_places} decimal places."
|
||||||
msgstr "تأكد انه لا يوجد اكثر من {max_decimal_places} منازل عشرية."
|
msgstr "تأكد انه لا يوجد اكثر من {max_decimal_places} أرقام عشرية."
|
||||||
|
|
||||||
#: fields.py:1009
|
#: fields.py:1009
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -261,7 +262,7 @@ msgstr "تأكد انه لا يوجد اكثر من {max_whole_digits} أرقا
|
||||||
#: fields.py:1148
|
#: fields.py:1148
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
|
msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم احد الصيغ التالية: {format}."
|
||||||
|
|
||||||
#: fields.py:1149
|
#: fields.py:1149
|
||||||
msgid "Expected a datetime but got a date."
|
msgid "Expected a datetime but got a date."
|
||||||
|
@ -270,11 +271,11 @@ msgstr "متوقع تاريخ و وقت و وجد تاريخ فقط"
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "تاريخ و وقت غير صالح للمنطقة الزمنية \"{timezone}\"."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "قيمة التاريخ و الوقت خارج النطاق."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -293,12 +294,12 @@ msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واح
|
||||||
#: fields.py:1365
|
#: fields.py:1365
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "صيغة المدة غير صحيحه, يرجى إستخدام إحدى هذه الصيغ: {format}."
|
msgstr "صيغة المدة غير صحيحة. يرجى استخدام احد الصيغ التالية: {format}."
|
||||||
|
|
||||||
#: fields.py:1399 fields.py:1456
|
#: fields.py:1399 fields.py:1456
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "\"{input}\" is not a valid choice."
|
msgid "\"{input}\" is not a valid choice."
|
||||||
msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
|
msgstr "\"{input}\" ليس خياراً صالحاً."
|
||||||
|
|
||||||
#: fields.py:1402
|
#: fields.py:1402
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -317,7 +318,7 @@ msgstr "هذا التحديد لا يجب أن يكون فارغا."
|
||||||
#: fields.py:1495
|
#: fields.py:1495
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "\"{input}\" is not a valid path choice."
|
msgid "\"{input}\" is not a valid path choice."
|
||||||
msgstr "{input} كإختيار مسار غير صالح."
|
msgstr "{input} ليس خيار مسار صالح."
|
||||||
|
|
||||||
#: fields.py:1514
|
#: fields.py:1514
|
||||||
msgid "No file was submitted."
|
msgid "No file was submitted."
|
||||||
|
@ -326,41 +327,41 @@ msgstr "لم يتم إرسال أي ملف."
|
||||||
#: fields.py:1515
|
#: fields.py:1515
|
||||||
msgid ""
|
msgid ""
|
||||||
"The submitted data was not a file. Check the encoding type on the form."
|
"The submitted data was not a file. Check the encoding type on the form."
|
||||||
msgstr "المعطيات المرسولة ليست ملف. إفحص نوع الترميز في النموذج."
|
msgstr "البيانات المرسلة ليست ملف. تأكد من نوع الترميز في النموذج."
|
||||||
|
|
||||||
#: fields.py:1516
|
#: fields.py:1516
|
||||||
msgid "No filename could be determined."
|
msgid "No filename could be determined."
|
||||||
msgstr "ما من إسم ملف أمكن تحديده."
|
msgstr "تعذر تحديد اسم الملف."
|
||||||
|
|
||||||
#: fields.py:1517
|
#: fields.py:1517
|
||||||
msgid "The submitted file is empty."
|
msgid "The submitted file is empty."
|
||||||
msgstr "الملف الذي تم إرساله فارغ."
|
msgstr "الملف المرسل فارغ."
|
||||||
|
|
||||||
#: fields.py:1518
|
#: fields.py:1518
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||||
msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)."
|
msgstr "تأكد ان طول إسم الملف لا يتجاوز {max_length} حرف (عدد الحروف الحالي {length})."
|
||||||
|
|
||||||
#: fields.py:1566
|
#: fields.py:1566
|
||||||
msgid ""
|
msgid ""
|
||||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||||
"corrupted image."
|
"corrupted image."
|
||||||
msgstr "الرجاء تحميل صورة صالحة. الملف الذي تم تحميله إما لم يكن صورة او انه كان صورة تالفة."
|
msgstr "يرجى رفع صورة صالحة. الملف الذي قمت برفعه ليس صورة أو أنه ملف تالف."
|
||||||
|
|
||||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||||
msgid "This list may not be empty."
|
msgid "This list may not be empty."
|
||||||
msgstr "القائمة يجب أن لا تكون فارغة."
|
msgstr "يجب أن لا تكون القائمة فارغة."
|
||||||
|
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "تأكد ان عدد العناصر في هذا الحقل لا يقل عن {min_length}."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "تأكد ان عدد العناصر في هذا الحقل لا يتجاوز {max_length}."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -369,7 +370,7 @@ msgstr "المتوقع كان قاموس عناصر و لكن النوع الم
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "يجب أن لا يكون القاموس فارغاً."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -381,7 +382,7 @@ msgstr "بحث"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "مصطلح البحث."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -389,7 +390,7 @@ msgstr "الترتيب"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "أي حقل يجب استخدامه عند ترتيب النتائج."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -401,11 +402,11 @@ msgstr "تنازلي"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "رقم الصفحة ضمن النتائج المقسمة."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "عدد النتائج التي يجب إرجاعها في كل صفحة."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -413,11 +414,11 @@ msgstr "صفحة غير صحيحة."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "الفهرس الأولي الذي يجب البدء منه لإرجاع النتائج."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "قيمة المؤشر للتقسيم."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -431,24 +432,24 @@ msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غ
|
||||||
#: relations.py:247
|
#: relations.py:247
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||||
msgstr "نوع خاطئ. المتوقع قيمة من pk، لكن المتحصل عليه {data_type}."
|
msgstr "نوع غير صحيح. يتوقع قيمة معرف العنصر، بينما حصل على {data_type}."
|
||||||
|
|
||||||
#: relations.py:280
|
#: relations.py:280
|
||||||
msgid "Invalid hyperlink - No URL match."
|
msgid "Invalid hyperlink - No URL match."
|
||||||
msgstr "إرتباط تشعبي غير صالح - لا مطابقة لURL."
|
msgstr "رابط تشعبي غير صالح - لا يوجد تطابق URL."
|
||||||
|
|
||||||
#: relations.py:281
|
#: relations.py:281
|
||||||
msgid "Invalid hyperlink - Incorrect URL match."
|
msgid "Invalid hyperlink - Incorrect URL match."
|
||||||
msgstr "إرتباط تشعبي غير صالح - مطابقة خاطئة لURL."
|
msgstr "رابط تشعبي غير صالح - تطابق URL غير صحيح."
|
||||||
|
|
||||||
#: relations.py:282
|
#: relations.py:282
|
||||||
msgid "Invalid hyperlink - Object does not exist."
|
msgid "Invalid hyperlink - Object does not exist."
|
||||||
msgstr "إرتباط تشعبي غير صالح - عنصر غير موجود."
|
msgstr "رابط تشعبي غير صالح - العنصر غير موجود."
|
||||||
|
|
||||||
#: relations.py:283
|
#: relations.py:283
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||||
msgstr "نوع خاطئ. المتوقع سلسلة URL، لكن المتحصل عليه {data_type}."
|
msgstr "نوع غير صحيح. المتوقع هو رابط URL، ولكن تم الحصول على {data_type}."
|
||||||
|
|
||||||
#: relations.py:448
|
#: relations.py:448
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -461,20 +462,20 @@ msgstr "قيمة غير صالحة."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "رقم صحيح فريد"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "نص UUID"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "قيمة فريدة"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "نوع {value_type} يحدد هذا {name}."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -484,7 +485,7 @@ msgstr "معطيات غير صالحة. المتوقع هو قاموس، لكن
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "إجراءات إضافية"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -493,27 +494,27 @@ msgstr "مرشحات"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "شريط التنقل"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "المحتوى"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "نموذج الطلب"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "المحتوى الرئيسي"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "معلومات الطلب"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "معلومات الرد"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -525,7 +526,7 @@ msgstr "لا شيء"
|
||||||
#: templates/rest_framework/inline/select_multiple.html:3
|
#: templates/rest_framework/inline/select_multiple.html:3
|
||||||
#: templates/rest_framework/vertical/select_multiple.html:3
|
#: templates/rest_framework/vertical/select_multiple.html:3
|
||||||
msgid "No items to select."
|
msgid "No items to select."
|
||||||
msgstr "ما من عناصر للتحديد."
|
msgstr "لا يوجد عناصر لتحديدها."
|
||||||
|
|
||||||
#: validators.py:39
|
#: validators.py:39
|
||||||
msgid "This field must be unique."
|
msgid "This field must be unique."
|
||||||
|
@ -539,7 +540,7 @@ msgstr "الحقول {field_names} يجب أن تشكل مجموعة فريدة.
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "لا يُسمح بالحروف البديلة: U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -558,11 +559,11 @@ msgstr "الحقل يجب ان يكون فريد للعام {date_field}."
|
||||||
|
|
||||||
#: versioning.py:40
|
#: versioning.py:40
|
||||||
msgid "Invalid version in \"Accept\" header."
|
msgid "Invalid version in \"Accept\" header."
|
||||||
msgstr "إصدار غير صالح في الرٱس \"Accept\"."
|
msgstr "إصدار غير صالح في ترويسة \"Accept\"."
|
||||||
|
|
||||||
#: versioning.py:71
|
#: versioning.py:71
|
||||||
msgid "Invalid version in URL path."
|
msgid "Invalid version in URL path."
|
||||||
msgstr "إصدار غير صالح في المسار URL."
|
msgstr "نسخة غير صالحة في مسار URL."
|
||||||
|
|
||||||
#: versioning.py:116
|
#: versioning.py:116
|
||||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# Thomas Tanner, 2015
|
# Thomas Tanner, 2015
|
||||||
# Tom Jaster <futur3.tom@googlemail.com>, 2015
|
# Tom Jaster <futur3.tom@googlemail.com>, 2015
|
||||||
# Xavier Ordoquy <xordoquy@linovia.com>, 2015
|
# Xavier Ordoquy <xordoquy@linovia.com>, 2015
|
||||||
|
# stefan6419846, 2025
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
|
@ -27,19 +28,19 @@ msgstr ""
|
||||||
|
|
||||||
#: authentication.py:70
|
#: authentication.py:70
|
||||||
msgid "Invalid basic header. No credentials provided."
|
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
|
#: authentication.py:73
|
||||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
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
|
#: authentication.py:83
|
||||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
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
|
#: authentication.py:101
|
||||||
msgid "Invalid username/password."
|
msgid "Invalid username/password."
|
||||||
msgstr "Ungültiger Benutzername/Passwort"
|
msgstr "Ungültiger Benutzername/Passwort."
|
||||||
|
|
||||||
#: authentication.py:104 authentication.py:206
|
#: authentication.py:104 authentication.py:206
|
||||||
msgid "User inactive or deleted."
|
msgid "User inactive or deleted."
|
||||||
|
@ -47,16 +48,16 @@ msgstr "Benutzer inaktiv oder gelöscht."
|
||||||
|
|
||||||
#: authentication.py:184
|
#: authentication.py:184
|
||||||
msgid "Invalid token header. No credentials provided."
|
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
|
#: authentication.py:187
|
||||||
msgid "Invalid token header. Token string should not contain spaces."
|
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
|
#: authentication.py:193
|
||||||
msgid ""
|
msgid ""
|
||||||
"Invalid token header. Token string should not contain invalid characters."
|
"Invalid token header. Token string should not contain invalid characters."
|
||||||
msgstr "Ungültiger Token Header. Tokens dürfen keine ungültigen Zeichen enthalten."
|
msgstr "Ungültiger Token Header. Zugangsdaten dürfen keine ungültigen Zeichen enthalten."
|
||||||
|
|
||||||
#: authentication.py:203
|
#: authentication.py:203
|
||||||
msgid "Invalid token."
|
msgid "Invalid token."
|
||||||
|
@ -108,7 +109,7 @@ msgstr "Ein Serverfehler ist aufgetreten."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "Ungültige Eingabe."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -124,7 +125,7 @@ msgstr "Anmeldedaten fehlen."
|
||||||
|
|
||||||
#: exceptions.py:179
|
#: exceptions.py:179
|
||||||
msgid "You do not have permission to perform this action."
|
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
|
#: exceptions.py:185
|
||||||
msgid "Not found."
|
msgid "Not found."
|
||||||
|
@ -151,17 +152,17 @@ msgstr "Die Anfrage wurde gedrosselt."
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "Erwarte Verfügbarkeit in {wait} Sekunde."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "Erwarte Verfügbarkeit in {wait} Sekunden."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
msgid "This field is required."
|
msgid "This field is required."
|
||||||
msgstr "Dieses Feld ist erforderlich."
|
msgstr "Dieses Feld ist zwingend erforderlich."
|
||||||
|
|
||||||
#: fields.py:317
|
#: fields.py:317
|
||||||
msgid "This field may not be null."
|
msgid "This field may not be null."
|
||||||
|
@ -169,11 +170,11 @@ msgstr "Dieses Feld darf nicht null sein."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "Muss ein gültiger Wahrheitswert sein."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "Kein gültiger String."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -207,7 +208,7 @@ msgstr "Gib ein gültiges \"slug\" aus Buchstaben, Ziffern, Unterstrichen und Mi
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
"or hyphens."
|
"or hyphens."
|
||||||
msgstr ""
|
msgstr "Gib ein gültiges \"slug\" aus Unicode-Buchstaben, Ziffern, Unterstrichen und Minuszeichen ein."
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
|
@ -215,11 +216,11 @@ msgstr "Gib eine gültige URL ein."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "Muss eine gültige UUID sein."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
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
|
#: fields.py:931
|
||||||
msgid "A valid integer is required."
|
msgid "A valid integer is required."
|
||||||
|
@ -273,11 +274,11 @@ msgstr "Erwarte eine Datums- und Zeitangabe, erhielt aber ein Datum."
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "Ungültige Datumsangabe für die Zeitzone \"{timezone}\"."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "Datumsangabe außerhalb des Bereichs."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -358,12 +359,12 @@ msgstr "Diese Liste darf nicht leer sein."
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "Dieses Feld muss mindestens {min_length} Einträge enthalten."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "Dieses Feld darf nicht mehr als {max_length} Einträge enthalten."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -372,7 +373,7 @@ msgstr "Erwartete ein Dictionary mit Elementen, erhielt aber den Typ \"{input_ty
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "Dieses Dictionary darf nicht leer sein."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -384,7 +385,7 @@ msgstr "Suche"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "Ein Suchbegriff."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -392,7 +393,7 @@ msgstr "Sortierung"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "Feld, das zum Sortieren der Ergebnisse verwendet werden soll."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -404,11 +405,11 @@ msgstr "Absteigend"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "Eine Seitenzahl in der paginierten Ergebnismenge."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "Anzahl der pro Seite zurückzugebenden Ergebnisse."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -416,11 +417,11 @@ msgstr "Ungültige Seite."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "Der initiale Index, von dem die Ergebnisse zurückgegeben werden sollen."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "Der Zeigerwert für die Paginierung"
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -434,7 +435,7 @@ msgstr "Ungültiger pk \"{pk_value}\" - Object existiert nicht."
|
||||||
#: relations.py:247
|
#: relations.py:247
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
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
|
#: relations.py:280
|
||||||
msgid "Invalid hyperlink - No URL match."
|
msgid "Invalid hyperlink - No URL match."
|
||||||
|
@ -451,7 +452,7 @@ msgstr "Ungültiger Hyperlink - Objekt existiert nicht."
|
||||||
#: relations.py:283
|
#: relations.py:283
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
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
|
#: relations.py:448
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -464,20 +465,20 @@ msgstr "Ungültiger Wert."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "eindeutiger Ganzzahl-Wert"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "UUID-String"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "eindeutiger Wert"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "Ein {value_type}, der {name} identifiziert."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -487,7 +488,7 @@ msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten."
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "Zusätzliche Aktionen"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -496,27 +497,27 @@ msgstr "Filter"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "Navigation"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "Inhalt"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "Anfrage-Formular"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "Hauptteil"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "Anfrage-Informationen"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "Antwort-Informationen"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -542,7 +543,7 @@ msgstr "Die Felder {field_names} müssen eine eindeutige Menge bilden."
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "Ersatzzeichen sind nicht erlaubt: U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -565,7 +566,7 @@ msgstr "Ungültige Version in der \"Accept\" Kopfzeile."
|
||||||
|
|
||||||
#: versioning.py:71
|
#: versioning.py:71
|
||||||
msgid "Invalid version in URL path."
|
msgid "Invalid version in URL path."
|
||||||
msgstr "Ungültige Version im URL Pfad."
|
msgstr "Ungültige Version im URL-Pfad."
|
||||||
|
|
||||||
#: versioning.py:116
|
#: versioning.py:116
|
||||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||||
|
|
|
@ -10,12 +10,13 @@
|
||||||
# Miguel Gonzalez <migonzalvar@gmail.com>, 2016
|
# Miguel Gonzalez <migonzalvar@gmail.com>, 2016
|
||||||
# Miguel Gonzalez <migonzalvar@gmail.com>, 2015-2016
|
# Miguel Gonzalez <migonzalvar@gmail.com>, 2015-2016
|
||||||
# Sergio Infante <rsinfante@gmail.com>, 2015
|
# Sergio Infante <rsinfante@gmail.com>, 2015
|
||||||
|
# Federico Bond <federicobond@gmail.com>, 2025
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
||||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
"PO-Revision-Date: 2025-05-19 00:05+1000\n"
|
||||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
||||||
"Language-Team: Spanish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/es/)\n"
|
"Language-Team: Spanish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/es/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -107,7 +108,7 @@ msgstr "Se ha producido un error en el servidor."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "Entrada inválida."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -150,12 +151,12 @@ msgstr "Solicitud fue regulada (throttled)."
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "Se espera que esté disponible en {wait} segundo."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "Se espera que esté disponible en {wait} segundos."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -168,11 +169,11 @@ msgstr "Este campo no puede ser nulo."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "Debe ser un booleano válido."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "No es una cadena válida."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -204,9 +205,8 @@ msgstr "Introduzca un \"slug\" válido consistente en letras, números, guiones
|
||||||
|
|
||||||
#: fields.py:839
|
#: fields.py:839
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, or hyphens."
|
||||||
"or hyphens."
|
msgstr "Introduzca un “slug” válido compuesto por letras Unicode, números, guiones bajos o medios."
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
|
@ -214,7 +214,7 @@ msgstr "Introduzca una URL válida."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "Debe ser un UUID válido."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -272,11 +272,11 @@ msgstr "Se esperaba un fecha/hora en vez de una fecha."
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "Fecha y hora inválida para la zona horaria \"{timezone}\"."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "Valor de fecha y hora fuera de rango."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -357,12 +357,12 @@ msgstr "Esta lista no puede estar vacía."
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "Asegúrese de que este campo tiene al menos {min_length} elementos."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "Asegúrese de que este campo no tiene más de {max_length} elementos."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -371,7 +371,7 @@ msgstr "Se esperaba un diccionario de elementos en vez del tipo \"{input_type}\"
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "Este diccionario no debe estar vacío."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -383,7 +383,7 @@ msgstr "Buscar"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "Un término de búsqueda."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -391,7 +391,7 @@ msgstr "Ordenamiento"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "Qué campo usar para ordenar los resultados."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -403,11 +403,11 @@ msgstr "descendiente"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "Un número de página dentro del conjunto de resultados paginado."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "Número de resultados a devolver por página."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -415,11 +415,11 @@ msgstr "Página inválida."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "El índice inicial a partir del cual devolver los resultados."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "El valor del cursor de paginación."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -463,20 +463,20 @@ msgstr "Valor inválido."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "valor de entero único"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "Cadena UUID"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "valor único"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "Un {value_type} que identifique este {name}."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -486,7 +486,7 @@ msgstr "Datos inválidos. Se esperaba un diccionario pero es un {datatype}."
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "Acciones extras"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
|
|
@ -105,7 +105,7 @@ msgstr "خطای سمت سرور رخ داده است."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "ورودی نامعتبر"
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -148,12 +148,12 @@ msgstr "تعداد درخواستهای شما محدود شده است."
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "انتظار میرود در {wait} ثانیه در دسترس باشد."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "انتظار میرود در {wait} ثانیه در دسترس باشد."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -166,11 +166,11 @@ msgstr "این مقدار نباید توهی باشد."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "باید یک مقدار منطقی(بولی) معتبر باشد."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "یک رشته معتبر نیست."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -212,7 +212,7 @@ msgstr "یک URL معتبر وارد کنید"
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "باید یک UUID معتبر باشد."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -270,11 +270,11 @@ msgstr "باید datetime باشد اما date دریافت شد."
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "تاریخ و زمان برای منطقه زمانی \"{timezone}\" نامعتبر است."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "مقدار تاریخ و زمان خارج از محدوده است."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -355,12 +355,12 @@ msgstr "این لیست نمی تواند خالی باشد"
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "اطمینان حاصل کنید که این فیلد حداقل {min_length} عنصر دارد."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "اطمینان حاصل کنید که این فیلد بیش از {max_length} عنصر ندارد."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -369,7 +369,7 @@ msgstr "باید دیکشنری از آیتم ها ارسال می شد، اما
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "این دیکشنری نمیتواند خالی باشد."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -381,7 +381,7 @@ msgstr "جستجو"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "یک عبارت جستجو."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -389,7 +389,7 @@ msgstr "ترتیب"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "کدام فیلد باید هنگام مرتبسازی نتایج استفاده شود."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -401,11 +401,11 @@ msgstr "نزولی"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "یک شماره صفحه در مجموعه نتایج صفحهبندی شده."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "تعداد نتایج برای نمایش در هر صفحه."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -413,11 +413,11 @@ msgstr "صفحه نامعتبر"
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "ایندکس اولیهای که از آن نتایج بازگردانده میشود."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "مقدار نشانگر صفحهبندی."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -461,20 +461,20 @@ msgstr "مقدار نامعتبر."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "مقداد عدد یکتا"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "رشته UUID"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "مقدار یکتا"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "یک {value_type} که این {name} را شناسایی میکند."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -484,7 +484,7 @@ msgstr "داده نامعتبر. باید دیکشنری ارسال می شد،
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "اقدامات اضافی"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -493,27 +493,27 @@ msgstr "فیلترها"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "نوار ناوبری"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "محتوا"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "فرم درخواست"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "محتوای اصلی"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "اطلاعات درخواست"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "اطلاعات پاسخ"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -539,7 +539,7 @@ msgstr "فیلدهای {field_names} باید یک مجموعه یکتا باش
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "کاراکترهای جایگزین مجاز نیستند: U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
# Erwann Mest <m+transifex@kud.io>, 2019
|
# Erwann Mest <m+transifex@kud.io>, 2019
|
||||||
# Etienne Desgagné <etienne.desgagne@evimbec.ca>, 2015
|
# Etienne Desgagné <etienne.desgagne@evimbec.ca>, 2015
|
||||||
# Martin Maillard <martin.maillard@gmail.com>, 2015
|
# Martin Maillard <martin.maillard@gmail.com>, 2015
|
||||||
# Martin Maillard <martin.maillard@gmail.com>, 2015
|
|
||||||
# Stéphane Raimbault <stephane.raimbault@gmail.com>, 2019
|
# Stéphane Raimbault <stephane.raimbault@gmail.com>, 2019
|
||||||
# Xavier Ordoquy <xordoquy@linovia.com>, 2015-2016
|
# Xavier Ordoquy <xordoquy@linovia.com>, 2015-2016
|
||||||
|
# Sébastien Corbin <seb.corbin@gmail.com>, 2025
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
||||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
"PO-Revision-Date: 2025-08-17 20:30+0200\n"
|
||||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
"Last-Translator: Sébastien Corbin <seb.corbin@gmail.com>\n"
|
||||||
"Language-Team: French (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fr/)\n"
|
"Language-Team: French (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fr/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
@ -52,8 +52,7 @@ msgid "Invalid token header. Token string should not contain spaces."
|
||||||
msgstr "En-tête « token » non valide. Un token ne doit pas contenir d'espaces."
|
msgstr "En-tête « token » non valide. Un token ne doit pas contenir d'espaces."
|
||||||
|
|
||||||
#: authentication.py:193
|
#: authentication.py:193
|
||||||
msgid ""
|
msgid "Invalid token header. Token string should not contain invalid characters."
|
||||||
"Invalid token header. Token string should not contain invalid characters."
|
|
||||||
msgstr "En-tête « token » non valide. Un token ne doit pas contenir de caractères invalides."
|
msgstr "En-tête « token » non valide. Un token ne doit pas contenir de caractères invalides."
|
||||||
|
|
||||||
#: authentication.py:203
|
#: authentication.py:203
|
||||||
|
@ -106,11 +105,11 @@ msgstr "Une erreur du serveur est survenue."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "Entrée invalide."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
msgstr "Requête malformée"
|
msgstr "Requête malformée."
|
||||||
|
|
||||||
#: exceptions.py:167
|
#: exceptions.py:167
|
||||||
msgid "Incorrect authentication credentials."
|
msgid "Incorrect authentication credentials."
|
||||||
|
@ -149,12 +148,12 @@ msgstr "Requête ralentie."
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "Disponible à nouveau dans {wait} seconde."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "Disponible à nouveau dans {wait} secondes."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -167,11 +166,11 @@ msgstr "Ce champ ne peut être nul."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "Doit être un booléen valide."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "Chaîne de charactère invalide."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -196,16 +195,12 @@ msgid "This value does not match the required pattern."
|
||||||
msgstr "Cette valeur ne satisfait pas le motif imposé."
|
msgstr "Cette valeur ne satisfait pas le motif imposé."
|
||||||
|
|
||||||
#: fields.py:838
|
#: fields.py:838
|
||||||
msgid ""
|
msgid "Enter a valid \"slug\" consisting of letters, numbers, underscores or hyphens."
|
||||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
|
||||||
"hyphens."
|
|
||||||
msgstr "Ce champ ne doit contenir que des lettres, des nombres, des tirets bas _ et des traits d'union."
|
msgstr "Ce champ ne doit contenir que des lettres, des nombres, des tirets bas _ et des traits d'union."
|
||||||
|
|
||||||
#: fields.py:839
|
#: fields.py:839
|
||||||
msgid ""
|
msgid "Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, or hyphens."
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
msgstr "Ce champ ne doit contenir que des lettres Unicode, des nombres, des tirets bas _ et des traits d'union."
|
||||||
"or hyphens."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
|
@ -213,7 +208,7 @@ msgstr "Saisissez une URL valide."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "Doit être un UUID valide."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -226,7 +221,7 @@ msgstr "Un nombre entier valide est requis."
|
||||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this value is less than or equal to {max_value}."
|
msgid "Ensure this value is less than or equal to {max_value}."
|
||||||
msgstr "Assurez-vous que cette valeur est inférieure ou égale à {max_value}."
|
msgstr "Assurez-vous que cette valeur est inférieure ou égale à {max_value}."
|
||||||
|
|
||||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -248,15 +243,12 @@ msgstr "Assurez-vous qu'il n'y a pas plus de {max_digits} chiffres au total."
|
||||||
|
|
||||||
#: fields.py:1008
|
#: fields.py:1008
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
|
||||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
|
||||||
msgstr "Assurez-vous qu'il n'y a pas plus de {max_decimal_places} chiffres après la virgule."
|
msgstr "Assurez-vous qu'il n'y a pas plus de {max_decimal_places} chiffres après la virgule."
|
||||||
|
|
||||||
#: fields.py:1009
|
#: fields.py:1009
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid "Ensure that there are no more than {max_whole_digits} digits before the decimal point."
|
||||||
"Ensure that there are no more than {max_whole_digits} digits before the "
|
|
||||||
"decimal point."
|
|
||||||
msgstr "Assurez-vous qu'il n'y a pas plus de {max_whole_digits} chiffres avant la virgule."
|
msgstr "Assurez-vous qu'il n'y a pas plus de {max_whole_digits} chiffres avant la virgule."
|
||||||
|
|
||||||
#: fields.py:1148
|
#: fields.py:1148
|
||||||
|
@ -271,11 +263,11 @@ msgstr "Attendait une date + heure mais a reçu une date."
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "Date et heure non valides pour le fuseau horaire \"{timezone}\"."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "Valeur de date et heure hors de l'intervalle."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -325,8 +317,7 @@ msgid "No file was submitted."
|
||||||
msgstr "Aucun fichier n'a été soumis."
|
msgstr "Aucun fichier n'a été soumis."
|
||||||
|
|
||||||
#: fields.py:1515
|
#: fields.py:1515
|
||||||
msgid ""
|
msgid "The submitted data was not a file. Check the encoding type on the form."
|
||||||
"The submitted data was not a file. Check the encoding type on the form."
|
|
||||||
msgstr "La donnée soumise n'est pas un fichier. Vérifiez le type d'encodage du formulaire."
|
msgstr "La donnée soumise n'est pas un fichier. Vérifiez le type d'encodage du formulaire."
|
||||||
|
|
||||||
#: fields.py:1516
|
#: fields.py:1516
|
||||||
|
@ -339,14 +330,11 @@ msgstr "Le fichier soumis est vide."
|
||||||
|
|
||||||
#: fields.py:1518
|
#: fields.py:1518
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid "Ensure this filename has at most {max_length} characters (it has {length})."
|
||||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
|
||||||
msgstr "Assurez-vous que le nom de fichier comporte au plus {max_length} caractères (il en comporte {length})."
|
msgstr "Assurez-vous que le nom de fichier comporte au plus {max_length} caractères (il en comporte {length})."
|
||||||
|
|
||||||
#: fields.py:1566
|
#: fields.py:1566
|
||||||
msgid ""
|
msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image."
|
||||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
|
||||||
"corrupted image."
|
|
||||||
msgstr "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."
|
msgstr "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."
|
||||||
|
|
||||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||||
|
@ -356,12 +344,12 @@ msgstr "Cette liste ne peut pas être vide."
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "Vérifier que ce champ a au moins {min_length} éléments."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "Vérifier que ce champ n'a pas plus de {max_length} éléments."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -370,7 +358,7 @@ msgstr "Attendait un dictionnaire d'éléments mais a reçu « {input_type} »
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "Ce dictionnaire ne peut être vide."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -382,7 +370,7 @@ msgstr "Recherche"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "Un terme de recherche."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -390,7 +378,7 @@ msgstr "Ordre"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "Quel champ utiliser pour classer les résultats."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -402,11 +390,11 @@ msgstr "décroissant"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "Un numéro de page de l'ensemble des résultats."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "Nombre de résultats à retourner par page."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -414,11 +402,11 @@ msgstr "Page non valide."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "L'index initial depuis lequel retourner les résultats."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "La valeur du curseur de pagination."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -454,7 +442,7 @@ msgstr "Type incorrect. Attendait une URL, a reçu {data_type}."
|
||||||
#: relations.py:448
|
#: relations.py:448
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Object with {slug_name}={value} does not exist."
|
msgid "Object with {slug_name}={value} does not exist."
|
||||||
msgstr "L'object avec {slug_name}={value} n'existe pas."
|
msgstr "L'objet avec {slug_name}={value} n'existe pas."
|
||||||
|
|
||||||
#: relations.py:449
|
#: relations.py:449
|
||||||
msgid "Invalid value."
|
msgid "Invalid value."
|
||||||
|
@ -462,20 +450,20 @@ msgstr "Valeur non valide."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "valeur entière unique"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "Chaîne UUID"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "valeur unique"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "Un(une) {value_type} identifiant ce(cette) {name}."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -485,7 +473,7 @@ msgstr "Donnée non valide. Attendait un dictionnaire, a reçu {datatype}."
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "Actions supplémentaires"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -494,27 +482,27 @@ msgstr "Filtres"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "barre de navigation"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "contenu"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "formulaire de requête"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "contenu principal"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "information de la requête"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "information de la réponse"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -540,7 +528,7 @@ msgstr "Les champs {field_names} doivent former un ensemble unique."
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "Les caractères de substitution ne sont pas autorisés : U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
|
BIN
rest_framework/locale/kk/LC_MESSAGES/django.mo
Normal file
578
rest_framework/locale/kk/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,578 @@
|
||||||
|
# This file is distributed under the same license as the Django REST framework package.
|
||||||
|
# Translators:
|
||||||
|
# Dulat Kushibayev <kushibayev@gmail.com>, 2025
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Django REST framework\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2025-06-01 23:03+0300\n"
|
||||||
|
"PO-Revision-Date: 2025-06-01 20:03+0000\n"
|
||||||
|
"Last-Translator: Dulat Kushibayev <kushibayev@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: kk\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
|
||||||
|
#: authentication.py:70
|
||||||
|
msgid "Invalid basic header. No credentials provided."
|
||||||
|
msgstr "Негізгі тақырыптама дұрыс емес. Тіркелгі деректері берілмеген."
|
||||||
|
|
||||||
|
#: authentication.py:73
|
||||||
|
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||||
|
msgstr "Негізгі тақырыптама дұрыс емес. Тіркелгі деректері бос орындарсыз болуы керек."
|
||||||
|
|
||||||
|
#: authentication.py:84
|
||||||
|
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||||
|
msgstr "Негізгі тақырыптама дұрыс емес. Тіркелгі деректері base64 форматында дұрыс кодталмаған."
|
||||||
|
|
||||||
|
#: authentication.py:101
|
||||||
|
msgid "Invalid username/password."
|
||||||
|
msgstr "Қате пайдаланушы аты немесе құпиясөз."
|
||||||
|
|
||||||
|
#: authentication.py:104 authentication.py:206
|
||||||
|
msgid "User inactive or deleted."
|
||||||
|
msgstr "Пайдаланушы өшірулі немесе жойылған."
|
||||||
|
|
||||||
|
#: authentication.py:184
|
||||||
|
msgid "Invalid token header. No credentials provided."
|
||||||
|
msgstr "Токен тақырыптамасы дұрыс емес. Тіркелгі деректері берілмеген."
|
||||||
|
|
||||||
|
#: authentication.py:187
|
||||||
|
msgid "Invalid token header. Token string should not contain spaces."
|
||||||
|
msgstr "Токен тақырыптамасы дұрыс емес. Токен жолында бос орын болмауы керек."
|
||||||
|
|
||||||
|
#: authentication.py:193
|
||||||
|
msgid ""
|
||||||
|
"Invalid token header. Token string should not contain invalid characters."
|
||||||
|
msgstr "Токен тақырыптамасы дұрыс емес. Токен құрамында жарамсыз таңбалар болмауы керек."
|
||||||
|
|
||||||
|
#: authentication.py:203
|
||||||
|
msgid "Invalid token."
|
||||||
|
msgstr "Жарамсыз токен."
|
||||||
|
|
||||||
|
#: authtoken/admin.py:28 authtoken/serializers.py:9
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "Пайдаланушы аты"
|
||||||
|
|
||||||
|
#: authtoken/apps.py:7
|
||||||
|
msgid "Auth Token"
|
||||||
|
msgstr "Аутентификация токені"
|
||||||
|
|
||||||
|
#: authtoken/models.py:13
|
||||||
|
msgid "Key"
|
||||||
|
msgstr "Кілт"
|
||||||
|
|
||||||
|
#: authtoken/models.py:16
|
||||||
|
msgid "User"
|
||||||
|
msgstr "Пайдаланушы"
|
||||||
|
|
||||||
|
#: authtoken/models.py:18
|
||||||
|
msgid "Created"
|
||||||
|
msgstr "Құрылған"
|
||||||
|
|
||||||
|
#: authtoken/models.py:27 authtoken/models.py:54 authtoken/serializers.py:19
|
||||||
|
msgid "Token"
|
||||||
|
msgstr "Токен"
|
||||||
|
|
||||||
|
#: authtoken/models.py:28 authtoken/models.py:55
|
||||||
|
msgid "Tokens"
|
||||||
|
msgstr "Токендер"
|
||||||
|
|
||||||
|
#: authtoken/serializers.py:13
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Құпиясөз"
|
||||||
|
|
||||||
|
#: authtoken/serializers.py:35
|
||||||
|
msgid "Unable to log in with provided credentials."
|
||||||
|
msgstr "Берілген тіркелгі деректерімен кіру мүмкін емес."
|
||||||
|
|
||||||
|
#: authtoken/serializers.py:38
|
||||||
|
msgid "Must include \"username\" and \"password\"."
|
||||||
|
msgstr "\"username\" мен \"password\" енгізілуі керек."
|
||||||
|
|
||||||
|
#: exceptions.py:105
|
||||||
|
msgid "A server error occurred."
|
||||||
|
msgstr "Серверде қате орын алды."
|
||||||
|
|
||||||
|
#: exceptions.py:145
|
||||||
|
msgid "Invalid input."
|
||||||
|
msgstr "Қате енгізу деректері."
|
||||||
|
|
||||||
|
#: exceptions.py:166
|
||||||
|
msgid "Malformed request."
|
||||||
|
msgstr "Сұраныс дұрыс құрылмаған."
|
||||||
|
|
||||||
|
#: exceptions.py:172
|
||||||
|
msgid "Incorrect authentication credentials."
|
||||||
|
msgstr "Аутентификация деректері қате."
|
||||||
|
|
||||||
|
#: exceptions.py:178
|
||||||
|
msgid "Authentication credentials were not provided."
|
||||||
|
msgstr "Аутентификация деректері берілмеген."
|
||||||
|
|
||||||
|
#: exceptions.py:184
|
||||||
|
msgid "You do not have permission to perform this action."
|
||||||
|
msgstr "Бұл әрекетті орындауға рұқсатыңыз жоқ."
|
||||||
|
|
||||||
|
#: exceptions.py:190
|
||||||
|
msgid "Not found."
|
||||||
|
msgstr "Табылмады."
|
||||||
|
|
||||||
|
#: exceptions.py:196
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Method \"{method}\" not allowed."
|
||||||
|
msgstr "\"{method}\" әдісіне рұқсат етілмейді."
|
||||||
|
|
||||||
|
#: exceptions.py:207
|
||||||
|
msgid "Could not satisfy the request Accept header."
|
||||||
|
msgstr "Сұраныстағы Accept тақырыбын қанағаттандыру мүмкін емес."
|
||||||
|
|
||||||
|
#: exceptions.py:217
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unsupported media type \"{media_type}\" in request."
|
||||||
|
msgstr "Сұраныстағы \"{media_type}\" медиа түрі қолдау көрсетілмейді."
|
||||||
|
|
||||||
|
#: exceptions.py:228
|
||||||
|
msgid "Request was throttled."
|
||||||
|
msgstr "Сұраныс жиілігі шектелді."
|
||||||
|
|
||||||
|
#: exceptions.py:229
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Expected available in {wait} second."
|
||||||
|
msgstr "{wait} секундтан кейін қайта қолжетімді болады."
|
||||||
|
|
||||||
|
#: exceptions.py:230
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Expected available in {wait} seconds."
|
||||||
|
msgstr "{wait} секундтан кейін қайта қолжетімді болады."
|
||||||
|
|
||||||
|
#: fields.py:292 relations.py:240 relations.py:276 validators.py:112
|
||||||
|
#: validators.py:238
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Бұл мән міндетті."
|
||||||
|
|
||||||
|
#: fields.py:293
|
||||||
|
msgid "This field may not be null."
|
||||||
|
msgstr "Бұл мән null болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:661
|
||||||
|
msgid "Must be a valid boolean."
|
||||||
|
msgstr "Дұрыс логикалық мән болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:724
|
||||||
|
msgid "Not a valid string."
|
||||||
|
msgstr "Мәтін дұрыс емес."
|
||||||
|
|
||||||
|
#: fields.py:725
|
||||||
|
msgid "This field may not be blank."
|
||||||
|
msgstr "Бұл мән бос болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:726 fields.py:1881
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this field has no more than {max_length} characters."
|
||||||
|
msgstr "Бұл мән ең көбі {max_length} таңбадан аспауы керек."
|
||||||
|
|
||||||
|
#: fields.py:727
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this field has at least {min_length} characters."
|
||||||
|
msgstr "Бұл мән кемінде {min_length} таңба болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:774
|
||||||
|
msgid "Enter a valid email address."
|
||||||
|
msgstr "Дұрыс электрондық пошта енгізіңіз."
|
||||||
|
|
||||||
|
#: fields.py:785
|
||||||
|
msgid "This value does not match the required pattern."
|
||||||
|
msgstr "Бұл мән қажетті үлгіге сәйкес келмейді."
|
||||||
|
|
||||||
|
#: fields.py:796
|
||||||
|
msgid ""
|
||||||
|
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||||
|
"hyphens."
|
||||||
|
msgstr "Әріптерден, сандардан, астын сызу және сызықшалардан тұратын дұрыс \"slug\" енгізіңіз."
|
||||||
|
|
||||||
|
#: fields.py:797
|
||||||
|
msgid ""
|
||||||
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
|
"or hyphens."
|
||||||
|
msgstr "Юникод әріптері, сандар, астын сызу және сызықшалардан тұратын дұрыс \"slug\" енгізіңіз."
|
||||||
|
|
||||||
|
#: fields.py:812
|
||||||
|
msgid "Enter a valid URL."
|
||||||
|
msgstr "Дұрыс URL енгізіңіз."
|
||||||
|
|
||||||
|
#: fields.py:825
|
||||||
|
msgid "Must be a valid UUID."
|
||||||
|
msgstr "Дұрыс UUID болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:861
|
||||||
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
msgstr "Дұрыс IPv4 немесе IPv6 адрес енгізіңіз."
|
||||||
|
|
||||||
|
#: fields.py:889
|
||||||
|
msgid "A valid integer is required."
|
||||||
|
msgstr "Дұрыс бүтін сан енгізілуі қажет."
|
||||||
|
|
||||||
|
#: fields.py:890 fields.py:927 fields.py:966 fields.py:1349
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this value is less than or equal to {max_value}."
|
||||||
|
msgstr "Бұл мән {max_value} немесе одан аз болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:891 fields.py:928 fields.py:967 fields.py:1350
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||||
|
msgstr "Бұл мән кемінде {min_value} болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:892 fields.py:929 fields.py:971
|
||||||
|
msgid "String value too large."
|
||||||
|
msgstr "Жолдың мәні тым үлкен."
|
||||||
|
|
||||||
|
#: fields.py:926 fields.py:965
|
||||||
|
msgid "A valid number is required."
|
||||||
|
msgstr "Дұрыс сан енгізілуі керек."
|
||||||
|
|
||||||
|
#: fields.py:930
|
||||||
|
msgid "Integer value too large to convert to float"
|
||||||
|
msgstr "Бүтін сан тым үлкен - қалқымалы санға айналдыру мүмкін емес."
|
||||||
|
|
||||||
|
#: fields.py:968
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure that there are no more than {max_digits} digits in total."
|
||||||
|
msgstr "Барлығы {max_digits} саннан аспауы керек."
|
||||||
|
|
||||||
|
#: fields.py:969
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
|
||||||
|
msgstr "Ондық бөлшектер саны ең көбі {max_decimal_places} болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:970
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"Ensure that there are no more than {max_whole_digits} digits before the "
|
||||||
|
"decimal point."
|
||||||
|
msgstr "Ондық нүктеге дейінгі сандар саны ең көбі {max_whole_digits} болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:1129
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||||
|
msgstr "Datetime пішімі дұрыс емес. Осы пішімдердің бірін пайдаланыңыз: {format}."
|
||||||
|
|
||||||
|
#: fields.py:1130
|
||||||
|
msgid "Expected a datetime but got a date."
|
||||||
|
msgstr "Күтілгені - datetime, берілгені - date."
|
||||||
|
|
||||||
|
#: fields.py:1131
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
|
msgstr "\"{timezone}\" уақыт белдеуі үшін күн мен уақыт дұрыс емес."
|
||||||
|
|
||||||
|
#: fields.py:1132
|
||||||
|
msgid "Datetime value out of range."
|
||||||
|
msgstr "Datetime мәні рұқсат етілген ауқымнан тыс."
|
||||||
|
|
||||||
|
#: fields.py:1219
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||||
|
msgstr "Date пішімі дұрыс емес. Осы пішімдердің бірін пайдаланыңыз: {format}."
|
||||||
|
|
||||||
|
#: fields.py:1220
|
||||||
|
msgid "Expected a date but got a datetime."
|
||||||
|
msgstr "Күтілгені - date, берілгені - datetime."
|
||||||
|
|
||||||
|
#: fields.py:1286
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||||
|
msgstr "Уақыт пішімі дұрыс емес. Осы пішімдердің бірін пайдаланыңыз: {format}."
|
||||||
|
|
||||||
|
#: fields.py:1348
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||||
|
msgstr "Ұзақтық пішімі дұрыс емес. Осы пішімдердің бірін пайдаланыңыз: {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
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "\"{input}\" is not a valid choice."
|
||||||
|
msgstr "\"{input}\" - дұрыс таңдау емес."
|
||||||
|
|
||||||
|
#: fields.py:1389
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "More than {count} items..."
|
||||||
|
msgstr "{count} элементтен артық..."
|
||||||
|
|
||||||
|
#: fields.py:1447 fields.py:1596 relations.py:486 serializers.py:595
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||||
|
msgstr "Элементтер тізімі күтілді, бірақ \"{input_type}\" түрі берілген."
|
||||||
|
|
||||||
|
#: fields.py:1448
|
||||||
|
msgid "This selection may not be empty."
|
||||||
|
msgstr "Бұл таңдау бос болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:1487
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "\"{input}\" is not a valid path choice."
|
||||||
|
msgstr "\"{input}\" - дұрыс жол таңдауы емес."
|
||||||
|
|
||||||
|
#: fields.py:1507
|
||||||
|
msgid "No file was submitted."
|
||||||
|
msgstr "Файл жіберілмеді."
|
||||||
|
|
||||||
|
#: fields.py:1508
|
||||||
|
msgid "The submitted data was not a file. Check the encoding type on the form."
|
||||||
|
msgstr "Жіберілген деректер файл емес. Формадағы кодтау түрін тексеріңіз."
|
||||||
|
|
||||||
|
#: fields.py:1509
|
||||||
|
msgid "No filename could be determined."
|
||||||
|
msgstr "Файл атауы анықталмады."
|
||||||
|
|
||||||
|
#: fields.py:1510
|
||||||
|
msgid "The submitted file is empty."
|
||||||
|
msgstr "Жіберілген файл бос."
|
||||||
|
|
||||||
|
#: fields.py:1511
|
||||||
|
#, python-brace-format
|
||||||
|
msgid ""
|
||||||
|
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||||
|
msgstr "Файл атауы {max_length} таңбадан аспауы керек (қазір - {length})."
|
||||||
|
|
||||||
|
#: fields.py:1559
|
||||||
|
msgid ""
|
||||||
|
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||||
|
"corrupted image."
|
||||||
|
msgstr "Дұрыс кескін жүктеңіз. Жүктелген файл кескін емес немесе бүлінген."
|
||||||
|
|
||||||
|
#: fields.py:1597 relations.py:487 serializers.py:596
|
||||||
|
msgid "This list may not be empty."
|
||||||
|
msgstr "Бұл тізім бос болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:1598 serializers.py:598
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
|
msgstr "Бұл мәнде кемінде {min_length} элемент болуы керек."
|
||||||
|
|
||||||
|
#: fields.py:1599 serializers.py:597
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
|
msgstr "Бұл мәнде {max_length} элементтен көп болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:1677
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||||
|
msgstr "Элементтер жиыны ретінде сөздік күтілді, бірақ \"{input_type}\" түрі берілген."
|
||||||
|
|
||||||
|
#: fields.py:1678
|
||||||
|
msgid "This dictionary may not be empty."
|
||||||
|
msgstr "Бұл сөздік бос болмауы керек."
|
||||||
|
|
||||||
|
#: fields.py:1750
|
||||||
|
msgid "Value must be valid JSON."
|
||||||
|
msgstr "Мән дұрыс JSON пішімінде болуы керек."
|
||||||
|
|
||||||
|
#: filters.py:72 templates/rest_framework/filters/search.html:2
|
||||||
|
#: templates/rest_framework/filters/search.html:8
|
||||||
|
msgid "Search"
|
||||||
|
msgstr "Іздеу"
|
||||||
|
|
||||||
|
#: filters.py:73
|
||||||
|
msgid "A search term."
|
||||||
|
msgstr "Іздеу сөзі."
|
||||||
|
|
||||||
|
#: filters.py:224 templates/rest_framework/filters/ordering.html:3
|
||||||
|
msgid "Ordering"
|
||||||
|
msgstr "Реттеу"
|
||||||
|
|
||||||
|
#: filters.py:225
|
||||||
|
msgid "Which field to use when ordering the results."
|
||||||
|
msgstr "Нәтижелерді реттеу үшін қай мән пайдалану керектігін көрсетеді."
|
||||||
|
|
||||||
|
#: filters.py:341
|
||||||
|
msgid "ascending"
|
||||||
|
msgstr "өсу ретімен"
|
||||||
|
|
||||||
|
#: filters.py:342
|
||||||
|
msgid "descending"
|
||||||
|
msgstr "кему ретімен"
|
||||||
|
|
||||||
|
#: pagination.py:180
|
||||||
|
msgid "A page number within the paginated result set."
|
||||||
|
msgstr "Беттелген нәтиже жиынындағы бет нөмірі."
|
||||||
|
|
||||||
|
#: pagination.py:185 pagination.py:382 pagination.py:599
|
||||||
|
msgid "Number of results to return per page."
|
||||||
|
msgstr "Әр бетте қайтарылатын нәтиже саны."
|
||||||
|
|
||||||
|
#: pagination.py:195
|
||||||
|
msgid "Invalid page."
|
||||||
|
msgstr "Қате бет нөмірі."
|
||||||
|
|
||||||
|
#: pagination.py:384
|
||||||
|
msgid "The initial index from which to return the results."
|
||||||
|
msgstr "Нәтижелер қайтарылатын бастапқы индекс."
|
||||||
|
|
||||||
|
#: pagination.py:590
|
||||||
|
msgid "The pagination cursor value."
|
||||||
|
msgstr "Нәтижелерді беттеуге арналған курсор мәні."
|
||||||
|
|
||||||
|
#: pagination.py:592
|
||||||
|
msgid "Invalid cursor"
|
||||||
|
msgstr "Қате курсор"
|
||||||
|
|
||||||
|
#: relations.py:241
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||||
|
msgstr "Қате pk \"{pk_value}\" - нысан табылмады."
|
||||||
|
|
||||||
|
#: relations.py:242
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||||
|
msgstr "Дерек түрі дұрыс емес. Күтілгені - pk мәні, берілгені - {data_type}."
|
||||||
|
|
||||||
|
#: relations.py:277
|
||||||
|
msgid "Invalid hyperlink - No URL match."
|
||||||
|
msgstr "Қате гиперсілтеме - URL сәйкестігі жоқ."
|
||||||
|
|
||||||
|
#: relations.py:278
|
||||||
|
msgid "Invalid hyperlink - Incorrect URL match."
|
||||||
|
msgstr "Қате гиперсілтеме - URL сәйкестігі дұрыс емес."
|
||||||
|
|
||||||
|
#: relations.py:279
|
||||||
|
msgid "Invalid hyperlink - Object does not exist."
|
||||||
|
msgstr "Қате гиперсілтеме - нысан табылмады."
|
||||||
|
|
||||||
|
#: relations.py:280
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||||
|
msgstr "Дерек түрі дұрыс емес. Күтілгені - URL жолы, берілгені - {data_type}"
|
||||||
|
|
||||||
|
#: relations.py:445
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Object with {slug_name}={value} does not exist."
|
||||||
|
msgstr "{slug_name}={value} параметрі бар нысан табылмады."
|
||||||
|
|
||||||
|
#: relations.py:446
|
||||||
|
msgid "Invalid value."
|
||||||
|
msgstr "Қате мән."
|
||||||
|
|
||||||
|
#: schemas/utils.py:32
|
||||||
|
msgid "unique integer value"
|
||||||
|
msgstr "бірегей бүтін сан мәні"
|
||||||
|
|
||||||
|
#: schemas/utils.py:34
|
||||||
|
msgid "UUID string"
|
||||||
|
msgstr "UUID жолы"
|
||||||
|
|
||||||
|
#: schemas/utils.py:36
|
||||||
|
msgid "unique value"
|
||||||
|
msgstr "бірегей мән"
|
||||||
|
|
||||||
|
#: schemas/utils.py:38
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "A {value_type} identifying this {name}."
|
||||||
|
msgstr "{name} нысанын анықтайтын {value_type}."
|
||||||
|
|
||||||
|
#: serializers.py:342
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Invalid data. Expected a dictionary, but got {datatype}."
|
||||||
|
msgstr "Деректер қате. Күтілгені - сөздік түрі, берілгені - {datatype}."
|
||||||
|
|
||||||
|
#: templates/rest_framework/admin.html:116
|
||||||
|
#: templates/rest_framework/base.html:136
|
||||||
|
msgid "Extra Actions"
|
||||||
|
msgstr "Қосымша әрекеттер"
|
||||||
|
|
||||||
|
#: templates/rest_framework/admin.html:130
|
||||||
|
#: templates/rest_framework/base.html:150
|
||||||
|
msgid "Filters"
|
||||||
|
msgstr "Сүзгілер"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:37
|
||||||
|
msgid "navbar"
|
||||||
|
msgstr "навигация панелі"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:75
|
||||||
|
msgid "content"
|
||||||
|
msgstr "мазмұн"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:78
|
||||||
|
msgid "request form"
|
||||||
|
msgstr "сұрау формасы"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:157
|
||||||
|
msgid "main content"
|
||||||
|
msgstr "негізгі бөлім"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:173
|
||||||
|
msgid "request info"
|
||||||
|
msgstr "сұрау ақпараты"
|
||||||
|
|
||||||
|
#: templates/rest_framework/base.html:177
|
||||||
|
msgid "response info"
|
||||||
|
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 "Ешқайсысы"
|
||||||
|
|
||||||
|
#: templates/rest_framework/horizontal/select_multiple.html:4
|
||||||
|
#: templates/rest_framework/inline/select_multiple.html:3
|
||||||
|
#: templates/rest_framework/vertical/select_multiple.html:3
|
||||||
|
msgid "No items to select."
|
||||||
|
msgstr "Таңдайтын элементтер жоқ."
|
||||||
|
|
||||||
|
#: validators.py:52
|
||||||
|
msgid "This field must be unique."
|
||||||
|
msgstr "Бұл енгізу жолы бірегей болуы керек."
|
||||||
|
|
||||||
|
#: validators.py:111
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The fields {field_names} must make a unique set."
|
||||||
|
msgstr "{field_names} енгізу жолдары бірегей жинақ құрауы тиіс."
|
||||||
|
|
||||||
|
#: validators.py:219
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
|
msgstr "Суррогат таңбалар рұқсат етілмейді: U+{code_point:X}."
|
||||||
|
|
||||||
|
#: validators.py:309
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "This field must be unique for the \"{date_field}\" date."
|
||||||
|
msgstr "\"{date_field}\" күніне бұл енгізу жолы бірегей болуы керек."
|
||||||
|
|
||||||
|
#: validators.py:324
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "This field must be unique for the \"{date_field}\" month."
|
||||||
|
msgstr "\"{date_field}\" айына бұл енгізу жолы бірегей болуы керек."
|
||||||
|
|
||||||
|
#: validators.py:337
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "This field must be unique for the \"{date_field}\" year."
|
||||||
|
msgstr "\"{date_field}\" жылына бұл енгізу жолы бірегей болуы керек."
|
||||||
|
|
||||||
|
#: versioning.py:40
|
||||||
|
msgid "Invalid version in \"Accept\" header."
|
||||||
|
msgstr "\"Accept\" тақырыбында нұсқа дұрыс көрсетілмеген."
|
||||||
|
|
||||||
|
#: versioning.py:71
|
||||||
|
msgid "Invalid version in URL path."
|
||||||
|
msgstr "URL жолында нұсқа қате көрсетілген."
|
||||||
|
|
||||||
|
#: versioning.py:118
|
||||||
|
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||||
|
msgstr "URL жолында нұсқа дұрыс көрсетілмеген. Ешбір нұсқа кеңістігімен сәйкес келмейді."
|
||||||
|
|
||||||
|
#: versioning.py:150
|
||||||
|
msgid "Invalid version in hostname."
|
||||||
|
msgstr "Хост атауында нұсқа қате көрсетілген."
|
||||||
|
|
||||||
|
#: versioning.py:172
|
||||||
|
msgid "Invalid version in query parameter."
|
||||||
|
msgstr "Сұраныс параметрінде нұсқа қате көрсетілген."
|
|
@ -3,35 +3,35 @@
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# GarakdongBigBoy <novelview9@gmail.com>, 2017
|
# JAEGYUN JUNG <twicegoddessana1229@gmail.com>, 2024
|
||||||
# Hochul Kwak <rhkrghcjf@gmail.com>, 2018
|
# Hochul Kwak <rhkrghcjf@gmail.com>, 2018
|
||||||
|
# GarakdongBigBoy <novelview9@gmail.com>, 2017
|
||||||
# Joon Hwan 김준환 <xncbf12@gmail.com>, 2017
|
# Joon Hwan 김준환 <xncbf12@gmail.com>, 2017
|
||||||
# SUN CHOI <best2378@gmail.com>, 2015
|
# SUN CHOI <best2378@gmail.com>, 2015
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
"POT-Creation-Date: 2024-10-22 16:13+0900\n"
|
||||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
||||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
"Last-Translator: JAEGYUN JUNG <twicegoddessana1229@gmail.com>\n"
|
||||||
"Language-Team: Korean (Korea) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ko_KR/)\n"
|
"Language: ko_KR\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: ko_KR\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
#: authentication.py:70
|
#: authentication.py:70
|
||||||
msgid "Invalid basic header. No credentials provided."
|
msgid "Invalid basic header. No credentials provided."
|
||||||
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
|
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터가 제공되지 않았습니다."
|
||||||
|
|
||||||
#: authentication.py:73
|
#: authentication.py:73
|
||||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||||
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials) 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
|
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터 문자열은 공백을 포함하지 않아야 합니다."
|
||||||
|
|
||||||
#: authentication.py:83
|
#: authentication.py:84
|
||||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||||
msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 base64로 적절히 부호화(encode)되지 않았습니다."
|
msgstr "기본 헤더가 유효하지 않습니다. 인증 데이터가 올바르게 base64 인코딩되지 않았습니다."
|
||||||
|
|
||||||
#: authentication.py:101
|
#: authentication.py:101
|
||||||
msgid "Invalid username/password."
|
msgid "Invalid username/password."
|
||||||
|
@ -43,11 +43,11 @@ msgstr "계정이 중지되었거나 삭제되었습니다."
|
||||||
|
|
||||||
#: authentication.py:184
|
#: authentication.py:184
|
||||||
msgid "Invalid token header. No credentials provided."
|
msgid "Invalid token header. No credentials provided."
|
||||||
msgstr "토큰 헤더가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다."
|
msgstr "토큰 헤더가 유효하지 않습니다. 인증 데이터가 제공되지 않았습니다."
|
||||||
|
|
||||||
#: authentication.py:187
|
#: authentication.py:187
|
||||||
msgid "Invalid token header. Token string should not contain spaces."
|
msgid "Invalid token header. Token string should not contain spaces."
|
||||||
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
|
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 공백을 포함하지 않아야 합니다."
|
||||||
|
|
||||||
#: authentication.py:193
|
#: authentication.py:193
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -58,6 +58,10 @@ msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 유효
|
||||||
msgid "Invalid token."
|
msgid "Invalid token."
|
||||||
msgstr "토큰이 유효하지 않습니다."
|
msgstr "토큰이 유효하지 않습니다."
|
||||||
|
|
||||||
|
#: authtoken/admin.py:28 authtoken/serializers.py:9
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "사용자 이름"
|
||||||
|
|
||||||
#: authtoken/apps.py:7
|
#: authtoken/apps.py:7
|
||||||
msgid "Auth Token"
|
msgid "Auth Token"
|
||||||
msgstr "인증 토큰"
|
msgstr "인증 토큰"
|
||||||
|
@ -68,23 +72,19 @@ msgstr "키"
|
||||||
|
|
||||||
#: authtoken/models.py:16
|
#: authtoken/models.py:16
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "유저"
|
msgstr "사용자"
|
||||||
|
|
||||||
#: authtoken/models.py:18
|
#: authtoken/models.py:18
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr "생성됨"
|
msgstr "생성일시"
|
||||||
|
|
||||||
#: authtoken/models.py:27 authtoken/serializers.py:19
|
#: authtoken/models.py:27 authtoken/models.py:54 authtoken/serializers.py:19
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr "토큰"
|
msgstr "토큰"
|
||||||
|
|
||||||
#: authtoken/models.py:28
|
#: authtoken/models.py:28 authtoken/models.py:55
|
||||||
msgid "Tokens"
|
msgid "Tokens"
|
||||||
msgstr ""
|
msgstr "토큰(들)"
|
||||||
|
|
||||||
#: authtoken/serializers.py:9
|
|
||||||
msgid "Username"
|
|
||||||
msgstr "유저이름"
|
|
||||||
|
|
||||||
#: authtoken/serializers.py:13
|
#: authtoken/serializers.py:13
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
|
@ -92,390 +92,398 @@ msgstr "비밀번호"
|
||||||
|
|
||||||
#: authtoken/serializers.py:35
|
#: authtoken/serializers.py:35
|
||||||
msgid "Unable to log in with provided credentials."
|
msgid "Unable to log in with provided credentials."
|
||||||
msgstr "제공된 인증데이터(credentials)로는 로그인할 수 없습니다."
|
msgstr "제공된 인증 데이터로는 로그인할 수 없습니다."
|
||||||
|
|
||||||
#: authtoken/serializers.py:38
|
#: authtoken/serializers.py:38
|
||||||
msgid "Must include \"username\" and \"password\"."
|
msgid "Must include \"username\" and \"password\"."
|
||||||
msgstr "\"아이디\"와 \"비밀번호\"를 포함해야 합니다."
|
msgstr "\"아이디\"와 \"비밀번호\"를 포함해야 합니다."
|
||||||
|
|
||||||
#: exceptions.py:102
|
#: exceptions.py:105
|
||||||
msgid "A server error occurred."
|
msgid "A server error occurred."
|
||||||
msgstr "서버 장애가 발생했습니다."
|
msgstr "서버 장애가 발생했습니다."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:145
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "유효하지 않은 입력입니다."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:166
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
msgstr "잘못된 요청입니다."
|
msgstr "잘못된 요청입니다."
|
||||||
|
|
||||||
#: exceptions.py:167
|
#: exceptions.py:172
|
||||||
msgid "Incorrect authentication credentials."
|
msgid "Incorrect authentication credentials."
|
||||||
msgstr "자격 인증데이터(authentication credentials)가 정확하지 않습니다."
|
msgstr "자격 인증 데이터가 올바르지 않습니다."
|
||||||
|
|
||||||
#: exceptions.py:173
|
#: exceptions.py:178
|
||||||
msgid "Authentication credentials were not provided."
|
msgid "Authentication credentials were not provided."
|
||||||
msgstr "자격 인증데이터(authentication credentials)가 제공되지 않았습니다."
|
msgstr "자격 인증 데이터가 제공되지 않았습니다."
|
||||||
|
|
||||||
#: exceptions.py:179
|
#: exceptions.py:184
|
||||||
msgid "You do not have permission to perform this action."
|
msgid "You do not have permission to perform this action."
|
||||||
msgstr "이 작업을 수행할 권한(permission)이 없습니다."
|
msgstr "이 작업을 수행할 권한이 없습니다."
|
||||||
|
|
||||||
#: exceptions.py:185
|
#: exceptions.py:190
|
||||||
msgid "Not found."
|
msgid "Not found."
|
||||||
msgstr "찾을 수 없습니다."
|
msgstr "찾을 수 없습니다."
|
||||||
|
|
||||||
#: exceptions.py:191
|
#: exceptions.py:196
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Method \"{method}\" not allowed."
|
msgid "Method \"{method}\" not allowed."
|
||||||
msgstr "메소드(Method) \"{method}\"는 허용되지 않습니다."
|
msgstr "메서드 \"{method}\"는 허용되지 않습니다."
|
||||||
|
|
||||||
#: exceptions.py:202
|
#: exceptions.py:207
|
||||||
msgid "Could not satisfy the request Accept header."
|
msgid "Could not satisfy the request Accept header."
|
||||||
msgstr "Accept header 요청을 만족할 수 없습니다."
|
msgstr "요청 Accept 헤더를 만족시킬 수 없습니다."
|
||||||
|
|
||||||
#: exceptions.py:212
|
#: exceptions.py:217
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Unsupported media type \"{media_type}\" in request."
|
msgid "Unsupported media type \"{media_type}\" in request."
|
||||||
msgstr "요청된 \"{media_type}\"가 지원되지 않는 미디어 형태입니다."
|
msgstr "요청된 \"{media_type}\"가 지원되지 않는 미디어 형태입니다."
|
||||||
|
|
||||||
#: exceptions.py:223
|
#: exceptions.py:228
|
||||||
msgid "Request was throttled."
|
msgid "Request was throttled."
|
||||||
msgstr "요청이 지연(throttled)되었습니다."
|
msgstr "요청이 제한되었습니다."
|
||||||
|
|
||||||
#: exceptions.py:224
|
#: exceptions.py:229
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "{wait} 초 후에 사용 가능합니다."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:230
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "{wait} 초 후에 사용 가능합니다."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:292 relations.py:240 relations.py:276 validators.py:99
|
||||||
#: validators.py:183
|
#: validators.py:219
|
||||||
msgid "This field is required."
|
msgid "This field is required."
|
||||||
msgstr "이 필드는 필수 항목입니다."
|
msgstr "이 필드는 필수 항목입니다."
|
||||||
|
|
||||||
#: fields.py:317
|
#: fields.py:293
|
||||||
msgid "This field may not be null."
|
msgid "This field may not be null."
|
||||||
msgstr "이 필드는 null일 수 없습니다."
|
msgstr "이 필드는 null일 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:661
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "유효한 불리언이어야 합니다."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:724
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "유효한 문자열이 아닙니다."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:725
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
msgstr "이 필드는 blank일 수 없습니다."
|
msgstr "이 필드는 blank일 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:768 fields.py:1881
|
#: fields.py:726 fields.py:1881
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} characters."
|
msgid "Ensure this field has no more than {max_length} characters."
|
||||||
msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하십시오."
|
msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:769
|
#: fields.py:727
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} characters."
|
msgid "Ensure this field has at least {min_length} characters."
|
||||||
msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하십시오."
|
msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:816
|
#: fields.py:774
|
||||||
msgid "Enter a valid email address."
|
msgid "Enter a valid email address."
|
||||||
msgstr "유효한 이메일 주소를 입력하십시오."
|
msgstr "유효한 이메일 주소를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:827
|
#: fields.py:785
|
||||||
msgid "This value does not match the required pattern."
|
msgid "This value does not match the required pattern."
|
||||||
msgstr "형식에 맞지 않는 값입니다."
|
msgstr "이 값은 요구되는 패턴과 일치하지 않습니다."
|
||||||
|
|
||||||
#: fields.py:838
|
#: fields.py:796
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||||
"hyphens."
|
"hyphens."
|
||||||
msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하십시오."
|
msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:839
|
#: fields.py:797
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
"or hyphens."
|
"or hyphens."
|
||||||
msgstr ""
|
msgstr "유니코드 문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:812
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
msgstr "유효한 URL을 입력하십시오."
|
msgstr "유효한 URL을 입력하세요."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:825
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "유효한 UUID 이어야 합니다."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:861
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
msgstr "유효한 IPv4 또는 IPv6 주소를 입력하십시오."
|
msgstr "유효한 IPv4 또는 IPv6 주소를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:931
|
#: fields.py:889
|
||||||
msgid "A valid integer is required."
|
msgid "A valid integer is required."
|
||||||
msgstr "유효한 정수(integer)를 넣어주세요."
|
msgstr "유효한 정수를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
#: fields.py:890 fields.py:927 fields.py:966 fields.py:1349
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this value is less than or equal to {max_value}."
|
msgid "Ensure this value is less than or equal to {max_value}."
|
||||||
msgstr "이 값이 {max_value}보다 작거나 같은지 확인하십시오."
|
msgstr "이 값이 {max_value}보다 작거나 같은지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
#: fields.py:891 fields.py:928 fields.py:967 fields.py:1350
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this value is greater than or equal to {min_value}."
|
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||||
msgstr "이 값이 {min_value}보다 크거나 같은지 확인하십시오."
|
msgstr "이 값이 {min_value}보다 크거나 같은지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:934 fields.py:971 fields.py:1010
|
#: fields.py:892 fields.py:929 fields.py:971
|
||||||
msgid "String value too large."
|
msgid "String value too large."
|
||||||
msgstr "문자열 값이 너무 큽니다."
|
msgstr "문자열 값이 너무 깁니다."
|
||||||
|
|
||||||
#: fields.py:968 fields.py:1004
|
#: fields.py:926 fields.py:965
|
||||||
msgid "A valid number is required."
|
msgid "A valid number is required."
|
||||||
msgstr "유효한 숫자를 넣어주세요."
|
msgstr "유효한 숫자를 입력하세요."
|
||||||
|
|
||||||
#: fields.py:1007
|
#: fields.py:930
|
||||||
|
msgid "Integer value too large to convert to float"
|
||||||
|
msgstr "정수 값이 너무 커서 부동 소수점으로 변환할 수 없습니다."
|
||||||
|
|
||||||
|
#: fields.py:968
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure that there are no more than {max_digits} digits in total."
|
msgid "Ensure that there are no more than {max_digits} digits in total."
|
||||||
msgstr "전체 숫자(digits)가 {max_digits} 이하인지 확인하십시오."
|
msgstr "총 자릿수가 {max_digits}을(를) 초과하지 않는지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:1008
|
#: fields.py:969
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid "Ensure that there are no more than {max_decimal_places} decimal places."
|
||||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
msgstr "소수점 이하 자릿수가 {max_decimal_places}을(를) 초과하지 않는지 확인하세요."
|
||||||
msgstr "소수점 자릿수가 {max_decimal_places} 이하인지 확인하십시오."
|
|
||||||
|
|
||||||
#: fields.py:1009
|
#: fields.py:970
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Ensure that there are no more than {max_whole_digits} digits before the "
|
"Ensure that there are no more than {max_whole_digits} digits before the "
|
||||||
"decimal point."
|
"decimal point."
|
||||||
msgstr "소수점 자리 앞에 숫자(digits)가 {max_whole_digits} 이하인지 확인하십시오."
|
msgstr "소수점 앞 자릿수가 {max_whole_digits}을(를) 초과하지 않는지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:1148
|
#: fields.py:1129
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "Datetime의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
msgstr "Datetime의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
||||||
|
|
||||||
#: fields.py:1149
|
#: fields.py:1130
|
||||||
msgid "Expected a datetime but got a date."
|
msgid "Expected a datetime but got a date."
|
||||||
msgstr "예상된 datatime 대신 date를 받았습니다."
|
msgstr "datatime이 예상되었지만 date를 받았습니다."
|
||||||
|
|
||||||
#: fields.py:1150
|
#: fields.py:1131
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "\"{timezone}\" 시간대에 대한 유효하지 않은 datetime 입니다."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1132
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "Datetime 값이 범위를 벗어났습니다."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1219
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "Date의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
msgstr "Date의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
||||||
|
|
||||||
#: fields.py:1237
|
#: fields.py:1220
|
||||||
msgid "Expected a date but got a datetime."
|
msgid "Expected a date but got a datetime."
|
||||||
msgstr "예상된 date 대신 datetime을 받았습니다."
|
msgstr "예상된 date 대신 datetime을 받았습니다."
|
||||||
|
|
||||||
#: fields.py:1303
|
#: fields.py:1286
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
||||||
|
|
||||||
#: fields.py:1365
|
#: fields.py:1348
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||||
msgstr "Duration의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
msgstr "Duration의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
||||||
|
|
||||||
#: fields.py:1399 fields.py:1456
|
#: 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
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "\"{input}\" is not a valid choice."
|
msgid "\"{input}\" is not a valid choice."
|
||||||
msgstr "\"{input}\"이 유효하지 않은 선택(choice)입니다."
|
msgstr "\"{input}\"은 유효하지 않은 선택입니다."
|
||||||
|
|
||||||
#: fields.py:1402
|
#: fields.py:1389
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "More than {count} items..."
|
msgid "More than {count} items..."
|
||||||
msgstr ""
|
msgstr "{count}개 이상의 아이템이 있습니다..."
|
||||||
|
|
||||||
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
|
#: fields.py:1447 fields.py:1596 relations.py:486 serializers.py:593
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected a list of items but got type \"{input_type}\"."
|
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||||
msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습니다."
|
msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습니다."
|
||||||
|
|
||||||
#: fields.py:1458
|
#: fields.py:1448
|
||||||
msgid "This selection may not be empty."
|
msgid "This selection may not be empty."
|
||||||
msgstr "이 선택 항목은 비워 둘 수 없습니다."
|
msgstr "이 선택 항목은 비워 둘 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:1495
|
#: fields.py:1487
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "\"{input}\" is not a valid path choice."
|
msgid "\"{input}\" is not a valid path choice."
|
||||||
msgstr "\"{input}\"이 유효하지 않은 경로 선택입니다."
|
msgstr "\"{input}\"은 유효하지 않은 경로 선택입니다."
|
||||||
|
|
||||||
#: fields.py:1514
|
#: fields.py:1507
|
||||||
msgid "No file was submitted."
|
msgid "No file was submitted."
|
||||||
msgstr "파일이 제출되지 않았습니다."
|
msgstr "파일이 제출되지 않았습니다."
|
||||||
|
|
||||||
#: fields.py:1515
|
#: fields.py:1508
|
||||||
msgid ""
|
msgid "The submitted data was not a file. Check the encoding type on the form."
|
||||||
"The submitted data was not a file. Check the encoding type on the form."
|
|
||||||
msgstr "제출된 데이터는 파일이 아닙니다. 제출된 서식의 인코딩 형식을 확인하세요."
|
msgstr "제출된 데이터는 파일이 아닙니다. 제출된 서식의 인코딩 형식을 확인하세요."
|
||||||
|
|
||||||
#: fields.py:1516
|
#: fields.py:1509
|
||||||
msgid "No filename could be determined."
|
msgid "No filename could be determined."
|
||||||
msgstr "파일명을 알 수 없습니다."
|
msgstr "파일명을 알 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:1517
|
#: fields.py:1510
|
||||||
msgid "The submitted file is empty."
|
msgid "The submitted file is empty."
|
||||||
msgstr "제출한 파일이 비어있습니다."
|
msgstr "제출한 파일이 비어있습니다."
|
||||||
|
|
||||||
#: fields.py:1518
|
#: fields.py:1511
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||||
msgstr "이 파일명의 글자수가 최대 {max_length}를 넘지 않는지 확인하십시오. (이것은 {length}가 있습니다)."
|
msgstr "이 파일명의 글자수가 최대 {max_length}자를 넘지 않는지 확인하세요. (현재 {length}자입니다)."
|
||||||
|
|
||||||
#: fields.py:1566
|
#: fields.py:1559
|
||||||
msgid ""
|
msgid ""
|
||||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||||
"corrupted image."
|
"corrupted image."
|
||||||
msgstr "유효한 이미지 파일을 업로드 하십시오. 업로드 하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다."
|
msgstr "유효한 이미지 파일을 업로드하세요. 업로드하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다."
|
||||||
|
|
||||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
#: fields.py:1597 relations.py:487 serializers.py:594
|
||||||
msgid "This list may not be empty."
|
msgid "This list may not be empty."
|
||||||
msgstr "이 리스트는 비워 둘 수 없습니다."
|
msgstr "이 리스트는 비워 둘 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:1605
|
#: fields.py:1598 serializers.py:596
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "이 필드가 최소 {min_length} 개의 요소를 가지는지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1599 serializers.py:595
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "이 필드가 최대 {max_length} 개의 요소를 가지는지 확인하세요."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1677
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||||
msgstr "아이템 딕셔너리가 예상되었으나 \"{input_type}\" 타입을 받았습니다."
|
msgstr "아이템 딕셔너리가 예상되었으나 \"{input_type}\" 타입을 받았습니다."
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1678
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "이 딕셔너리는 비어있을 수 없습니다."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1750
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Value 는 유효한 JSON형식이어야 합니다."
|
msgstr "유효한 JSON 값이어야 합니다."
|
||||||
|
|
||||||
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
#: filters.py:72 templates/rest_framework/filters/search.html:2
|
||||||
|
#: templates/rest_framework/filters/search.html:8
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "검색"
|
msgstr "검색"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:73
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "검색어."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:224 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
msgstr "순서"
|
msgstr "순서"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:225
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "결과 정렬 시 사용할 필드."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:341
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
msgstr "오름차순"
|
msgstr "오름차순"
|
||||||
|
|
||||||
#: filters.py:288
|
#: filters.py:342
|
||||||
msgid "descending"
|
msgid "descending"
|
||||||
msgstr "내림차순"
|
msgstr "내림차순"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:180
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "페이지네이션된 결과 집합 내의 페이지 번호."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:185 pagination.py:382 pagination.py:599
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "페이지당 반환할 결과 수."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:195
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
msgstr "페이지가 유효하지 않습니다."
|
msgstr "페이지가 유효하지 않습니다."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:384
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "결과를 반환할 초기 인덱스."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:590
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "페이지네이션 커서 값."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:592
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
msgstr "커서(cursor)가 유효하지 않습니다."
|
msgstr "커서가 유효하지 않습니다."
|
||||||
|
|
||||||
#: relations.py:246
|
#: relations.py:241
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||||
msgstr "유효하지 않은 pk \"{pk_value}\" - 객체가 존재하지 않습니다."
|
msgstr "유효하지 않은 pk \"{pk_value}\" - 객체가 존재하지 않습니다."
|
||||||
|
|
||||||
#: relations.py:247
|
#: relations.py:242
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||||
msgstr "잘못된 형식입니다. pk 값 대신 {data_type}를 받았습니다."
|
msgstr "잘못된 형식입니다. pk 값이 예상되었지만, {data_type}을(를) 받았습니다."
|
||||||
|
|
||||||
#: relations.py:280
|
#: relations.py:277
|
||||||
msgid "Invalid hyperlink - No URL match."
|
msgid "Invalid hyperlink - No URL match."
|
||||||
msgstr "유효하지 않은 하이퍼링크 - 일치하는 URL이 없습니다."
|
msgstr "유효하지 않은 하이퍼링크 - 일치하는 URL이 없습니다."
|
||||||
|
|
||||||
#: relations.py:281
|
#: relations.py:278
|
||||||
msgid "Invalid hyperlink - Incorrect URL match."
|
msgid "Invalid hyperlink - Incorrect URL match."
|
||||||
msgstr "유효하지 않은 하이퍼링크 - URL이 일치하지 않습니다."
|
msgstr "유효하지 않은 하이퍼링크 - URL이 일치하지 않습니다."
|
||||||
|
|
||||||
#: relations.py:282
|
#: relations.py:279
|
||||||
msgid "Invalid hyperlink - Object does not exist."
|
msgid "Invalid hyperlink - Object does not exist."
|
||||||
msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않습니다."
|
msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않습니다."
|
||||||
|
|
||||||
#: relations.py:283
|
#: relations.py:280
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||||
msgstr "잘못된 형식입니다. URL 문자열을 예상했으나 {data_type}을 받았습니다."
|
msgstr "잘못된 형식입니다. URL 문자열을 예상했으나 {data_type}을 받았습니다."
|
||||||
|
|
||||||
#: relations.py:448
|
#: relations.py:445
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Object with {slug_name}={value} does not exist."
|
msgid "Object with {slug_name}={value} does not exist."
|
||||||
msgstr "{slug_name}={value} 객체가 존재하지 않습니다."
|
msgstr "{slug_name}={value} 객체가 존재하지 않습니다."
|
||||||
|
|
||||||
#: relations.py:449
|
#: relations.py:446
|
||||||
msgid "Invalid value."
|
msgid "Invalid value."
|
||||||
msgstr "값이 유효하지 않습니다."
|
msgstr "값이 유효하지 않습니다."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "고유한 정수 값"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "UUID 문자열"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "고유한 값"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "{name}을 식별하는 {value_type}."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:340
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid data. Expected a dictionary, but got {datatype}."
|
msgid "Invalid data. Expected a dictionary, but got {datatype}."
|
||||||
msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}를 받았습니다."
|
msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}를 받았습니다."
|
||||||
|
@ -483,7 +491,7 @@ msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "추가 Action들"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -492,33 +500,33 @@ msgstr "필터"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "네비게이션 바"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "콘텐츠"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "요청 폼"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "메인 콘텐츠"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "요청 정보"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "응답 정보"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
#: templates/rest_framework/vertical/radio.html:3
|
#: templates/rest_framework/vertical/radio.html:3
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr "없음"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/select_multiple.html:4
|
#: templates/rest_framework/horizontal/select_multiple.html:4
|
||||||
#: templates/rest_framework/inline/select_multiple.html:3
|
#: templates/rest_framework/inline/select_multiple.html:3
|
||||||
|
@ -528,49 +536,49 @@ msgstr "선택할 아이템이 없습니다."
|
||||||
|
|
||||||
#: validators.py:39
|
#: validators.py:39
|
||||||
msgid "This field must be unique."
|
msgid "This field must be unique."
|
||||||
msgstr "이 필드는 반드시 고유(unique)해야 합니다."
|
msgstr "이 필드는 반드시 고유해야 합니다."
|
||||||
|
|
||||||
#: validators.py:89
|
#: validators.py:98
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "The fields {field_names} must make a unique set."
|
msgid "The fields {field_names} must make a unique set."
|
||||||
msgstr "필드 {field_names} 는 반드시 고유(unique)해야 합니다."
|
msgstr "필드 {field_names} 는 반드시 고유해야 합니다."
|
||||||
|
|
||||||
#: validators.py:171
|
#: validators.py:200
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "대체(surrogate) 문자는 허용되지 않습니다: U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:290
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "This field must be unique for the \"{date_field}\" date."
|
msgid "This field must be unique for the \"{date_field}\" date."
|
||||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 날짜를 갖습니다."
|
msgstr "이 필드는 \"{date_field}\" 날짜에 대해 고유해야 합니다."
|
||||||
|
|
||||||
#: validators.py:258
|
#: validators.py:305
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "This field must be unique for the \"{date_field}\" month."
|
msgid "This field must be unique for the \"{date_field}\" month."
|
||||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 월을 갖습니다. "
|
msgstr "이 필드는 \"{date_field}\" 월에 대해 고유해야 합니다."
|
||||||
|
|
||||||
#: validators.py:271
|
#: validators.py:318
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "This field must be unique for the \"{date_field}\" year."
|
msgid "This field must be unique for the \"{date_field}\" year."
|
||||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 년을 갖습니다. "
|
msgstr "이 필드는 \"{date_field}\" 연도에 대해 고유해야 합니다."
|
||||||
|
|
||||||
#: versioning.py:40
|
#: versioning.py:40
|
||||||
msgid "Invalid version in \"Accept\" header."
|
msgid "Invalid version in \"Accept\" header."
|
||||||
msgstr "\"Accept\" 헤더(header)의 버전이 유효하지 않습니다."
|
msgstr "\"Accept\" 헤더의 버전이 유효하지 않습니다."
|
||||||
|
|
||||||
#: versioning.py:71
|
#: versioning.py:71
|
||||||
msgid "Invalid version in URL path."
|
msgid "Invalid version in URL path."
|
||||||
msgstr "URL path의 버전이 유효하지 않습니다."
|
msgstr "URL 경로의 버전이 유효하지 않습니다."
|
||||||
|
|
||||||
#: versioning.py:116
|
#: versioning.py:118
|
||||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||||
msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임스페이스와 일치하지 않습니다."
|
msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임스페이스와 일치하지 않습니다."
|
||||||
|
|
||||||
#: versioning.py:148
|
#: versioning.py:150
|
||||||
msgid "Invalid version in hostname."
|
msgid "Invalid version in hostname."
|
||||||
msgstr "hostname 내 버전이 유효하지 않습니다."
|
msgstr "hostname 내 버전이 유효하지 않습니다."
|
||||||
|
|
||||||
#: versioning.py:170
|
#: versioning.py:172
|
||||||
msgid "Invalid version in query parameter."
|
msgid "Invalid version in query parameter."
|
||||||
msgstr "쿼리 파라메터 내 버전이 유효하지 않습니다."
|
msgstr "쿼리 파라메터 내 버전이 유효하지 않습니다."
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
# Hugo Leonardo Chalhoub Mendonça <hugoleonardocm@live.com>, 2015
|
# Hugo Leonardo Chalhoub Mendonça <hugoleonardocm@live.com>, 2015
|
||||||
# Jonatas Baldin <jonatas.baldin@gmail.com>, 2017
|
# Jonatas Baldin <jonatas.baldin@gmail.com>, 2017
|
||||||
# Gabriel Mitelman Tkacz <gmtkacz@proton.me>, 2024
|
# Gabriel Mitelman Tkacz <gmtkacz@proton.me>, 2024
|
||||||
|
# Matheus Oliveira <moliveiracdev@gmail.com>, 2025
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
|
@ -200,17 +201,17 @@ msgstr "Este valor não corresponde ao padrão exigido."
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||||
"hyphens."
|
"hyphens."
|
||||||
msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
|
msgstr "Insira um \"slug\" válido que consista de letras, números, sublinhados ou hífens."
|
||||||
|
|
||||||
#: fields.py:839
|
#: fields.py:839
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
"or hyphens."
|
"or hyphens."
|
||||||
msgstr "Digite um \"slug\" válido que consista de letras Unicode, números, sublinhados ou hífens."
|
msgstr "Insira um \"slug\" válido que consista de letras Unicode, números, sublinhados ou hífens."
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
msgstr "Entrar um URL válido."
|
msgstr "Insira um URL válido."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
|
@ -290,12 +291,12 @@ msgstr "Necessário uma data mas recebeu uma data e hora."
|
||||||
#: fields.py:1303
|
#: fields.py:1303
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Time has wrong format. Use one of these formats instead: {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
|
#: fields.py:1365
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Duration has wrong format. Use one of these formats instead: {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
|
#: fields.py:1399 fields.py:1456
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -348,7 +349,7 @@ msgstr "Certifique-se de que o nome do arquivo tem menos de {max_length} caracte
|
||||||
msgid ""
|
msgid ""
|
||||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||||
"corrupted image."
|
"corrupted image."
|
||||||
msgstr "Fazer upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido."
|
msgstr "Faça 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
|
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||||
msgid "This list may not be empty."
|
msgid "This list may not be empty."
|
||||||
|
@ -375,7 +376,7 @@ msgstr "Este dicionário não pode estar vazio."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Valor devo ser JSON válido."
|
msgstr "Valor deve ser JSON válido."
|
||||||
|
|
||||||
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
|
@ -395,11 +396,11 @@ msgstr "Qual campo usar ao ordenar os resultados."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
msgstr "ascendente"
|
msgstr "crescente"
|
||||||
|
|
||||||
#: filters.py:288
|
#: filters.py:288
|
||||||
msgid "descending"
|
msgid "descending"
|
||||||
msgstr "descendente"
|
msgstr "decrescente"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
# Murat Çorlu <muratcorlu@me.com>, 2015
|
# Murat Çorlu <muratcorlu@me.com>, 2015
|
||||||
# Recep KIRMIZI <rkirmizi@gmail.com>, 2015
|
# Recep KIRMIZI <rkirmizi@gmail.com>, 2015
|
||||||
# Ülgen Sarıkavak <ulgensrkvk@gmail.com>, 2015
|
# Ülgen Sarıkavak <ulgensrkvk@gmail.com>, 2015
|
||||||
|
# Sezer BOZKIR <natgho@hotmail.com>, 2025
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django REST framework\n"
|
"Project-Id-Version: Django REST framework\n"
|
||||||
|
@ -108,7 +109,7 @@ msgstr "Sunucu hatası oluştu."
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "Geçersiz girdi."
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -151,12 +152,12 @@ msgstr "Üst üste çok fazla istek yapıldı."
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "{wait} saniye içinde erişilebilir olması bekleniyor."
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "{wait} saniye içinde erişilebilir olması bekleniyor."
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -169,11 +170,11 @@ msgstr "Bu alan boş bırakılmamalı."
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "Geçerli bir boolean olmalı."
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "Geçerli bir string değil."
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -215,7 +216,7 @@ msgstr "Geçerli bir URL girin."
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "Geçerli bir UUID olmalı."
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -273,11 +274,11 @@ msgstr "Datetime değeri bekleniyor, ama date değeri geldi."
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "\"{timezone}\" zaman dilimi için geçersiz datetime."
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "Datetime değeri aralığın dışında."
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -358,12 +359,12 @@ msgstr "Bu liste boş olmamalı."
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "Bu alanın en az {min_length} eleman içerdiğinden emin olun."
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "Bu alanın en fazla {max_length} eleman içerdiğinden emin olun."
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -372,7 +373,7 @@ msgstr "Sözlük tipi bir değişken beklenirken \"{input_type}\" tipi bir deği
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "Bu sözlük boş olmamalı."
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -384,7 +385,7 @@ msgstr "Arama"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "Bir arama terimi."
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -392,23 +393,23 @@ msgstr "Sıralama"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "Sonuçların sıralanmasında kullanılacak alan."
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
msgstr ""
|
msgstr "artan"
|
||||||
|
|
||||||
#: filters.py:288
|
#: filters.py:288
|
||||||
msgid "descending"
|
msgid "descending"
|
||||||
msgstr ""
|
msgstr "azalan"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "Sayfalanmış sonuç kümesinde bir sayfa numarası."
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "Her sayfada döndürülecek sonuç sayısı."
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -416,11 +417,11 @@ msgstr "Geçersiz sayfa."
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "Döndürülecek sonuçların başlangıç indeksi."
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "Sayfalandırma imleci değeri."
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -464,20 +465,20 @@ msgstr "Geçersiz değer."
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "benzersiz tamsayı değeri"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "UUID metni"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "benzersiz değer"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "Bir {name} öğesini tanımlayan {value_type}."
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -487,7 +488,7 @@ msgstr "Geçersiz veri. Sözlük bekleniyordu fakat {datatype} geldi. "
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "Ekstra Eylemler"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -496,27 +497,27 @@ msgstr "Filtreler"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "navigasyon çubuğu"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "içerik"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "istek formu"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "ana içerik"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "istek bilgisi"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "cevap bilgisi"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -542,7 +543,7 @@ msgstr "{field_names} hep birlikte eşsiz bir küme oluşturmalılar."
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "Yerine konulmuş karakterlere izin verilmiyor: U+{code_point:X}."
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -569,7 +570,7 @@ msgstr "URL dizininde geçersiz versiyon."
|
||||||
|
|
||||||
#: versioning.py:116
|
#: versioning.py:116
|
||||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||||
msgstr ""
|
msgstr "Geçersiz versiyon URL dizininde. Hiçbir versiyon ad alanı ile eşleşmiyor."
|
||||||
|
|
||||||
#: versioning.py:148
|
#: versioning.py:148
|
||||||
msgid "Invalid version in hostname."
|
msgid "Invalid version in hostname."
|
||||||
|
|
|
@ -104,7 +104,7 @@ msgstr "服务器出现了错误。"
|
||||||
|
|
||||||
#: exceptions.py:142
|
#: exceptions.py:142
|
||||||
msgid "Invalid input."
|
msgid "Invalid input."
|
||||||
msgstr ""
|
msgstr "无效的输入。"
|
||||||
|
|
||||||
#: exceptions.py:161
|
#: exceptions.py:161
|
||||||
msgid "Malformed request."
|
msgid "Malformed request."
|
||||||
|
@ -142,17 +142,17 @@ msgstr "不支持请求中的媒体类型 “{media_type}”。"
|
||||||
|
|
||||||
#: exceptions.py:223
|
#: exceptions.py:223
|
||||||
msgid "Request was throttled."
|
msgid "Request was throttled."
|
||||||
msgstr "请求超过了限速。"
|
msgstr "请求已被限流。"
|
||||||
|
|
||||||
#: exceptions.py:224
|
#: exceptions.py:224
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} second."
|
msgid "Expected available in {wait} second."
|
||||||
msgstr ""
|
msgstr "预计 {wait} 秒后可用。"
|
||||||
|
|
||||||
#: exceptions.py:225
|
#: exceptions.py:225
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Expected available in {wait} seconds."
|
msgid "Expected available in {wait} seconds."
|
||||||
msgstr ""
|
msgstr "预计 {wait} 秒后可用。"
|
||||||
|
|
||||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||||
#: validators.py:183
|
#: validators.py:183
|
||||||
|
@ -165,11 +165,11 @@ msgstr "该字段不能为 null。"
|
||||||
|
|
||||||
#: fields.py:701
|
#: fields.py:701
|
||||||
msgid "Must be a valid boolean."
|
msgid "Must be a valid boolean."
|
||||||
msgstr ""
|
msgstr "必须是有效的布尔值。"
|
||||||
|
|
||||||
#: fields.py:766
|
#: fields.py:766
|
||||||
msgid "Not a valid string."
|
msgid "Not a valid string."
|
||||||
msgstr ""
|
msgstr "不是有效的字符串。"
|
||||||
|
|
||||||
#: fields.py:767
|
#: fields.py:767
|
||||||
msgid "This field may not be blank."
|
msgid "This field may not be blank."
|
||||||
|
@ -203,7 +203,7 @@ msgstr "请输入合法的“短语“,只能包含字母,数字,下划线
|
||||||
msgid ""
|
msgid ""
|
||||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||||
"or hyphens."
|
"or hyphens."
|
||||||
msgstr ""
|
msgstr "请输入有效的“slug”,由 Unicode 字母、数字、下划线或连字符组成。"
|
||||||
|
|
||||||
#: fields.py:854
|
#: fields.py:854
|
||||||
msgid "Enter a valid URL."
|
msgid "Enter a valid URL."
|
||||||
|
@ -211,7 +211,7 @@ msgstr "请输入合法的URL。"
|
||||||
|
|
||||||
#: fields.py:867
|
#: fields.py:867
|
||||||
msgid "Must be a valid UUID."
|
msgid "Must be a valid UUID."
|
||||||
msgstr ""
|
msgstr "必须是有效的 UUID。"
|
||||||
|
|
||||||
#: fields.py:903
|
#: fields.py:903
|
||||||
msgid "Enter a valid IPv4 or IPv6 address."
|
msgid "Enter a valid IPv4 or IPv6 address."
|
||||||
|
@ -269,11 +269,11 @@ msgstr "期望为日期时间,获得的是日期。"
|
||||||
#: fields.py:1150
|
#: fields.py:1150
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||||
msgstr ""
|
msgstr "时区“{timezone}”的时间格式无效。"
|
||||||
|
|
||||||
#: fields.py:1151
|
#: fields.py:1151
|
||||||
msgid "Datetime value out of range."
|
msgid "Datetime value out of range."
|
||||||
msgstr ""
|
msgstr "时间数值超出有效范围。"
|
||||||
|
|
||||||
#: fields.py:1236
|
#: fields.py:1236
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -354,12 +354,12 @@ msgstr "列表不能为空。"
|
||||||
#: fields.py:1605
|
#: fields.py:1605
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has at least {min_length} elements."
|
msgid "Ensure this field has at least {min_length} elements."
|
||||||
msgstr ""
|
msgstr "该字段必须包含至少 {min_length} 个元素。"
|
||||||
|
|
||||||
#: fields.py:1606
|
#: fields.py:1606
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Ensure this field has no more than {max_length} elements."
|
msgid "Ensure this field has no more than {max_length} elements."
|
||||||
msgstr ""
|
msgstr "该字段不能超过 {max_length} 个元素。"
|
||||||
|
|
||||||
#: fields.py:1682
|
#: fields.py:1682
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -368,7 +368,7 @@ msgstr "期望是包含类目的字典,得到类型为 “{input_type}”。"
|
||||||
|
|
||||||
#: fields.py:1683
|
#: fields.py:1683
|
||||||
msgid "This dictionary may not be empty."
|
msgid "This dictionary may not be empty."
|
||||||
msgstr ""
|
msgstr "该字典不能为空。"
|
||||||
|
|
||||||
#: fields.py:1755
|
#: fields.py:1755
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
|
@ -380,7 +380,7 @@ msgstr " 搜索"
|
||||||
|
|
||||||
#: filters.py:50
|
#: filters.py:50
|
||||||
msgid "A search term."
|
msgid "A search term."
|
||||||
msgstr ""
|
msgstr "搜索关键词。"
|
||||||
|
|
||||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||||
msgid "Ordering"
|
msgid "Ordering"
|
||||||
|
@ -388,7 +388,7 @@ msgstr "排序"
|
||||||
|
|
||||||
#: filters.py:181
|
#: filters.py:181
|
||||||
msgid "Which field to use when ordering the results."
|
msgid "Which field to use when ordering the results."
|
||||||
msgstr ""
|
msgstr "用于排序结果的字段。"
|
||||||
|
|
||||||
#: filters.py:287
|
#: filters.py:287
|
||||||
msgid "ascending"
|
msgid "ascending"
|
||||||
|
@ -400,11 +400,11 @@ msgstr "倒排序"
|
||||||
|
|
||||||
#: pagination.py:174
|
#: pagination.py:174
|
||||||
msgid "A page number within the paginated result set."
|
msgid "A page number within the paginated result set."
|
||||||
msgstr ""
|
msgstr "分页结果集中的页码。"
|
||||||
|
|
||||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||||
msgid "Number of results to return per page."
|
msgid "Number of results to return per page."
|
||||||
msgstr ""
|
msgstr "每页返回的结果数量。"
|
||||||
|
|
||||||
#: pagination.py:189
|
#: pagination.py:189
|
||||||
msgid "Invalid page."
|
msgid "Invalid page."
|
||||||
|
@ -412,11 +412,11 @@ msgstr "无效页面。"
|
||||||
|
|
||||||
#: pagination.py:374
|
#: pagination.py:374
|
||||||
msgid "The initial index from which to return the results."
|
msgid "The initial index from which to return the results."
|
||||||
msgstr ""
|
msgstr "返回结果的起始索引位置。"
|
||||||
|
|
||||||
#: pagination.py:581
|
#: pagination.py:581
|
||||||
msgid "The pagination cursor value."
|
msgid "The pagination cursor value."
|
||||||
msgstr ""
|
msgstr "分页游标值"
|
||||||
|
|
||||||
#: pagination.py:583
|
#: pagination.py:583
|
||||||
msgid "Invalid cursor"
|
msgid "Invalid cursor"
|
||||||
|
@ -460,20 +460,20 @@ msgstr "无效值。"
|
||||||
|
|
||||||
#: schemas/utils.py:32
|
#: schemas/utils.py:32
|
||||||
msgid "unique integer value"
|
msgid "unique integer value"
|
||||||
msgstr ""
|
msgstr "唯一整数值"
|
||||||
|
|
||||||
#: schemas/utils.py:34
|
#: schemas/utils.py:34
|
||||||
msgid "UUID string"
|
msgid "UUID string"
|
||||||
msgstr ""
|
msgstr "UUID 字符串"
|
||||||
|
|
||||||
#: schemas/utils.py:36
|
#: schemas/utils.py:36
|
||||||
msgid "unique value"
|
msgid "unique value"
|
||||||
msgstr ""
|
msgstr "唯一值"
|
||||||
|
|
||||||
#: schemas/utils.py:38
|
#: schemas/utils.py:38
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "A {value_type} identifying this {name}."
|
msgid "A {value_type} identifying this {name}."
|
||||||
msgstr ""
|
msgstr "标识此 {name} 的 {value_type}。"
|
||||||
|
|
||||||
#: serializers.py:337
|
#: serializers.py:337
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -483,7 +483,7 @@ msgstr "无效数据。期待为字典类型,得到的是 {datatype} 。"
|
||||||
#: templates/rest_framework/admin.html:116
|
#: templates/rest_framework/admin.html:116
|
||||||
#: templates/rest_framework/base.html:136
|
#: templates/rest_framework/base.html:136
|
||||||
msgid "Extra Actions"
|
msgid "Extra Actions"
|
||||||
msgstr ""
|
msgstr "扩展操作"
|
||||||
|
|
||||||
#: templates/rest_framework/admin.html:130
|
#: templates/rest_framework/admin.html:130
|
||||||
#: templates/rest_framework/base.html:150
|
#: templates/rest_framework/base.html:150
|
||||||
|
@ -492,27 +492,27 @@ msgstr "过滤器"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:37
|
#: templates/rest_framework/base.html:37
|
||||||
msgid "navbar"
|
msgid "navbar"
|
||||||
msgstr ""
|
msgstr "导航栏"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:75
|
#: templates/rest_framework/base.html:75
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr ""
|
msgstr "内容主体"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:78
|
#: templates/rest_framework/base.html:78
|
||||||
msgid "request form"
|
msgid "request form"
|
||||||
msgstr ""
|
msgstr "请求表单"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:157
|
#: templates/rest_framework/base.html:157
|
||||||
msgid "main content"
|
msgid "main content"
|
||||||
msgstr ""
|
msgstr "主要内容区"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:173
|
#: templates/rest_framework/base.html:173
|
||||||
msgid "request info"
|
msgid "request info"
|
||||||
msgstr ""
|
msgstr "请求信息"
|
||||||
|
|
||||||
#: templates/rest_framework/base.html:177
|
#: templates/rest_framework/base.html:177
|
||||||
msgid "response info"
|
msgid "response info"
|
||||||
msgstr ""
|
msgstr "响应信息"
|
||||||
|
|
||||||
#: templates/rest_framework/horizontal/radio.html:4
|
#: templates/rest_framework/horizontal/radio.html:4
|
||||||
#: templates/rest_framework/inline/radio.html:3
|
#: templates/rest_framework/inline/radio.html:3
|
||||||
|
@ -538,7 +538,7 @@ msgstr "字段 {field_names} 必须能构成唯一集合。"
|
||||||
#: validators.py:171
|
#: validators.py:171
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||||
msgstr ""
|
msgstr "不允许使用代理字符: U+{code_point:X}。"
|
||||||
|
|
||||||
#: validators.py:243
|
#: validators.py:243
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
|
|
@ -65,7 +65,7 @@ class DefaultContentNegotiation(BaseContentNegotiation):
|
||||||
full_media_type = ';'.join(
|
full_media_type = ';'.join(
|
||||||
(renderer.media_type,) +
|
(renderer.media_type,) +
|
||||||
tuple(
|
tuple(
|
||||||
'{}={}'.format(key, value)
|
f'{key}={value}'
|
||||||
for key, value in media_type_wrapper.params.items()
|
for key, value in media_type_wrapper.params.items()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -225,7 +225,7 @@ class DjangoModelPermissions(BasePermission):
|
||||||
if hasattr(view, 'get_queryset'):
|
if hasattr(view, 'get_queryset'):
|
||||||
queryset = view.get_queryset()
|
queryset = view.get_queryset()
|
||||||
assert queryset is not None, (
|
assert queryset is not None, (
|
||||||
'{}.get_queryset() returned None'.format(view.__class__.__name__)
|
f'{view.__class__.__name__}.get_queryset() returned None'
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
return view.queryset
|
return view.queryset
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Response(SimpleTemplateResponse):
|
||||||
content_type = self.content_type
|
content_type = self.content_type
|
||||||
|
|
||||||
if content_type is None and charset is not None:
|
if content_type is None and charset is not None:
|
||||||
content_type = "{}; charset={}".format(media_type, charset)
|
content_type = f"{media_type}; charset={charset}"
|
||||||
elif content_type is None:
|
elif content_type is None:
|
||||||
content_type = media_type
|
content_type = media_type
|
||||||
self['Content-Type'] = content_type
|
self['Content-Type'] = content_type
|
||||||
|
|
|
@ -68,7 +68,7 @@ class LinkNode(dict):
|
||||||
current_val = self.methods_counter[preferred_key]
|
current_val = self.methods_counter[preferred_key]
|
||||||
self.methods_counter[preferred_key] += 1
|
self.methods_counter[preferred_key] += 1
|
||||||
|
|
||||||
key = '{}_{}'.format(preferred_key, current_val)
|
key = f'{preferred_key}_{current_val}'
|
||||||
if key not in self:
|
if key not in self:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ from django.core.validators import (
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from rest_framework import (
|
from rest_framework import exceptions, renderers, serializers
|
||||||
RemovedInDRF316Warning, exceptions, renderers, serializers
|
|
||||||
)
|
|
||||||
from rest_framework.compat import inflection, uritemplate
|
from rest_framework.compat import inflection, uritemplate
|
||||||
from rest_framework.fields import _UnvalidatedField, empty
|
from rest_framework.fields import _UnvalidatedField, empty
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -84,7 +82,7 @@ class SchemaGenerator(BaseSchemaGenerator):
|
||||||
continue
|
continue
|
||||||
if components_schemas[k] == components[k]:
|
if components_schemas[k] == components[k]:
|
||||||
continue
|
continue
|
||||||
warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k))
|
warnings.warn(f'Schema component "{k}" has been overridden with a different value.')
|
||||||
|
|
||||||
components_schemas.update(components)
|
components_schemas.update(components)
|
||||||
|
|
||||||
|
@ -430,7 +428,7 @@ class AutoSchema(ViewInspector):
|
||||||
}
|
}
|
||||||
|
|
||||||
# "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
|
# "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
|
||||||
# see: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types
|
# see: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#data-types
|
||||||
# see also: https://swagger.io/docs/specification/data-models/data-types/#string
|
# see also: https://swagger.io/docs/specification/data-models/data-types/#string
|
||||||
if isinstance(field, serializers.EmailField):
|
if isinstance(field, serializers.EmailField):
|
||||||
return {
|
return {
|
||||||
|
@ -557,7 +555,7 @@ class AutoSchema(ViewInspector):
|
||||||
"""
|
"""
|
||||||
for v in field.validators:
|
for v in field.validators:
|
||||||
# "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
|
# "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
|
||||||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types
|
# https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md#data-types
|
||||||
if isinstance(v, EmailValidator):
|
if isinstance(v, EmailValidator):
|
||||||
schema['format'] = 'email'
|
schema['format'] = 'email'
|
||||||
if isinstance(v, URLValidator):
|
if isinstance(v, URLValidator):
|
||||||
|
@ -646,7 +644,7 @@ class AutoSchema(ViewInspector):
|
||||||
return self.get_serializer(path, method)
|
return self.get_serializer(path, method)
|
||||||
|
|
||||||
def get_reference(self, serializer):
|
def get_reference(self, serializer):
|
||||||
return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))}
|
return {'$ref': f'#/components/schemas/{self.get_component_name(serializer)}'}
|
||||||
|
|
||||||
def get_request_body(self, path, method):
|
def get_request_body(self, path, method):
|
||||||
if method not in ('PUT', 'PATCH', 'POST'):
|
if method not in ('PUT', 'PATCH', 'POST'):
|
||||||
|
@ -721,11 +719,3 @@ class AutoSchema(ViewInspector):
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
|
|
||||||
return [path.split('/')[0].replace('_', '-')]
|
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.16.",
|
|
||||||
RemovedInDRF316Warning, stacklevel=2
|
|
||||||
)
|
|
||||||
return self.get_reference(serializer)
|
|
||||||
|
|
|
@ -26,7 +26,9 @@ from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from rest_framework.compat import postgres_fields
|
from rest_framework.compat import (
|
||||||
|
get_referenced_base_fields_from_q, postgres_fields
|
||||||
|
)
|
||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||||
from rest_framework.fields import get_error_detail
|
from rest_framework.fields import get_error_detail
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -1088,6 +1090,13 @@ class ModelSerializer(Serializer):
|
||||||
# Determine the fields that should be included on the serializer.
|
# Determine the fields that should be included on the serializer.
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
|
# If it's a ManyToMany field, and the default is None, then raises an exception to prevent exceptions on .set()
|
||||||
|
for field_name in declared_fields.keys():
|
||||||
|
if field_name in info.relations and info.relations[field_name].to_many and declared_fields[field_name].default is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"The field '{field_name}' on serializer '{self.__class__.__name__}' is a ManyToMany field and cannot have a default value of None."
|
||||||
|
)
|
||||||
|
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
# If the field is explicitly declared on the class then use that.
|
# If the field is explicitly declared on the class then use that.
|
||||||
if field_name in declared_fields:
|
if field_name in declared_fields:
|
||||||
|
@ -1425,20 +1434,20 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
def get_unique_together_constraints(self, model):
|
def get_unique_together_constraints(self, model):
|
||||||
"""
|
"""
|
||||||
Returns iterator of (fields, queryset), each entry describes an unique together
|
Returns iterator of (fields, queryset, condition_fields, condition),
|
||||||
constraint on `fields` in `queryset`.
|
each entry describes an unique together constraint on `fields` in `queryset`
|
||||||
|
with respect of constraint's `condition`.
|
||||||
"""
|
"""
|
||||||
for parent_class in [model] + list(model._meta.parents):
|
for parent_class in [model] + list(model._meta.parents):
|
||||||
for unique_together in parent_class._meta.unique_together:
|
for unique_together in parent_class._meta.unique_together:
|
||||||
yield unique_together, model._default_manager
|
yield unique_together, model._default_manager, [], None
|
||||||
for constraint in parent_class._meta.constraints:
|
for constraint in parent_class._meta.constraints:
|
||||||
if isinstance(constraint, models.UniqueConstraint) and len(constraint.fields) > 1:
|
if isinstance(constraint, models.UniqueConstraint) and len(constraint.fields) > 1:
|
||||||
yield (
|
if constraint.condition is None:
|
||||||
constraint.fields,
|
condition_fields = []
|
||||||
model._default_manager
|
else:
|
||||||
if constraint.condition is None
|
condition_fields = list(get_referenced_base_fields_from_q(constraint.condition))
|
||||||
else model._default_manager.filter(constraint.condition)
|
yield (constraint.fields, model._default_manager, condition_fields, constraint.condition)
|
||||||
)
|
|
||||||
|
|
||||||
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
|
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -1467,12 +1476,14 @@ class ModelSerializer(Serializer):
|
||||||
model_field.unique_for_year}
|
model_field.unique_for_year}
|
||||||
|
|
||||||
unique_constraint_names -= {None}
|
unique_constraint_names -= {None}
|
||||||
|
model_fields_names = set(model_fields.keys())
|
||||||
|
|
||||||
# Include each of the `unique_together` and `UniqueConstraint` field names,
|
# Include each of the `unique_together` and `UniqueConstraint` field names,
|
||||||
# so long as all the field names are included on the serializer.
|
# so long as all the field names are included on the serializer.
|
||||||
for unique_together_list, queryset in self.get_unique_together_constraints(model):
|
for unique_together_list, queryset, condition_fields, condition in self.get_unique_together_constraints(model):
|
||||||
if set(field_names).issuperset(unique_together_list):
|
unique_together_list_and_condition_fields = set(unique_together_list) | set(condition_fields)
|
||||||
unique_constraint_names |= set(unique_together_list)
|
if model_fields_names.issuperset(unique_together_list_and_condition_fields):
|
||||||
|
unique_constraint_names |= unique_together_list_and_condition_fields
|
||||||
|
|
||||||
# Now we have all the field names that have uniqueness constraints
|
# Now we have all the field names that have uniqueness constraints
|
||||||
# applied, we can add the extra 'required=...' or 'default=...'
|
# applied, we can add the extra 'required=...' or 'default=...'
|
||||||
|
@ -1490,6 +1501,8 @@ class ModelSerializer(Serializer):
|
||||||
default = timezone.now
|
default = timezone.now
|
||||||
elif unique_constraint_field.has_default():
|
elif unique_constraint_field.has_default():
|
||||||
default = unique_constraint_field.default
|
default = unique_constraint_field.default
|
||||||
|
elif unique_constraint_field.null:
|
||||||
|
default = None
|
||||||
else:
|
else:
|
||||||
default = empty
|
default = empty
|
||||||
|
|
||||||
|
@ -1563,6 +1576,17 @@ class ModelSerializer(Serializer):
|
||||||
self.get_unique_for_date_validators()
|
self.get_unique_for_date_validators()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_constraint_violation_error_message(self, constraint):
|
||||||
|
"""
|
||||||
|
Returns the violation error message for the UniqueConstraint,
|
||||||
|
or None if the message is the default.
|
||||||
|
"""
|
||||||
|
violation_error_message = constraint.get_violation_error_message()
|
||||||
|
default_error_message = constraint.default_violation_error_message % {"name": constraint.name}
|
||||||
|
if violation_error_message == default_error_message:
|
||||||
|
return None
|
||||||
|
return violation_error_message
|
||||||
|
|
||||||
def get_unique_together_validators(self):
|
def get_unique_together_validators(self):
|
||||||
"""
|
"""
|
||||||
Determine a default set of validators for any unique_together constraints.
|
Determine a default set of validators for any unique_together constraints.
|
||||||
|
@ -1589,15 +1613,23 @@ class ModelSerializer(Serializer):
|
||||||
for name, source in field_sources.items():
|
for name, source in field_sources.items():
|
||||||
source_map[source].append(name)
|
source_map[source].append(name)
|
||||||
|
|
||||||
|
unique_constraint_by_fields = {
|
||||||
|
constraint.fields: constraint
|
||||||
|
for model_cls in (*self.Meta.model._meta.parents, self.Meta.model)
|
||||||
|
for constraint in model_cls._meta.constraints
|
||||||
|
if isinstance(constraint, models.UniqueConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
# Note that we make sure to check `unique_together` both on the
|
# Note that we make sure to check `unique_together` both on the
|
||||||
# base model class, but also on any parent classes.
|
# base model class, but also on any parent classes.
|
||||||
validators = []
|
validators = []
|
||||||
for unique_together, queryset in self.get_unique_together_constraints(self.Meta.model):
|
for unique_together, queryset, condition_fields, condition in self.get_unique_together_constraints(self.Meta.model):
|
||||||
# Skip if serializer does not map to all unique together sources
|
# Skip if serializer does not map to all unique together sources
|
||||||
if not set(source_map).issuperset(unique_together):
|
unique_together_and_condition_fields = set(unique_together) | set(condition_fields)
|
||||||
|
if not set(source_map).issuperset(unique_together_and_condition_fields):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for source in unique_together:
|
for source in unique_together_and_condition_fields:
|
||||||
assert len(source_map[source]) == 1, (
|
assert len(source_map[source]) == 1, (
|
||||||
"Unable to create `UniqueTogetherValidator` for "
|
"Unable to create `UniqueTogetherValidator` for "
|
||||||
"`{model}.{field}` as `{serializer}` has multiple "
|
"`{model}.{field}` as `{serializer}` has multiple "
|
||||||
|
@ -1614,9 +1646,17 @@ class ModelSerializer(Serializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
field_names = tuple(source_map[f][0] for f in unique_together)
|
field_names = tuple(source_map[f][0] for f in unique_together)
|
||||||
|
|
||||||
|
constraint = unique_constraint_by_fields.get(tuple(unique_together))
|
||||||
|
violation_error_message = self._get_constraint_violation_error_message(constraint) if constraint else None
|
||||||
|
|
||||||
validator = UniqueTogetherValidator(
|
validator = UniqueTogetherValidator(
|
||||||
queryset=queryset,
|
queryset=queryset,
|
||||||
fields=field_names
|
fields=field_names,
|
||||||
|
condition_fields=tuple(source_map[f][0] for f in condition_fields),
|
||||||
|
condition=condition,
|
||||||
|
message=violation_error_message,
|
||||||
|
code=getattr(constraint, 'violation_error_code', None),
|
||||||
)
|
)
|
||||||
validators.append(validator)
|
validators.append(validator)
|
||||||
return validators
|
return validators
|
||||||
|
|
|
@ -24,7 +24,7 @@ from django.conf import settings
|
||||||
from django.core.signals import setting_changed
|
from django.core.signals import setting_changed
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
from rest_framework import ISO_8601
|
from rest_framework import DJANGO_DURATION_FORMAT, ISO_8601
|
||||||
|
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
# Base API policies
|
# Base API policies
|
||||||
|
@ -109,6 +109,8 @@ DEFAULTS = {
|
||||||
'TIME_FORMAT': ISO_8601,
|
'TIME_FORMAT': ISO_8601,
|
||||||
'TIME_INPUT_FORMATS': [ISO_8601],
|
'TIME_INPUT_FORMATS': [ISO_8601],
|
||||||
|
|
||||||
|
'DURATION_FORMAT': DJANGO_DURATION_FORMAT,
|
||||||
|
|
||||||
# Encoding
|
# Encoding
|
||||||
'UNICODE_JSON': True,
|
'UNICODE_JSON': True,
|
||||||
'COMPACT_JSON': True,
|
'COMPACT_JSON': True,
|
||||||
|
|
|
@ -176,7 +176,7 @@
|
||||||
|
|
||||||
<div class="response-info" aria-label="{% trans "response info" %}">
|
<div class="response-info" aria-label="{% trans "response info" %}">
|
||||||
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% for key, val in response_headers|items %}
|
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% for key, val in response_headers|items %}
|
||||||
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize }}</span>{% endfor %}
|
<b>{{ key }}:</b> <span class="lit">{{ val|urlize }}</span>{% endfor %}
|
||||||
|
|
||||||
</span>{{ content|urlize }}</pre>
|
</span>{{ content|urlize }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -313,14 +313,3 @@ def smart_urlquote_wrapper(matched_url):
|
||||||
return smart_urlquote(matched_url)
|
return smart_urlquote(matched_url)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def break_long_headers(header):
|
|
||||||
"""
|
|
||||||
Breaks headers longer than 160 characters (~page length)
|
|
||||||
when possible (are comma separated)
|
|
||||||
"""
|
|
||||||
if len(header) > 160 and ',' in header:
|
|
||||||
header = mark_safe('<br> ' + ', <br>'.join(escape(header).split(',')))
|
|
||||||
return header
|
|
||||||
|
|