Merge branch 'master' into master
1
.github/FUNDING.yml
vendored
|
@ -1 +1,2 @@
|
|||
github: encode
|
||||
custom: https://fund.django-rest-framework.org/topics/funding/
|
||||
|
|
10
.github/ISSUE_TEMPLATE/1-issue.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Issue
|
||||
about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Raised initially as discussion #...
|
||||
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
|
||||
- [ ] I have reduced the issue to the simplest possible case.
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discussions
|
||||
url: https://github.com/encode/django-rest-framework/discussions
|
||||
about: >
|
||||
The "Discussions" forum is where you want to start. 💖
|
2
.gitignore
vendored
|
@ -2,6 +2,8 @@
|
|||
*.db
|
||||
*~
|
||||
.*
|
||||
*.py.bak
|
||||
|
||||
|
||||
/site/
|
||||
/htmlcov/
|
||||
|
|
38
.travis.yml
|
@ -1,31 +1,36 @@
|
|||
language: python
|
||||
cache: pip
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
|
||||
- { python: "3.5", env: DJANGO=1.11 }
|
||||
- { python: "3.5", env: DJANGO=2.0 }
|
||||
- { python: "3.5", env: DJANGO=2.1 }
|
||||
- { python: "3.5", env: DJANGO=2.2 }
|
||||
|
||||
- { python: "3.6", env: DJANGO=1.11 }
|
||||
- { python: "3.6", env: DJANGO=2.0 }
|
||||
- { python: "3.6", env: DJANGO=2.1 }
|
||||
- { python: "3.6", env: DJANGO=2.2 }
|
||||
- { python: "3.6", env: DJANGO=master }
|
||||
- { python: "3.6", env: DJANGO=3.0 }
|
||||
- { python: "3.6", env: DJANGO=3.1 }
|
||||
- { python: "3.6", env: DJANGO=3.2 }
|
||||
|
||||
- { python: "3.7", env: DJANGO=2.0 }
|
||||
- { python: "3.7", env: DJANGO=2.1 }
|
||||
- { python: "3.7", env: DJANGO=2.2 }
|
||||
- { python: "3.7", env: DJANGO=master }
|
||||
- { python: "3.7", env: DJANGO=3.0 }
|
||||
- { python: "3.7", env: DJANGO=3.1 }
|
||||
- { python: "3.7", env: DJANGO=3.2 }
|
||||
|
||||
- { python: "3.7", env: TOXENV=base }
|
||||
- { python: "3.7", env: TOXENV=lint }
|
||||
- { python: "3.7", env: TOXENV=docs }
|
||||
- { python: "3.8", env: DJANGO=3.0 }
|
||||
- { python: "3.8", env: DJANGO=3.1 }
|
||||
- { python: "3.8", env: DJANGO=3.2 }
|
||||
- { python: "3.8", env: DJANGO=master }
|
||||
|
||||
- python: "3.7"
|
||||
- { python: "3.9", env: DJANGO=3.1 }
|
||||
- { python: "3.9", env: DJANGO=3.2 }
|
||||
- { python: "3.9", env: DJANGO=master }
|
||||
|
||||
- { python: "3.8", env: TOXENV=base }
|
||||
- { python: "3.8", env: TOXENV=lint }
|
||||
- { python: "3.8", env: TOXENV=docs }
|
||||
|
||||
- python: "3.8"
|
||||
env: TOXENV=dist
|
||||
script:
|
||||
- python setup.py bdist_wheel
|
||||
|
@ -35,9 +40,10 @@ matrix:
|
|||
|
||||
allow_failures:
|
||||
- env: DJANGO=master
|
||||
- env: DJANGO=3.2
|
||||
|
||||
install:
|
||||
- pip install tox tox-venv tox-travis
|
||||
- pip install tox tox-travis
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
|
9
.tx/config
Normal file
|
@ -0,0 +1,9 @@
|
|||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = sr@latin:sr_Latn, zh-Hans:zh_Hans, zh-Hant:zh_Hant
|
||||
|
||||
[django-rest-framework.djangopo]
|
||||
file_filter = rest_framework/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_file = rest_framework/locale/en_US/LC_MESSAGES/django.po
|
||||
source_lang = en_US
|
||||
type = PO
|
|
@ -1,14 +0,0 @@
|
|||
## Checklist
|
||||
|
||||
- [ ] I have verified that that issue exists against the `master` branch of Django REST framework.
|
||||
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
|
||||
- [ ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
|
||||
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
|
||||
- [ ] I have reduced the issue to the simplest possible case.
|
||||
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
## Expected behavior
|
||||
|
||||
## Actual behavior
|
|
@ -1,5 +1,6 @@
|
|||
include README.md
|
||||
include LICENSE.md
|
||||
recursive-include tests/* *
|
||||
recursive-include rest_framework/static *.js *.css *.png *.ico *.eot *.svg *.ttf *.woff *.woff2
|
||||
recursive-include rest_framework/templates *.html schema.js
|
||||
recursive-include rest_framework/locale *.mo
|
||||
|
|
29
README.md
|
@ -22,12 +22,11 @@ The initial aim is to provide a single full-time position on REST framework.
|
|||
[![][sentry-img]][sentry-url]
|
||||
[![][stream-img]][stream-url]
|
||||
[![][rollbar-img]][rollbar-url]
|
||||
[![][cadre-img]][cadre-url]
|
||||
[![][kloudless-img]][kloudless-url]
|
||||
[![][esg-img]][esg-url]
|
||||
[![][lightson-img]][lightson-url]
|
||||
[![][retool-img]][retool-url]
|
||||
[![][bitio-img]][bitio-url]
|
||||
|
||||
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [ESG][esg-url], and [Lights On Software][lightson-url].
|
||||
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], and [bit.io][bitio-url].
|
||||
|
||||
---
|
||||
|
||||
|
@ -53,8 +52,8 @@ There is a live example API for testing purposes, [available here][sandbox].
|
|||
|
||||
# Requirements
|
||||
|
||||
* Python (3.5, 3.6, 3.7)
|
||||
* Django (1.11, 2.0, 2.1, 2.2)
|
||||
* Python (3.5, 3.6, 3.7, 3.8, 3.9)
|
||||
* Django (2.2, 3.0, 3.1)
|
||||
|
||||
We **highly recommend** and only officially support the latest patch release of
|
||||
each Python and Django series.
|
||||
|
@ -88,7 +87,7 @@ Startup up a new project like so...
|
|||
Now edit the `example/urls.py` module in your project:
|
||||
|
||||
```python
|
||||
from django.conf.urls import url, include
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import serializers, viewsets, routers
|
||||
|
||||
|
@ -113,8 +112,8 @@ router.register(r'users', UserViewSet)
|
|||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
path('', include(router.urls)),
|
||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -190,23 +189,19 @@ Please see the [security policy][security-policy].
|
|||
[funding]: https://fund.django-rest-framework.org/topics/funding/
|
||||
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
||||
|
||||
[rover-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png
|
||||
[sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png
|
||||
[stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png
|
||||
[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png
|
||||
[cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png
|
||||
[load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png
|
||||
[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
|
||||
[esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png
|
||||
[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
|
||||
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png
|
||||
[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png
|
||||
|
||||
[sentry-url]: https://getsentry.com/welcome/
|
||||
[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
|
||||
[rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial
|
||||
[cadre-url]: https://cadre.com/
|
||||
[kloudless-url]: https://hubs.ly/H0f30Lf0
|
||||
[esg-url]: https://software.esg-usa.com/
|
||||
[lightson-url]: https://lightsonsoftware.com
|
||||
[retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship
|
||||
[bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship
|
||||
|
||||
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
||||
|
|
|
@ -60,8 +60,8 @@ using the `APIView` class-based views.
|
|||
|
||||
def get(self, request, format=None):
|
||||
content = {
|
||||
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
||||
'auth': unicode(request.auth), # None
|
||||
'user': str(request.user), # `django.contrib.auth.User` instance.
|
||||
'auth': str(request.auth), # None
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
|
@ -72,8 +72,8 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
@permission_classes([IsAuthenticated])
|
||||
def example_view(request, format=None):
|
||||
content = {
|
||||
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
||||
'auth': unicode(request.auth), # None
|
||||
'user': str(request.user), # `django.contrib.auth.User` instance.
|
||||
'auth': str(request.auth), # None
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
|
@ -199,7 +199,7 @@ When using `TokenAuthentication`, you may want to provide a mechanism for client
|
|||
|
||||
from rest_framework.authtoken import views
|
||||
urlpatterns += [
|
||||
url(r'^api-token-auth/', views.obtain_auth_token)
|
||||
path('api-token-auth/', views.obtain_auth_token)
|
||||
]
|
||||
|
||||
Note that the URL part of the pattern can be whatever you want to use.
|
||||
|
@ -238,7 +238,7 @@ For example, you may return additional user information beyond the `token` value
|
|||
And in your `urls.py`:
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^api-token-auth/', CustomAuthToken.as_view())
|
||||
path('api-token-auth/', CustomAuthToken.as_view())
|
||||
]
|
||||
|
||||
|
||||
|
@ -304,7 +304,7 @@ If successfully authenticated, `RemoteUserAuthentication` provides the following
|
|||
Consult your web server's documentation for information about configuring an authentication method, e.g.:
|
||||
|
||||
* [Apache Authentication How-To](https://httpd.apache.org/docs/2.4/howto/auth.html)
|
||||
* [NGINX (Restricting Access)](https://www.nginx.com/resources/admin-guide/#restricting_access)
|
||||
* [NGINX (Restricting Access)](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/)
|
||||
|
||||
|
||||
# Custom authentication
|
||||
|
@ -357,7 +357,7 @@ The following third party packages are also available.
|
|||
|
||||
## Django OAuth Toolkit
|
||||
|
||||
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
|
||||
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [jazzband][jazzband] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**.
|
||||
|
||||
#### Installation & configuration
|
||||
|
||||
|
@ -410,9 +410,15 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
|||
|
||||
[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system.
|
||||
|
||||
## django-rest-auth
|
||||
## django-rest-auth / dj-rest-auth
|
||||
|
||||
[Django-rest-auth][django-rest-auth] library provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for user management.
|
||||
This library provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for user management.
|
||||
|
||||
|
||||
There are currently two forks of this project.
|
||||
|
||||
* [Django-rest-auth][django-rest-auth] is the original project, [but is not currently receiving updates](https://github.com/Tivix/django-rest-auth/issues/568).
|
||||
* [Dj-rest-auth][dj-rest-auth] is a newer fork of the project.
|
||||
|
||||
## django-rest-framework-social-oauth2
|
||||
|
||||
|
@ -442,7 +448,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
|||
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
|
||||
[oauth-1.0a]: https://oauth.net/core/1.0a/
|
||||
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
|
||||
[evonove]: https://github.com/evonove/
|
||||
[jazzband]: https://github.com/jazzband/
|
||||
[oauthlib]: https://github.com/idan/oauthlib
|
||||
[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt
|
||||
[etoccalino]: https://github.com/etoccalino/
|
||||
|
@ -456,6 +462,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
|
|||
[mac]: https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05
|
||||
[djoser]: https://github.com/sunscrapers/djoser
|
||||
[django-rest-auth]: https://github.com/Tivix/django-rest-auth
|
||||
[dj-rest-auth]: https://github.com/jazzband/dj-rest-auth
|
||||
[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2
|
||||
[django-rest-knox]: https://github.com/James1345/django-rest-knox
|
||||
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
|
||||
|
|
|
@ -17,11 +17,16 @@ other cache decorators such as [`cache_page`][page] and
|
|||
[`vary_on_cookie`][cookie].
|
||||
|
||||
```python
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.vary import vary_on_cookie
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import viewsets
|
||||
|
||||
class UserViewSet(viewsets.Viewset):
|
||||
|
||||
class UserViewSet(viewsets.ViewSet):
|
||||
|
||||
# Cache requested url for each user for 2 hours
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
|
@ -32,6 +37,7 @@ class UserViewSet(viewsets.Viewset):
|
|||
}
|
||||
return Response(content)
|
||||
|
||||
|
||||
class PostView(APIView):
|
||||
|
||||
# Cache page for the requested url
|
||||
|
|
|
@ -38,7 +38,7 @@ Might receive an error response indicating that the `DELETE` method is not allow
|
|||
|
||||
Validation errors are handled slightly differently, and will include the field names as the keys in the response. If the validation error was not specific to a particular field then it will use the "non_field_errors" key, or whatever string value has been set for the `NON_FIELD_ERRORS_KEY` setting.
|
||||
|
||||
Any example validation error might look like this:
|
||||
An example validation error might look like this:
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
Content-Type: application/json
|
||||
|
|
|
@ -50,9 +50,21 @@ If set, this gives the default value that will be used for the field if no input
|
|||
|
||||
The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned.
|
||||
|
||||
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
|
||||
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `requires_context = True` attribute, then the serializer field will be passed as an argument.
|
||||
|
||||
When serializing the instance, default will be used if the the object attribute or dictionary key is not present in the instance.
|
||||
For example:
|
||||
|
||||
class CurrentUserDefault:
|
||||
"""
|
||||
May be applied as a `default=...` value on a serializer field.
|
||||
Returns the current user.
|
||||
"""
|
||||
requires_context = True
|
||||
|
||||
def __call__(self, serializer_field):
|
||||
return serializer_field.context['request'].user
|
||||
|
||||
When serializing the instance, default will be used if the object attribute or dictionary key is not present in the instance.
|
||||
|
||||
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.
|
||||
|
||||
|
@ -359,7 +371,7 @@ Corresponds to `django.db.models.fields.TimeField`
|
|||
* `format` - A string representing the output format. If not specified, this defaults to the same value as the `TIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `time` objects should be returned by `to_representation`. In this case the time encoding will be determined by the renderer.
|
||||
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
|
||||
|
||||
#### `TimeField` format strings
|
||||
#### `TimeField` format strings
|
||||
|
||||
Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
|
||||
|
||||
|
@ -583,9 +595,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o
|
|||
|
||||
The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype.
|
||||
|
||||
The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid.
|
||||
|
||||
Note that the `WritableField` class that was present in version 2.x no longer exists. You should subclass `Field` and override `to_internal_value()` if the field supports data input.
|
||||
The `.to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid.
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -593,7 +603,7 @@ Note that the `WritableField` class that was present in version 2.x no longer ex
|
|||
|
||||
Let's look at an example of serializing a class that represents an RGB color value:
|
||||
|
||||
class Color(object):
|
||||
class Color:
|
||||
"""
|
||||
A color represented in the RGB colorspace.
|
||||
"""
|
||||
|
@ -713,7 +723,7 @@ the coordinate pair:
|
|||
fields = ['label', 'coordinates']
|
||||
|
||||
Note that this example doesn't handle validation. Partly for that reason, in a
|
||||
real project, the coordinate nesting might be better handled with a nested serialiser
|
||||
real project, the coordinate nesting might be better handled with a nested serializer
|
||||
using `source='*'`, with two `IntegerField` instances, each with their own `source`
|
||||
pointing to the relevant field.
|
||||
|
||||
|
@ -746,7 +756,7 @@ suitable for updating our target object. With `source='*'`, the return from
|
|||
('y_coordinate', 4),
|
||||
('x_coordinate', 3)])
|
||||
|
||||
For completeness lets do the same thing again but with the nested serialiser
|
||||
For completeness lets do the same thing again but with the nested serializer
|
||||
approach suggested above:
|
||||
|
||||
class NestedCoordinateSerializer(serializers.Serializer):
|
||||
|
@ -768,14 +778,14 @@ declarations. It's our `NestedCoordinateSerializer` that takes `source='*'`.
|
|||
Our new `DataPointSerializer` exhibits the same behaviour as the custom field
|
||||
approach.
|
||||
|
||||
Serialising:
|
||||
Serializing:
|
||||
|
||||
>>> out_serializer = DataPointSerializer(instance)
|
||||
>>> out_serializer.data
|
||||
ReturnDict([('label', 'testing'),
|
||||
('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
|
||||
|
||||
Deserialising:
|
||||
Deserializing:
|
||||
|
||||
>>> in_serializer = DataPointSerializer(data=data)
|
||||
>>> in_serializer.is_valid()
|
||||
|
@ -802,8 +812,8 @@ But we also get the built-in validation for free:
|
|||
{'x': ['A valid integer is required.'],
|
||||
'y': ['A valid integer is required.']})])
|
||||
|
||||
For this reason, the nested serialiser approach would be the first to try. You
|
||||
would use the custom field approach when the nested serialiser becomes infeasible
|
||||
For this reason, the nested serializer approach would be the first to try. You
|
||||
would use the custom field approach when the nested serializer becomes infeasible
|
||||
or overly complex.
|
||||
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ Another style of filtering might involve restricting the queryset based on some
|
|||
|
||||
For example if your URL config contained an entry like this:
|
||||
|
||||
url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
|
||||
re_path('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
|
||||
|
||||
You could then write a view that returned a purchase queryset filtered by the username portion of the URL:
|
||||
|
||||
|
@ -145,10 +145,18 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
|
|||
The [`django-filter`][django-filter-docs] library includes a `DjangoFilterBackend` class which
|
||||
supports highly customizable field filtering for REST framework.
|
||||
|
||||
To use `DjangoFilterBackend`, first install `django-filter`. Then add `django_filters` to Django's `INSTALLED_APPS`
|
||||
To use `DjangoFilterBackend`, first install `django-filter`.
|
||||
|
||||
pip install django-filter
|
||||
|
||||
Then add `'django_filters'` to Django's `INSTALLED_APPS`:
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
'django_filters',
|
||||
...
|
||||
]
|
||||
|
||||
You should now either add the filter backend to your settings:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
|
@ -205,6 +213,10 @@ This will allow the client to filter the items in the list by making queries suc
|
|||
You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API double-underscore notation:
|
||||
|
||||
search_fields = ['username', 'email', 'profile__profession']
|
||||
|
||||
For [JSONField][JSONField] and [HStoreField][HStoreField] fields you can filter based on nested values within the data structure using the same double-underscore notation:
|
||||
|
||||
search_fields = ['data__breed', 'data__owner__other_pets__0__name']
|
||||
|
||||
By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched.
|
||||
|
||||
|
@ -212,7 +224,7 @@ The search behavior may be restricted by prepending various characters to the `s
|
|||
|
||||
* '^' Starts-with search.
|
||||
* '=' Exact matches.
|
||||
* '@' Full-text search. (Currently only supported Django's MySQL backend.)
|
||||
* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend](https://docs.djangoproject.com/en/dev/ref/contrib/postgres/search/).)
|
||||
* '$' Regex search.
|
||||
|
||||
For example:
|
||||
|
@ -360,3 +372,5 @@ 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-url-filter]: https://github.com/miki725/django-url-filter
|
||||
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
|
||||
[HStoreField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#hstorefield
|
||||
[JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield
|
||||
|
|
|
@ -32,9 +32,9 @@ Example:
|
|||
from blog import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^/$', views.apt_root),
|
||||
url(r'^comments/$', views.comment_list),
|
||||
url(r'^comments/(?P<pk>[0-9]+)/$', views.comment_detail)
|
||||
path('', views.apt_root),
|
||||
path('comments/', views.comment_list),
|
||||
path('comments/<int:pk>/', views.comment_detail)
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
|
||||
|
|
|
@ -45,7 +45,7 @@ For more complex cases you might also want to override various methods on the vi
|
|||
|
||||
For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something like the following entry:
|
||||
|
||||
url(r'^/users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list')
|
||||
path('users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list')
|
||||
|
||||
---
|
||||
|
||||
|
@ -175,8 +175,6 @@ You can also use these hooks to provide additional validation, by raising a `Val
|
|||
raise ValidationError('You have already signed up')
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
**Note**: These methods replace the old-style version 2.x `pre_save`, `post_save`, `pre_delete` and `post_delete` methods, which are no longer available.
|
||||
|
||||
**Other methods**:
|
||||
|
||||
You won't typically need to override the following methods, although you might need to call into them if you're writing custom views using `GenericAPIView`.
|
||||
|
@ -321,7 +319,7 @@ Often you'll want to use the existing generic views, but use some slightly custo
|
|||
|
||||
For example, if you need to lookup objects based on multiple fields in the URL conf, you could create a mixin class like the following:
|
||||
|
||||
class MultipleFieldLookupMixin(object):
|
||||
class MultipleFieldLookupMixin:
|
||||
"""
|
||||
Apply this mixin to any view or viewset to get multiple field filtering
|
||||
based on a `lookup_fields` attribute, instead of the default single field filtering.
|
||||
|
@ -378,10 +376,6 @@ If you need to generic PUT-as-create behavior you may want to include something
|
|||
|
||||
The following third party packages provide additional generic view implementations.
|
||||
|
||||
## Django REST Framework bulk
|
||||
|
||||
The [django-rest-framework-bulk package][django-rest-framework-bulk] implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.
|
||||
|
||||
## Django Rest Multiple Models
|
||||
|
||||
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
||||
|
@ -394,5 +388,4 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements
|
|||
[RetrieveModelMixin]: #retrievemodelmixin
|
||||
[UpdateModelMixin]: #updatemodelmixin
|
||||
[DestroyModelMixin]: #destroymodelmixin
|
||||
[django-rest-framework-bulk]: https://github.com/miki725/django-rest-framework-bulk
|
||||
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
||||
|
|
|
@ -71,7 +71,7 @@ If you have specific requirements for creating schema endpoints that are accesse
|
|||
For example, the following additional route could be used on a viewset to provide a linkable schema endpoint.
|
||||
|
||||
@action(methods=['GET'], detail=False)
|
||||
def schema(self, request):
|
||||
def api_schema(self, request):
|
||||
meta = self.metadata_class()
|
||||
data = meta.determine_metadata(request, self)
|
||||
return Response(data)
|
||||
|
|
|
@ -73,7 +73,7 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
|
||||
## JSONParser
|
||||
|
||||
Parses `JSON` request content.
|
||||
Parses `JSON` request content. `request.data` will be populated with a dictionary of data.
|
||||
|
||||
**.media_type**: `application/json`
|
||||
|
||||
|
@ -125,7 +125,7 @@ If it is called without a `filename` URL keyword argument, then the client must
|
|||
# urls.py
|
||||
urlpatterns = [
|
||||
# ...
|
||||
url(r'^upload/(?P<filename>[^/]+)$', FileUploadView.as_view())
|
||||
re_path(r'^upload/(?P<filename>[^/]+)$', FileUploadView.as_view())
|
||||
]
|
||||
|
||||
---
|
||||
|
|
|
@ -231,7 +231,7 @@ If you need to test if a request is a read operation or a write operation, you s
|
|||
|
||||
---
|
||||
|
||||
Custom permissions will raise a `PermissionDenied` exception if the test fails. To change the error message associated with the exception, implement a `message` attribute directly on your custom permission. Otherwise the `default_detail` attribute from `PermissionDenied` will be used.
|
||||
Custom permissions will raise a `PermissionDenied` exception if the test fails. To change the error message associated with the exception, implement a `message` attribute directly on your custom permission. Otherwise the `default_detail` attribute from `PermissionDenied` will be used. Similarly, to change the code identifier associated with the exception, implement a `code` attribute directly on your custom permission - otherwise the `default_code` attribute from `PermissionDenied` will be used.
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
|
@ -243,19 +243,19 @@ Custom permissions will raise a `PermissionDenied` exception if the test fails.
|
|||
|
||||
## Examples
|
||||
|
||||
The following is an example of a permission class that checks the incoming request's IP address against a blacklist, and denies the request if the IP has been blacklisted.
|
||||
The following is an example of a permission class that checks the incoming request's IP address against a blocklist, and denies the request if the IP has been blocked.
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
class BlacklistPermission(permissions.BasePermission):
|
||||
class BlocklistPermission(permissions.BasePermission):
|
||||
"""
|
||||
Global permission check for blacklisted IPs.
|
||||
Global permission check for blocked IPs.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
ip_addr = request.META['REMOTE_ADDR']
|
||||
blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
|
||||
return not blacklisted
|
||||
blocked = Blocklist.objects.filter(ip_addr=ip_addr).exists()
|
||||
return not blocked
|
||||
|
||||
As well as global permissions, that are run against all incoming requests, you can also create object-level permissions, that are only run against operations that affect a particular object instance. For example:
|
||||
|
||||
|
@ -312,6 +312,11 @@ The [Django REST Framework API Key][djangorestframework-api-key] package provide
|
|||
|
||||
The [Django Rest Framework Role Filters][django-rest-framework-role-filters] package provides simple filtering over multiple types of roles.
|
||||
|
||||
## Django Rest Framework PSQ
|
||||
|
||||
The [Django Rest Framework PSQ][drf-psq] package is an extension that gives support for having action-based **permission_classes**, **serializer_class**, and **queryset** dependent on permission-based rules.
|
||||
|
||||
|
||||
[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
|
||||
[authentication]: authentication.md
|
||||
[throttling]: throttling.md
|
||||
|
@ -322,9 +327,10 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac
|
|||
[filtering]: filtering.md
|
||||
[composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions
|
||||
[rest-condition]: https://github.com/caxap/rest_condition
|
||||
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
|
||||
[dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions
|
||||
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles
|
||||
[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-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
|
||||
[drf-access-policy]: https://github.com/rsinger86/drf-access-policy
|
||||
[drf-psq]: https://github.com/drf-psq/drf-psq
|
||||
|
|
|
@ -56,7 +56,7 @@ In order to explain the various types of relational fields, we'll use a couple o
|
|||
|
||||
`StringRelatedField` may be used to represent the target of the relationship using its `__str__` method.
|
||||
|
||||
For example, the following serializer.
|
||||
For example, the following serializer:
|
||||
|
||||
class AlbumSerializer(serializers.ModelSerializer):
|
||||
tracks = serializers.StringRelatedField(many=True)
|
||||
|
@ -65,7 +65,7 @@ For example, the following serializer.
|
|||
model = Album
|
||||
fields = ['album_name', 'artist', 'tracks']
|
||||
|
||||
Would serialize to the following representation.
|
||||
Would serialize to the following representation:
|
||||
|
||||
{
|
||||
'album_name': 'Things We Lost In The Fire',
|
||||
|
@ -245,7 +245,9 @@ This field is always read-only.
|
|||
|
||||
# Nested relationships
|
||||
|
||||
Nested relationships can be expressed by using serializers as fields.
|
||||
As opposed to previously discussed _references_ to another entity, the referred entity can instead also be embedded or _nested_
|
||||
in the representation of the object that refers to it.
|
||||
Such nested relationships can be expressed by using serializers as fields.
|
||||
|
||||
If the field is used to represent a to-many relationship, you should add the `many=True` flag to the serializer field.
|
||||
|
||||
|
@ -289,7 +291,7 @@ Would serialize to a nested representation like this:
|
|||
|
||||
## Writable nested serializers
|
||||
|
||||
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved.
|
||||
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved:
|
||||
|
||||
class TrackSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
@ -335,13 +337,13 @@ output representation should be generated from the model instance.
|
|||
|
||||
To implement a custom relational field, you should override `RelatedField`, and implement the `.to_representation(self, value)` method. This method takes the target of the field as the `value` argument, and should return the representation that should be used to serialize the target. The `value` argument will typically be a model instance.
|
||||
|
||||
If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method.
|
||||
If you want to implement a read-write relational field, you must also implement the [`.to_internal_value(self, data)` method][to_internal_value].
|
||||
|
||||
To provide a dynamic queryset based on the `context`, you can also override `.get_queryset(self)` instead of specifying `.queryset` on the class or when initializing the field.
|
||||
|
||||
## Example
|
||||
|
||||
For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration.
|
||||
For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration:
|
||||
|
||||
import time
|
||||
|
||||
|
@ -357,7 +359,7 @@ For example, we could define a relational field to serialize a track to a custom
|
|||
model = Album
|
||||
fields = ['album_name', 'artist', 'tracks']
|
||||
|
||||
This custom field would then serialize to the following representation.
|
||||
This custom field would then serialize to the following representation:
|
||||
|
||||
{
|
||||
'album_name': 'Sometimes I Wish We Were an Eagle',
|
||||
|
@ -533,7 +535,7 @@ And the following two models, which may have associated tags:
|
|||
text = models.CharField(max_length=1000)
|
||||
tags = GenericRelation(TaggedItem)
|
||||
|
||||
We could define a custom field that could be used to serialize tagged instances, using the type of each instance to determine how it should be serialized.
|
||||
We could define a custom field that could be used to serialize tagged instances, using the type of each instance to determine how it should be serialized:
|
||||
|
||||
class TaggedObjectRelatedField(serializers.RelatedField):
|
||||
"""
|
||||
|
@ -601,5 +603,6 @@ The [rest-framework-generic-relations][drf-nested-relations] library provides re
|
|||
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
|
||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations
|
||||
[django-intermediary-manytomany]: https://docs.djangoproject.com/en/2.2/topics/db/models/#intermediary-manytomany
|
||||
[django-intermediary-manytomany]: https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany
|
||||
[dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects
|
||||
[to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data
|
||||
|
|
|
@ -103,6 +103,16 @@ Unlike other renderers, the data passed to the `Response` does not need to be se
|
|||
|
||||
The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context.
|
||||
|
||||
---
|
||||
|
||||
**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionay and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
|
||||
|
||||
```
|
||||
response.data = {'results': response.data}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The template name is determined by (in order of preference):
|
||||
|
||||
1. An explicit `template_name` argument passed to the response.
|
||||
|
@ -273,7 +283,7 @@ By default this will include the following keys: `view`, `request`, `response`,
|
|||
|
||||
The following is an example plaintext renderer that will return a response with the `data` parameter as the content of the response.
|
||||
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import smart_text
|
||||
from rest_framework import renderers
|
||||
|
||||
|
||||
|
@ -282,7 +292,7 @@ The following is an example plaintext renderer that will return a response with
|
|||
format = 'txt'
|
||||
|
||||
def render(self, data, media_type=None, renderer_context=None):
|
||||
return data.encode(self.charset)
|
||||
return smart_text(data, encoding=self.charset)
|
||||
|
||||
## Setting the character set
|
||||
|
||||
|
@ -503,7 +513,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
|
|||
|
||||
## UltraJSON
|
||||
|
||||
[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Jacob Haslehurst][hzy] maintains the [drf-ujson-renderer][drf-ujson-renderer] package which implements JSON rendering using the UJSON package.
|
||||
[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Adam Mertz][Amertz08] maintains [drf_ujson2][drf_ujson2], a fork of the now unmaintained [drf-ujson-renderer][drf-ujson-renderer], which implements JSON rendering using the UJSON package.
|
||||
|
||||
## CamelCase JSON
|
||||
|
||||
|
@ -547,8 +557,9 @@ Comma-separated values are a plain-text tabular data format, that can be easily
|
|||
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
||||
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
|
||||
[ultrajson]: https://github.com/esnme/ultrajson
|
||||
[hzy]: https://github.com/hzy
|
||||
[Amertz08]: https://github.com/Amertz08
|
||||
[drf-ujson-renderer]: https://github.com/gizmag/drf-ujson-renderer
|
||||
[drf_ujson2]: https://github.com/Amertz08/drf_ujson2
|
||||
[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case
|
||||
[Django REST Pandas]: https://github.com/wq/django-rest-pandas
|
||||
[Pandas]: https://pandas.pydata.org/
|
||||
|
|
|
@ -23,7 +23,7 @@ REST framework's Request objects provide flexible request parsing that allows yo
|
|||
|
||||
* It includes all parsed content, including *file and non-file* inputs.
|
||||
* It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.
|
||||
* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming JSON data in the same way that you handle incoming form data.
|
||||
* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming [JSON data] similarly to how you handle incoming [form data].
|
||||
|
||||
For more details see the [parsers documentation].
|
||||
|
||||
|
@ -49,7 +49,7 @@ If a client sends a request with a content-type that cannot be parsed then a `Un
|
|||
|
||||
# Content negotiation
|
||||
|
||||
The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialisation schemes for different media types.
|
||||
The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialization schemes for different media types.
|
||||
|
||||
## .accepted_renderer
|
||||
|
||||
|
@ -136,5 +136,7 @@ Note that due to implementation reasons the `Request` class does not inherit fro
|
|||
|
||||
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
|
||||
[parsers documentation]: parsers.md
|
||||
[JSON data]: parsers.md#jsonparser
|
||||
[form data]: parsers.md#formparser
|
||||
[authentication documentation]: authentication.md
|
||||
[browser enhancements documentation]: ../topics/browser-enhancements.md
|
||||
|
|
|
@ -94,5 +94,5 @@ As with any other `TemplateResponse`, this method is called to render the serial
|
|||
|
||||
You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/
|
||||
[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/
|
||||
[statuscodes]: status-codes.md
|
||||
|
|
|
@ -63,7 +63,7 @@ For example, you can append `router.urls` to a list of existing views...
|
|||
router.register(r'accounts', AccountViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||
path('forgot-password/', ForgotPasswordFormView.as_view()),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
@ -71,22 +71,22 @@ For example, you can append `router.urls` to a list of existing views...
|
|||
Alternatively you can use Django's `include` function, like so...
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||
url(r'^', include(router.urls)),
|
||||
path('forgot-password', ForgotPasswordFormView.as_view()),
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
You may use `include` with an application namespace:
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||
url(r'^api/', include((router.urls, 'app_name'))),
|
||||
path('forgot-password/', ForgotPasswordFormView.as_view()),
|
||||
path('api/', include((router.urls, 'app_name'))),
|
||||
]
|
||||
|
||||
Or both an application and instance namespace:
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||
url(r'^api/', include((router.urls, 'app_name'), namespace='instance_name')),
|
||||
path('forgot-password/', ForgotPasswordFormView.as_view()),
|
||||
path('api/', include((router.urls, 'app_name'), namespace='instance_name')),
|
||||
]
|
||||
|
||||
See Django's [URL namespaces docs][url-namespace-docs] and the [`include` API reference][include-api-reference] for more details.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source:
|
||||
- schemas.py
|
||||
- schemas
|
||||
---
|
||||
|
||||
# Schema
|
||||
|
@ -16,21 +16,40 @@ can interact with your API.
|
|||
Django REST Framework provides support for automatic generation of
|
||||
[OpenAPI][openapi] schemas.
|
||||
|
||||
## Overview
|
||||
|
||||
Schema generation has several moving parts. It's worth having an overview:
|
||||
|
||||
* `SchemaGenerator` is a top-level class that is responsible for walking your
|
||||
configured URL patterns, finding `APIView` subclasses, enquiring for their
|
||||
schema representation, and compiling the final schema object.
|
||||
* `AutoSchema` encapsulates all the details necessary for per-view schema
|
||||
introspection. Is attached to each view via the `schema` attribute. You
|
||||
subclass `AutoSchema` in order to customize your schema.
|
||||
* The `generateschema` management command allows you to generate a static schema
|
||||
offline.
|
||||
* Alternatively, you can route `SchemaView` to dynamically generate and serve
|
||||
your schema.
|
||||
* `settings.DEFAULT_SCHEMA_CLASS` allows you to specify an `AutoSchema`
|
||||
subclass to serve as your project's default.
|
||||
|
||||
The following sections explain more.
|
||||
|
||||
## Generating an OpenAPI Schema
|
||||
|
||||
### Install `pyyaml`
|
||||
### Install dependencies
|
||||
|
||||
You'll need to install `pyyaml`, so that you can render your generated schema
|
||||
into the commonly used YAML-based OpenAPI format.
|
||||
pip install pyyaml uritemplate
|
||||
|
||||
pip install pyyaml
|
||||
* `pyyaml` is used to generate schema into YAML-based OpenAPI format.
|
||||
* `uritemplate` is used internally to get parameters in path.
|
||||
|
||||
### Generating a static schema with the `generateschema` management command
|
||||
|
||||
If your schema is static, you can use the `generateschema` management command:
|
||||
|
||||
```bash
|
||||
./manage.py generateschema > openapi-schema.yml
|
||||
./manage.py generateschema --file openapi-schema.yml
|
||||
```
|
||||
|
||||
Once you've generated a schema in this way you can annotate it with any
|
||||
|
@ -60,7 +79,8 @@ urlpatterns = [
|
|||
# * Provide view name for use with `reverse()`.
|
||||
path('openapi', get_schema_view(
|
||||
title="Your Project",
|
||||
description="API for all things …"
|
||||
description="API for all things …",
|
||||
version="1.0.0"
|
||||
), name='openapi-schema'),
|
||||
# ...
|
||||
]
|
||||
|
@ -72,6 +92,7 @@ The `get_schema_view()` helper takes the following keyword arguments:
|
|||
|
||||
* `title`: May be used to provide a descriptive title for the schema definition.
|
||||
* `description`: Longer descriptive text.
|
||||
* `version`: The version of the API.
|
||||
* `url`: May be used to pass a canonical base URL for the schema.
|
||||
|
||||
schema_view = get_schema_view(
|
||||
|
@ -88,11 +109,12 @@ The `get_schema_view()` helper takes the following keyword arguments:
|
|||
url='https://www.example.org/api/',
|
||||
urlconf='myproject.urls'
|
||||
)
|
||||
|
||||
* `patterns`: List of url patterns to limit the schema introspection to. If you
|
||||
only want the `myproject.api` urls to be exposed in the schema:
|
||||
|
||||
schema_url_patterns = [
|
||||
url(r'^api/', include('myproject.api.urls')),
|
||||
path('api/', include('myproject.api.urls')),
|
||||
]
|
||||
|
||||
schema_view = get_schema_view(
|
||||
|
@ -113,23 +135,19 @@ The `get_schema_view()` helper takes the following keyword arguments:
|
|||
be used to render the API root endpoint.
|
||||
|
||||
|
||||
## Customizing Schema Generation
|
||||
## SchemaGenerator
|
||||
|
||||
You may customize schema generation at the level of the schema as a whole, or
|
||||
on a per-view basis.
|
||||
**Schema-level customization**
|
||||
|
||||
### Schema Level Customization
|
||||
```python
|
||||
from rest_framework.schemas.openapi import SchemaGenerator
|
||||
```
|
||||
|
||||
In order to customize the top-level schema sublass
|
||||
`rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument
|
||||
to the `generateschema` command or `get_schema_view()` helper function.
|
||||
`SchemaGenerator` is a class that walks a list of routed URL patterns, requests
|
||||
the schema for each view and collates the resulting OpenAPI schema.
|
||||
|
||||
#### SchemaGenerator
|
||||
|
||||
A class that walks a list of routed URL patterns, requests the schema for each
|
||||
view and collates the resulting OpenAPI schema.
|
||||
|
||||
Typically you'll instantiate `SchemaGenerator` with a `title` argument, like so:
|
||||
Typically you won't need to instantiate `SchemaGenerator` yourself, but you can
|
||||
do so like so:
|
||||
|
||||
generator = SchemaGenerator(title='Stock Prices API')
|
||||
|
||||
|
@ -137,11 +155,17 @@ Arguments:
|
|||
|
||||
* `title` **required**: The name of the API.
|
||||
* `description`: Longer descriptive text.
|
||||
* `version`: The version of the API. Defaults to `0.1.0`.
|
||||
* `url`: The root URL of the API schema. This option is not required unless the schema is included under path prefix.
|
||||
* `patterns`: A list of URLs to inspect when generating the schema. Defaults to the project's URL conf.
|
||||
* `urlconf`: A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`.
|
||||
|
||||
##### get_schema(self, request)
|
||||
In order to customize the top-level schema, subclass
|
||||
`rest_framework.schemas.openapi.SchemaGenerator` and provide your subclass
|
||||
as an argument to the `generateschema` command or `get_schema_view()` helper
|
||||
function.
|
||||
|
||||
### get_schema(self, request)
|
||||
|
||||
Returns a dictionary that represents the OpenAPI schema:
|
||||
|
||||
|
@ -151,68 +175,245 @@ Returns a dictionary that represents the OpenAPI schema:
|
|||
The `request` argument is optional, and may be used if you want to apply
|
||||
per-user permissions to the resulting schema generation.
|
||||
|
||||
This is a good point to override if you want to customise the generated
|
||||
dictionary, for example to add custom
|
||||
[specification extensions][openapi-specification-extensions].
|
||||
This is a good point to override if you want to customize the generated
|
||||
dictionary For example you might wish to add terms of service to the [top-level
|
||||
`info` object][info-object]:
|
||||
|
||||
### Per-View Customization
|
||||
```
|
||||
class TOSSchemaGenerator(SchemaGenerator):
|
||||
def get_schema(self, *args, **kwargs):
|
||||
schema = super().get_schema(*args, **kwargs)
|
||||
schema["info"]["termsOfService"] = "https://example.com/tos.html"
|
||||
return schema
|
||||
```
|
||||
|
||||
## AutoSchema
|
||||
|
||||
**Per-View Customization**
|
||||
|
||||
```python
|
||||
from rest_framework.schemas.openapi import AutoSchema
|
||||
```
|
||||
|
||||
By default, view introspection is performed by an `AutoSchema` instance
|
||||
accessible via the `schema` attribute on `APIView`. This provides the
|
||||
appropriate [Open API operation object][openapi-operation] for the view,
|
||||
request method and path:
|
||||
accessible via the `schema` attribute on `APIView`.
|
||||
|
||||
auto_schema = view.schema
|
||||
operation = auto_schema.get_operation(...)
|
||||
auto_schema = some_view.schema
|
||||
|
||||
In compiling the schema, `SchemaGenerator` calls `view.schema.get_operation()`
|
||||
for each view, allowed method, and path.
|
||||
`AutoSchema` provides the OpenAPI elements needed for each view, request method
|
||||
and path:
|
||||
|
||||
---
|
||||
* A list of [OpenAPI components][openapi-components]. In DRF terms these are
|
||||
mappings of serializers that describe request and response bodies.
|
||||
* The appropriate [OpenAPI operation object][openapi-operation] that describes
|
||||
the endpoint, including path and query parameters for pagination, filtering,
|
||||
and so on.
|
||||
|
||||
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
||||
limited to the URL kwarg path parameters. For `GenericAPIView`
|
||||
subclasses, which includes all the provided class based views, `AutoSchema` will
|
||||
attempt to introspect serialiser, pagination and filter fields, as well as
|
||||
provide richer path field descriptions. (The key hooks here are the relevant
|
||||
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
||||
`filter_backends` and so on.)
|
||||
```python
|
||||
components = auto_schema.get_components(...)
|
||||
operation = auto_schema.get_operation(...)
|
||||
```
|
||||
|
||||
---
|
||||
In compiling the schema, `SchemaGenerator` calls `get_components()` and
|
||||
`get_operation()` for each view, allowed method, and path.
|
||||
|
||||
In order to customise the operation generation, you should provide an `AutoSchema` subclass, overriding `get_operation()` as you need:
|
||||
----
|
||||
|
||||
**Note**: The automatic introspection of components, and many operation
|
||||
parameters relies on the relevant attributes and methods of
|
||||
`GenericAPIView`: `get_serializer()`, `pagination_class`, `filter_backends`,
|
||||
etc. For basic `APIView` subclasses, default introspection is essentially limited to
|
||||
the URL kwarg path parameters for this reason.
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.schemas.openapi import AutoSchema
|
||||
----
|
||||
|
||||
class CustomSchema(AutoSchema):
|
||||
def get_link(...):
|
||||
# Implement custom introspection here (or in other sub-methods)
|
||||
`AutoSchema` encapsulates the view introspection needed for schema generation.
|
||||
Because of this all the schema generation logic is kept in a single place,
|
||||
rather than being spread around the already extensive view, serializer and
|
||||
field APIs.
|
||||
|
||||
class CustomView(APIView):
|
||||
"""APIView subclass with custom schema introspection."""
|
||||
schema = CustomSchema()
|
||||
Keeping with this pattern, try not to let schema logic leak into your own
|
||||
views, serializers, or fields when customizing the schema generation. You might
|
||||
be tempted to do something like this:
|
||||
|
||||
This provides complete control over view introspection.
|
||||
```python
|
||||
class CustomSchema(AutoSchema):
|
||||
"""
|
||||
AutoSchema subclass using schema_extra_info on the view.
|
||||
"""
|
||||
...
|
||||
|
||||
You may disable schema generation for a view by setting `schema` to `None`:
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema()
|
||||
schema_extra_info = ... some extra info ...
|
||||
```
|
||||
|
||||
class CustomView(APIView):
|
||||
...
|
||||
schema = None # Will not appear in schema
|
||||
Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the
|
||||
view. This is _OK_ (it doesn't actually hurt) but it means you'll end up with
|
||||
your schema logic spread out in a number of different places.
|
||||
|
||||
This also applies to extra actions for `ViewSet`s:
|
||||
Instead try to subclass `AutoSchema` such that the `extra_info` doesn't leak
|
||||
out into the view:
|
||||
|
||||
class CustomViewSet(viewsets.ModelViewSet):
|
||||
```python
|
||||
class BaseSchema(AutoSchema):
|
||||
"""
|
||||
AutoSchema subclass that knows how to use extra_info.
|
||||
"""
|
||||
...
|
||||
|
||||
@action(detail=True, schema=None)
|
||||
def extra_action(self, request, pk=None):
|
||||
...
|
||||
class CustomSchema(BaseSchema):
|
||||
extra_info = ... some extra info ...
|
||||
|
||||
If you wish to provide a base `AutoSchema` subclass to be used throughout your
|
||||
project you may adjust `settings.DEFAULT_SCHEMA_CLASS` appropriately.
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema()
|
||||
```
|
||||
|
||||
This style is slightly more verbose but maintains the encapsulation of the
|
||||
schema related code. It's more _cohesive_ in the _parlance_. It'll keep the
|
||||
rest of your API code more tidy.
|
||||
|
||||
If an option applies to many view classes, rather than creating a specific
|
||||
subclass per-view, you may find it more convenient to allow specifying the
|
||||
option as an `__init__()` kwarg to your base `AutoSchema` subclass:
|
||||
|
||||
```python
|
||||
class CustomSchema(BaseSchema):
|
||||
def __init__(self, **kwargs):
|
||||
# store extra_info for later
|
||||
self.extra_info = kwargs.pop("extra_info")
|
||||
super().__init__(**kwargs)
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = CustomSchema(
|
||||
extra_info=... some extra info ...
|
||||
)
|
||||
```
|
||||
|
||||
This saves you having to create a custom subclass per-view for a commonly used option.
|
||||
|
||||
Not all `AutoSchema` methods expose related `__init__()` kwargs, but those for
|
||||
the more commonly needed options do.
|
||||
|
||||
### `AutoSchema` methods
|
||||
|
||||
#### `get_components()`
|
||||
|
||||
Generates the OpenAPI components that describe request and response bodies,
|
||||
deriving their properties from the serializer.
|
||||
|
||||
Returns a dictionary mapping the component name to the generated
|
||||
representation. By default this has just a single pair but you may override
|
||||
`get_components()` to return multiple pairs if your view uses multiple
|
||||
serializers.
|
||||
|
||||
#### `get_component_name()`
|
||||
|
||||
Computes the component's name from the serializer.
|
||||
|
||||
You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names.
|
||||
|
||||
#### `map_serializer()`
|
||||
|
||||
Maps serializers to their OpenAPI representations.
|
||||
|
||||
Most serializers should conform to the standard OpenAPI `object` type, but you may
|
||||
wish to override `map_serializer()` in order to customize this or other
|
||||
serializer-level fields.
|
||||
|
||||
#### `map_field()`
|
||||
|
||||
Maps individual serializer fields to their schema representation. The base implementation
|
||||
will handle the default fields that Django REST Framework provides.
|
||||
|
||||
For `SerializerMethodField` instances, for which the schema is unknown, or custom field subclasses you should override `map_field()` to generate the correct schema:
|
||||
|
||||
```python
|
||||
class CustomSchema(AutoSchema):
|
||||
"""Extension of ``AutoSchema`` to add support for custom field schemas."""
|
||||
|
||||
def map_field(self, field):
|
||||
# Handle SerializerMethodFields or custom fields here...
|
||||
# ...
|
||||
return super().map_field(field)
|
||||
```
|
||||
|
||||
Authors of third-party packages should aim to provide an `AutoSchema` subclass,
|
||||
and a mixin, overriding `map_field()` so that users can easily generate schemas
|
||||
for their custom fields.
|
||||
|
||||
#### `get_tags()`
|
||||
|
||||
OpenAPI groups operations by tags. By default tags taken from the first path
|
||||
segment of the routed URL. For example, a URL like `/users/{id}/` will generate
|
||||
the tag `users`.
|
||||
|
||||
You can pass an `__init__()` kwarg to manually specify tags (see below), or
|
||||
override `get_tags()` to provide custom logic.
|
||||
|
||||
#### `get_operation()`
|
||||
|
||||
Returns the [OpenAPI operation object][openapi-operation] that describes the
|
||||
endpoint, including path and query parameters for pagination, filtering, and so
|
||||
on.
|
||||
|
||||
Together with `get_components()`, this is the main entry point to the view
|
||||
introspection.
|
||||
|
||||
#### `get_operation_id()`
|
||||
|
||||
There must be a unique [operationid](openapi-operationid) for each operation.
|
||||
By default the `operationId` is deduced from the model name, serializer name or
|
||||
view name. The operationId looks like "listItems", "retrieveItem",
|
||||
"updateItem", etc. The `operationId` is camelCase by convention.
|
||||
|
||||
#### `get_operation_id_base()`
|
||||
|
||||
If you have several views with the same model name, you may see duplicate
|
||||
operationIds.
|
||||
|
||||
In order to work around this, you can override `get_operation_id_base()` to
|
||||
provide a different base for name part of the ID.
|
||||
|
||||
### `AutoSchema.__init__()` kwargs
|
||||
|
||||
`AutoSchema` provides a number of `__init__()` kwargs that can be used for
|
||||
common customizations, if the default generated values are not appropriate.
|
||||
|
||||
The available kwargs are:
|
||||
|
||||
* `tags`: Specify a list of tags.
|
||||
* `component_name`: Specify the component name.
|
||||
* `operation_id_base`: Specify the resource-name part of operation IDs.
|
||||
|
||||
You pass the kwargs when declaring the `AutoSchema` instance on your view:
|
||||
|
||||
```
|
||||
class PetDetailView(generics.RetrieveUpdateDestroyAPIView):
|
||||
schema = AutoSchema(
|
||||
tags=['Pets'],
|
||||
component_name='Pet',
|
||||
operation_id_base='Pet',
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
Assuming a `Pet` model and `PetSerializer` serializer, the kwargs in this
|
||||
example are probably not needed. Often, though, you'll need to pass the kwargs
|
||||
if you have multiple view targeting the same model, or have multiple views with
|
||||
identically named serializers.
|
||||
|
||||
If your views have related customizations that are needed frequently, you can
|
||||
create a base `AutoSchema` subclass for your project that takes additional
|
||||
`__init__()` kwargs to save subclassing `AutoSchema` for each view.
|
||||
|
||||
[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-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
||||
[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
||||
[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-components]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
|
||||
[openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject
|
||||
[openapi-generator]: https://github.com/OpenAPITools/openapi-generator
|
||||
[swagger-codegen]: https://github.com/swagger-api/swagger-codegen
|
||||
[info-object]: https://swagger.io/specification/#infoObject
|
||||
|
|
|
@ -21,7 +21,7 @@ Let's start by creating a simple object we can use for example purposes:
|
|||
|
||||
from datetime import datetime
|
||||
|
||||
class Comment(object):
|
||||
class Comment:
|
||||
def __init__(self, email, content, created=None):
|
||||
self.email = email
|
||||
self.content = content
|
||||
|
@ -161,7 +161,7 @@ Each key in the dictionary will be the field name, and the values will be lists
|
|||
|
||||
When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items.
|
||||
|
||||
#### Raising an exception on invalid data
|
||||
#### Raising an exception on invalid data
|
||||
|
||||
The `.is_valid()` method takes an optional `raise_exception` flag that will cause it to raise a `serializers.ValidationError` exception if there are validation errors.
|
||||
|
||||
|
@ -238,10 +238,12 @@ Serializer classes can also include reusable validators that are applied to the
|
|||
|
||||
class Meta:
|
||||
# Each room only has one event per day.
|
||||
validators = UniqueTogetherValidator(
|
||||
queryset=Event.objects.all(),
|
||||
fields=['room_number', 'date']
|
||||
)
|
||||
validators = [
|
||||
UniqueTogetherValidator(
|
||||
queryset=Event.objects.all(),
|
||||
fields=['room_number', 'date']
|
||||
)
|
||||
]
|
||||
|
||||
For more information see the [validators documentation](validators.md).
|
||||
|
||||
|
@ -249,7 +251,7 @@ For more information see the [validators documentation](validators.md).
|
|||
|
||||
When passing an initial object or queryset to a serializer instance, the object will be made available as `.instance`. If no initial object is passed then the `.instance` attribute will be `None`.
|
||||
|
||||
When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the data keyword argument is not passed then the `.initial_data` attribute will not exist.
|
||||
When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the `data` keyword argument is not passed then the `.initial_data` attribute will not exist.
|
||||
|
||||
## Partial updates
|
||||
|
||||
|
@ -280,7 +282,7 @@ If a nested representation may optionally accept the `None` value you should pas
|
|||
content = serializers.CharField(max_length=200)
|
||||
created = serializers.DateTimeField()
|
||||
|
||||
Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serialized.
|
||||
Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serializer.
|
||||
|
||||
class CommentSerializer(serializers.Serializer):
|
||||
user = UserSerializer(required=False)
|
||||
|
@ -333,7 +335,7 @@ Here's an example for an `.update()` method on our previous `UserSerializer` cla
|
|||
def update(self, instance, validated_data):
|
||||
profile_data = validated_data.pop('profile')
|
||||
# Unless the application properly enforces that this field is
|
||||
# always set, the follow could raise a `DoesNotExist`, which
|
||||
# always set, the following could raise a `DoesNotExist`, which
|
||||
# would need to be handled.
|
||||
profile = instance.profile
|
||||
|
||||
|
@ -382,8 +384,8 @@ This manager class now more nicely encapsulates that user instances and profile
|
|||
def create(self, validated_data):
|
||||
return User.objects.create(
|
||||
username=validated_data['username'],
|
||||
email=validated_data['email']
|
||||
is_premium_member=validated_data['profile']['is_premium_member']
|
||||
email=validated_data['email'],
|
||||
is_premium_member=validated_data['profile']['is_premium_member'],
|
||||
has_support_contract=validated_data['profile']['has_support_contract']
|
||||
)
|
||||
|
||||
|
@ -593,7 +595,7 @@ Normally if a `ModelSerializer` does not generate the fields you need by default
|
|||
|
||||
### `.serializer_field_mapping`
|
||||
|
||||
A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class.
|
||||
A mapping of Django model fields to REST framework serializer fields. You can override this mapping to alter the default serializer fields that should be used for each model field.
|
||||
|
||||
### `.serializer_related_field`
|
||||
|
||||
|
@ -887,10 +889,10 @@ To implement a read-only serializer using the `BaseSerializer` class, we just ne
|
|||
It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types.
|
||||
|
||||
class HighScoreSerializer(serializers.BaseSerializer):
|
||||
def to_representation(self, obj):
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
'score': obj.score,
|
||||
'player_name': obj.player_name
|
||||
'score': instance.score,
|
||||
'player_name': instance.player_name
|
||||
}
|
||||
|
||||
We can now use this class to serialize single `HighScore` instances:
|
||||
|
@ -945,10 +947,10 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
|
|||
'player_name': player_name
|
||||
}
|
||||
|
||||
def to_representation(self, obj):
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
'score': obj.score,
|
||||
'player_name': obj.player_name
|
||||
'score': instance.score,
|
||||
'player_name': instance.player_name
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
|
@ -965,10 +967,10 @@ The following class is an example of a generic serializer that can handle coerci
|
|||
A read-only serializer that coerces arbitrary complex objects
|
||||
into primitive representations.
|
||||
"""
|
||||
def to_representation(self, obj):
|
||||
def to_representation(self, instance):
|
||||
output = {}
|
||||
for attribute_name in dir(obj):
|
||||
attribute = getattr(obj, attribute_name)
|
||||
for attribute_name in dir(instance):
|
||||
attribute = getattr(instance, attribute_name)
|
||||
if attribute_name.startswith('_'):
|
||||
# Ignore private attributes.
|
||||
pass
|
||||
|
@ -1010,11 +1012,11 @@ Some reasons this might be useful include...
|
|||
|
||||
The signatures for these methods are as follows:
|
||||
|
||||
#### `.to_representation(self, obj)`
|
||||
#### `.to_representation(self, instance)`
|
||||
|
||||
Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API.
|
||||
|
||||
May be overridden in order modify the representation style. For example:
|
||||
May be overridden in order to modify the representation style. For example:
|
||||
|
||||
def to_representation(self, instance):
|
||||
"""Convert `username` to lowercase."""
|
||||
|
@ -1176,6 +1178,11 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
|
|||
|
||||
The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows to create/update models with nested related data.
|
||||
|
||||
## DRF Encrypt Content
|
||||
|
||||
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.
|
||||
|
||||
|
||||
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
|
||||
[relations]: relations.md
|
||||
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
|
||||
|
@ -1197,3 +1204,4 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested
|
|||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||
[djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/
|
||||
[drf-writable-nested]: https://github.com/beda-software/drf-writable-nested
|
||||
[drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content
|
||||
|
|
|
@ -101,7 +101,7 @@ Default: `'rest_framework.negotiation.DefaultContentNegotiation'`
|
|||
|
||||
A view inspector class that will be used for schema generation.
|
||||
|
||||
Default: `'rest_framework.schemas.AutoSchema'`
|
||||
Default: `'rest_framework.schemas.openapi.AutoSchema'`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -221,7 +221,7 @@ If you're using `RequestsClient` you'll want to ensure that test setup, and resu
|
|||
## Headers & Authentication
|
||||
|
||||
Custom headers and authentication credentials can be provided in the same way
|
||||
as [when using a standard `requests.Session` instance](http://docs.python-requests.org/en/master/user/advanced/#session-objects).
|
||||
as [when using a standard `requests.Session` instance][session_objects].
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
|
@ -414,3 +414,4 @@ 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
|
||||
[configuration]: #configuration
|
||||
[refresh_from_db_docs]: https://docs.djangoproject.com/en/1.11/ref/models/instances/#django.db.models.Model.refresh_from_db
|
||||
[session_objects]: https://requests.readthedocs.io/en/master/user/advanced/#session-objects
|
||||
|
|
|
@ -59,7 +59,7 @@ using the `APIView` class-based views.
|
|||
}
|
||||
return Response(content)
|
||||
|
||||
Or, if you're using the `@api_view` decorator with function based views.
|
||||
If you're using the `@api_view` decorator with function based views you can use the following decorator.
|
||||
|
||||
@api_view(['GET'])
|
||||
@throttle_classes([UserRateThrottle])
|
||||
|
@ -69,6 +69,16 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
}
|
||||
return Response(content)
|
||||
|
||||
It's also possible to set throttle classes for routes that are created using the `@action` decorator.
|
||||
Throttle classes set in this way will override any viewset level class settings.
|
||||
|
||||
@action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle])
|
||||
def example_adhoc_method(request, pk=None):
|
||||
content = {
|
||||
'status': 'request was permitted'
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
## How clients are identified
|
||||
|
||||
The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used.
|
||||
|
|
|
@ -218,7 +218,7 @@ in the `.validate()` method, or else in the view.
|
|||
For example:
|
||||
|
||||
class BillingRecordSerializer(serializers.ModelSerializer):
|
||||
def validate(self, data):
|
||||
def validate(self, attrs):
|
||||
# Apply custom validation either here, or in the view.
|
||||
|
||||
class Meta:
|
||||
|
@ -282,7 +282,7 @@ to your `Serializer` subclass. This is documented in the
|
|||
|
||||
To write a class-based validator, use the `__call__` method. Class-based validators are useful as they allow you to parameterize and reuse behavior.
|
||||
|
||||
class MultipleOf(object):
|
||||
class MultipleOf:
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
|
@ -291,13 +291,17 @@ To write a class-based validator, use the `__call__` method. Class-based validat
|
|||
message = 'This field must be a multiple of %d.' % self.base
|
||||
raise serializers.ValidationError(message)
|
||||
|
||||
#### Using `set_context()`
|
||||
#### Accessing the context
|
||||
|
||||
In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a `set_context` method on a class-based validator.
|
||||
In some advanced cases you might want a validator to be passed the serializer
|
||||
field it is being used with as additional context. You can do so by setting
|
||||
a `requires_context = True` attribute on the validator. The `__call__` method
|
||||
will then be called with the `serializer_field`
|
||||
or `serializer` as an additional argument.
|
||||
|
||||
def set_context(self, serializer_field):
|
||||
# Determine if this is an update or a create operation.
|
||||
# In `__call__` we can then use that information to modify the validation behavior.
|
||||
self.is_update = serializer_field.parent.instance is not None
|
||||
requires_context = True
|
||||
|
||||
def __call__(self, value, serializer_field):
|
||||
...
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/
|
||||
|
|
|
@ -132,12 +132,12 @@ This scheme requires the client to specify the version as part of the URL path.
|
|||
Your URL conf must include a pattern that matches the version with a `'version'` keyword argument, so that this information is available to the versioning scheme.
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
re_path(
|
||||
r'^(?P<version>(v1|v2))/bookings/$',
|
||||
bookings_list,
|
||||
name='bookings-list'
|
||||
),
|
||||
url(
|
||||
re_path(
|
||||
r'^(?P<version>(v1|v2))/bookings/(?P<pk>[0-9]+)/$',
|
||||
bookings_detail,
|
||||
name='bookings-detail'
|
||||
|
@ -158,14 +158,14 @@ In the following example we're giving a set of views two different possible URL
|
|||
|
||||
# bookings/urls.py
|
||||
urlpatterns = [
|
||||
url(r'^$', bookings_list, name='bookings-list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
|
||||
re_path(r'^$', bookings_list, name='bookings-list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
|
||||
]
|
||||
|
||||
# urls.py
|
||||
urlpatterns = [
|
||||
url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
|
||||
url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
|
||||
re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
|
||||
re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
|
||||
]
|
||||
|
||||
Both `URLPathVersioning` and `NamespaceVersioning` are reasonable if you just need a simple versioning scheme. The `URLPathVersioning` approach might be better suitable for small ad-hoc projects, and the `NamespaceVersioning` is probably easier to manage for larger projects.
|
||||
|
|
|
@ -169,7 +169,7 @@ To override the default settings, REST framework provides a set of additional de
|
|||
from rest_framework.throttling import UserRateThrottle
|
||||
|
||||
class OncePerDayUserThrottle(UserRateThrottle):
|
||||
rate = '1/day'
|
||||
rate = '1/day'
|
||||
|
||||
@api_view(['GET'])
|
||||
@throttle_classes([OncePerDayUserThrottle])
|
||||
|
|
|
@ -152,7 +152,7 @@ A more complete example of extra actions:
|
|||
user = self.get_object()
|
||||
serializer = PasswordSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user.set_password(serializer.data['password'])
|
||||
user.set_password(serializer.validated_data['password'])
|
||||
user.save()
|
||||
return Response({'status': 'password set'})
|
||||
else:
|
||||
|
@ -171,11 +171,6 @@ A more complete example of extra actions:
|
|||
serializer = self.get_serializer(recent_users, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
The decorator can additionally take extra arguments that will be set for the routed view only. For example:
|
||||
|
||||
@action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
|
||||
def set_password(self, request, pk=None):
|
||||
...
|
||||
|
||||
The `action` decorator will route `GET` requests by default, but may also accept other HTTP methods by setting the `methods` argument. For example:
|
||||
|
||||
|
@ -183,7 +178,14 @@ The `action` decorator will route `GET` requests by default, but may also accept
|
|||
def unset_password(self, request, pk=None):
|
||||
...
|
||||
|
||||
The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`
|
||||
|
||||
The decorator allows you to override any viewset-level configuration such as `permission_classes`, `serializer_class`, `filter_backends`...:
|
||||
|
||||
@action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
|
||||
def set_password(self, request, pk=None):
|
||||
...
|
||||
|
||||
The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`. Use the `url_path` and `url_name` parameters to change the URL segement and the reverse URL name of the action.
|
||||
|
||||
To view all extra actions, call the `.get_extra_actions()` method.
|
||||
|
||||
|
@ -317,5 +319,5 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope
|
|||
|
||||
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple viewsets across your API.
|
||||
|
||||
[cite]: https://guides.rubyonrails.org/routing.html
|
||||
[cite]: https://guides.rubyonrails.org/action_controller_overview.html
|
||||
[routers]: routers.md
|
||||
|
|
|
@ -84,7 +84,7 @@ urlpatterns = [
|
|||
|
||||
### Customization
|
||||
|
||||
For customizations that you want to apply across the the entire API, you can subclass `rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument
|
||||
For customizations that you want to apply across the entire API, you can subclass `rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument
|
||||
to the `generateschema` command or `get_schema_view()` helper function.
|
||||
|
||||
For specific per-view customizations, you can subclass `AutoSchema`,
|
||||
|
@ -144,4 +144,4 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
|
||||
[legacy-core-api-docs]:https://github.com/encode/django-rest-framework/blob/master/docs/coreapi/index.md
|
||||
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
||||
[funding]: community/funding.md
|
||||
[funding]: funding.md
|
||||
|
|
117
docs/community/3.11-announcement.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
<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.11
|
||||
|
||||
The 3.11 release adds support for Django 3.0.
|
||||
|
||||
* Our supported Python versions are now: 3.5, 3.6, 3.7, and 3.8.
|
||||
* Our supported Django versions are now: 1.11, 2.0, 2.1, 2.2, and 3.0.
|
||||
|
||||
This release will be the last to support Python 3.5 or Django 1.11.
|
||||
|
||||
## OpenAPI Schema Generation Improvements
|
||||
|
||||
The OpenAPI schema generation continues to mature. Some highlights in 3.11
|
||||
include:
|
||||
|
||||
* Automatic mapping of Django REST Framework renderers and parsers into OpenAPI
|
||||
request and response media-types.
|
||||
* Improved mapping JSON schema mapping types, for example in HStoreFields, and
|
||||
with large integer values.
|
||||
* Porting of the old CoreAPI parsing of docstrings to form OpenAPI operation
|
||||
descriptions.
|
||||
|
||||
In this example view operation descriptions for the `get` and `post` methods will
|
||||
be extracted from the class docstring:
|
||||
|
||||
```python
|
||||
class DocStringExampleListView(APIView):
|
||||
"""
|
||||
get: A description of my GET operation.
|
||||
post: A description of my POST operation.
|
||||
"""
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
...
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
...
|
||||
```
|
||||
|
||||
## Validator / Default Context
|
||||
|
||||
In some circumstances a Validator class or a Default class may need to access the serializer field with which it is called, or the `.context` with which the serializer was instantiated. In particular:
|
||||
|
||||
* Uniqueness validators need to be able to determine the name of the field to which they are applied, in order to run an appropriate database query.
|
||||
* The `CurrentUserDefault` needs to be able to determine the context with which the serializer was instantiated, in order to return the current user instance.
|
||||
|
||||
Previous our approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13.
|
||||
|
||||
Instead, validators or defaults which require the serializer context, should include a `requires_context = True` attribute on the class.
|
||||
|
||||
The `__call__` method should then include an additional `serializer_field` argument.
|
||||
|
||||
Validator implementations will look like this:
|
||||
|
||||
```python
|
||||
class CustomValidator:
|
||||
requires_context = True
|
||||
|
||||
def __call__(self, value, serializer_field):
|
||||
...
|
||||
```
|
||||
|
||||
Default implementations will look like this:
|
||||
|
||||
```python
|
||||
class CustomDefault:
|
||||
requires_context = True
|
||||
|
||||
def __call__(self, serializer_field):
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funding
|
||||
|
||||
REST framework is a *collaboratively funded project*. If you use
|
||||
REST framework commercially we strongly encourage you to invest in its
|
||||
continued development by **[signing up for a paid plan][funding]**.
|
||||
|
||||
*Every single sign-up helps us make REST framework long-term financially sustainable.*
|
||||
|
||||
<ul class="premium-promo promo">
|
||||
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
||||
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||
<li><a href="https://software.esg-usa.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/esg-new-logo.png)">ESG</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
|
||||
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
|
||||
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
|
||||
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
|
||||
<li><a href="https://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), and [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship).*
|
||||
|
||||
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
||||
[funding]: funding.md
|
169
docs/community/3.12-announcement.md
Normal file
|
@ -0,0 +1,169 @@
|
|||
<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.12
|
||||
|
||||
REST framework 3.12 brings a handful of refinements to the OpenAPI schema
|
||||
generation, plus support for Django's new database-agnostic `JSONField`,
|
||||
and some improvements to the `SearchFilter` class.
|
||||
|
||||
## Grouping operations with tags.
|
||||
|
||||
Open API schemas will now automatically include tags, based on the first element
|
||||
in the URL path.
|
||||
|
||||
For example...
|
||||
|
||||
Method | Path | Tags
|
||||
--------------------------------|-----------------|-------------
|
||||
`GET`, `PUT`, `PATCH`, `DELETE` | `/users/{id}/` | `['users']`
|
||||
`GET`, `POST` | `/users/` | `['users']`
|
||||
`GET`, `PUT`, `PATCH`, `DELETE` | `/orders/{id}/` | `['orders']`
|
||||
`GET`, `POST` | `/orders/` | `['orders']`
|
||||
|
||||
The tags used for a particular view may also be overridden...
|
||||
|
||||
```python
|
||||
class MyOrders(APIView):
|
||||
schema = AutoSchema(tags=['users', 'orders'])
|
||||
...
|
||||
```
|
||||
|
||||
See [the schema documentation](https://www.django-rest-framework.org/api-guide/schemas/#grouping-operations-with-tags) for more information.
|
||||
|
||||
## Customizing the operation ID.
|
||||
|
||||
REST framework automatically determines operation IDs to use in OpenAPI
|
||||
schemas. The latest version provides more control for overriding the behaviour
|
||||
used to generate the operation IDs.
|
||||
|
||||
See [the schema documentation](https://www.django-rest-framework.org/api-guide/schemas/#operationid) for more information.
|
||||
|
||||
## Support for OpenAPI components.
|
||||
|
||||
In order to output more graceful OpenAPI schemes, REST framework 3.12 now
|
||||
defines components in the schema, and then references them inside request
|
||||
and response objects. This is in contrast with the previous approach, which
|
||||
fully expanded the request and response bodies for each operation.
|
||||
|
||||
The names used for a component default to using the serializer class name, [but
|
||||
may be overridden if needed](https://www.django-rest-framework.org/api-guide/schemas/#components
|
||||
)...
|
||||
|
||||
```python
|
||||
class MyOrders(APIView):
|
||||
schema = AutoSchema(component_name="OrderDetails")
|
||||
```
|
||||
|
||||
## More Public API
|
||||
|
||||
Many methods on the `AutoSchema` class have now been promoted to public API,
|
||||
allowing you to more fully customize the schema generation. The following methods
|
||||
are now available for overriding...
|
||||
|
||||
* `get_path_parameters`
|
||||
* `get_pagination_parameters`
|
||||
* `get_filter_parameters`
|
||||
* `get_request_body`
|
||||
* `get_responses`
|
||||
* `get_serializer`
|
||||
* `get_paginator`
|
||||
* `map_serializer`
|
||||
* `map_field`
|
||||
* `map_choice_field`
|
||||
* `map_field_validators`
|
||||
* `allows_filters`.
|
||||
|
||||
See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#per-view-customization)
|
||||
for details on using custom `AutoSchema` subclasses.
|
||||
|
||||
## Support for JSONField.
|
||||
|
||||
Django 3.1 deprecated the existing `django.contrib.postgres.fields.JSONField`
|
||||
in favour of a new database-agnositic `JSONField`.
|
||||
|
||||
REST framework 3.12 now supports this new model field, and `ModelSerializer`
|
||||
classes will correctly map the model field.
|
||||
|
||||
## SearchFilter improvements
|
||||
|
||||
There are a couple of significant improvements to the `SearchFilter` class.
|
||||
|
||||
### Nested searches against JSONField and HStoreField
|
||||
|
||||
The class now supports nested search within `JSONField` and `HStoreField`, using
|
||||
the double underscore notation for traversing which element of the field the
|
||||
search should apply to.
|
||||
|
||||
```python
|
||||
class SitesSearchView(generics.ListAPIView):
|
||||
"""
|
||||
An API view to return a list of archaeological sites, optionally filtered
|
||||
by a search against the site name or location. (Location searches are
|
||||
matched against the region and country names.)
|
||||
"""
|
||||
queryset = Sites.objects.all()
|
||||
serializer_class = SitesSerializer
|
||||
filter_backends = [filters.SearchFilter]
|
||||
search_fields = ['site_name', 'location__region', 'location__country']
|
||||
```
|
||||
|
||||
### Searches against annotate fields
|
||||
|
||||
Django allows querysets to create additional virtual fields, using the `.annotate`
|
||||
method. We now support searching against annotate fields.
|
||||
|
||||
```python
|
||||
class PublisherSearchView(generics.ListAPIView):
|
||||
"""
|
||||
Search for publishers, optionally filtering the search against the average
|
||||
rating of all their books.
|
||||
"""
|
||||
queryset = Publisher.objects.annotate(avg_rating=Avg('book__rating'))
|
||||
serializer_class = PublisherSerializer
|
||||
filter_backends = [filters.SearchFilter]
|
||||
search_fields = ['avg_rating']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funding
|
||||
|
||||
REST framework is a *collaboratively funded project*. If you use
|
||||
REST framework commercially we strongly encourage you to invest in its
|
||||
continued development by **[signing up for a paid plan][funding]**.
|
||||
|
||||
*Every single sign-up helps us make REST framework long-term financially sustainable.*
|
||||
|
||||
<ul class="premium-promo promo">
|
||||
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
||||
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||
<li><a href="https://software.esg-usa.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/esg-new-logo.png)">ESG</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
|
||||
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
|
||||
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
|
||||
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
|
||||
<li><a href="https://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), and [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship).*
|
||||
|
||||
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
|
||||
[funding]: funding.md
|
|
@ -69,7 +69,7 @@ schema_view = get_schema_view(
|
|||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^swagger/$', schema_view),
|
||||
path('swagger/', schema_view),
|
||||
...
|
||||
]
|
||||
```
|
||||
|
@ -198,8 +198,8 @@ Make sure to include the view before your router urls. For example:
|
|||
schema_view = get_schema_view(title='Example API')
|
||||
|
||||
urlpatterns = [
|
||||
url('^$', schema_view),
|
||||
url(r'^', include(router.urls)),
|
||||
path('', schema_view),
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
||||
### Schema path representations
|
||||
|
|
|
@ -73,7 +73,7 @@ To install the API documentation, you'll need to include it in your projects URL
|
|||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION))
|
||||
path('docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION))
|
||||
]
|
||||
|
||||
Once installed you should see something a little like this:
|
||||
|
|
|
@ -62,6 +62,7 @@ Here's an example of adding an OpenAPI schema to the URL conf:
|
|||
```python
|
||||
from rest_framework.schemas import get_schema_view
|
||||
from rest_framework.renderers import JSONOpenAPIRenderer
|
||||
from django.urls import path
|
||||
|
||||
schema_view = get_schema_view(
|
||||
title='Server Monitoring API',
|
||||
|
@ -70,7 +71,7 @@ schema_view = get_schema_view(
|
|||
)
|
||||
|
||||
urlpatterns = [
|
||||
url('^schema.json$', schema_view),
|
||||
path('schema.json', schema_view),
|
||||
...
|
||||
]
|
||||
```
|
||||
|
|
|
@ -195,7 +195,6 @@ If `@tomchristie` ceases to participate in the project then `@j4mie` has respons
|
|||
|
||||
The following issues still need to be addressed:
|
||||
|
||||
* [Consider moving the repo into a proper GitHub organization][github-org].
|
||||
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
|
||||
* Document ownership of the [live example][sandbox] API.
|
||||
* Document ownership of the [mailing list][mailing-list] and IRC channel.
|
||||
|
@ -206,6 +205,5 @@ The following issues still need to be addressed:
|
|||
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
|
||||
[transifex-client]: https://pypi.org/project/transifex-client/
|
||||
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
|
||||
[github-org]: https://github.com/encode/django-rest-framework/issues/2162
|
||||
[sandbox]: https://restframework.herokuapp.com/
|
||||
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
# Release Notes
|
||||
|
||||
> Release Early, Release Often
|
||||
>
|
||||
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
|
||||
|
||||
## Versioning
|
||||
|
||||
Minor version numbers (0.0.x) are used for changes that are API compatible. You should be able to upgrade between minor point releases without any other code changes.
|
||||
|
@ -38,8 +34,132 @@ You can determine your currently installed version using `pip show`:
|
|||
|
||||
---
|
||||
|
||||
## 3.12.x series
|
||||
|
||||
### 3.12.2
|
||||
|
||||
Date: 13th October 2020
|
||||
|
||||
* Fix issue if `rest_framework.authtoken.models` is imported, but `rest_framework.authtoken` is not in INSTALLED_APPS. [#7571]
|
||||
* Ignore subclasses of BrowsableAPIRenderer in OpenAPI schema. [#7497]
|
||||
* Narrower exception catching in serilizer fields, to ensure that any errors in broken `get_queryset()` methods are not masked. [#7480]
|
||||
|
||||
### 3.12.1
|
||||
|
||||
Date: 28th September 2020
|
||||
|
||||
* Add `TokenProxy` migration. [#7557]
|
||||
|
||||
### 3.12.0
|
||||
|
||||
Date: 28th September 2020
|
||||
|
||||
* Add `--file` option to `generateschema` command. [#7130]
|
||||
* Support `tags` for OpenAPI schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#grouping-operations-with-tags). [#7184]
|
||||
* Support customising the operation ID for schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#operationid). [#7190]
|
||||
* Support OpenAPI components for schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#components). [#7124]
|
||||
* The following methods on `AutoSchema` become public API: `get_path_parameters`, `get_pagination_parameters`, `get_filter_parameters`, `get_request_body`, `get_responses`, `get_serializer`, `get_paginator`, `map_serializer`, `map_field`, `map_choice_field`, `map_field_validators`, `allows_filters`. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#autoschema)
|
||||
* Add support for Django 3.1's database-agnositic `JSONField`. [#7467]
|
||||
* `SearchFilter` now supports nested search on `JSONField` and `HStoreField` model fields. [#7121]
|
||||
* `SearchFilter` now supports searching on `annotate()` fields. [#6240]
|
||||
* The authtoken model no longer exposes the `pk` in the admin URL. [#7341]
|
||||
* Add `__repr__` for Request instances. [#7239]
|
||||
* UTF-8 decoding with Latin-1 fallback for basic auth credentials. [#7193]
|
||||
* CharField treats surrogate characters as a validation failure. [#7026]
|
||||
* Don't include callables as default values in schemas. [#7105]
|
||||
* Improve `ListField` schema output to include all available child information. [#7137]
|
||||
* Allow `default=False` to be included for `BooleanField` schema outputs. [#7165]
|
||||
* Include `"type"` information in `ChoiceField` schema outputs. [#7161]
|
||||
* Include `"type": "object"` on schema objects. [#7169]
|
||||
* Don't include component in schema output for DELETE requests. [#7229]
|
||||
* Fix schema types for `DecimalField`. [#7254]
|
||||
* Fix schema generation for `ObtainAuthToken` view. [#7211]
|
||||
* Support passing `context=...` to view `.get_serializer()` methods. [#7298]
|
||||
* Pass custom code to `PermissionDenied` if permission class has one set. [#7306]
|
||||
* Include "example" in schema pagination output. [#7275]
|
||||
* Default status code of 201 on schema output for POST requests. [#7206]
|
||||
* Use camelCase for operation IDs in schema output. [#7208]
|
||||
* Warn if duplicate operation IDs exist in schema output. [#7207]
|
||||
* Improve handling of decimal type when mapping `ChoiceField` to a schema output. [#7264]
|
||||
* Disable YAML aliases for OpenAPI schema outputs. [#7131]
|
||||
* Fix action URL names for APIs included under a namespaced URL. [#7287]
|
||||
* Update jQuery version from 3.4 to 3.5. [#7313]
|
||||
* Fix `UniqueTogether` handling when serializer fields use `source=...`. [#7143]
|
||||
* HTTP `HEAD` requests now set `self.action` correctly on a ViewSet instance. [#7223]
|
||||
* Return a valid OpenAPI schema for the case where no API schema paths exist. [#7125]
|
||||
* Include tests in package distribution. [#7145]
|
||||
* Allow type checkers to support annotations like `ModelSerializer[Author]`. [#7385]
|
||||
* Don't include invalid `charset=None` portion in the request `Content-Type` header when using APIClient. [#7400]
|
||||
* Fix `\Z`/`\z` tokens in OpenAPI regexs. [#7389]
|
||||
* Fix `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` when source field is actually a property. [#7142]
|
||||
* `Token.generate_key` is now a class method. [#7502]
|
||||
* `@action` warns if method is wrapped in a decorator that does not preserve information using `@functools.wraps`. [#7098]
|
||||
|
||||
---
|
||||
|
||||
## 3.11.x series
|
||||
|
||||
### 3.11.2
|
||||
|
||||
**Date**: 30th September 2020
|
||||
|
||||
* **Security**: Drop `urlize_quoted_links` template tag in favour of Django's built-in `urlize`. Removes a XSS vulnerability for some kinds of content in the browsable API.
|
||||
|
||||
### 3.11.1
|
||||
|
||||
**Date**: 5th August 2020
|
||||
|
||||
* Fix compat with Django 3.1
|
||||
|
||||
### 3.11.0
|
||||
|
||||
**Date**: 12th December 2019
|
||||
|
||||
* Drop `.set_context` API [in favour of a `requires_context` marker](3.11-announcement.md#validator-default-context).
|
||||
* Changed default widget for TextField with choices to select box. [#6892][gh6892]
|
||||
* Supported nested writes on non-relational fields, such as JSONField. [#6916][gh6916]
|
||||
* Include request/response media types in OpenAPI schemas, based on configured parsers/renderers. [#6865][gh6865]
|
||||
* Include operation descriptions in OpenAPI schemas, based on the docstring on the view. [#6898][gh6898]
|
||||
* Fix representation of serializers with all optional fields in OpenAPI schemas. [#6941][gh6941], [#6944][gh6944]
|
||||
* Fix representation of `serializers.HStoreField` in OpenAPI schemas. [#6914][gh6914]
|
||||
* Fix OpenAPI generation when title or version is not provided. [#6912][gh6912]
|
||||
* Use `int64` representation for large integers in OpenAPI schemas. [#7018][gh7018]
|
||||
* Improved error messages if no `.to_representation` implementation is provided on a field subclass. [#6996][gh6996]
|
||||
* Fix for serializer classes that use multiple inheritance. [#6980][gh6980]
|
||||
* Fix for reversing Hyperlinked URL fields with percent encoded components in the path. [#7059][gh7059]
|
||||
* Update bootstrap to 3.4.1. [#6923][gh6923]
|
||||
|
||||
## 3.10.x series
|
||||
|
||||
### 3.10.3
|
||||
|
||||
**Date**: 4th September 2019
|
||||
|
||||
* Include API version in OpenAPI schema generation, defaulting to empty string.
|
||||
* Add pagination properties to OpenAPI response schemas.
|
||||
* Add missing "description" property to OpenAPI response schemas.
|
||||
* Only include "required" for non-empty cases in OpenAPI schemas.
|
||||
* Fix response schemas for "DELETE" case in OpenAPI schemas.
|
||||
* Use an array type for list view response schemas.
|
||||
* Use consistent `lowerInitialCamelCase` style in OpenAPI operation IDs.
|
||||
* Fix `minLength`/`maxLength`/`minItems`/`maxItems` properties in OpenAPI schemas.
|
||||
* Only call `FileField.url` once in serialization, for improved performance.
|
||||
* Fix an edge case where throttling calculations could error after a configuration change.
|
||||
|
||||
### 3.10.2
|
||||
|
||||
**Date**: 29th July 2019
|
||||
|
||||
* Various `OpenAPI` schema fixes.
|
||||
* Ability to specify urlconf in include_docs_urls.
|
||||
|
||||
### 3.10.1
|
||||
|
||||
**Date**: 17th July 2019
|
||||
|
||||
* Don't include autocomplete fields on TokenAuth admin, since it forces constraints on custom user models & admin.
|
||||
* Require `uritemplate` for OpenAPI schema generation, but not `coreapi`.
|
||||
|
||||
### 3.10.0
|
||||
|
||||
**Date**: [15th July 2019][3.10.0-milestone]
|
||||
|
@ -129,7 +249,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
|||
* Add testing of Python 3.7 support [#6141][gh6141]
|
||||
* Test using Django 2.1 final release. [#6109][gh6109]
|
||||
* Added djangorestframework-datatables to third-party packages [#5931][gh5931]
|
||||
* Change ISO 8601 date format to exclude year/month [#5936][gh5936]
|
||||
* Change ISO 8601 date format to exclude year/month-only options [#5936][gh5936]
|
||||
* Update all pypi.python.org URLs to pypi.org [#5942][gh5942]
|
||||
* Ensure that html forms (multipart form data) respect optional fields [#5927][gh5927]
|
||||
* Allow hashing of ErrorDetail. [#5932][gh5932]
|
||||
|
@ -142,7 +262,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
|||
* Fixed Javascript `e.indexOf` is not a function error [#5982][gh5982]
|
||||
* Fix schemas for extra actions [#5992][gh5992]
|
||||
* Improved get_error_detail to use error_dict/error_list [#5785][gh5785]
|
||||
* Imprvied URLs in Admin renderer [#5988][gh5988]
|
||||
* Improved URLs in Admin renderer [#5988][gh5988]
|
||||
* Add "Community" section to docs, minor cleanup [#5993][gh5993]
|
||||
* Moved guardian imports out of compat [#6054][gh6054]
|
||||
* Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075]
|
||||
|
@ -193,11 +313,11 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
|||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user)
|
||||
|
||||
Alternatively you may override `save()` or `create()` or `update()` on the serialiser as appropriate.
|
||||
Alternatively you may override `save()` or `create()` or `update()` on the serializer as appropriate.
|
||||
|
||||
* Correct allow_null behaviour when required=False [#5888][gh5888]
|
||||
|
||||
Without an explicit `default`, `allow_null` implies a default of `null` for outgoing serialisation. Previously such
|
||||
Without an explicit `default`, `allow_null` implies a default of `null` for outgoing serialization. Previously such
|
||||
fields were being skipped when read-only or otherwise not required.
|
||||
|
||||
**Possible backwards compatibility break** if you were relying on such fields being excluded from the outgoing
|
||||
|
@ -435,7 +555,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
|||
* Deprecated `exclude_from_schema` on `APIView` and `api_view` decorator. Set `schema = None` or `@schema(None)` as appropriate. [#5422][gh5422]
|
||||
* Timezone-aware `DateTimeField`s now respect active or default `timezone` during serialization, instead of always using UTC. [#5435][gh5435]
|
||||
|
||||
Resolves inconsistency whereby instances were serialised with supplied datetime for `create` but UTC for `retrieve`. [#3732][gh3732]
|
||||
Resolves inconsistency whereby instances were serialized with supplied datetime for `create` but UTC for `retrieve`. [#3732][gh3732]
|
||||
|
||||
**Possible backwards compatibility break** if you were relying on datetime strings being UTC. Have client interpret datetimes or [set default or active timezone (docs)][djangodocs-set-timezone] to UTC if needed.
|
||||
|
||||
|
@ -2150,3 +2270,18 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
|||
<!-- 3.10.0 -->
|
||||
[gh6680]: https://github.com/encode/django-rest-framework/issues/6680
|
||||
[gh6317]: https://github.com/encode/django-rest-framework/issues/6317
|
||||
|
||||
<!-- 3.11.0 -->
|
||||
[gh6892]: https://github.com/encode/django-rest-framework/issues/6892
|
||||
[gh6916]: https://github.com/encode/django-rest-framework/issues/6916
|
||||
[gh6865]: https://github.com/encode/django-rest-framework/issues/6865
|
||||
[gh6898]: https://github.com/encode/django-rest-framework/issues/6898
|
||||
[gh6941]: https://github.com/encode/django-rest-framework/issues/6941
|
||||
[gh6944]: https://github.com/encode/django-rest-framework/issues/6944
|
||||
[gh6914]: https://github.com/encode/django-rest-framework/issues/6914
|
||||
[gh6912]: https://github.com/encode/django-rest-framework/issues/6912
|
||||
[gh7018]: https://github.com/encode/django-rest-framework/issues/7018
|
||||
[gh6996]: https://github.com/encode/django-rest-framework/issues/6996
|
||||
[gh6980]: https://github.com/encode/django-rest-framework/issues/6980
|
||||
[gh7059]: https://github.com/encode/django-rest-framework/issues/7059
|
||||
[gh6923]: https://github.com/encode/django-rest-framework/issues/6923
|
||||
|
|
|
@ -198,6 +198,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [rest_condition][rest-condition] - Another extension for building complex permissions in a simple and convenient way.
|
||||
* [dry-rest-permissions][dry-rest-permissions] - Provides a simple way to define permissions for individual api actions.
|
||||
* [drf-access-policy][drf-access-policy] - Declarative and flexible permissions inspired by AWS' IAM policies.
|
||||
* [drf-psq][drf-psq] - An extension that gives support for having action-based **permission_classes**, **serializer_class**, and **queryset** dependent on permission-based rules.
|
||||
|
||||
### Serializers
|
||||
|
||||
|
@ -211,6 +212,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response.
|
||||
* [drf-flex-fields][drf-flex-fields] - Serializer providing dynamic field expansion and sparse field sets via URL parameters.
|
||||
* [drf-action-serializer][drf-action-serializer] - Serializer providing per-action fields config for use with ViewSets to prevent having to write multiple serializers.
|
||||
* [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).
|
||||
|
||||
### Serializer fields
|
||||
|
||||
|
@ -220,8 +223,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
|
||||
### Views
|
||||
|
||||
* [djangorestframework-bulk][djangorestframework-bulk] - Implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.
|
||||
* [django-rest-multiple-models][django-rest-multiple-models] - Provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
||||
* [drf-typed-views][drf-typed-views] - Use Python type annotations to validate/deserialize request parameters. Inspired by API Star, Hug and FastAPI.
|
||||
|
||||
### Routers
|
||||
|
||||
|
@ -238,7 +241,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
|
||||
* [djangorestframework-csv][djangorestframework-csv] - Provides CSV renderer support.
|
||||
* [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec.
|
||||
* [drf_ujson][drf_ujson] - Implements JSON rendering using the UJSON package.
|
||||
* [drf_ujson2][drf_ujson2] - Implements JSON rendering using the UJSON package.
|
||||
* [rest-pandas][rest-pandas] - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats.
|
||||
* [djangorestframework-rapidjson][djangorestframework-rapidjson] - Provides rapidjson support with parser and renderer.
|
||||
|
||||
|
@ -252,8 +255,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
### Misc
|
||||
|
||||
* [cookiecutter-django-rest][cookiecutter-django-rest] - A cookiecutter template that takes care of the setup and configuration so you can focus on making your REST apis awesome.
|
||||
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
|
||||
* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI.
|
||||
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serializer that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
|
||||
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
|
||||
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framework
|
||||
* [drf-extensions][drf-extensions] - A collection of custom extensions
|
||||
|
@ -270,6 +272,10 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [django-rest-framework-condition][django-rest-framework-condition] - Decorators for managing HTTP cache headers for Django REST framework (ETag and Last-modified).
|
||||
* [django-rest-witchcraft][django-rest-witchcraft] - Provides DRF integration with SQLAlchemy with SQLAlchemy model serializers/viewsets and a bunch of other goodies
|
||||
* [djangorestframework-mvt][djangorestframework-mvt] - An extension for creating views that serve Postgres data as Map Box Vector Tiles.
|
||||
* [drf-viewset-profiler][drf-viewset-profiler] - Lib to profile all methods from a viewset line by line.
|
||||
* [djangorestframework-features][djangorestframework-features] - Advanced schema generation and more based on named features.
|
||||
* [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons.
|
||||
* [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models..
|
||||
|
||||
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
|
||||
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
|
||||
|
@ -303,19 +309,17 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
|
||||
[drf-compound-fields]: https://github.com/estebistec/drf-compound-fields
|
||||
[django-extra-fields]: https://github.com/Hipo/drf-extra-fields
|
||||
[djangorestframework-bulk]: https://github.com/miki725/django-rest-framework-bulk
|
||||
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||
[wq.db.rest]: https://wq.io/docs/about-rest
|
||||
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
|
||||
[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case
|
||||
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
|
||||
[drf_ujson]: https://github.com/gizmag/drf-ujson-renderer
|
||||
[drf_ujson2]: https://github.com/Amertz08/drf_ujson2
|
||||
[rest-pandas]: https://github.com/wq/django-rest-pandas
|
||||
[djangorestframework-rapidjson]: https://github.com/allisson/django-rest-framework-rapidjson
|
||||
[djangorestframework-chain]: https://github.com/philipn/django-rest-framework-chain
|
||||
[djangorestrelationalhyperlink]: https://github.com/fredkingham/django_rest_model_hyperlink_serializers_project
|
||||
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
|
||||
[django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy
|
||||
[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
|
||||
[drf-extensions]: https://github.com/chibisov/drf-extensions
|
||||
|
@ -325,7 +329,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html
|
||||
[drf-tracking]: https://github.com/aschn/drf-tracking
|
||||
[django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces
|
||||
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
|
||||
[dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions
|
||||
[django-url-filter]: https://github.com/miki725/django-url-filter
|
||||
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters
|
||||
[cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest
|
||||
|
@ -347,6 +351,14 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[django-rest-witchcraft]: https://github.com/shosca/django-rest-witchcraft
|
||||
[drf-access-policy]: https://github.com/rsinger86/drf-access-policy
|
||||
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
|
||||
[drf-typed-views]: https://github.com/rsinger86/drf-typed-views
|
||||
[drf-action-serializer]: https://github.com/gregschmit/drf-action-serializer
|
||||
[djangorestframework-dataclasses]: https://github.com/oxan/djangorestframework-dataclasses
|
||||
[django-restql]: https://github.com/yezyilomo/django-restql
|
||||
[djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt
|
||||
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
|
||||
[drf-viewset-profiler]: https://github.com/fvlima/drf-viewset-profiler
|
||||
[djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/
|
||||
[django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf
|
||||
[django-api-client]: https://github.com/rhenter/django-api-client
|
||||
[drf-psq]: https://github.com/drf-psq/drf-psq
|
||||
|
|
|
@ -11,8 +11,8 @@ There are a wide range of resources available for learning and using Django REST
|
|||
<a class="book-cover" href="https://www.twoscoopspress.com/products/two-scoops-of-django-1-11">
|
||||
<img src="../../img/books/tsd-cover.png"/>
|
||||
</a>
|
||||
<a class="book-cover" href="https://wsvincent.com/books/">
|
||||
<img src="../../img/books/rad-cover.png"/>
|
||||
<a class="book-cover" href="https://djangoforapis.com">
|
||||
<img src="../../img/books/dfa-cover.jpg"/>
|
||||
</a>
|
||||
<a class="book-cover" href="https://books.agiliq.com/projects/django-api-polls-tutorial/en/latest/">
|
||||
<img src="../../img/books/bda-cover.png"/>
|
||||
|
@ -101,12 +101,12 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
|
|||
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
|
||||
[pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg
|
||||
[django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E
|
||||
[integrating-pandas-drf-and-bokeh]: https://machinalis.com/blog/pandas-django-rest-framework-bokeh/
|
||||
[controlling-uncertainty-on-web-apps-and-apis]: https://machinalis.com/blog/controlling-uncertainty-on-web-applications-and-apis/
|
||||
[full-text-search-in-drf]: https://machinalis.com/blog/full-text-search-on-django-rest-framework/
|
||||
[oauth2-authentication-with-drf]: https://machinalis.com/blog/oauth2-authentication/
|
||||
[nested-resources-with-drf]: https://machinalis.com/blog/nested-resources-with-django/
|
||||
[image-fields-with-drf]: https://machinalis.com/blog/image-fields-with-django-rest-framework/
|
||||
[integrating-pandas-drf-and-bokeh]: https://web.archive.org/web/20180104205117/http://machinalis.com/blog/pandas-django-rest-framework-bokeh/
|
||||
[controlling-uncertainty-on-web-apps-and-apis]: https://web.archive.org/web/20180104205043/https://machinalis.com/blog/controlling-uncertainty-on-web-applications-and-apis/
|
||||
[full-text-search-in-drf]: https://web.archive.org/web/20180104205059/http://machinalis.com/blog/full-text-search-on-django-rest-framework/
|
||||
[oauth2-authentication-with-drf]: https://web.archive.org/web/20180104205054/http://machinalis.com/blog/oauth2-authentication/
|
||||
[nested-resources-with-drf]: https://web.archive.org/web/20180104205109/http://machinalis.com/blog/nested-resources-with-django/
|
||||
[image-fields-with-drf]: https://web.archive.org/web/20180104205048/http://machinalis.com/blog/image-fields-with-django-rest-framework/
|
||||
[chatbot-using-drf-part1]: https://chatbotslife.com/chatbot-using-django-rest-framework-api-ai-slack-part-1-3-69c7e38b7b1e#.g2aceuncf
|
||||
[new-django-admin-with-drf-and-emberjs]: https://blog.levit.be/new-django-admin-with-emberjs-what-are-the-news/
|
||||
[drf-schema]: https://drf-schema-adapter.readthedocs.io/en/latest/
|
||||
|
|
|
@ -19,7 +19,7 @@ To install the API documentation, you'll need to include it in your project's UR
|
|||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^docs/', include_docs_urls(title='My API title'))
|
||||
path('docs/', include_docs_urls(title='My API title'))
|
||||
]
|
||||
|
||||
This will include two different views:
|
||||
|
@ -41,7 +41,7 @@ You may ensure views are given a `request` instance by calling `include_docs_url
|
|||
urlpatterns = [
|
||||
...
|
||||
# Generate schema with valid `request` instance:
|
||||
url(r'^docs/', include_docs_urls(title='My API title', public=False))
|
||||
path('docs/', include_docs_urls(title='My API title', public=False))
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -43,11 +43,12 @@ To add a dynamically generated schema view to your API, use `get_schema_view`.
|
|||
|
||||
```python
|
||||
from rest_framework.schemas import get_schema_view
|
||||
from django.urls import path
|
||||
|
||||
schema_view = get_schema_view(title="Example API")
|
||||
|
||||
urlpatterns = [
|
||||
url('^schema$', schema_view),
|
||||
path('schema', schema_view),
|
||||
...
|
||||
]
|
||||
```
|
||||
|
@ -191,7 +192,7 @@ each view, allowed method and path.)
|
|||
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
||||
limited to the URL kwarg path parameters. For `GenericAPIView`
|
||||
subclasses, which includes all the provided class based views, `AutoSchema` will
|
||||
attempt to introspect serialiser, pagination and filter fields, as well as
|
||||
attempt to introspect serializer, pagination and filter fields, as well as
|
||||
provide richer path field descriptions. (The key hooks here are the relevant
|
||||
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
||||
`filter_backends` and so on.)
|
||||
|
@ -292,7 +293,7 @@ The simplest way to include a schema in your project is to use the
|
|||
schema_view = get_schema_view(title="Server Monitoring API")
|
||||
|
||||
urlpatterns = [
|
||||
url('^$', schema_view),
|
||||
path('', schema_view),
|
||||
...
|
||||
]
|
||||
|
||||
|
@ -358,7 +359,7 @@ List of url patterns to limit the schema introspection to. If you only want the
|
|||
to be exposed in the schema:
|
||||
|
||||
schema_url_patterns = [
|
||||
url(r'^api/', include('myproject.api.urls')),
|
||||
path('api/', include('myproject.api.urls')),
|
||||
]
|
||||
|
||||
schema_view = get_schema_view(
|
||||
|
@ -411,7 +412,7 @@ return the schema.
|
|||
**urls.py:**
|
||||
|
||||
urlpatterns = [
|
||||
url('/', schema_view),
|
||||
path('', schema_view),
|
||||
...
|
||||
]
|
||||
|
||||
|
@ -827,10 +828,17 @@ A short description of the meaning and intended usage of the input field.
|
|||
[drf-yasg][drf-yasg] generates [OpenAPI][open-api] documents suitable for code generation - nested schemas,
|
||||
named models, response bodies, enum/pattern/min/max validators, form parameters, etc.
|
||||
|
||||
|
||||
## drf-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework
|
||||
|
||||
[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility,
|
||||
customizability and client generation. It's usage patterns are very similar to [drf-yasg][drf-yasg].
|
||||
|
||||
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
|
||||
[coreapi]: https://www.coreapi.org/
|
||||
[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding
|
||||
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
|
||||
[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/
|
||||
[open-api]: https://openapis.org/
|
||||
[json-hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
|
||||
[api-blueprint]: https://apiblueprint.org/
|
||||
|
|
Before Width: | Height: | Size: 54 KiB |
BIN
docs/img/books/dfa-cover.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 75 KiB |
BIN
docs/img/premium/bitio-readme.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
BIN
docs/img/premium/retool-readme.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -52,7 +52,7 @@ Some reasons you might want to use REST framework:
|
|||
* [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
|
||||
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
|
||||
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
|
||||
* [Extensive documentation][index], and [great community support][group].
|
||||
* Extensive documentation, and [great community support][group].
|
||||
* Used and trusted by internationally recognised companies including [Mozilla][mozilla], [Red Hat][redhat], [Heroku][heroku], and [Eventbrite][eventbrite].
|
||||
|
||||
---
|
||||
|
@ -70,13 +70,12 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||
<li><a href="https://software.esg-usa.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/esg-new-logo.png)">ESG</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
|
||||
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
|
||||
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
|
||||
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
|
||||
<li><a href="https://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
|
||||
<li><a href="https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/bitio_logo_gold_background.png)">bit.io</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).*
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), and [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).*
|
||||
|
||||
---
|
||||
|
||||
|
@ -84,15 +83,15 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
|
||||
REST framework requires the following:
|
||||
|
||||
* Python (3.5, 3.6, 3.7)
|
||||
* Django (1.11, 2.0, 2.1, 2.2)
|
||||
* Python (3.5, 3.6, 3.7, 3.8, 3.9)
|
||||
* Django (2.2, 3.0, 3.1)
|
||||
|
||||
We **highly recommend** and only officially support the latest patch release of
|
||||
each Python and Django series.
|
||||
|
||||
The following packages are optional:
|
||||
|
||||
* [coreapi][coreapi] (1.32.0+) - Schema generation support.
|
||||
* [PyYAML][pyyaml], [uritemplate][uriteemplate] (5.1+, 3.0.0+) - Schema generation support.
|
||||
* [Markdown][markdown] (3.0.0+) - Markdown support for the browsable API.
|
||||
* [Pygments][pygments] (2.4.0+) - Add syntax highlighting to Markdown processing.
|
||||
* [django-filter][django-filter] (1.0.1+) - Filtering support.
|
||||
|
@ -121,7 +120,7 @@ If you're intending to use the browsable API you'll probably also want to add RE
|
|||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^api-auth/', include('rest_framework.urls'))
|
||||
path('api-auth/', include('rest_framework.urls'))
|
||||
]
|
||||
|
||||
Note that the URL path can be whatever you want.
|
||||
|
@ -147,7 +146,7 @@ Don't forget to make sure you've also added `rest_framework` to your `INSTALLED_
|
|||
We're ready to create our API now.
|
||||
Here's our project's root `urls.py` module:
|
||||
|
||||
from django.conf.urls import url, include
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import routers, serializers, viewsets
|
||||
|
||||
|
@ -169,8 +168,8 @@ Here's our project's root `urls.py` module:
|
|||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
path('', include(router.urls)),
|
||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
]
|
||||
|
||||
You can now open the API in your browser at [http://127.0.0.1:8000/](http://127.0.0.1:8000/), and view your new 'users' API. If you use the login control in the top right corner you'll also be able to add, create and delete users from the system.
|
||||
|
@ -191,11 +190,6 @@ For support please see the [REST framework discussion group][group], try the `#
|
|||
|
||||
For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
|
||||
|
||||
For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter.
|
||||
|
||||
<a style="padding-top: 10px" href="https://twitter.com/_tomchristie" class="twitter-follow-button" data-show-count="false">Follow @_tomchristie</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
|
||||
## Security
|
||||
|
||||
If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**.
|
||||
|
@ -236,7 +230,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[redhat]: https://www.redhat.com/
|
||||
[heroku]: https://www.heroku.com/
|
||||
[eventbrite]: https://www.eventbrite.co.uk/about/
|
||||
[coreapi]: https://pypi.org/project/coreapi/
|
||||
[pyyaml]: https://pypi.org/project/PyYAML/
|
||||
[uriteemplate]: https://pypi.org/project/uritemplate/
|
||||
[markdown]: https://pypi.org/project/Markdown/
|
||||
[pygments]: https://pypi.org/project/Pygments/
|
||||
[django-filter]: https://pypi.org/project/django-filter/
|
||||
|
|
|
@ -384,7 +384,7 @@ First, install the API documentation views. These will include the schema resour
|
|||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^docs/', include_docs_urls(title='My API service'))
|
||||
path('docs/', include_docs_urls(title='My API service'), name='api-docs'),
|
||||
]
|
||||
|
||||
Once the API documentation URLs are installed, you'll be able to include both the required JavaScript resources. Note that the ordering of these two lines is important, as the schema loading requires CoreAPI to already be installed.
|
||||
|
@ -401,14 +401,14 @@ Once the API documentation URLs are installed, you'll be able to include both th
|
|||
|
||||
The `coreapi` library, and the `schema` object will now both be available on the `window` instance.
|
||||
|
||||
const coreapi = window.coreapi
|
||||
const schema = window.schema
|
||||
const coreapi = window.coreapi;
|
||||
const schema = window.schema;
|
||||
|
||||
## Instantiating a client
|
||||
|
||||
In order to interact with the API you'll need a client instance.
|
||||
|
||||
var client = new coreapi.Client()
|
||||
var client = new coreapi.Client();
|
||||
|
||||
Typically you'll also want to provide some authentication credentials when
|
||||
instantiating the client.
|
||||
|
@ -421,9 +421,9 @@ the user to login, and then instantiate a client using session authentication:
|
|||
|
||||
let auth = new coreapi.auth.SessionAuthentication({
|
||||
csrfCookieName: 'csrftoken',
|
||||
csrfHeaderName: 'X-CSRFToken'
|
||||
})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
csrfHeaderName: 'X-CSRFToken',
|
||||
});
|
||||
let client = new coreapi.Client({auth: auth});
|
||||
|
||||
The authentication scheme will handle including a CSRF header in any outgoing
|
||||
requests for unsafe HTTP methods.
|
||||
|
@ -434,10 +434,10 @@ The `TokenAuthentication` class can be used to support REST framework's built-in
|
|||
`TokenAuthentication`, as well as OAuth and JWT schemes.
|
||||
|
||||
let auth = new coreapi.auth.TokenAuthentication({
|
||||
scheme: 'JWT'
|
||||
token: '<token>'
|
||||
})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
scheme: 'JWT',
|
||||
token: '<token>',
|
||||
});
|
||||
let client = new coreapi.Client({auth: auth});
|
||||
|
||||
When using TokenAuthentication you'll probably need to implement a login flow
|
||||
using the CoreAPI client.
|
||||
|
@ -448,20 +448,20 @@ request to an "obtain token" endpoint
|
|||
For example, using the "Django REST framework JWT" package
|
||||
|
||||
// Setup some globally accessible state
|
||||
window.client = new coreapi.Client()
|
||||
window.loggedIn = false
|
||||
window.client = new coreapi.Client();
|
||||
window.loggedIn = false;
|
||||
|
||||
function loginUser(username, password) {
|
||||
let action = ["api-token-auth", "obtain-token"]
|
||||
let params = {username: "example", email: "example@example.com"}
|
||||
let action = ["api-token-auth", "obtain-token"];
|
||||
let params = {username: "example", email: "example@example.com"};
|
||||
client.action(schema, action, params).then(function(result) {
|
||||
// On success, instantiate an authenticated client.
|
||||
let auth = window.coreapi.auth.TokenAuthentication({
|
||||
scheme: 'JWT',
|
||||
token: result['token']
|
||||
token: result['token'],
|
||||
})
|
||||
window.client = coreapi.Client({auth: auth})
|
||||
window.loggedIn = true
|
||||
window.client = coreapi.Client({auth: auth});
|
||||
window.loggedIn = true;
|
||||
}).catch(function (error) {
|
||||
// Handle error case where eg. user provides incorrect credentials.
|
||||
})
|
||||
|
@ -473,23 +473,23 @@ The `BasicAuthentication` class can be used to support HTTP Basic Authentication
|
|||
|
||||
let auth = new coreapi.auth.BasicAuthentication({
|
||||
username: '<username>',
|
||||
password: '<password>'
|
||||
password: '<password>',
|
||||
})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
let client = new coreapi.Client({auth: auth});
|
||||
|
||||
## Using the client
|
||||
|
||||
Making requests:
|
||||
|
||||
let action = ["users", "list"]
|
||||
let action = ["users", "list"];
|
||||
client.action(schema, action).then(function(result) {
|
||||
// Return value is in 'result'
|
||||
})
|
||||
|
||||
Including parameters:
|
||||
|
||||
let action = ["users", "create"]
|
||||
let params = {username: "example", email: "example@example.com"}
|
||||
let action = ["users", "create"];
|
||||
let params = {username: "example", email: "example@example.com"};
|
||||
client.action(schema, action, params).then(function(result) {
|
||||
// Return value is in 'result'
|
||||
})
|
||||
|
@ -512,12 +512,12 @@ The coreapi package is available on NPM.
|
|||
|
||||
You'll either want to include the API schema in your codebase directly, by copying it from the `schema.js` resource, or else load the schema asynchronously. For example:
|
||||
|
||||
let client = new coreapi.Client()
|
||||
let schema = null
|
||||
let client = new coreapi.Client();
|
||||
let schema = null;
|
||||
client.get("https://api.example.org/").then(function(data) {
|
||||
// Load a CoreJSON API schema.
|
||||
schema = data
|
||||
console.log('schema loaded')
|
||||
schema = data;
|
||||
console.log('schema loaded');
|
||||
})
|
||||
|
||||
[heroku-api]: https://devcenter.heroku.com/categories/platform-api
|
||||
|
|
|
@ -45,7 +45,11 @@ this:
|
|||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout"
|
||||
layout: "BaseLayout",
|
||||
requestInterceptor: (request) => {
|
||||
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
|
||||
return request;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
@ -74,7 +78,7 @@ See the [Swagger UI documentation][swagger-ui] for advanced usage.
|
|||
### A minimal example with ReDoc.
|
||||
|
||||
Assuming you've followed the example from the schemas documentation for routing
|
||||
a dynamic `SchemaView`, a minimal Django template for using Swagger UI might be
|
||||
a dynamic `SchemaView`, a minimal Django template for using ReDoc might be
|
||||
this:
|
||||
|
||||
```html
|
||||
|
@ -138,55 +142,15 @@ This also translates into a very useful interactive documentation viewer in the
|
|||
|
||||
![Screenshot - drf-yasg][image-drf-yasg]
|
||||
|
||||
---
|
||||
#### drf-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework
|
||||
|
||||
#### Django REST Swagger
|
||||
[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility,
|
||||
customizability and client generation. Usage patterns are very similar to [drf-yasg][drf-yasg].
|
||||
|
||||
Marc Gibbons' [Django REST Swagger][django-rest-swagger] integrates REST framework with the [Swagger][swagger] API documentation tool. The package produces well presented API documentation, and includes interactive tools for testing API endpoints.
|
||||
|
||||
Django REST Swagger supports REST framework versions 2.3 and above.
|
||||
|
||||
Mark is also the author of the [REST Framework Docs][rest-framework-docs] package which offers clean, simple autogenerated documentation for your API but is deprecated and has moved to Django REST Swagger.
|
||||
|
||||
This package is fully documented, well supported, and comes highly recommended.
|
||||
|
||||
![Screenshot - Django REST Swagger][image-django-rest-swagger]
|
||||
|
||||
---
|
||||
|
||||
### DRF AutoDocs
|
||||
|
||||
Oleksander Mashianovs' [DRF Auto Docs][drfautodocs-repo] automated api renderer.
|
||||
|
||||
Collects almost all the code you written into documentation effortlessly.
|
||||
|
||||
Supports:
|
||||
|
||||
* functional view docs
|
||||
* tree-like structure
|
||||
* Docstrings:
|
||||
* markdown
|
||||
* preserve space & newlines
|
||||
* formatting with nice syntax
|
||||
* Fields:
|
||||
* choices rendering
|
||||
* help_text (to specify SerializerMethodField output, etc)
|
||||
* smart read_only/required rendering
|
||||
* Endpoint properties:
|
||||
* filter_backends
|
||||
* authentication_classes
|
||||
* permission_classes
|
||||
* extra url params(GET params)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### Apiary
|
||||
|
||||
There are various other online tools and services for providing API documentation. One notable service is [Apiary][apiary]. With Apiary, you describe your API using a simple markdown-like syntax. The generated documentation includes API interaction, a mock server for testing & prototyping, and various other tools.
|
||||
|
||||
![Screenshot - Apiary][image-apiary]
|
||||
It aims to extract as much schema information as possible, while providing decorators and extensions for easy
|
||||
customization. There is explicit support for [swagger-codegen][swagger], [SwaggerUI][swagger-ui] and [Redoc][redoc],
|
||||
i18n, versioning, authentication, polymorphism (dynamic requests and responses), query/path/header parameters,
|
||||
documentation and more. Several popular plugins for DRF are supported out-of-the-box as well.
|
||||
|
||||
---
|
||||
|
||||
|
@ -221,7 +185,7 @@ If the python `Markdown` library is installed, then [markdown syntax][markdown]
|
|||
[ref]: http://example.com/activating-accounts
|
||||
"""
|
||||
|
||||
Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
|
||||
Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
|
||||
|
||||
#### The `OPTIONS` method
|
||||
|
||||
|
@ -253,22 +217,18 @@ In this approach, rather than documenting the available API endpoints up front,
|
|||
To implement a hypermedia API you'll need to decide on an appropriate media type for the API, and implement a custom renderer and parser for that media type. The [REST, Hypermedia & HATEOAS][hypermedia-docs] section of the documentation includes pointers to background reading, as well as links to various hypermedia formats.
|
||||
|
||||
[cite]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
|
||||
[image-drf-yasg]: ../img/drf-yasg.png
|
||||
[drfautodocs-repo]: https://github.com/iMakedonsky/drf-autodocs
|
||||
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
|
||||
[swagger]: https://swagger.io/
|
||||
[open-api]: https://openapis.org/
|
||||
[rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs
|
||||
[apiary]: https://apiary.io/
|
||||
[markdown]: https://daringfireball.net/projects/markdown/syntax
|
||||
|
||||
[hypermedia-docs]: rest-hypermedia-hateoas.md
|
||||
[image-django-rest-swagger]: ../img/django-rest-swagger.png
|
||||
[image-apiary]: ../img/apiary.png
|
||||
[metadata-docs]: ../api-guide/metadata.md
|
||||
[schemas-examples]: ../api-guide/schemas.md#examples
|
||||
|
||||
[image-drf-yasg]: ../img/drf-yasg.png
|
||||
[image-self-describing-api]: ../img/self-describing.png
|
||||
[metadata-docs]: ../api-guide/metadata/
|
||||
|
||||
[schemas-examples]: ../api-guide/schemas/#examples
|
||||
[swagger-ui]: https://swagger.io/tools/swagger-ui/
|
||||
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
|
||||
[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/
|
||||
[markdown]: https://daringfireball.net/projects/markdown/syntax
|
||||
[open-api]: https://openapis.org/
|
||||
[redoc]: https://github.com/Rebilly/ReDoc
|
||||
|
||||
[swagger]: https://swagger.io/
|
||||
[swagger-ui]: https://swagger.io/tools/swagger-ui/
|
||||
|
|
|
@ -103,10 +103,10 @@ You can find more information on how the language preference is determined in th
|
|||
For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes.
|
||||
|
||||
[cite]: https://youtu.be/Wa0VfS2q94Y
|
||||
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
|
||||
[django-translation]: https://docs.djangoproject.com/en/stable/topics/i18n/translation
|
||||
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
|
||||
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
|
||||
[django-po-source]: https://raw.githubusercontent.com/encode/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
|
||||
[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
|
||||
[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
|
||||
[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name
|
||||
[django-language-preference]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#how-django-discovers-language-preference
|
||||
[django-locale-paths]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOCALE_PATHS
|
||||
[django-locale-name]: https://docs.djangoproject.com/en/stable/topics/i18n/#term-locale-name
|
||||
|
|
|
@ -34,7 +34,7 @@ REST framework also includes [serialization] and [parser]/[renderer] components
|
|||
|
||||
What REST framework doesn't do is give you machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
|
||||
|
||||
[cite]: https://vimeo.com/channels/restfest/page:2
|
||||
[cite]: https://vimeo.com/channels/restfest/49503453
|
||||
[dissertation]: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
|
||||
[hypertext-driven]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||
[restful-web-apis]: http://restfulwebapis.org/
|
||||
|
|
|
@ -29,13 +29,11 @@ REST framework provides two wrappers you can use to write API views.
|
|||
|
||||
These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed.
|
||||
|
||||
The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.data` with malformed input.
|
||||
The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exceptions that occur when accessing `request.data` with malformed input.
|
||||
|
||||
## Pulling it all together
|
||||
|
||||
Okay, let's go ahead and start using these new components to write a few views.
|
||||
|
||||
We don't need our `JSONResponse` class in `views.py` any more, so go ahead and delete that. Once that's done we can start refactoring our views slightly.
|
||||
Okay, let's go ahead and start using these new components to refactor our views slightly.
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
|
|
|
@ -137,7 +137,7 @@ 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:
|
||||
|
||||
from django.conf.urls import 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.
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
REST framework includes an abstraction for dealing with `ViewSets`, that allows the developer to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically, based on common conventions.
|
||||
|
||||
`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `read`, or `update`, and not method handlers such as `get` or `put`.
|
||||
`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `retrieve`, or `update`, and not method handlers such as `get` or `put`.
|
||||
|
||||
A `ViewSet` class is only bound to a set of method handlers at the last moment, when it is instantiated into a set of views, typically by using a `Router` class which handles the complexities of defining the URL conf for you.
|
||||
|
||||
|
@ -16,7 +16,7 @@ First of all let's refactor our `UserList` and `UserDetail` views into a single
|
|||
|
||||
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
This viewset automatically provides `list` and `detail` actions.
|
||||
This viewset automatically provides `list` and `retrieve` actions.
|
||||
"""
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
|
@ -27,6 +27,7 @@ Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighl
|
|||
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import permissions
|
||||
|
||||
class SnippetViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
|
|
|
@ -85,6 +85,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
|||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from rest_framework import viewsets
|
||||
from rest_framework import permissions
|
||||
from quickstart.serializers import UserSerializer, GroupSerializer
|
||||
|
||||
|
||||
|
@ -94,6 +95,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
|||
"""
|
||||
queryset = User.objects.all().order_by('-date_joined')
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
|
||||
class GroupViewSet(viewsets.ModelViewSet):
|
||||
|
@ -102,6 +104,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a
|
|||
"""
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`.
|
||||
|
||||
|
@ -134,12 +137,12 @@ Finally, we're including default login and logout views for use with the browsab
|
|||
|
||||
## Pagination
|
||||
Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
|
||||
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py`
|
||||
|
@ -175,7 +178,7 @@ We can now access our API, both from the command-line, using tools like `curl`..
|
|||
},
|
||||
{
|
||||
"email": "tom@example.com",
|
||||
"groups": [ ],
|
||||
"groups": [],
|
||||
"url": "http://127.0.0.1:8000/users/2/",
|
||||
"username": "tom"
|
||||
}
|
||||
|
@ -201,7 +204,7 @@ Or using the [httpie][httpie], command line tool...
|
|||
},
|
||||
{
|
||||
"email": "tom@example.com",
|
||||
"groups": [ ],
|
||||
"groups": [],
|
||||
"url": "http://127.0.0.1:8000/users/2/",
|
||||
"username": "tom"
|
||||
}
|
||||
|
@ -221,5 +224,5 @@ If you want to get a more in depth understanding of how REST framework fits toge
|
|||
|
||||
[image]: ../img/quickstart.png
|
||||
[tutorial]: 1-serialization.md
|
||||
[guide]: ../#api-guide
|
||||
[guide]: ../api-guide/requests.md
|
||||
[httpie]: https://github.com/jakubroztocil/httpie#installation
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
<h1 id="404-page-not-found" style="text-align: center">404</h1>
|
||||
<p style="text-align: center"><strong>Page not found</strong></p>
|
||||
<p style="text-align: center">Try the <a href="https://www.django-rest-framework.org/">homepage</a>, or <a href="#searchModal" data-toggle="modal">search the documentation</a>.</p>
|
||||
<p style="text-align: center">Try the <a href="{{ base_url }}">homepage</a>, or <a href="#mkdocs_search_modal" data-toggle="modal">search the documentation</a>.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,7 +6,7 @@ pre {
|
|||
|
||||
.dropdown .dropdown-menu {
|
||||
display: none;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dropdown.open .dropdown-menu {
|
||||
|
@ -74,6 +74,12 @@ pre {
|
|||
white-space: pre;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
/* Preserve the spacing of the navbar across different screen sizes. */
|
||||
.navbar-inner {
|
||||
/*padding: 5px 0;*/
|
||||
|
@ -432,3 +438,4 @@ ul.sponsor {
|
|||
margin: 0 !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,22 +5,18 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<title>{% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}</title>
|
||||
<link href="{{ base_url }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||
<link href="{{ 'img/favicon.ico'|url }}" rel="icon" type="image/x-icon">
|
||||
<link rel="canonical" href="{{ page.canonical_url|url }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Django, API, REST{% if page %}, {{ page.title }}{% endif %}">
|
||||
<meta name="author" content="Tom Christie">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="{{ base_url }}/css/prettify.css" rel="stylesheet">
|
||||
<link href="{{ base_url }}/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="{{ base_url }}/css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="{{ base_url }}/css/default.css" rel="stylesheet">
|
||||
<link href="{{ 'css/prettify.css'|url }}" rel="stylesheet">
|
||||
<link href="{{ 'css/bootstrap.css'|url }}" rel="stylesheet">
|
||||
<link href="{{ 'css/bootstrap-responsive.css'|url }}" rel="stylesheet">
|
||||
<link href="{{ 'css/default.css'|url }}" rel="stylesheet">
|
||||
|
||||
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
|
@ -102,7 +98,7 @@
|
|||
{% endfor %}
|
||||
|
||||
<div class="promo">
|
||||
<hr/>
|
||||
{% if page.toc %}<hr/>{% endif %}
|
||||
<div id="sidebarInclude">
|
||||
</div>
|
||||
</ul>
|
||||
|
@ -139,10 +135,10 @@
|
|||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script async src="https://fund.django-rest-framework.org/sidebar_include.js"></script>
|
||||
<script src="{{ base_url }}/js/jquery-1.8.1-min.js"></script>
|
||||
<script src="{{ base_url }}/js/prettify-1.0.js"></script>
|
||||
<script src="{{ base_url }}/js/bootstrap-2.1.1-min.js"></script>
|
||||
<script src="{{ base_url }}/js/theme.js"></script>
|
||||
<script src="{{ 'js/jquery-1.8.1-min.js'|url }}"></script>
|
||||
<script src="{{ 'js/prettify-1.0.js'|url }}"></script>
|
||||
<script src="{{ 'js/bootstrap-2.1.1-min.js'|url }}"></script>
|
||||
<script src="{{ 'js/theme.js'|url }}"></script>
|
||||
|
||||
<script>var base_url = '{{ base_url }}';</script>
|
||||
{% for path in config.extra_javascript %}
|
||||
|
|
|
@ -66,6 +66,8 @@ nav:
|
|||
- 'Contributing to REST framework': 'community/contributing.md'
|
||||
- 'Project management': 'community/project-management.md'
|
||||
- 'Release Notes': 'community/release-notes.md'
|
||||
- '3.12 Announcement': 'community/3.12-announcement.md'
|
||||
- '3.11 Announcement': 'community/3.11-announcement.md'
|
||||
- '3.10 Announcement': 'community/3.10-announcement.md'
|
||||
- '3.9 Announcement': 'community/3.9-announcement.md'
|
||||
- '3.8 Announcement': 'community/3.8-announcement.md'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# PEP8 code linting, which we run on all commits.
|
||||
flake8==3.5.0
|
||||
flake8-tidy-imports==1.1.0
|
||||
pycodestyle==2.3.1
|
||||
flake8==3.8.3
|
||||
flake8-tidy-imports==4.1.0
|
||||
pycodestyle==2.6.0
|
||||
|
||||
# Sort and lint imports
|
||||
isort==4.3.3
|
||||
isort==5.4.2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# MkDocs to build our documentation.
|
||||
mkdocs==1.0.4
|
||||
mkdocs==1.1
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Optional packages which may be used with REST framework.
|
||||
psycopg2-binary>=2.8.2, <2.9
|
||||
markdown==3.1.1
|
||||
psycopg2-binary>=2.8.5, <2.9
|
||||
markdown==3.3;python_version>="3.6"
|
||||
markdown==3.2.2;python_version=="3.5"
|
||||
pygments==2.4.2
|
||||
django-guardian==1.5.0
|
||||
django-guardian==2.2.0
|
||||
django-filter>=2.2.0, <2.3
|
||||
coreapi==2.3.1
|
||||
coreschema==0.0.4
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Wheel for PyPI installs.
|
||||
wheel==0.30.0
|
||||
wheel==0.34.2
|
||||
|
||||
# Twine for secured PyPI uploads.
|
||||
twine==1.11.0
|
||||
twine==3.1.1
|
||||
|
||||
# Transifex client for managing translation resources.
|
||||
transifex-client==0.11
|
||||
transifex-client==0.13.9
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Pytest for running the tests.
|
||||
pytest>=5.0,<5.1
|
||||
pytest-django>=3.5.1,<3.6
|
||||
pytest>=5.4.1,<5.5
|
||||
pytest-django>=3.9.0,<3.10
|
||||
pytest-cov>=2.7.1
|
||||
six>=1.14.0
|
||||
|
|
|
@ -7,10 +7,12 @@ ______ _____ _____ _____ __
|
|||
\_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
|
||||
"""
|
||||
|
||||
import django
|
||||
|
||||
__title__ = 'Django REST framework'
|
||||
__version__ = '3.10.1'
|
||||
__version__ = '3.12.2'
|
||||
__author__ = 'Tom Christie'
|
||||
__license__ = 'BSD 2-Clause'
|
||||
__license__ = 'BSD 3-Clause'
|
||||
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
|
||||
|
||||
# Version synonym
|
||||
|
@ -22,12 +24,14 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
|
|||
# Default datetime input and output formats
|
||||
ISO_8601 = 'iso-8601'
|
||||
|
||||
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
|
||||
|
||||
if django.VERSION < (3, 2):
|
||||
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
|
||||
|
||||
|
||||
class RemovedInDRF311Warning(DeprecationWarning):
|
||||
class RemovedInDRF313Warning(DeprecationWarning):
|
||||
pass
|
||||
|
||||
|
||||
class RemovedInDRF312Warning(PendingDeprecationWarning):
|
||||
class RemovedInDRF314Warning(PendingDeprecationWarning):
|
||||
pass
|
||||
|
|
|
@ -74,7 +74,11 @@ class BasicAuthentication(BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
try:
|
||||
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
|
||||
try:
|
||||
auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
|
||||
auth_parts = auth_decoded.partition(':')
|
||||
except (TypeError, UnicodeDecodeError, binascii.Error):
|
||||
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
|
||||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
@ -132,7 +136,10 @@ class SessionAuthentication(BaseAuthentication):
|
|||
"""
|
||||
Enforce CSRF validation for session based authentication.
|
||||
"""
|
||||
check = CSRFCheck()
|
||||
def dummy_get_response(request): # pragma: no cover
|
||||
return None
|
||||
|
||||
check = CSRFCheck(dummy_get_response)
|
||||
# populates request.META['CSRF_COOKIE'], which is used in process_view()
|
||||
check.process_request(request)
|
||||
reason = check.process_view(request, None, (), {})
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'
|
||||
import django
|
||||
|
||||
if django.VERSION < (3, 2):
|
||||
default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig'
|
||||
|
|
|
@ -1,12 +1,51 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin.utils import quote
|
||||
from django.contrib.admin.views.main import ChangeList
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.authtoken.models import Token, TokenProxy
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class TokenChangeList(ChangeList):
|
||||
"""Map to matching User id"""
|
||||
def url_for_result(self, result):
|
||||
pk = result.user.pk
|
||||
return reverse('admin:%s_%s_change' % (self.opts.app_label,
|
||||
self.opts.model_name),
|
||||
args=(quote(pk),),
|
||||
current_app=self.model_admin.admin_site.name)
|
||||
|
||||
|
||||
class TokenAdmin(admin.ModelAdmin):
|
||||
list_display = ('key', 'user', 'created')
|
||||
fields = ('user',)
|
||||
ordering = ('-created',)
|
||||
actions = None # Actions not compatible with mapped IDs.
|
||||
|
||||
def get_changelist(self, request, **kwargs):
|
||||
return TokenChangeList
|
||||
|
||||
def get_object(self, request, object_id, from_field=None):
|
||||
"""
|
||||
Map from User ID to matching Token.
|
||||
"""
|
||||
queryset = self.get_queryset(request)
|
||||
field = User._meta.pk
|
||||
try:
|
||||
object_id = field.to_python(object_id)
|
||||
user = User.objects.get(**{field.name: object_id})
|
||||
return queryset.get(user=user)
|
||||
except (queryset.model.DoesNotExist, User.DoesNotExist, ValidationError, ValueError):
|
||||
return None
|
||||
|
||||
def delete_model(self, request, obj):
|
||||
# Map back to actual Token, since delete() uses pk.
|
||||
token = Token.objects.get(key=obj.key)
|
||||
return super().delete_model(request, token)
|
||||
|
||||
|
||||
admin.site.register(Token, TokenAdmin)
|
||||
admin.site.register(TokenProxy, TokenAdmin)
|
||||
|
|
25
rest_framework/authtoken/migrations/0003_tokenproxy.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-28 09:34
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authtoken', '0002_auto_20160226_1747'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TokenProxy',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'token',
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('authtoken.token',),
|
||||
),
|
||||
]
|
|
@ -32,8 +32,23 @@ class Token(models.Model):
|
|||
self.key = self.generate_key()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def generate_key(self):
|
||||
@classmethod
|
||||
def generate_key(cls):
|
||||
return binascii.hexlify(os.urandom(20)).decode()
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
||||
|
||||
class TokenProxy(Token):
|
||||
"""
|
||||
Proxy mapping pk to user pk for use in admin.
|
||||
"""
|
||||
@property
|
||||
def pk(self):
|
||||
return self.user.pk
|
||||
|
||||
class Meta:
|
||||
proxy = 'rest_framework.authtoken' in settings.INSTALLED_APPS
|
||||
abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
|
||||
verbose_name = "token"
|
||||
|
|
|
@ -5,11 +5,19 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class AuthTokenSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(label=_("Username"))
|
||||
username = serializers.CharField(
|
||||
label=_("Username"),
|
||||
write_only=True
|
||||
)
|
||||
password = serializers.CharField(
|
||||
label=_("Password"),
|
||||
style={'input_type': 'password'},
|
||||
trim_whitespace=False
|
||||
trim_whitespace=False,
|
||||
write_only=True
|
||||
)
|
||||
token = serializers.CharField(
|
||||
label=_("Token"),
|
||||
read_only=True
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
|
|
|
@ -4,6 +4,7 @@ from rest_framework.authtoken.serializers import AuthTokenSerializer
|
|||
from rest_framework.compat import coreapi, coreschema
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.schemas import ManualSchema
|
||||
from rest_framework.schemas import coreapi as coreapi_schema
|
||||
from rest_framework.views import APIView
|
||||
|
||||
|
||||
|
@ -13,7 +14,8 @@ class ObtainAuthToken(APIView):
|
|||
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
|
||||
renderer_classes = (renderers.JSONRenderer,)
|
||||
serializer_class = AuthTokenSerializer
|
||||
if coreapi is not None and coreschema is not None:
|
||||
|
||||
if coreapi_schema.is_enabled():
|
||||
schema = ManualSchema(
|
||||
fields=[
|
||||
coreapi.Field(
|
||||
|
@ -38,9 +40,19 @@ class ObtainAuthToken(APIView):
|
|||
encoding="application/json",
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'format': self.format_kwarg,
|
||||
'view': self
|
||||
}
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data,
|
||||
context={'request': request})
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.validated_data['user']
|
||||
token, created = Token.objects.get_or_create(user=user)
|
||||
|
|
|
@ -9,7 +9,7 @@ def pagination_system_check(app_configs, **kwargs):
|
|||
if api_settings.PAGE_SIZE and not api_settings.DEFAULT_PAGINATION_CLASS:
|
||||
errors.append(
|
||||
Warning(
|
||||
"You have specified a default PAGE_SIZE pagination rest_framework setting,"
|
||||
"You have specified a default PAGE_SIZE pagination rest_framework setting, "
|
||||
"without specifying also a DEFAULT_PAGINATION_CLASS.",
|
||||
hint="The default for DEFAULT_PAGINATION_CLASS is None. "
|
||||
"In previous versions this was PageNumberPagination. "
|
||||
|
|
|
@ -2,75 +2,9 @@
|
|||
The `compat` module provides support for backwards compatibility with older
|
||||
versions of Django/Python, and compatibility wrappers around optional packages.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.generic import View
|
||||
|
||||
try:
|
||||
from django.urls import ( # noqa
|
||||
URLPattern,
|
||||
URLResolver,
|
||||
)
|
||||
except ImportError:
|
||||
# Will be removed in Django 2.0
|
||||
from django.urls import ( # noqa
|
||||
RegexURLPattern as URLPattern,
|
||||
RegexURLResolver as URLResolver,
|
||||
)
|
||||
|
||||
try:
|
||||
from django.core.validators import ProhibitNullCharactersValidator # noqa
|
||||
except ImportError:
|
||||
ProhibitNullCharactersValidator = None
|
||||
|
||||
|
||||
def get_original_route(urlpattern):
|
||||
"""
|
||||
Get the original route/regex that was typed in by the user into the path(), re_path() or url() directive. This
|
||||
is in contrast with get_regex_pattern below, which for RoutePattern returns the raw regex generated from the path().
|
||||
"""
|
||||
if hasattr(urlpattern, 'pattern'):
|
||||
# Django 2.0
|
||||
return str(urlpattern.pattern)
|
||||
else:
|
||||
# Django < 2.0
|
||||
return urlpattern.regex.pattern
|
||||
|
||||
|
||||
def get_regex_pattern(urlpattern):
|
||||
"""
|
||||
Get the raw regex out of the urlpattern's RegexPattern or RoutePattern. This is always a regular expression,
|
||||
unlike get_original_route above.
|
||||
"""
|
||||
if hasattr(urlpattern, 'pattern'):
|
||||
# Django 2.0
|
||||
return urlpattern.pattern.regex.pattern
|
||||
else:
|
||||
# Django < 2.0
|
||||
return urlpattern.regex.pattern
|
||||
|
||||
|
||||
def is_route_pattern(urlpattern):
|
||||
if hasattr(urlpattern, 'pattern'):
|
||||
# Django 2.0
|
||||
from django.urls.resolvers import RoutePattern
|
||||
return isinstance(urlpattern.pattern, RoutePattern)
|
||||
else:
|
||||
# Django < 2.0
|
||||
return False
|
||||
|
||||
|
||||
def make_url_resolver(regex, urlpatterns):
|
||||
try:
|
||||
# Django 2.0
|
||||
from django.urls.resolvers import RegexPattern
|
||||
return URLResolver(RegexPattern(regex), urlpatterns)
|
||||
|
||||
except ImportError:
|
||||
# Django < 2.0
|
||||
return URLResolver(regex, urlpatterns)
|
||||
|
||||
|
||||
def unicode_http_header(value):
|
||||
# Coerce HTTP header value to unicode.
|
||||
|
@ -162,8 +96,8 @@ except ImportError:
|
|||
|
||||
try:
|
||||
import pygments
|
||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import TextLexer, get_lexer_by_name
|
||||
|
||||
def pygments_highlight(text, lang, style):
|
||||
lexer = get_lexer_by_name(lang, stripall=False)
|
||||
|
@ -187,9 +121,10 @@ if markdown is not None and pygments is not None:
|
|||
# starting from this blogpost and modified to support current markdown extensions API
|
||||
# https://zerokspot.com/weblog/2008/06/18/syntax-highlighting-in-markdown-with-pygments/
|
||||
|
||||
from markdown.preprocessors import Preprocessor
|
||||
import re
|
||||
|
||||
from markdown.preprocessors import Preprocessor
|
||||
|
||||
class CodeBlockPreprocessor(Preprocessor):
|
||||
pattern = re.compile(
|
||||
r'^\s*``` *([^\n]+)\n(.+?)^\s*```', re.M | re.S)
|
||||
|
@ -217,22 +152,8 @@ else:
|
|||
return False
|
||||
|
||||
|
||||
# Django 1.x url routing syntax. Remove when dropping Django 1.11 support.
|
||||
try:
|
||||
from django.urls import include, path, re_path, register_converter # noqa
|
||||
except ImportError:
|
||||
from django.conf.urls import include, url # noqa
|
||||
path = None
|
||||
register_converter = None
|
||||
re_path = url
|
||||
|
||||
|
||||
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
|
||||
# See: https://bugs.python.org/issue22767
|
||||
SHORT_SEPARATORS = (',', ':')
|
||||
LONG_SEPARATORS = (', ', ': ')
|
||||
INDENT_SEPARATORS = (',', ': ')
|
||||
|
||||
|
||||
# Version Constants.
|
||||
PY36 = sys.version_info >= (3, 6)
|
||||
|
|
|
@ -124,8 +124,23 @@ def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
|
|||
"""
|
||||
Mark a ViewSet method as a routable action.
|
||||
|
||||
Set the `detail` boolean to determine if this action should apply to
|
||||
instance/detail requests or collection/list requests.
|
||||
`@action`-decorated functions will be endowed with a `mapping` property,
|
||||
a `MethodMapper` that can be used to add additional method-based behaviors
|
||||
on the routed action.
|
||||
|
||||
:param methods: A list of HTTP method names this action responds to.
|
||||
Defaults to GET only.
|
||||
:param detail: Required. Determines whether this action applies to
|
||||
instance/detail requests or collection/list requests.
|
||||
:param url_path: Define the URL segment for this action. Defaults to the
|
||||
name of the method decorated.
|
||||
:param url_name: Define the internal (`reverse`) URL name for this action.
|
||||
Defaults to the name of the method decorated with underscores
|
||||
replaced with dashes.
|
||||
:param kwargs: Additional properties to set on the view. This can be used
|
||||
to override viewset-level *_classes settings, equivalent to
|
||||
how the `@renderer_classes` etc. decorators work for function-
|
||||
based API views.
|
||||
"""
|
||||
methods = ['get'] if (methods is None) else methods
|
||||
methods = [method.lower() for method in methods]
|
||||
|
@ -144,6 +159,10 @@ def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
|
|||
func.detail = detail
|
||||
func.url_path = url_path if url_path else func.__name__
|
||||
func.url_name = url_name if url_name else func.__name__.replace('_', '-')
|
||||
|
||||
# These kwargs will end up being passed to `ViewSet.as_view()` within
|
||||
# the router, which eventually delegates to Django's CBV `View`,
|
||||
# which assigns them as instance attributes for each request.
|
||||
func.kwargs = kwargs
|
||||
|
||||
# Set descriptive arguments for viewsets
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.conf.urls import include, url
|
||||
from django.urls import include, path
|
||||
|
||||
from rest_framework.renderers import (
|
||||
CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
|
||||
|
@ -82,7 +82,7 @@ def include_docs_urls(
|
|||
permission_classes=permission_classes,
|
||||
)
|
||||
urls = [
|
||||
url(r'^$', docs_view, name='docs-index'),
|
||||
url(r'^schema.js$', schema_js_view, name='schema-js')
|
||||
path('', docs_view, name='docs-index'),
|
||||
path('schema.js', schema_js_view, name='schema-js')
|
||||
]
|
||||
return include((urls, 'api-docs'), namespace='api-docs')
|
||||
|
|
|
@ -20,7 +20,7 @@ def _get_error_details(data, default_code=None):
|
|||
Descend into a nested data structure, forcing any
|
||||
lazy translation strings or strings into `ErrorDetail`.
|
||||
"""
|
||||
if isinstance(data, list):
|
||||
if isinstance(data, (list, tuple)):
|
||||
ret = [
|
||||
_get_error_details(item, default_code) for item in data
|
||||
]
|
||||
|
@ -73,6 +73,8 @@ class ErrorDetail(str):
|
|||
|
||||
def __eq__(self, other):
|
||||
r = super().__eq__(other)
|
||||
if r is NotImplemented:
|
||||
return NotImplemented
|
||||
try:
|
||||
return r and self.code == other.code
|
||||
except AttributeError:
|
||||
|
@ -148,7 +150,9 @@ class ValidationError(APIException):
|
|||
|
||||
# For validation failures, we may collect many errors together,
|
||||
# so the details should always be coerced to a list if not already.
|
||||
if not isinstance(detail, dict) and not isinstance(detail, list):
|
||||
if isinstance(detail, tuple):
|
||||
detail = list(detail)
|
||||
elif not isinstance(detail, dict) and not isinstance(detail, list):
|
||||
detail = [detail]
|
||||
|
||||
self.detail = _get_error_details(detail, code)
|
||||
|
|
|
@ -5,6 +5,7 @@ import functools
|
|||
import inspect
|
||||
import re
|
||||
import uuid
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
@ -13,7 +14,8 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.core.validators import (
|
||||
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
||||
MinValueValidator, RegexValidator, URLValidator, ip_address_validators
|
||||
MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
|
||||
URLValidator, ip_address_validators
|
||||
)
|
||||
from django.forms import FilePathField as DjangoFilePathField
|
||||
from django.forms import ImageField as DjangoImageField
|
||||
|
@ -22,19 +24,21 @@ from django.utils.dateparse import (
|
|||
parse_date, parse_datetime, parse_duration, parse_time
|
||||
)
|
||||
from django.utils.duration import duration_string
|
||||
from django.utils.encoding import is_protected_type, smart_text
|
||||
from django.utils.encoding import is_protected_type, smart_str
|
||||
from django.utils.formats import localize_input, sanitize_separators
|
||||
from django.utils.ipv6 import clean_ipv6_address
|
||||
from django.utils.timezone import utc
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pytz.exceptions import InvalidTimeError
|
||||
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework.compat import ProhibitNullCharactersValidator
|
||||
from rest_framework import (
|
||||
ISO_8601, RemovedInDRF313Warning, RemovedInDRF314Warning
|
||||
)
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import html, humanize_datetime, json, representation
|
||||
from rest_framework.utils.formatting import lazy_format
|
||||
from rest_framework.validators import ProhibitSurrogateCharactersValidator
|
||||
|
||||
|
||||
class empty:
|
||||
|
@ -249,19 +253,30 @@ class CreateOnlyDefault:
|
|||
for create operations, but that do not return any value for update
|
||||
operations.
|
||||
"""
|
||||
requires_context = True
|
||||
|
||||
def __init__(self, default):
|
||||
self.default = default
|
||||
|
||||
def set_context(self, serializer_field):
|
||||
self.is_update = serializer_field.parent.instance is not None
|
||||
if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_update:
|
||||
self.default.set_context(serializer_field)
|
||||
|
||||
def __call__(self):
|
||||
if self.is_update:
|
||||
def __call__(self, serializer_field):
|
||||
is_update = serializer_field.parent.instance is not None
|
||||
if is_update:
|
||||
raise SkipField()
|
||||
if callable(self.default):
|
||||
return self.default()
|
||||
if hasattr(self.default, 'set_context'):
|
||||
warnings.warn(
|
||||
"Method `set_context` on defaults is deprecated and will "
|
||||
"no longer be called starting with 3.13. Instead set "
|
||||
"`requires_context = True` on the class, and accept the "
|
||||
"context as an additional argument.",
|
||||
RemovedInDRF313Warning, stacklevel=2
|
||||
)
|
||||
self.default.set_context(self)
|
||||
|
||||
if getattr(self.default, 'requires_context', False):
|
||||
return self.default(serializer_field)
|
||||
else:
|
||||
return self.default()
|
||||
return self.default
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -269,11 +284,10 @@ class CreateOnlyDefault:
|
|||
|
||||
|
||||
class CurrentUserDefault:
|
||||
def set_context(self, serializer_field):
|
||||
self.user = serializer_field.context['request'].user
|
||||
requires_context = True
|
||||
|
||||
def __call__(self):
|
||||
return self.user
|
||||
def __call__(self, serializer_field):
|
||||
return serializer_field.context['request'].user
|
||||
|
||||
def __repr__(self):
|
||||
return '%s()' % self.__class__.__name__
|
||||
|
@ -489,8 +503,20 @@ class Field:
|
|||
raise SkipField()
|
||||
if callable(self.default):
|
||||
if hasattr(self.default, 'set_context'):
|
||||
warnings.warn(
|
||||
"Method `set_context` on defaults is deprecated and will "
|
||||
"no longer be called starting with 3.13. Instead set "
|
||||
"`requires_context = True` on the class, and accept the "
|
||||
"context as an additional argument.",
|
||||
RemovedInDRF313Warning, stacklevel=2
|
||||
)
|
||||
self.default.set_context(self)
|
||||
return self.default()
|
||||
|
||||
if getattr(self.default, 'requires_context', False):
|
||||
return self.default(self)
|
||||
else:
|
||||
return self.default()
|
||||
|
||||
return self.default
|
||||
|
||||
def validate_empty_values(self, data):
|
||||
|
@ -551,10 +577,20 @@ class Field:
|
|||
errors = []
|
||||
for validator in self.validators:
|
||||
if hasattr(validator, 'set_context'):
|
||||
warnings.warn(
|
||||
"Method `set_context` on validators is deprecated and will "
|
||||
"no longer be called starting with 3.13. Instead set "
|
||||
"`requires_context = True` on the class, and accept the "
|
||||
"context as an additional argument.",
|
||||
RemovedInDRF313Warning, stacklevel=2
|
||||
)
|
||||
validator.set_context(self)
|
||||
|
||||
try:
|
||||
validator(value)
|
||||
if getattr(validator, 'requires_context', False):
|
||||
validator(value, self)
|
||||
else:
|
||||
validator(value)
|
||||
except ValidationError as exc:
|
||||
# If the validation error contains a mapping of fields to
|
||||
# errors then simply raise it immediately rather than
|
||||
|
@ -572,8 +608,11 @@ class Field:
|
|||
Transform the *incoming* primitive data into a native value.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'{cls}.to_internal_value() must be implemented.'.format(
|
||||
cls=self.__class__.__name__
|
||||
'{cls}.to_internal_value() must be implemented for field '
|
||||
'{field_name}. If you do not need to support write operations '
|
||||
'you probably want to subclass `ReadOnlyField` instead.'.format(
|
||||
cls=self.__class__.__name__,
|
||||
field_name=self.field_name,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -582,9 +621,7 @@ class Field:
|
|||
Transform the *outgoing* native value into primitive data.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'{cls}.to_representation() must be implemented for field '
|
||||
'{field_name}. If you do not need to support write operations '
|
||||
'you probably want to subclass `ReadOnlyField` instead.'.format(
|
||||
'{cls}.to_representation() must be implemented for field {field_name}.'.format(
|
||||
cls=self.__class__.__name__,
|
||||
field_name=self.field_name,
|
||||
)
|
||||
|
@ -705,55 +742,22 @@ class BooleanField(Field):
|
|||
return bool(value)
|
||||
|
||||
|
||||
class NullBooleanField(Field):
|
||||
default_error_messages = {
|
||||
'invalid': _('Must be a valid boolean.')
|
||||
}
|
||||
class NullBooleanField(BooleanField):
|
||||
initial = None
|
||||
TRUE_VALUES = {
|
||||
't', 'T',
|
||||
'y', 'Y', 'yes', 'YES',
|
||||
'true', 'True', 'TRUE',
|
||||
'on', 'On', 'ON',
|
||||
'1', 1,
|
||||
True
|
||||
}
|
||||
FALSE_VALUES = {
|
||||
'f', 'F',
|
||||
'n', 'N', 'no', 'NO',
|
||||
'false', 'False', 'FALSE',
|
||||
'off', 'Off', 'OFF',
|
||||
'0', 0, 0.0,
|
||||
False
|
||||
}
|
||||
NULL_VALUES = {'null', 'Null', 'NULL', '', None}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
warnings.warn(
|
||||
"The `NullBooleanField` is deprecated and will be removed starting "
|
||||
"with 3.14. Instead use the `BooleanField` field and set "
|
||||
"`allow_null=True` which does the same thing.",
|
||||
RemovedInDRF314Warning, stacklevel=2
|
||||
)
|
||||
|
||||
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.'
|
||||
kwargs['allow_null'] = True
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
try:
|
||||
if data in self.TRUE_VALUES:
|
||||
return True
|
||||
elif data in self.FALSE_VALUES:
|
||||
return False
|
||||
elif data in self.NULL_VALUES:
|
||||
return None
|
||||
except TypeError: # Input is an unhashable type
|
||||
pass
|
||||
self.fail('invalid', input=data)
|
||||
|
||||
def to_representation(self, value):
|
||||
if value in self.NULL_VALUES:
|
||||
return None
|
||||
if value in self.TRUE_VALUES:
|
||||
return True
|
||||
elif value in self.FALSE_VALUES:
|
||||
return False
|
||||
return bool(value)
|
||||
|
||||
|
||||
# String types...
|
||||
|
||||
|
@ -781,9 +785,8 @@ class CharField(Field):
|
|||
self.validators.append(
|
||||
MinLengthValidator(self.min_length, message=message))
|
||||
|
||||
# ProhibitNullCharactersValidator is None on Django < 2.0
|
||||
if ProhibitNullCharactersValidator is not None:
|
||||
self.validators.append(ProhibitNullCharactersValidator())
|
||||
self.validators.append(ProhibitNullCharactersValidator())
|
||||
self.validators.append(ProhibitSurrogateCharactersValidator())
|
||||
|
||||
def run_validation(self, data=empty):
|
||||
# Test for the empty string here so that it does not get validated,
|
||||
|
@ -1049,7 +1052,7 @@ class DecimalField(Field):
|
|||
instance.
|
||||
"""
|
||||
|
||||
data = smart_text(data).strip()
|
||||
data = smart_str(data).strip()
|
||||
|
||||
if self.localize:
|
||||
data = sanitize_separators(data)
|
||||
|
@ -1062,9 +1065,7 @@ class DecimalField(Field):
|
|||
except decimal.DecimalException:
|
||||
self.fail('invalid')
|
||||
|
||||
# Check for NaN. It is the only value that isn't equal to itself,
|
||||
# so we can use this to identify NaN values.
|
||||
if value != value:
|
||||
if value.is_nan():
|
||||
self.fail('invalid')
|
||||
|
||||
# Check for infinity and negative infinity.
|
||||
|
@ -1757,6 +1758,7 @@ class JSONField(Field):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.binary = kwargs.pop('binary', False)
|
||||
self.encoder = kwargs.pop('encoder', None)
|
||||
self.decoder = kwargs.pop('decoder', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_value(self, dictionary):
|
||||
|
@ -1764,8 +1766,8 @@ class JSONField(Field):
|
|||
# When HTML form input is used, mark up the input
|
||||
# as being a JSON string, rather than a JSON primitive.
|
||||
class JSONString(str):
|
||||
def __new__(self, value):
|
||||
ret = str.__new__(self, value)
|
||||
def __new__(cls, value):
|
||||
ret = str.__new__(cls, value)
|
||||
ret.is_json_string = True
|
||||
return ret
|
||||
return JSONString(dictionary[self.field_name])
|
||||
|
@ -1776,7 +1778,7 @@ class JSONField(Field):
|
|||
if self.binary or getattr(data, 'is_json_string', False):
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode()
|
||||
return json.loads(data)
|
||||
return json.loads(data, cls=self.decoder)
|
||||
else:
|
||||
json.dumps(data, cls=self.encoder)
|
||||
except (TypeError, ValueError):
|
||||
|
@ -1857,14 +1859,9 @@ class SerializerMethodField(Field):
|
|||
super().__init__(**kwargs)
|
||||
|
||||
def bind(self, field_name, parent):
|
||||
# In order to enforce a consistent style, we error if a redundant
|
||||
# 'method_name' argument has been used. For example:
|
||||
# my_field = serializer.SerializerMethodField(method_name='get_my_field')
|
||||
default_method_name = 'get_{field_name}'.format(field_name=field_name)
|
||||
|
||||
# The method name should default to `get_{field_name}`.
|
||||
# The method name defaults to `get_{field_name}`.
|
||||
if self.method_name is None:
|
||||
self.method_name = default_method_name
|
||||
self.method_name = 'get_{field_name}'.format(field_name=field_name)
|
||||
|
||||
super().bind(field_name, parent)
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ from functools import reduce
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import models
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.sql.constants import ORDER_PATTERN
|
||||
from django.template import loader
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -86,7 +85,7 @@ class SearchFilter(BaseFilterBackend):
|
|||
search_field = search_field[1:]
|
||||
# Annotated fields do not need to be distinct
|
||||
if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
|
||||
return False
|
||||
continue
|
||||
parts = search_field.split(LOOKUP_SEP)
|
||||
for part in parts:
|
||||
field = opts.get_field(part)
|
||||
|
@ -97,6 +96,9 @@ class SearchFilter(BaseFilterBackend):
|
|||
if any(path.m2m for path in path_info):
|
||||
# This field is a m2m relation so we know we need to call distinct
|
||||
return True
|
||||
else:
|
||||
# This field has a custom __ query transform but is not a relational field.
|
||||
break
|
||||
return False
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
|
@ -256,7 +258,13 @@ class OrderingFilter(BaseFilterBackend):
|
|||
|
||||
def remove_invalid_fields(self, queryset, fields, view, request):
|
||||
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
|
||||
return [term for term in fields if term.lstrip('-') in valid_fields and ORDER_PATTERN.match(term)]
|
||||
|
||||
def term_valid(term):
|
||||
if term.startswith("-"):
|
||||
term = term[1:]
|
||||
return term in valid_fields
|
||||
|
||||
return [term for term in fields if term_valid(term)]
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ordering = self.get_ordering(request, queryset, view)
|
||||
|
|
|
@ -106,7 +106,7 @@ class GenericAPIView(views.APIView):
|
|||
deserializing input, and for serializing output.
|
||||
"""
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
# aymen chaieb <chaieb.aymen1992@gmail.com>, 2017
|
||||
# Bashar Al-Abdulhadi, 2016-2017
|
||||
# Eyad Toma <d.eyad.t@gmail.com>, 2015,2017
|
||||
# zak zak <zakaria.bendifallah@gmail.com>, 2020
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django REST framework\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
|
||||
"PO-Revision-Date: 2017-10-18 09:51+0000\n"
|
||||
"Last-Translator: Andrew Ayoub <andrew.ayoub@connectads.com>\n"
|
||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
||||
"Language-Team: Arabic (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -21,40 +22,40 @@ msgstr ""
|
|||
"Language: ar\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
|
||||
#: authentication.py:73
|
||||
#: authentication.py:70
|
||||
msgid "Invalid basic header. No credentials provided."
|
||||
msgstr ""
|
||||
msgstr "رأس أساسي غير صالح, لم تقدم اي بيانات."
|
||||
|
||||
#: authentication.py:76
|
||||
#: authentication.py:73
|
||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||
msgstr ""
|
||||
msgstr "رأس أساسي غير صالح, سلسلة البيانات لا يجب أن تحتوي على أي أحرف مسافات"
|
||||
|
||||
#: authentication.py:82
|
||||
#: authentication.py:83
|
||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||
msgstr ""
|
||||
msgstr "رأس أساسي غير صالح, البيانات ليست مرمّزة بصحة على أساس64."
|
||||
|
||||
#: authentication.py:99
|
||||
#: authentication.py:101
|
||||
msgid "Invalid username/password."
|
||||
msgstr "اسم المستخدم/كلمة السر غير صحيحين."
|
||||
|
||||
#: authentication.py:102 authentication.py:198
|
||||
#: authentication.py:104 authentication.py:206
|
||||
msgid "User inactive or deleted."
|
||||
msgstr "المستخدم غير مفعل او تم حذفه."
|
||||
|
||||
#: authentication.py:176
|
||||
#: authentication.py:184
|
||||
msgid "Invalid token header. No credentials provided."
|
||||
msgstr ""
|
||||
msgstr "رمز الراْس المميّز غير صالح, لم تقدم أي بيانات."
|
||||
|
||||
#: authentication.py:179
|
||||
#: authentication.py:187
|
||||
msgid "Invalid token header. Token string should not contain spaces."
|
||||
msgstr ""
|
||||
msgstr "رمز الراْس المميّز غير صالح, سلسلة الرمز المميّز لا يجب أن تحتوي على أي أحرف مسافات."
|
||||
|
||||
#: authentication.py:185
|
||||
#: authentication.py:193
|
||||
msgid ""
|
||||
"Invalid token header. Token string should not contain invalid characters."
|
||||
msgstr ""
|
||||
msgstr "رمز الراْس المميّز غير صالح, سلسلة الرمز المميّز لا يجب أن تحتوي على أي أحرف غير صالحة."
|
||||
|
||||
#: authentication.py:195
|
||||
#: authentication.py:203
|
||||
msgid "Invalid token."
|
||||
msgstr "رمز غير صحيح."
|
||||
|
||||
|
@ -62,382 +63,515 @@ msgstr "رمز غير صحيح."
|
|||
msgid "Auth Token"
|
||||
msgstr "رمز التفويض"
|
||||
|
||||
#: authtoken/models.py:15
|
||||
#: authtoken/models.py:13
|
||||
msgid "Key"
|
||||
msgstr "المفتاح"
|
||||
|
||||
#: authtoken/models.py:18
|
||||
#: authtoken/models.py:16
|
||||
msgid "User"
|
||||
msgstr "المستخدم"
|
||||
|
||||
#: authtoken/models.py:20
|
||||
#: authtoken/models.py:18
|
||||
msgid "Created"
|
||||
msgstr "أنشئ"
|
||||
|
||||
#: authtoken/models.py:29
|
||||
#: authtoken/models.py:27 authtoken/serializers.py:19
|
||||
msgid "Token"
|
||||
msgstr "الرمز"
|
||||
|
||||
#: authtoken/models.py:30
|
||||
#: authtoken/models.py:28
|
||||
msgid "Tokens"
|
||||
msgstr "الرموز"
|
||||
|
||||
#: authtoken/serializers.py:8
|
||||
#: authtoken/serializers.py:9
|
||||
msgid "Username"
|
||||
msgstr "اسم المستخدم"
|
||||
|
||||
#: authtoken/serializers.py:9
|
||||
#: authtoken/serializers.py:13
|
||||
msgid "Password"
|
||||
msgstr "كلمة المرور"
|
||||
|
||||
#: authtoken/serializers.py:20
|
||||
msgid "User account is disabled."
|
||||
msgstr "حساب المستخدم غير مفعل."
|
||||
|
||||
#: authtoken/serializers.py:23
|
||||
#: authtoken/serializers.py:35
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "تعذر تسجيل الدخول بالبيانات التي ادخلتها."
|
||||
|
||||
#: authtoken/serializers.py:26
|
||||
#: authtoken/serializers.py:38
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "يجب أن تتضمن \"اسم المستخدم\" و \"كلمة المرور\"."
|
||||
|
||||
#: exceptions.py:49
|
||||
#: exceptions.py:102
|
||||
msgid "A server error occurred."
|
||||
msgstr "حدث خطأ في المخدم."
|
||||
|
||||
#: exceptions.py:84
|
||||
msgid "Malformed request."
|
||||
#: exceptions.py:142
|
||||
msgid "Invalid input."
|
||||
msgstr ""
|
||||
|
||||
#: exceptions.py:89
|
||||
#: exceptions.py:161
|
||||
msgid "Malformed request."
|
||||
msgstr "الطلب صيغ بشكل سيء."
|
||||
|
||||
#: exceptions.py:167
|
||||
msgid "Incorrect authentication credentials."
|
||||
msgstr "بيانات الدخول غير صحيحة."
|
||||
|
||||
#: exceptions.py:94
|
||||
#: exceptions.py:173
|
||||
msgid "Authentication credentials were not provided."
|
||||
msgstr "لم يتم تزويد بيانات الدخول."
|
||||
|
||||
#: exceptions.py:99
|
||||
#: exceptions.py:179
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "ليس لديك صلاحية للقيام بهذا الإجراء."
|
||||
|
||||
#: exceptions.py:104 views.py:81
|
||||
#: exceptions.py:185
|
||||
msgid "Not found."
|
||||
msgstr "غير موجود."
|
||||
|
||||
#: exceptions.py:109
|
||||
#: exceptions.py:191
|
||||
#, python-brace-format
|
||||
msgid "Method \"{method}\" not allowed."
|
||||
msgstr "طلب غير مسموح به"
|
||||
msgstr "الطريقة \"{method}\" غير مسموح بها."
|
||||
|
||||
#: exceptions.py:120
|
||||
#: exceptions.py:202
|
||||
msgid "Could not satisfy the request Accept header."
|
||||
msgstr ""
|
||||
msgstr "لم نتمكن من تلبية الرٱس Accept في الطلب."
|
||||
|
||||
#: exceptions.py:132
|
||||
#: exceptions.py:212
|
||||
#, python-brace-format
|
||||
msgid "Unsupported media type \"{media_type}\" in request."
|
||||
msgstr ""
|
||||
msgstr "الوسيط \"{media_type}\" الموجود في الطلب غير معتمد."
|
||||
|
||||
#: exceptions.py:145
|
||||
#: exceptions.py:223
|
||||
msgid "Request was throttled."
|
||||
msgstr "تم تقييد الطلب."
|
||||
|
||||
#: exceptions.py:224
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} second."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
|
||||
#: validators.py:181
|
||||
#: exceptions.py:225
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||
#: validators.py:183
|
||||
msgid "This field is required."
|
||||
msgstr "هذا الحقل مطلوب."
|
||||
|
||||
#: fields.py:270
|
||||
#: fields.py:317
|
||||
msgid "This field may not be null."
|
||||
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null."
|
||||
|
||||
#: fields.py:608 fields.py:639
|
||||
msgid "\"{input}\" is not a valid boolean."
|
||||
msgstr "\"{input}\" ليس قيمة منطقية."
|
||||
#: fields.py:701
|
||||
msgid "Must be a valid boolean."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:674
|
||||
#: fields.py:766
|
||||
msgid "Not a valid string."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:767
|
||||
msgid "This field may not be blank."
|
||||
msgstr "لا يمكن لهذا الحقل ان يكون فارغاً."
|
||||
|
||||
#: fields.py:675 fields.py:1675
|
||||
#: fields.py:768 fields.py:1881
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} characters."
|
||||
msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف."
|
||||
|
||||
#: fields.py:676
|
||||
#: fields.py:769
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} characters."
|
||||
msgstr "تأكد ان الحقل {min_length} محرف على الاقل."
|
||||
|
||||
#: fields.py:713
|
||||
#: fields.py:816
|
||||
msgid "Enter a valid email address."
|
||||
msgstr "عليك ان تدخل بريد إلكتروني صالح."
|
||||
|
||||
#: fields.py:724
|
||||
#: fields.py:827
|
||||
msgid "This value does not match the required pattern."
|
||||
msgstr "هذه القيمة لا تطابق النمط المطلوب."
|
||||
|
||||
#: fields.py:735
|
||||
#: fields.py:838
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||
"hyphens."
|
||||
msgstr "أدخل \"slug\" صالح يحتوي على حروف، أرقام، شُرط سفلية أو واصلات."
|
||||
|
||||
#: fields.py:839
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||
"or hyphens."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:747
|
||||
#: fields.py:854
|
||||
msgid "Enter a valid URL."
|
||||
msgstr "الرجاء إدخال رابط إلكتروني صالح."
|
||||
|
||||
#: fields.py:760
|
||||
msgid "\"{value}\" is not a valid UUID."
|
||||
#: fields.py:867
|
||||
msgid "Must be a valid UUID."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:796
|
||||
#: fields.py:903
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr "برجاء إدخال عنوان IPV4 أو IPV6 صحيح"
|
||||
msgstr "أدخِل عنوان IPV4 أو IPV6 صحيح."
|
||||
|
||||
#: fields.py:821
|
||||
#: fields.py:931
|
||||
msgid "A valid integer is required."
|
||||
msgstr "الرجاء إدخال رقم صحيح صالح."
|
||||
|
||||
#: fields.py:822 fields.py:857 fields.py:891
|
||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is less than or equal to {max_value}."
|
||||
msgstr "تأكد ان القيمة أقل أو تساوي {max_value}."
|
||||
|
||||
#: fields.py:823 fields.py:858 fields.py:892
|
||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||
msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
|
||||
|
||||
#: fields.py:824 fields.py:859 fields.py:896
|
||||
#: fields.py:934 fields.py:971 fields.py:1010
|
||||
msgid "String value too large."
|
||||
msgstr "القيمه اكبر من المسموح"
|
||||
msgstr "السلسلة اطول من القيمة المسموح بها."
|
||||
|
||||
#: fields.py:856 fields.py:890
|
||||
#: fields.py:968 fields.py:1004
|
||||
msgid "A valid number is required."
|
||||
msgstr "الرجاء إدخال رقم صالح."
|
||||
|
||||
#: fields.py:893
|
||||
#: fields.py:1007
|
||||
#, python-brace-format
|
||||
msgid "Ensure that there are no more than {max_digits} digits in total."
|
||||
msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رقم."
|
||||
|
||||
#: fields.py:894
|
||||
#: fields.py:1008
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
||||
msgstr ""
|
||||
msgstr "تأكد انه لا يوجد اكثر من {max_decimal_places} منازل عشرية."
|
||||
|
||||
#: fields.py:895
|
||||
#: fields.py:1009
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_whole_digits} digits before the "
|
||||
"decimal point."
|
||||
msgstr ""
|
||||
msgstr "تأكد انه لا يوجد اكثر من {max_whole_digits} أرقام قبل النقطة العشرية."
|
||||
|
||||
#: fields.py:1025
|
||||
#: fields.py:1148
|
||||
#, python-brace-format
|
||||
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
|
||||
|
||||
#: fields.py:1026
|
||||
#: fields.py:1149
|
||||
msgid "Expected a datetime but got a date."
|
||||
msgstr "متوقع تاريخ و وقت و وجد تاريخ فقط"
|
||||
|
||||
#: fields.py:1103
|
||||
#: fields.py:1150
|
||||
#, python-brace-format
|
||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1151
|
||||
msgid "Datetime value out of range."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1236
|
||||
#, python-brace-format
|
||||
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
|
||||
|
||||
#: fields.py:1104
|
||||
#: fields.py:1237
|
||||
msgid "Expected a date but got a datetime."
|
||||
msgstr "متوقع تاريخ فقط و وجد تاريخ ووقت"
|
||||
|
||||
#: fields.py:1170
|
||||
#: fields.py:1303
|
||||
#, python-brace-format
|
||||
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}."
|
||||
|
||||
#: fields.py:1232
|
||||
#: fields.py:1365
|
||||
#, python-brace-format
|
||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "صيغة المده غير صحيحه, برجاء إستخدام أحد هذه الصيغ {format}"
|
||||
msgstr "صيغة المدة غير صحيحه, يرجى إستخدام إحدى هذه الصيغ: {format}."
|
||||
|
||||
#: fields.py:1251 fields.py:1300
|
||||
#: fields.py:1399 fields.py:1456
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid choice."
|
||||
msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
|
||||
|
||||
#: fields.py:1254 relations.py:71 relations.py:441
|
||||
#: fields.py:1402
|
||||
#, python-brace-format
|
||||
msgid "More than {count} items..."
|
||||
msgstr "أكثر من {count} عنصر..."
|
||||
|
||||
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
|
||||
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
|
||||
#, python-brace-format
|
||||
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||
msgstr ""
|
||||
msgstr "المتوقع وجود قائمة عناصر لكن وجد النوع \"{input_type}\"."
|
||||
|
||||
#: fields.py:1302
|
||||
#: fields.py:1458
|
||||
msgid "This selection may not be empty."
|
||||
msgstr ""
|
||||
msgstr "هذا التحديد لا يجب أن يكون فارغا."
|
||||
|
||||
#: fields.py:1339
|
||||
#: fields.py:1495
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid path choice."
|
||||
msgstr ""
|
||||
msgstr "{input} كإختيار مسار غير صالح."
|
||||
|
||||
#: fields.py:1358
|
||||
#: fields.py:1514
|
||||
msgid "No file was submitted."
|
||||
msgstr "لم يتم إرسال أي ملف."
|
||||
|
||||
#: fields.py:1359
|
||||
#: fields.py:1515
|
||||
msgid ""
|
||||
"The submitted data was not a file. Check the encoding type on the form."
|
||||
msgstr ""
|
||||
msgstr "المعطيات المرسولة ليست ملف. إفحص نوع الترميز في النموذج."
|
||||
|
||||
#: fields.py:1360
|
||||
#: fields.py:1516
|
||||
msgid "No filename could be determined."
|
||||
msgstr ""
|
||||
msgstr "ما من إسم ملف أمكن تحديده."
|
||||
|
||||
#: fields.py:1361
|
||||
#: fields.py:1517
|
||||
msgid "The submitted file is empty."
|
||||
msgstr "الملف الذي تم إرساله فارغ."
|
||||
|
||||
#: fields.py:1362
|
||||
#: fields.py:1518
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||
msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)."
|
||||
|
||||
#: fields.py:1410
|
||||
#: fields.py:1566
|
||||
msgid ""
|
||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||
"corrupted image."
|
||||
msgstr ""
|
||||
msgstr "الرجاء تحميل صورة صالحة. الملف الذي تم تحميله إما لم يكن صورة او انه كان صورة تالفة."
|
||||
|
||||
#: fields.py:1449 relations.py:438 serializers.py:525
|
||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||
msgid "This list may not be empty."
|
||||
msgstr "القائمة يجب أن لا تكون فارغة."
|
||||
|
||||
#: fields.py:1605
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1502
|
||||
#: fields.py:1606
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1682
|
||||
#, python-brace-format
|
||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||
msgstr "المتوقع كان قاموس عناصر و لكن النوع المتحصل عليه هو \"{input_type}\"."
|
||||
|
||||
#: fields.py:1683
|
||||
msgid "This dictionary may not be empty."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1549
|
||||
#: fields.py:1755
|
||||
msgid "Value must be valid JSON."
|
||||
msgstr ""
|
||||
msgstr "القيمة يجب أن تكون JSON صالح."
|
||||
|
||||
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
|
||||
msgid "Submit"
|
||||
msgstr "أرسل"
|
||||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr "تصاعدي"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr "تنازلي"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr "صفحة غير صحيحة."
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:207
|
||||
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||
msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود."
|
||||
|
||||
#: relations.py:208
|
||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:240
|
||||
msgid "Invalid hyperlink - No URL match."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:241
|
||||
msgid "Invalid hyperlink - Incorrect URL match."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:242
|
||||
msgid "Invalid hyperlink - Object does not exist."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:243
|
||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:401
|
||||
msgid "Object with {slug_name}={value} does not exist."
|
||||
msgstr ""
|
||||
|
||||
#: relations.py:402
|
||||
msgid "Invalid value."
|
||||
msgstr "قيمة غير صالحة."
|
||||
|
||||
#: serializers.py:326
|
||||
msgid "Invalid data. Expected a dictionary, but got {datatype}."
|
||||
msgstr ""
|
||||
|
||||
#: templates/rest_framework/admin.html:116
|
||||
#: templates/rest_framework/base.html:128
|
||||
msgid "Filters"
|
||||
msgstr "مرشحات"
|
||||
|
||||
#: templates/rest_framework/filters/django_filter.html:2
|
||||
#: templates/rest_framework/filters/django_filter_crispyforms.html:4
|
||||
msgid "Field filters"
|
||||
msgstr "مرشحات الحقول"
|
||||
|
||||
#: templates/rest_framework/filters/ordering.html:3
|
||||
msgid "Ordering"
|
||||
msgstr "الترتيب"
|
||||
|
||||
#: templates/rest_framework/filters/search.html:2
|
||||
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
||||
msgid "Search"
|
||||
msgstr "بحث"
|
||||
|
||||
#: templates/rest_framework/horizontal/radio.html:2
|
||||
#: templates/rest_framework/inline/radio.html:2
|
||||
#: templates/rest_framework/vertical/radio.html:2
|
||||
#: filters.py:50
|
||||
msgid "A search term."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||
msgid "Ordering"
|
||||
msgstr "الترتيب"
|
||||
|
||||
#: filters.py:181
|
||||
msgid "Which field to use when ordering the results."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:287
|
||||
msgid "ascending"
|
||||
msgstr "تصاعدي"
|
||||
|
||||
#: filters.py:288
|
||||
msgid "descending"
|
||||
msgstr "تنازلي"
|
||||
|
||||
#: pagination.py:174
|
||||
msgid "A page number within the paginated result set."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||
msgid "Number of results to return per page."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:189
|
||||
msgid "Invalid page."
|
||||
msgstr "صفحة غير صحيحة."
|
||||
|
||||
#: pagination.py:374
|
||||
msgid "The initial index from which to return the results."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:581
|
||||
msgid "The pagination cursor value."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:583
|
||||
msgid "Invalid cursor"
|
||||
msgstr "مؤشر غير صالح"
|
||||
|
||||
#: relations.py:246
|
||||
#, python-brace-format
|
||||
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||
msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود."
|
||||
|
||||
#: relations.py:247
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||
msgstr "نوع خاطئ. المتوقع قيمة من pk، لكن المتحصل عليه {data_type}."
|
||||
|
||||
#: relations.py:280
|
||||
msgid "Invalid hyperlink - No URL match."
|
||||
msgstr "إرتباط تشعبي غير صالح - لا مطابقة لURL."
|
||||
|
||||
#: relations.py:281
|
||||
msgid "Invalid hyperlink - Incorrect URL match."
|
||||
msgstr "إرتباط تشعبي غير صالح - مطابقة خاطئة لURL."
|
||||
|
||||
#: relations.py:282
|
||||
msgid "Invalid hyperlink - Object does not exist."
|
||||
msgstr "إرتباط تشعبي غير صالح - عنصر غير موجود."
|
||||
|
||||
#: relations.py:283
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||
msgstr "نوع خاطئ. المتوقع سلسلة URL، لكن المتحصل عليه {data_type}."
|
||||
|
||||
#: relations.py:448
|
||||
#, python-brace-format
|
||||
msgid "Object with {slug_name}={value} does not exist."
|
||||
msgstr "عنصر ب {slug_name}={value} غير موجود."
|
||||
|
||||
#: relations.py:449
|
||||
msgid "Invalid value."
|
||||
msgstr "قيمة غير صالحة."
|
||||
|
||||
#: schemas/utils.py:32
|
||||
msgid "unique integer value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:34
|
||||
msgid "UUID string"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:36
|
||||
msgid "unique value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:38
|
||||
#, python-brace-format
|
||||
msgid "A {value_type} identifying this {name}."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:337
|
||||
#, 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:2
|
||||
#: templates/rest_framework/inline/select_multiple.html:2
|
||||
#: templates/rest_framework/vertical/select_multiple.html:2
|
||||
#: 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 ""
|
||||
msgstr "ما من عناصر للتحديد."
|
||||
|
||||
#: validators.py:43
|
||||
#: validators.py:39
|
||||
msgid "This field must be unique."
|
||||
msgstr "هذا الحقل يجب أن يكون فريد"
|
||||
|
||||
#: validators.py:97
|
||||
#: validators.py:89
|
||||
#, python-brace-format
|
||||
msgid "The fields {field_names} must make a unique set."
|
||||
msgstr "الحقول {field_names} يجب أن تشكل مجموعة فريدة."
|
||||
|
||||
#: validators.py:171
|
||||
#, python-brace-format
|
||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:245
|
||||
#: validators.py:243
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" date."
|
||||
msgstr ""
|
||||
msgstr "الحقل يجب ان يكون فريد للتاريخ {date_field}."
|
||||
|
||||
#: validators.py:260
|
||||
#: validators.py:258
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" month."
|
||||
msgstr ""
|
||||
msgstr "الحقل يجب ان يكون فريد للشهر {date_field}."
|
||||
|
||||
#: validators.py:273
|
||||
#: validators.py:271
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" year."
|
||||
msgstr ""
|
||||
msgstr "الحقل يجب ان يكون فريد للعام {date_field}."
|
||||
|
||||
#: versioning.py:42
|
||||
#: versioning.py:40
|
||||
msgid "Invalid version in \"Accept\" header."
|
||||
msgstr ""
|
||||
msgstr "إصدار غير صالح في الرٱس \"Accept\"."
|
||||
|
||||
#: versioning.py:73
|
||||
#: versioning.py:71
|
||||
msgid "Invalid version in URL path."
|
||||
msgstr ""
|
||||
msgstr "إصدار غير صالح في المسار URL."
|
||||
|
||||
#: versioning.py:115
|
||||
#: versioning.py:116
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr " إصدار غير صالح في المسار URL. لا يطابق أي إصدار من مساحة الإسم."
|
||||
|
||||
#: versioning.py:147
|
||||
#: versioning.py:148
|
||||
msgid "Invalid version in hostname."
|
||||
msgstr ""
|
||||
msgstr "إصدار غير صالح في اسم المضيف."
|
||||
|
||||
#: versioning.py:169
|
||||
#: versioning.py:170
|
||||
msgid "Invalid version in query parameter."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:88
|
||||
msgid "Permission denied."
|
||||
msgstr "ليس لديك صلاحية."
|
||||
msgstr "إصدار غير صالح في معلمة الإستعلام."
|
||||
|
|
BIN
rest_framework/locale/az/LC_MESSAGES/django.mo
Normal file
573
rest_framework/locale/az/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,573 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Emin Mastizada <emin@linux.com>, 2020
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django REST framework\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
||||
"Language-Team: Azerbaijani (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/az/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: az\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: authentication.py:70
|
||||
msgid "Invalid basic header. No credentials provided."
|
||||
msgstr "Xətalı sadə başlıq. İstifadəçi məlumatları təchiz edilməyib."
|
||||
|
||||
#: authentication.py:73
|
||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||
msgstr "Xətalı sadə başlıq. İstifadəçi məlumatlarında boşluq olmamalıdır."
|
||||
|
||||
#: authentication.py:83
|
||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||
msgstr "Xətalı sadə başlıq. İstifadəçi məlumatları base64 ilə düzgün şifrələnməyib."
|
||||
|
||||
#: authentication.py:101
|
||||
msgid "Invalid username/password."
|
||||
msgstr "Xətalı istifadəçi adı/parol."
|
||||
|
||||
#: authentication.py:104 authentication.py:206
|
||||
msgid "User inactive or deleted."
|
||||
msgstr "İstifadəçi aktiv deyil və ya silinib."
|
||||
|
||||
#: authentication.py:184
|
||||
msgid "Invalid token header. No credentials provided."
|
||||
msgstr "Xətalı token başlığı. İstifadəçi məlumatları təchiz edilməyib."
|
||||
|
||||
#: authentication.py:187
|
||||
msgid "Invalid token header. Token string should not contain spaces."
|
||||
msgstr "Xətalı token başlığı. Token mətnində boşluqlar olmamalıdır."
|
||||
|
||||
#: authentication.py:193
|
||||
msgid ""
|
||||
"Invalid token header. Token string should not contain invalid characters."
|
||||
msgstr "Xətalı token başlığı. Token mətnində xətalı simvollar olmamalıdır."
|
||||
|
||||
#: authentication.py:203
|
||||
msgid "Invalid token."
|
||||
msgstr "Xətalı token."
|
||||
|
||||
#: authtoken/apps.py:7
|
||||
msgid "Auth Token"
|
||||
msgstr "Təsdiqləmə Tokeni"
|
||||
|
||||
#: authtoken/models.py:13
|
||||
msgid "Key"
|
||||
msgstr "Açar"
|
||||
|
||||
#: authtoken/models.py:16
|
||||
msgid "User"
|
||||
msgstr "İstifadəçi"
|
||||
|
||||
#: authtoken/models.py:18
|
||||
msgid "Created"
|
||||
msgstr "Yaradılıb"
|
||||
|
||||
#: authtoken/models.py:27 authtoken/serializers.py:19
|
||||
msgid "Token"
|
||||
msgstr "Token"
|
||||
|
||||
#: authtoken/models.py:28
|
||||
msgid "Tokens"
|
||||
msgstr "Tokenlər"
|
||||
|
||||
#: authtoken/serializers.py:9
|
||||
msgid "Username"
|
||||
msgstr "İstifadəçi adı"
|
||||
|
||||
#: authtoken/serializers.py:13
|
||||
msgid "Password"
|
||||
msgstr "Parol"
|
||||
|
||||
#: authtoken/serializers.py:35
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Təchiz edilən istifadəçi məlumatları ilə daxil olmaq mümkün olmadı."
|
||||
|
||||
#: authtoken/serializers.py:38
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
msgstr "Mütləq \"username\" və \"password\" olmalıdır."
|
||||
|
||||
#: exceptions.py:102
|
||||
msgid "A server error occurred."
|
||||
msgstr "Server xətası yaşandı."
|
||||
|
||||
#: exceptions.py:142
|
||||
msgid "Invalid input."
|
||||
msgstr ""
|
||||
|
||||
#: exceptions.py:161
|
||||
msgid "Malformed request."
|
||||
msgstr "Qüsurlu istək."
|
||||
|
||||
#: exceptions.py:167
|
||||
msgid "Incorrect authentication credentials."
|
||||
msgstr "Səhv təsdiqləmə məlumatları."
|
||||
|
||||
#: exceptions.py:173
|
||||
msgid "Authentication credentials were not provided."
|
||||
msgstr "Təsdiqləmə məlumatları təchiz edilməyib."
|
||||
|
||||
#: exceptions.py:179
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "Bu əməliyyat üçün icazəniz yoxdur."
|
||||
|
||||
#: exceptions.py:185
|
||||
msgid "Not found."
|
||||
msgstr "Tapılmadı."
|
||||
|
||||
#: exceptions.py:191
|
||||
#, python-brace-format
|
||||
msgid "Method \"{method}\" not allowed."
|
||||
msgstr "\"{method}\" yöntəminə icazə verilmir."
|
||||
|
||||
#: exceptions.py:202
|
||||
msgid "Could not satisfy the request Accept header."
|
||||
msgstr "İstəyin Accept başlığını qane etmək mümkün olmadı."
|
||||
|
||||
#: exceptions.py:212
|
||||
#, python-brace-format
|
||||
msgid "Unsupported media type \"{media_type}\" in request."
|
||||
msgstr "İstəkdə dəstəklənməyən \"{media_type}\" mediya növü."
|
||||
|
||||
#: exceptions.py:223
|
||||
msgid "Request was throttled."
|
||||
msgstr "İstək nəzərə alınmadı."
|
||||
|
||||
#: exceptions.py:224
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} second."
|
||||
msgstr ""
|
||||
|
||||
#: exceptions.py:225
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||
#: validators.py:183
|
||||
msgid "This field is required."
|
||||
msgstr "Bu sahə tələb edilir."
|
||||
|
||||
#: fields.py:317
|
||||
msgid "This field may not be null."
|
||||
msgstr "Bu sahə null ola bilməz."
|
||||
|
||||
#: fields.py:701
|
||||
msgid "Must be a valid boolean."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:766
|
||||
msgid "Not a valid string."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:767
|
||||
msgid "This field may not be blank."
|
||||
msgstr "Bu sahə boş ola bilməz."
|
||||
|
||||
#: fields.py:768 fields.py:1881
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} characters."
|
||||
msgstr "Bu sahənin ən çox {max_length} simvolu olduğuna əmin olun."
|
||||
|
||||
#: fields.py:769
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} characters."
|
||||
msgstr "Bu sahənin ən az {min_length} simvolu olduğuna əmin olun."
|
||||
|
||||
#: fields.py:816
|
||||
msgid "Enter a valid email address."
|
||||
msgstr "Keçərli e-poçt ünvanı daxil edin."
|
||||
|
||||
#: fields.py:827
|
||||
msgid "This value does not match the required pattern."
|
||||
msgstr "Bu dəyər tələb edilən formaya uyğun gəlmir."
|
||||
|
||||
#: fields.py:838
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||
"hyphens."
|
||||
msgstr "Hərf, rəqəm, alt xətt və defislərdən ibarət keçərli \"slug\" daxil edin."
|
||||
|
||||
#: fields.py:839
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||
"or hyphens."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:854
|
||||
msgid "Enter a valid URL."
|
||||
msgstr "Keçərli URL daxil edin."
|
||||
|
||||
#: fields.py:867
|
||||
msgid "Must be a valid UUID."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:903
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr "Keçərli IPv4 və ya IPv6 ünvanı daxil edin."
|
||||
|
||||
#: fields.py:931
|
||||
msgid "A valid integer is required."
|
||||
msgstr "Keçərli tam ədəd tələb edilir."
|
||||
|
||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is less than or equal to {max_value}."
|
||||
msgstr "Bu dəyərin uzunluğunun ən çox {max_value} olduğuna əmin olun."
|
||||
|
||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||
msgstr "Bu dəyərin uzunluğunun ən az {min_value} olduğuna əmin olun."
|
||||
|
||||
#: fields.py:934 fields.py:971 fields.py:1010
|
||||
msgid "String value too large."
|
||||
msgstr "Yazı dəyəri çox uzundur."
|
||||
|
||||
#: fields.py:968 fields.py:1004
|
||||
msgid "A valid number is required."
|
||||
msgstr "Keçərli rəqəm tələb edilir."
|
||||
|
||||
#: fields.py:1007
|
||||
#, python-brace-format
|
||||
msgid "Ensure that there are no more than {max_digits} digits in total."
|
||||
msgstr "Ən çox {max_digits} rəqəm olduğuna əmin olun."
|
||||
|
||||
#: fields.py:1008
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
||||
msgstr "Ən çox {max_decimal_places} onluq kəsr hissəsi olduğuna əmin olun."
|
||||
|
||||
#: fields.py:1009
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_whole_digits} digits before the "
|
||||
"decimal point."
|
||||
msgstr "Onluq kərsdən əvvəl ən çox {max_whole_digits} rəqəm olduğuna əmin olun."
|
||||
|
||||
#: fields.py:1148
|
||||
#, python-brace-format
|
||||
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Datetime dəyəri səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}."
|
||||
|
||||
#: fields.py:1149
|
||||
msgid "Expected a datetime but got a date."
|
||||
msgstr "Datetime gözlənirdi amma date gəldi."
|
||||
|
||||
#: fields.py:1150
|
||||
#, python-brace-format
|
||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1151
|
||||
msgid "Datetime value out of range."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1236
|
||||
#, python-brace-format
|
||||
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Date dəyəri səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}."
|
||||
|
||||
#: fields.py:1237
|
||||
msgid "Expected a date but got a datetime."
|
||||
msgstr "Date gözlənirdi amma datetime gəldi."
|
||||
|
||||
#: fields.py:1303
|
||||
#, python-brace-format
|
||||
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Vaxt formatı səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}."
|
||||
|
||||
#: fields.py:1365
|
||||
#, python-brace-format
|
||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Müddət formatı səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}."
|
||||
|
||||
#: fields.py:1399 fields.py:1456
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid choice."
|
||||
msgstr "\"{input}\" keçərli seçim deyil."
|
||||
|
||||
#: fields.py:1402
|
||||
#, python-brace-format
|
||||
msgid "More than {count} items..."
|
||||
msgstr "{count} elementdən daha çoxdur..."
|
||||
|
||||
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
|
||||
#, python-brace-format
|
||||
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||
msgstr "Elementlər siyahısı gözlənirdi, amma \"{input_type}\" növü gəldi."
|
||||
|
||||
#: fields.py:1458
|
||||
msgid "This selection may not be empty."
|
||||
msgstr "Bu seçim boş ola bilməz."
|
||||
|
||||
#: fields.py:1495
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid path choice."
|
||||
msgstr "\"{input}\" keçərli yol seçimi deyil."
|
||||
|
||||
#: fields.py:1514
|
||||
msgid "No file was submitted."
|
||||
msgstr "Heç bir fayl göndərilmədi."
|
||||
|
||||
#: fields.py:1515
|
||||
msgid ""
|
||||
"The submitted data was not a file. Check the encoding type on the form."
|
||||
msgstr "Göndərilən məlumat fayl deyildi. Anketin şifrələmə (encoding) növünü yoxlayın."
|
||||
|
||||
#: fields.py:1516
|
||||
msgid "No filename could be determined."
|
||||
msgstr "Faylın adı təyin edilə bilmədi."
|
||||
|
||||
#: fields.py:1517
|
||||
msgid "The submitted file is empty."
|
||||
msgstr "Göndərilən fayl boşdur."
|
||||
|
||||
#: fields.py:1518
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||
msgstr "Fayl adının ən çox {max_length} simvoldan ibarət olduğuna əmin olun (hazırki: {length})."
|
||||
|
||||
#: fields.py:1566
|
||||
msgid ""
|
||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||
"corrupted image."
|
||||
msgstr "Keçərli şəkil yükləyin. Yüklədiyiniz fayl ya şəkil deyil, ya da ola bilsin zədələnib."
|
||||
|
||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||
msgid "This list may not be empty."
|
||||
msgstr "Bu siyahı boş ola bilməz."
|
||||
|
||||
#: fields.py:1605
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1606
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1682
|
||||
#, python-brace-format
|
||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||
msgstr "Elementlərin kitabçası (dictionary) gözlənirdi amma \"{input_type}\" növü gəldi."
|
||||
|
||||
#: fields.py:1683
|
||||
msgid "This dictionary may not be empty."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1755
|
||||
msgid "Value must be valid JSON."
|
||||
msgstr "Dəyər keçərli JSON olmalıdır."
|
||||
|
||||
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
||||
msgid "Search"
|
||||
msgstr "Axtarış"
|
||||
|
||||
#: filters.py:50
|
||||
msgid "A search term."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||
msgid "Ordering"
|
||||
msgstr "Sıralama"
|
||||
|
||||
#: filters.py:181
|
||||
msgid "Which field to use when ordering the results."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:287
|
||||
msgid "ascending"
|
||||
msgstr "artan"
|
||||
|
||||
#: filters.py:288
|
||||
msgid "descending"
|
||||
msgstr "azalan"
|
||||
|
||||
#: pagination.py:174
|
||||
msgid "A page number within the paginated result set."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||
msgid "Number of results to return per page."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:189
|
||||
msgid "Invalid page."
|
||||
msgstr "Xətalı səhifə."
|
||||
|
||||
#: pagination.py:374
|
||||
msgid "The initial index from which to return the results."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:581
|
||||
msgid "The pagination cursor value."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:583
|
||||
msgid "Invalid cursor"
|
||||
msgstr "Xətalı kursor"
|
||||
|
||||
#: relations.py:246
|
||||
#, python-brace-format
|
||||
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||
msgstr "Xətalı pk \"{pk_value}\" - obyekt mövcud deyil."
|
||||
|
||||
#: relations.py:247
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||
msgstr "Xətalı növ. PK dəyəri gözlənirdi, {data_type} alındı."
|
||||
|
||||
#: relations.py:280
|
||||
msgid "Invalid hyperlink - No URL match."
|
||||
msgstr "Xətalı hiperkeçid - Heç bir URL uyğun gəlmir."
|
||||
|
||||
#: relations.py:281
|
||||
msgid "Invalid hyperlink - Incorrect URL match."
|
||||
msgstr "Xətalı hiperkeçid - Xətalı URL uyğunluğu."
|
||||
|
||||
#: relations.py:282
|
||||
msgid "Invalid hyperlink - Object does not exist."
|
||||
msgstr "Xətalı hiperkeçid - Obyekt mövcud deyil."
|
||||
|
||||
#: relations.py:283
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||
msgstr "Xətalı növ. URL yazısı gözlənirdi, {data_type} gəldi."
|
||||
|
||||
#: relations.py:448
|
||||
#, python-brace-format
|
||||
msgid "Object with {slug_name}={value} does not exist."
|
||||
msgstr "{slug_name}={value} üçün uyğun obyekt mövcud deyil."
|
||||
|
||||
#: relations.py:449
|
||||
msgid "Invalid value."
|
||||
msgstr "Xətalı dəyər."
|
||||
|
||||
#: schemas/utils.py:32
|
||||
msgid "unique integer value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:34
|
||||
msgid "UUID string"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:36
|
||||
msgid "unique value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:38
|
||||
#, python-brace-format
|
||||
msgid "A {value_type} identifying this {name}."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:337
|
||||
#, python-brace-format
|
||||
msgid "Invalid data. Expected a dictionary, but got {datatype}."
|
||||
msgstr "Xətalı məlumat. Kitabça (dictionary) gözlənirdi, {datatype} gəldi."
|
||||
|
||||
#: 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 "Filterlər"
|
||||
|
||||
#: 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 "Heç nə"
|
||||
|
||||
#: 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 "Seçiləcək element yoxdur."
|
||||
|
||||
#: validators.py:39
|
||||
msgid "This field must be unique."
|
||||
msgstr "Bu sahə unikal olmalıdır."
|
||||
|
||||
#: validators.py:89
|
||||
#, python-brace-format
|
||||
msgid "The fields {field_names} must make a unique set."
|
||||
msgstr "{field_names} sahələri birlikdə unikal dəst olmalıdırlar."
|
||||
|
||||
#: validators.py:171
|
||||
#, python-brace-format
|
||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:243
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" date."
|
||||
msgstr "Bu sahə \"{date_field}\" günü üçün unikal olmalıdır."
|
||||
|
||||
#: validators.py:258
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" month."
|
||||
msgstr "Bu sahə \"{date_field}\" ayı üçün unikal olmalıdır."
|
||||
|
||||
#: validators.py:271
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" year."
|
||||
msgstr "Bu sahə \"{date_field}\" ili üçün unikal olmalıdır."
|
||||
|
||||
#: versioning.py:40
|
||||
msgid "Invalid version in \"Accept\" header."
|
||||
msgstr "\"Accept\" başlığında xətalı versiya."
|
||||
|
||||
#: versioning.py:71
|
||||
msgid "Invalid version in URL path."
|
||||
msgstr "URL yolunda xətalı versiya."
|
||||
|
||||
#: versioning.py:116
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr "URL yolunda xətalı versiya. Heç bir versiya namespace-inə uyğun gəlmir."
|
||||
|
||||
#: versioning.py:148
|
||||
msgid "Invalid version in hostname."
|
||||
msgstr "Hostname-də xətalı versiya."
|
||||
|
||||
#: versioning.py:170
|
||||
msgid "Invalid version in query parameter."
|
||||
msgstr "Sorğu parametrində xətalı versiya."
|
BIN
rest_framework/locale/bg/LC_MESSAGES/django.mo
Normal file
572
rest_framework/locale/bg/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,572 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django REST framework\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-13 21:45+0200\n"
|
||||
"PO-Revision-Date: 2020-10-13 19:45+0000\n"
|
||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/bg/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: bg\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: authentication.py:70
|
||||
msgid "Invalid basic header. No credentials provided."
|
||||
msgstr "Невалиден header за базово удостоверение (basic authentication). Не се предоставени удостоверения."
|
||||
|
||||
#: authentication.py:73
|
||||
msgid "Invalid basic header. Credentials string should not contain spaces."
|
||||
msgstr "Невалиден header за базово удостоверение (basic authentication). Носителите на удостоверение не трябва да съдържат интервали."
|
||||
|
||||
#: authentication.py:83
|
||||
msgid "Invalid basic header. Credentials not correctly base64 encoded."
|
||||
msgstr "Невалиден header за базово удостоверение (basic authentication). Носителите на удостоверение не са кодирани в 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 "Невалиден token header. Не са предоставени носители на удостоверение."
|
||||
|
||||
#: authentication.py:187
|
||||
msgid "Invalid token header. Token string should not contain spaces."
|
||||
msgstr "Невалиден token header. Жетона (token-a) не трябва да съдържа интервали."
|
||||
|
||||
#: authentication.py:193
|
||||
msgid ""
|
||||
"Invalid token header. Token string should not contain invalid characters."
|
||||
msgstr "Невалиден token header. Жетона (token-a) не трябва да съдържа невалидни символи."
|
||||
|
||||
#: authentication.py:203
|
||||
msgid "Invalid token."
|
||||
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/serializers.py:19
|
||||
msgid "Token"
|
||||
msgstr "Жетон"
|
||||
|
||||
#: authtoken/models.py:28
|
||||
msgid "Tokens"
|
||||
msgstr "Жетони"
|
||||
|
||||
#: authtoken/serializers.py:9
|
||||
msgid "Username"
|
||||
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:102
|
||||
msgid "A server error occurred."
|
||||
msgstr "Сървърна грешка."
|
||||
|
||||
#: exceptions.py:142
|
||||
msgid "Invalid input."
|
||||
msgstr ""
|
||||
|
||||
#: exceptions.py:161
|
||||
msgid "Malformed request."
|
||||
msgstr "Неправилно оформена заявка."
|
||||
|
||||
#: exceptions.py:167
|
||||
msgid "Incorrect authentication credentials."
|
||||
msgstr "Невалидни носители на удостоверение."
|
||||
|
||||
#: exceptions.py:173
|
||||
msgid "Authentication credentials were not provided."
|
||||
msgstr "Носители на удостоверение не са предоставени."
|
||||
|
||||
#: exceptions.py:179
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "Нямате права да се направи това действие."
|
||||
|
||||
#: exceptions.py:185
|
||||
msgid "Not found."
|
||||
msgstr "Обектът не е намерен."
|
||||
|
||||
#: exceptions.py:191
|
||||
#, python-brace-format
|
||||
msgid "Method \"{method}\" not allowed."
|
||||
msgstr "Метод \"{method}\" не е позволен."
|
||||
|
||||
#: exceptions.py:202
|
||||
msgid "Could not satisfy the request Accept header."
|
||||
msgstr "Не може да бъде удовлетворен Accept header."
|
||||
|
||||
#: exceptions.py:212
|
||||
#, python-brace-format
|
||||
msgid "Unsupported media type \"{media_type}\" in request."
|
||||
msgstr "Неподдържан тип на документа \"{media_type}\" в заявката."
|
||||
|
||||
#: exceptions.py:223
|
||||
msgid "Request was throttled."
|
||||
msgstr "Заявката е ограничена."
|
||||
|
||||
#: exceptions.py:224
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} second."
|
||||
msgstr ""
|
||||
|
||||
#: exceptions.py:225
|
||||
#, python-brace-format
|
||||
msgid "Expected available in {wait} seconds."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:316 relations.py:245 relations.py:279 validators.py:90
|
||||
#: validators.py:183
|
||||
msgid "This field is required."
|
||||
msgstr "Това поле е задължително."
|
||||
|
||||
#: fields.py:317
|
||||
msgid "This field may not be null."
|
||||
msgstr "Това поле не може да има празна стойност."
|
||||
|
||||
#: fields.py:701
|
||||
msgid "Must be a valid boolean."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:766
|
||||
msgid "Not a valid string."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:767
|
||||
msgid "This field may not be blank."
|
||||
msgstr "Това поле не може да е празно."
|
||||
|
||||
#: fields.py:768 fields.py:1881
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} characters."
|
||||
msgstr "Това поле не трябва да съдържа повече от {max_length} символа."
|
||||
|
||||
#: fields.py:769
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} characters."
|
||||
msgstr "Това поле трябва да съдържа поде {min_length} символа."
|
||||
|
||||
#: fields.py:816
|
||||
msgid "Enter a valid email address."
|
||||
msgstr "Въведете валиден имейл адрес."
|
||||
|
||||
#: fields.py:827
|
||||
msgid "This value does not match the required pattern."
|
||||
msgstr "Тази стойност не съответства на изисквания шаблон."
|
||||
|
||||
#: fields.py:838
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of letters, numbers, underscores or "
|
||||
"hyphens."
|
||||
msgstr "Въведете валиден \"slug\" съдържащ латински букви, цифри, долни черти или тирета."
|
||||
|
||||
#: fields.py:839
|
||||
msgid ""
|
||||
"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, "
|
||||
"or hyphens."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:854
|
||||
msgid "Enter a valid URL."
|
||||
msgstr "Въведете валиден URL."
|
||||
|
||||
#: fields.py:867
|
||||
msgid "Must be a valid UUID."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:903
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr "Въведете валиден IPv4 или IPv6 адрес."
|
||||
|
||||
#: fields.py:931
|
||||
msgid "A valid integer is required."
|
||||
msgstr "Необходимо е валидно цяло число."
|
||||
|
||||
#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is less than or equal to {max_value}."
|
||||
msgstr "Тази стойност трябва да е не повече от {max_value}."
|
||||
|
||||
#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367
|
||||
#, python-brace-format
|
||||
msgid "Ensure this value is greater than or equal to {min_value}."
|
||||
msgstr "Тази стойност трябва да е поне {min_value}."
|
||||
|
||||
#: fields.py:934 fields.py:971 fields.py:1010
|
||||
msgid "String value too large."
|
||||
msgstr "Низът е прекалено голям."
|
||||
|
||||
#: fields.py:968 fields.py:1004
|
||||
msgid "A valid number is required."
|
||||
msgstr "Необходимо е валидно число."
|
||||
|
||||
#: fields.py:1007
|
||||
#, python-brace-format
|
||||
msgid "Ensure that there are no more than {max_digits} digits in total."
|
||||
msgstr "Допустими са не повече от {max_digits} цифри."
|
||||
|
||||
#: fields.py:1008
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_decimal_places} decimal places."
|
||||
msgstr "Допустими са не повече от {max_decimal_places} цифри след десетичната запетая."
|
||||
|
||||
#: fields.py:1009
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure that there are no more than {max_whole_digits} digits before the "
|
||||
"decimal point."
|
||||
msgstr "Допустими са не повече от {max_whole_digits} цифри преди десетичната запетая."
|
||||
|
||||
#: fields.py:1148
|
||||
#, python-brace-format
|
||||
msgid "Datetime has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Датата и часът са в невалиден формат. Валидни са следните формати: {format}."
|
||||
|
||||
#: fields.py:1149
|
||||
msgid "Expected a datetime but got a date."
|
||||
msgstr "Очаква се дата и час, но е намерена само дата."
|
||||
|
||||
#: fields.py:1150
|
||||
#, python-brace-format
|
||||
msgid "Invalid datetime for the timezone \"{timezone}\"."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1151
|
||||
msgid "Datetime value out of range."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1236
|
||||
#, python-brace-format
|
||||
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Дата е в невалиден формат. Валидни са следните формати: {format}."
|
||||
|
||||
#: fields.py:1237
|
||||
msgid "Expected a date but got a datetime."
|
||||
msgstr "Очаква се дата, но са подадени дата и час."
|
||||
|
||||
#: fields.py:1303
|
||||
#, python-brace-format
|
||||
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Времето е в невалиден формат. Валидни са следните формати: {format}."
|
||||
|
||||
#: fields.py:1365
|
||||
#, python-brace-format
|
||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr "Отрязъкът от време е в невалиден формат. Валидни са следните формати: {format}."
|
||||
|
||||
#: fields.py:1399 fields.py:1456
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid choice."
|
||||
msgstr "\"{input}\" не е валиден избор."
|
||||
|
||||
#: fields.py:1402
|
||||
#, python-brace-format
|
||||
msgid "More than {count} items..."
|
||||
msgstr "Повече от {count} неща..."
|
||||
|
||||
#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570
|
||||
#, python-brace-format
|
||||
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||
msgstr "Очаква се списък от неща, но е получен тип \"{input_type}\"."
|
||||
|
||||
#: fields.py:1458
|
||||
msgid "This selection may not be empty."
|
||||
msgstr "Този избор не може да е празен."
|
||||
|
||||
#: fields.py:1495
|
||||
#, python-brace-format
|
||||
msgid "\"{input}\" is not a valid path choice."
|
||||
msgstr "\"{input}\" не е валиден избор за път."
|
||||
|
||||
#: fields.py:1514
|
||||
msgid "No file was submitted."
|
||||
msgstr "Не е подаден файл."
|
||||
|
||||
#: fields.py:1515
|
||||
msgid ""
|
||||
"The submitted data was not a file. Check the encoding type on the form."
|
||||
msgstr "Подадените данни не са файл. Проверете кодировката на формата."
|
||||
|
||||
#: fields.py:1516
|
||||
msgid "No filename could be determined."
|
||||
msgstr "Не може да бъде определено името на файла."
|
||||
|
||||
#: fields.py:1517
|
||||
msgid "The submitted file is empty."
|
||||
msgstr "Подадения файл е празен."
|
||||
|
||||
#: fields.py:1518
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Ensure this filename has at most {max_length} characters (it has {length})."
|
||||
msgstr "Името на файла може да е до {max_length} символа (то е {length})."
|
||||
|
||||
#: fields.py:1566
|
||||
msgid ""
|
||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||
"corrupted image."
|
||||
msgstr "Невалидно изображение. Подадения файл не е изображение или е повредено изображение."
|
||||
|
||||
#: fields.py:1604 relations.py:486 serializers.py:571
|
||||
msgid "This list may not be empty."
|
||||
msgstr "Този списък не може да е празен."
|
||||
|
||||
#: fields.py:1605
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has at least {min_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1606
|
||||
#, python-brace-format
|
||||
msgid "Ensure this field has no more than {max_length} elements."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1682
|
||||
#, python-brace-format
|
||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||
msgstr "Очаква се асоциативен масив, но е получен \"{input_type}\"."
|
||||
|
||||
#: fields.py:1683
|
||||
msgid "This dictionary may not be empty."
|
||||
msgstr ""
|
||||
|
||||
#: fields.py:1755
|
||||
msgid "Value must be valid JSON."
|
||||
msgstr "Стойността трябва да е валиден JSON."
|
||||
|
||||
#: filters.py:49 templates/rest_framework/filters/search.html:2
|
||||
msgid "Search"
|
||||
msgstr "Търсене"
|
||||
|
||||
#: filters.py:50
|
||||
msgid "A search term."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:180 templates/rest_framework/filters/ordering.html:3
|
||||
msgid "Ordering"
|
||||
msgstr "Подредба"
|
||||
|
||||
#: filters.py:181
|
||||
msgid "Which field to use when ordering the results."
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:287
|
||||
msgid "ascending"
|
||||
msgstr "в нарастващ ред"
|
||||
|
||||
#: filters.py:288
|
||||
msgid "descending"
|
||||
msgstr "в намаляващ ред"
|
||||
|
||||
#: pagination.py:174
|
||||
msgid "A page number within the paginated result set."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:179 pagination.py:372 pagination.py:590
|
||||
msgid "Number of results to return per page."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:189
|
||||
msgid "Invalid page."
|
||||
msgstr "Невалидна страница."
|
||||
|
||||
#: pagination.py:374
|
||||
msgid "The initial index from which to return the results."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:581
|
||||
msgid "The pagination cursor value."
|
||||
msgstr ""
|
||||
|
||||
#: pagination.py:583
|
||||
msgid "Invalid cursor"
|
||||
msgstr "Невалиден курсор"
|
||||
|
||||
#: relations.py:246
|
||||
#, python-brace-format
|
||||
msgid "Invalid pk \"{pk_value}\" - object does not exist."
|
||||
msgstr "Невалиден идентификатор \"{pk_value}\" - обектът не съществува."
|
||||
|
||||
#: relations.py:247
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected pk value, received {data_type}."
|
||||
msgstr "Неправилен тип. Очакван е тип за основен ключ, получен е {data_type}."
|
||||
|
||||
#: relations.py:280
|
||||
msgid "Invalid hyperlink - No URL match."
|
||||
msgstr "Невалидна връзка (hyperlink) - няма намерен URL."
|
||||
|
||||
#: relations.py:281
|
||||
msgid "Invalid hyperlink - Incorrect URL match."
|
||||
msgstr "Невалидна връзка (hyperlink) - неправилно съвпадение на URL."
|
||||
|
||||
#: relations.py:282
|
||||
msgid "Invalid hyperlink - Object does not exist."
|
||||
msgstr "Невалидна връзка (hyperlink) - обектът не съществува."
|
||||
|
||||
#: relations.py:283
|
||||
#, python-brace-format
|
||||
msgid "Incorrect type. Expected URL string, received {data_type}."
|
||||
msgstr "Невалиден тип. Очаква се URL низ, получен е {data_type}."
|
||||
|
||||
#: relations.py:448
|
||||
#, python-brace-format
|
||||
msgid "Object with {slug_name}={value} does not exist."
|
||||
msgstr "Обект с {slug_name}={value} не съществува."
|
||||
|
||||
#: relations.py:449
|
||||
msgid "Invalid value."
|
||||
msgstr "Невалидна стойност."
|
||||
|
||||
#: schemas/utils.py:32
|
||||
msgid "unique integer value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:34
|
||||
msgid "UUID string"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:36
|
||||
msgid "unique value"
|
||||
msgstr ""
|
||||
|
||||
#: schemas/utils.py:38
|
||||
#, python-brace-format
|
||||
msgid "A {value_type} identifying this {name}."
|
||||
msgstr ""
|
||||
|
||||
#: serializers.py:337
|
||||
#, 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:39
|
||||
msgid "This field must be unique."
|
||||
msgstr "Това поле трябва да е уникално."
|
||||
|
||||
#: validators.py:89
|
||||
#, python-brace-format
|
||||
msgid "The fields {field_names} must make a unique set."
|
||||
msgstr "Полетата {field_names} трябва да образуват уникална комбинация."
|
||||
|
||||
#: validators.py:171
|
||||
#, python-brace-format
|
||||
msgid "Surrogate characters are not allowed: U+{code_point:X}."
|
||||
msgstr ""
|
||||
|
||||
#: validators.py:243
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" date."
|
||||
msgstr "Това поле трябва да е уникално за \"{date_field}\" дата."
|
||||
|
||||
#: validators.py:258
|
||||
#, python-brace-format
|
||||
msgid "This field must be unique for the \"{date_field}\" month."
|
||||
msgstr "Това поле трябва да е уникално за \"{date_field}\" месец."
|
||||
|
||||
#: validators.py:271
|
||||
#, 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\" header."
|
||||
|
||||
#: versioning.py:71
|
||||
msgid "Invalid version in URL path."
|
||||
msgstr "Невалидна версия в URL пътя."
|
||||
|
||||
#: versioning.py:116
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr "Невалидна версия в URL пътя. Няма съвпадение с пространството от имена на версии."
|
||||
|
||||
#: versioning.py:148
|
||||
msgid "Invalid version in hostname."
|
||||
msgstr "Невалидна версия в името на сървъра (hostname)."
|
||||
|
||||
#: versioning.py:170
|
||||
msgid "Invalid version in query parameter."
|
||||
msgstr "Невалидна версия в GET параметър."
|