mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-03 12:00:12 +03:00
Merge branch 'master' into master
This commit is contained in:
commit
18fa6372d2
7
.editorconfig
Normal file
7
.editorconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,6 +13,7 @@
|
|||
MANIFEST
|
||||
coverage.*
|
||||
|
||||
!.editorconfig
|
||||
!.gitignore
|
||||
!.travis.yml
|
||||
!.isort.cfg
|
||||
|
|
|
@ -3,5 +3,5 @@ skip=.tox
|
|||
atomic=true
|
||||
multi_line_output=5
|
||||
known_standard_library=types
|
||||
known_third_party=pytest,django
|
||||
known_third_party=pytest,_pytest,django
|
||||
known_first_party=rest_framework
|
||||
|
|
35
.travis.yml
35
.travis.yml
|
@ -1,4 +1,5 @@
|
|||
language: python
|
||||
cache: pip
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
|
@ -8,30 +9,36 @@ python:
|
|||
sudo: false
|
||||
|
||||
env:
|
||||
- DJANGO=1.8
|
||||
- DJANGO=1.9
|
||||
- DJANGO=1.10
|
||||
- DJANGO=1.11
|
||||
- DJANGO=2.0
|
||||
- DJANGO=master
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- { python: "3.6", env: DJANGO=master }
|
||||
- { python: "3.6", env: DJANGO=1.11 }
|
||||
- { python: "3.6", env: DJANGO=2.0 }
|
||||
- { python: "2.7", env: TOXENV=lint }
|
||||
- { python: "2.7", env: TOXENV=docs }
|
||||
|
||||
- python: "3.6"
|
||||
env: DJANGO=master
|
||||
env: TOXENV=dist
|
||||
script:
|
||||
- python setup.py bdist_wheel
|
||||
- tox --installpkg ./dist/djangorestframework-*.whl
|
||||
- tox # test sdist
|
||||
|
||||
- python: "3.6"
|
||||
env: DJANGO=1.11
|
||||
- python: "3.3"
|
||||
env: DJANGO=1.8
|
||||
- python: "2.7"
|
||||
env: TOXENV="lint"
|
||||
- python: "2.7"
|
||||
env: TOXENV="docs"
|
||||
env: TOXENV=readme
|
||||
addons:
|
||||
apt_packages: pandoc
|
||||
|
||||
exclude:
|
||||
- python: "2.7"
|
||||
env: DJANGO=master
|
||||
- python: "3.4"
|
||||
env: DJANGO=master
|
||||
- { python: "2.7", env: DJANGO=master }
|
||||
- { python: "2.7", env: DJANGO=2.0 }
|
||||
- { python: "3.4", env: DJANGO=master }
|
||||
|
||||
allow_failures:
|
||||
- env: DJANGO=master
|
||||
|
|
|
@ -7,4 +7,3 @@ 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,6 +1,6 @@
|
|||
include README.md
|
||||
include LICENSE.md
|
||||
recursive-include rest_framework/static *.js *.css *.png *.eot *.svg *.ttf *.woff
|
||||
recursive-include rest_framework/templates *.html
|
||||
recursive-exclude * __pycache__
|
||||
recursive-exclude * *.py[co]
|
||||
recursive-include rest_framework/templates *.html schema.js
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[co]
|
||||
|
|
35
README.md
35
README.md
|
@ -15,21 +15,18 @@ Full documentation for the project is available at [http://www.django-rest-frame
|
|||
|
||||
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]**.
|
||||
continued development by [signing up for a paid plan][funding].
|
||||
|
||||
The initial aim is to provide a single full-time position on REST framework.
|
||||
*Every single sign-up makes a significant impact towards making that possible.*
|
||||
|
||||
<p align="center">
|
||||
<a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a>
|
||||
<a href="https://getsentry.com/welcome/"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png"/></a>
|
||||
<a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a>
|
||||
<a href="https://hello.machinalis.co.uk/"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/machinalis-readme.png"/></a>
|
||||
<a href="https://rollbar.com/"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png"/></a>
|
||||
<a href="https://micropyramid.com/django-rest-framework-development-services/"><img src="https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/micropyramid-readme.png"/></a>
|
||||
</p>
|
||||
[![][rover-img]][rover-url]
|
||||
[![][sentry-img]][sentry-url]
|
||||
[![][stream-img]][stream-url]
|
||||
[![][machinalis-img]][machinalis-url]
|
||||
[![][rollbar-img]][rollbar-url]
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
|
||||
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover][rover-url], [Sentry][sentry-url], [Stream][stream-url], [Machinalis][machinalis-url], and [Rollbar][rollbar-url].
|
||||
|
||||
---
|
||||
|
||||
|
@ -51,10 +48,12 @@ There is a live example API for testing purposes, [available here][sandbox].
|
|||
|
||||
![Screenshot][image]
|
||||
|
||||
----
|
||||
|
||||
# Requirements
|
||||
|
||||
* Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
|
||||
* Django (1.8, 1.9, 1.10, 1.11)
|
||||
* Python (2.7, 3.4, 3.5, 3.6)
|
||||
* Django (1.10, 1.11, 2.0rc1)
|
||||
|
||||
# Installation
|
||||
|
||||
|
@ -189,6 +188,18 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
|
|||
[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
|
||||
[machinalis-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/machinalis-readme.png
|
||||
[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png
|
||||
|
||||
[rover-url]: http://jobs.rover.com/
|
||||
[sentry-url]: https://getsentry.com/welcome/
|
||||
[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
|
||||
[machinalis-url]: https://hello.machinalis.co.uk/
|
||||
[rollbar-url]: https://rollbar.com/
|
||||
|
||||
[oauth1-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
|
||||
[oauth2-section]: http://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit
|
||||
[serializer-section]: http://www.django-rest-framework.org/api-guide/serializers/#serializers
|
||||
|
|
|
@ -222,6 +222,21 @@ It is also possible to create Tokens manually through admin interface. In case y
|
|||
TokenAdmin.raw_id_fields = ('user',)
|
||||
|
||||
|
||||
#### Using Django manage.py command
|
||||
|
||||
Since version 3.6.4 it's possible to generate a user token using the following command:
|
||||
|
||||
./manage.py drf_create_token <username>
|
||||
|
||||
this command will return the API token for the given user, creating it if it doesn't exist:
|
||||
|
||||
Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1
|
||||
|
||||
In case you want to regenerate the token (for example if it has been compromised or leaked) you can pass an additional parameter:
|
||||
|
||||
./manage.py drf_create_token -r <username>
|
||||
|
||||
|
||||
## SessionAuthentication
|
||||
|
||||
This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
|
||||
|
@ -239,6 +254,28 @@ If you're using an AJAX style API with SessionAuthentication, you'll need to mak
|
|||
|
||||
CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied.
|
||||
|
||||
|
||||
## RemoteUserAuthentication
|
||||
|
||||
This authentication scheme allows you to delegate authentication to your web server, which sets the `REMOTE_USER`
|
||||
environment variable.
|
||||
|
||||
To use it, you must have `django.contrib.auth.backends.RemoteUserBackend` (or a subclass) in your
|
||||
`AUTHENTICATION_BACKENDS` setting. By default, `RemoteUserBackend` creates `User` objects for usernames that don't
|
||||
already exist. To change this and other behaviour, consult the
|
||||
[Django documentation](https://docs.djangoproject.com/en/stable/howto/auth-remote-user/).
|
||||
|
||||
If successfully authenticated, `RemoteUserAuthentication` provides the following credentials:
|
||||
|
||||
* `request.user` will be a Django `User` instance.
|
||||
* `request.auth` will be `None`.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# Custom authentication
|
||||
|
||||
To implement a custom authentication scheme, subclass `BaseAuthentication` and override the `.authenticate(self, request)` method. The method should return a two-tuple of `(user, auth)` if authentication succeeds, or `None` otherwise.
|
||||
|
@ -254,6 +291,12 @@ You *may* also override the `.authenticate_header(self, request)` method. If im
|
|||
|
||||
If the `.authenticate_header()` method is not overridden, the authentication scheme will return `HTTP 403 Forbidden` responses when an unauthenticated request is denied access.
|
||||
|
||||
---
|
||||
|
||||
**Note:** When your custom authenticator is invoked by the request object's `.user` or `.auth` properties, you may see an `AttributeError` re-raised as a `WrappedAttributeError`. This is necessary to prevent the original exception from being suppressed by the outer property access. Python will not recognize that the `AttributeError` orginates from your custom authenticator and will instead assume that the request object does not have a `.user` or `.auth` property. These errors should be fixed or otherwise handled by your authenticator.
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
The following example will authenticate any incoming request as the user given by the username in a custom request header named 'X_USERNAME'.
|
||||
|
|
54
docs/api-guide/caching.md
Normal file
54
docs/api-guide/caching.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Caching
|
||||
|
||||
> A certain woman had a very sharp conciousness but almost no
|
||||
> memory ... She remembered enough to work, and she worked hard.
|
||||
> - Lydia Davis
|
||||
|
||||
Caching in REST Framework works well with the cache utilities
|
||||
provided in Django.
|
||||
|
||||
---
|
||||
|
||||
## Using cache with apiview and viewsets
|
||||
|
||||
Django provides a [`method_decorator`][decorator] to use
|
||||
decorators with class based views. This can be used with
|
||||
with other cache decorators such as [`cache_page`][page] and
|
||||
[`vary_on_cookie`][cookie].
|
||||
|
||||
```python
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import viewsets
|
||||
|
||||
class UserViewSet(viewsets.Viewset):
|
||||
|
||||
# Cache requested url for each user for 2 hours
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def list(self, request, format=None):
|
||||
content = {
|
||||
'user_feed': request.user.get_user_feed()
|
||||
}
|
||||
return Response(content)
|
||||
|
||||
class PostView(APIView):
|
||||
|
||||
# Cache page for the requested url
|
||||
@method_decorator(cache_page(60*60*2))
|
||||
def get(self, request, format=None):
|
||||
content = {
|
||||
'title': 'Post title',
|
||||
'body': 'Post content'
|
||||
}
|
||||
return Response(content)
|
||||
```
|
||||
|
||||
**NOTE:** The [`cache_page`][page] decorator only caches the
|
||||
`GET` and `HEAD` responses with status 200.
|
||||
|
||||
|
||||
[django]: https://docs.djangoproject.com/en/dev/topics/cache/
|
||||
[page]: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-view-cache
|
||||
[cookie]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie
|
||||
[decorator]: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class
|
|
@ -45,6 +45,8 @@ Defaults to `True`.
|
|||
|
||||
Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value.
|
||||
|
||||
Note that setting this argument to `True` will imply a default value of `null` for serialization output, but does imply a default for input deserialization.
|
||||
|
||||
Defaults to `False`
|
||||
|
||||
### `default`
|
||||
|
@ -61,7 +63,7 @@ Note that setting a `default` value implies that the field is not required. Incl
|
|||
|
||||
### `source`
|
||||
|
||||
The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`.
|
||||
The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal.
|
||||
|
||||
The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation.
|
||||
|
||||
|
@ -122,6 +124,8 @@ A boolean representation.
|
|||
|
||||
When using HTML encoded form input be aware that omitting a value will always be treated as setting a field to `False`, even if it has a `default=True` option specified. This is because HTML checkbox inputs represent the unchecked state by omitting the value, so REST framework treats omission as if it is an empty checkbox input.
|
||||
|
||||
Note that default `BooleanField` instances will be generated with a `required=False` option (since Django `models.BooleanField` is always `blank=True`). If you want to change this behaviour explicitly declare the `BooleanField` on the serializer class.
|
||||
|
||||
Corresponds to `django.db.models.fields.BooleanField`.
|
||||
|
||||
**Signature:** `BooleanField()`
|
||||
|
@ -269,6 +273,7 @@ Corresponds to `django.db.models.fields.DecimalField`.
|
|||
- `max_value` Validate that the number provided is no greater than this value.
|
||||
- `min_value` Validate that the number provided is no less than this value.
|
||||
- `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file.
|
||||
- `rounding` Sets the rounding mode used when quantising to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`.
|
||||
|
||||
#### Example usage
|
||||
|
||||
|
@ -355,8 +360,6 @@ Corresponds to `django.db.models.fields.DurationField`
|
|||
The `validated_data` for these fields will contain a `datetime.timedelta` instance.
|
||||
The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu]'`.
|
||||
|
||||
**Note:** This field is only available with Django versions >= 1.8.
|
||||
|
||||
**Signature:** `DurationField()`
|
||||
|
||||
---
|
||||
|
@ -434,7 +437,7 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is
|
|||
|
||||
A field class that validates a list of objects.
|
||||
|
||||
**Signature**: `ListField(child, min_length=None, max_length=None)`
|
||||
**Signature**: `ListField(child=<A_FIELD_INSTANCE>, min_length=None, max_length=None)`
|
||||
|
||||
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
|
||||
- `min_length` - Validates that the list contains no fewer than this number of elements.
|
||||
|
@ -457,7 +460,7 @@ We can now reuse our custom `StringListField` class throughout our application,
|
|||
|
||||
A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.
|
||||
|
||||
**Signature**: `DictField(child)`
|
||||
**Signature**: `DictField(child=<A_FIELD_INSTANCE>)`
|
||||
|
||||
- `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated.
|
||||
|
||||
|
@ -641,7 +644,7 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
|
|||
|
||||
return Color(red, green, blue)
|
||||
|
||||
This style keeps you error messages more cleanly separated from your code, and should be preferred.
|
||||
This style keeps your error messages cleaner and more separated from your code, and should be preferred.
|
||||
|
||||
# Third party packages
|
||||
|
||||
|
@ -680,3 +683,4 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide
|
|||
[django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis
|
||||
[django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
|
||||
[django-hstore]: https://github.com/djangonauts/django-hstore
|
||||
[python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes
|
||||
|
|
|
@ -381,7 +381,7 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
|
|||
[cite]: https://docs.djangoproject.com/en/stable/topics/db/queries/#retrieving-specific-objects-with-filters
|
||||
[django-filter]: https://github.com/alex/django-filter
|
||||
[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html
|
||||
[django-filter-drf-docs]: https://django-filter.readthedocs.io/en/develop/guide/rest_framework.html
|
||||
[django-filter-drf-docs]: https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html
|
||||
[guardian]: https://django-guardian.readthedocs.io/
|
||||
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html
|
||||
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
|
||||
|
|
|
@ -113,11 +113,11 @@ For example:
|
|||
|
||||
Note that if your API doesn't include any object level permissions, you may optionally exclude the `self.check_object_permissions`, and simply return the object from the `get_object_or_404` lookup.
|
||||
|
||||
#### `filter_queryset(self, queryset)`
|
||||
#### `filter_queryset(self, queryset)`
|
||||
|
||||
Given a queryset, filter it with whichever filter backends are in use, returning a new queryset.
|
||||
Given a queryset, filter it with whichever filter backends are in use, returning a new queryset.
|
||||
|
||||
For example:
|
||||
For example:
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
filter_backends = (CategoryFilter,)
|
||||
|
@ -330,7 +330,9 @@ For example, if you need to lookup objects based on multiple fields in the URL c
|
|||
for field in self.lookup_fields:
|
||||
if self.kwargs[field]: # Ignore empty fields.
|
||||
filter[field] = self.kwargs[field]
|
||||
return get_object_or_404(queryset, **filter) # Lookup the object
|
||||
obj = get_object_or_404(queryset, **filter) # Lookup the object
|
||||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior.
|
||||
|
||||
|
|
|
@ -21,14 +21,14 @@ Pagination can be turned off by setting the pagination class to `None`.
|
|||
|
||||
## Setting the pagination style
|
||||
|
||||
The default pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` setting keys. For example, to use the built-in limit/offset pagination, you would do something like this:
|
||||
The pagination style may be set globally, using the `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` setting keys. For example, to use the built-in limit/offset pagination, you would do something like this:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'PAGE_SIZE': 100
|
||||
}
|
||||
|
||||
Note that you need to set both the pagination class, and the page size that should be used.
|
||||
Note that you need to set both the pagination class, and the page size that should be used. Both `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE` are `None` by default.
|
||||
|
||||
You can also set the pagination class on an individual view by using the `pagination_class` attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis.
|
||||
|
||||
|
@ -85,7 +85,7 @@ This pagination style accepts a single number page number in the request query p
|
|||
|
||||
#### Setup
|
||||
|
||||
To enable the `PageNumberPagination` style globally, use the following configuration, modifying the `PAGE_SIZE` as desired:
|
||||
To enable the `PageNumberPagination` style globally, use the following configuration, and set the `PAGE_SIZE` as desired:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
|
@ -179,6 +179,10 @@ Proper usage of cursor pagination should have an ordering field that satisfies t
|
|||
* Should be an unchanging value, such as a timestamp, slug, or other field that is only set once, on creation.
|
||||
* Should be unique, or nearly unique. Millisecond precision timestamps are a good example. This implementation of cursor pagination uses a smart "position plus offset" style that allows it to properly support not-strictly-unique values as the ordering.
|
||||
* Should be a non-nullable value that can be coerced to a string.
|
||||
* Should not be a float. Precision errors easily lead to incorrect results.
|
||||
Hint: use decimals instead.
|
||||
(If you already have a float field and must paginate on that, an
|
||||
[example `CursorPagination` subclass that uses decimals to limit precision is available here][float_cursor_pagination_example].)
|
||||
* The field should have a database index.
|
||||
|
||||
Using an ordering field that does not satisfy these constraints will generally still work, but you'll be losing some of the benefits of cursor pagination.
|
||||
|
@ -226,8 +230,8 @@ Suppose we want to replace the default pagination output style with a modified f
|
|||
def get_paginated_response(self, data):
|
||||
return Response({
|
||||
'links': {
|
||||
'next': self.get_next_link(),
|
||||
'previous': self.get_previous_link()
|
||||
'next': self.get_next_link(),
|
||||
'previous': self.get_previous_link()
|
||||
},
|
||||
'count': self.page.paginator.count,
|
||||
'results': data
|
||||
|
@ -242,29 +246,6 @@ We'd then need to setup the custom class in our configuration:
|
|||
|
||||
Note that if you care about how the ordering of keys is displayed in responses in the browsable API you might choose to use an `OrderedDict` when constructing the body of paginated responses, but this is optional.
|
||||
|
||||
## Header based pagination
|
||||
|
||||
Let's modify the built-in `PageNumberPagination` style, so that instead of include the pagination links in the body of the response, we'll instead include a `Link` header, in a [similar style to the GitHub API][github-link-pagination].
|
||||
|
||||
class LinkHeaderPagination(pagination.PageNumberPagination):
|
||||
def get_paginated_response(self, data):
|
||||
next_url = self.get_next_link()
|
||||
previous_url = self.get_previous_link()
|
||||
|
||||
if next_url is not None and previous_url is not None:
|
||||
link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"'
|
||||
elif next_url is not None:
|
||||
link = '<{next_url}>; rel="next"'
|
||||
elif previous_url is not None:
|
||||
link = '<{previous_url}>; rel="prev"'
|
||||
else:
|
||||
link = ''
|
||||
|
||||
link = link.format(next_url=next_url, previous_url=previous_url)
|
||||
headers = {'Link': link} if link else {}
|
||||
|
||||
return Response(data, headers=headers)
|
||||
|
||||
## Using your custom pagination class
|
||||
|
||||
To have your custom pagination class be used by default, use the `DEFAULT_PAGINATION_CLASS` setting:
|
||||
|
@ -328,10 +309,16 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`
|
|||
|
||||
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter.
|
||||
|
||||
## link-header-pagination
|
||||
|
||||
The [`django-rest-framework-link-header-pagination` package][drf-link-header-pagination] includes a `LinkHeaderPagination` class which provides pagination via an HTTP `Link` header as desribed in [Github's developer documentation](github-link-pagination).
|
||||
|
||||
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
|
||||
[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/
|
||||
[link-header]: ../img/link-header-pagination.png
|
||||
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
|
||||
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
|
||||
[drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination
|
||||
[drf-link-header-pagination]: https://github.com/tbeadle/django-rest-framework-link-header-pagination
|
||||
[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api
|
||||
[float_cursor_pagination_example]: https://gist.github.com/keturn/8bc88525a183fd41c73ffb729b8865be#file-fpcursorpagination-py
|
||||
|
|
|
@ -54,6 +54,7 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.decorators import parser_classes
|
||||
from rest_framework.parsers import JSONParser
|
||||
|
||||
@api_view(['POST'])
|
||||
@parser_classes((JSONParser,))
|
||||
|
|
|
@ -44,7 +44,7 @@ This will either raise a `PermissionDenied` or `NotAuthenticated` exception, or
|
|||
For example:
|
||||
|
||||
def get_object(self):
|
||||
obj = get_object_or_404(self.get_queryset())
|
||||
obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
|
||||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
|
@ -197,15 +197,15 @@ 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.
|
||||
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
class CustomerAccessPermission(permissions.BasePermission):
|
||||
message = 'Adding customers not allowed.'
|
||||
|
||||
|
||||
def has_permission(self, request, view):
|
||||
...
|
||||
|
||||
|
||||
## 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.
|
||||
|
|
|
@ -90,6 +90,10 @@ You won't typically need to access this property.
|
|||
|
||||
---
|
||||
|
||||
**Note:** You may see a `WrappedAttributeError` raised when calling the `.user` or `.auth` properties. These errors originate from an authenticator as a standard `AttributeError`, however it's necessary that they be re-raised as a different exception type in order to prevent them from being suppressed by the outer property access. Python will not recognize that the `AttributeError` orginates from the authenticator and will instaed assume that the request object does not have a `.user` or `.auth` property. The authenticator will need to be fixed.
|
||||
|
||||
---
|
||||
|
||||
# Browser enhancements
|
||||
|
||||
REST framework supports a few browser enhancements such as browser-based `PUT`, `PATCH` and `DELETE` forms.
|
||||
|
|
|
@ -42,7 +42,7 @@ Arguments:
|
|||
|
||||
## .data
|
||||
|
||||
The unrendered content of a `Request` object.
|
||||
The unrendered, serialized data of the response.
|
||||
|
||||
## .status_code
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ For example, you can append `router.urls` to a list of existing views…
|
|||
router = routers.SimpleRouter()
|
||||
router.register(r'users', UserViewSet)
|
||||
router.register(r'accounts', AccountViewSet)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||
]
|
||||
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
Alternatively you can use Django's `include` function, like so…
|
||||
|
@ -106,10 +106,10 @@ For example, if you want to change the URL for our custom action to `^users/{pk}
|
|||
|
||||
from myapp.permissions import IsAdminOrIsSelf
|
||||
from rest_framework.decorators import detail_route
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
...
|
||||
|
||||
|
||||
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
|
||||
def set_password(self, request, pk=None):
|
||||
...
|
||||
|
@ -124,10 +124,10 @@ For example, if you want to change the name of our custom action to `'user-chang
|
|||
|
||||
from myapp.permissions import IsAdminOrIsSelf
|
||||
from rest_framework.decorators import detail_route
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
...
|
||||
|
||||
|
||||
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_name='change-password')
|
||||
def set_password(self, request, pk=None):
|
||||
...
|
||||
|
|
|
@ -10,7 +10,14 @@ API schemas are a useful tool that allow for a range of use cases, including
|
|||
generating reference documentation, or driving dynamic client libraries that
|
||||
can interact with your API.
|
||||
|
||||
## Representing schemas internally
|
||||
## Install Core API
|
||||
|
||||
You'll need to install the `coreapi` package in order to add schema support
|
||||
for REST framework.
|
||||
|
||||
pip install coreapi
|
||||
|
||||
## Internal schema representation
|
||||
|
||||
REST framework uses [Core API][coreapi] in order to model schema information in
|
||||
a format-independent representation. This information can then be rendered
|
||||
|
@ -68,9 +75,34 @@ has to be rendered into the actual bytes that are used in the response.
|
|||
REST framework includes a renderer class for handling this media type, which
|
||||
is available as `renderers.CoreJSONRenderer`.
|
||||
|
||||
### Alternate schema formats
|
||||
|
||||
Other schema formats such as [Open API][open-api] ("Swagger"),
|
||||
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can
|
||||
also be supported by implementing a custom renderer class.
|
||||
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can also
|
||||
be supported by implementing a custom renderer class that handles converting a
|
||||
`Document` instance into a bytestring representation.
|
||||
|
||||
If there is a Core API codec package that supports encoding into the format you
|
||||
want to use then implementing the renderer class can be done by using the codec.
|
||||
|
||||
#### Example
|
||||
|
||||
For example, the `openapi_codec` package provides support for encoding or decoding
|
||||
to the Open API ("Swagger") format:
|
||||
|
||||
from rest_framework import renderers
|
||||
from openapi_codec import OpenAPICodec
|
||||
|
||||
class SwaggerRenderer(renderers.BaseRenderer):
|
||||
media_type = 'application/openapi+json'
|
||||
format = 'swagger'
|
||||
|
||||
def render(self, data, media_type=None, renderer_context=None):
|
||||
codec = OpenAPICodec()
|
||||
return codec.dump(data)
|
||||
|
||||
|
||||
|
||||
|
||||
## Schemas vs Hypermedia
|
||||
|
||||
|
@ -89,18 +121,139 @@ document, detailing both the current state and the available interactions.
|
|||
Further information and support on building Hypermedia APIs with REST framework
|
||||
is planned for a future version.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Adding a schema
|
||||
|
||||
You'll need to install the `coreapi` package in order to add schema support
|
||||
for REST framework.
|
||||
|
||||
pip install coreapi
|
||||
# Creating a schema
|
||||
|
||||
REST framework includes functionality for auto-generating a schema,
|
||||
or allows you to specify one explicitly. There are a few different ways to
|
||||
add a schema to your API, depending on exactly what you need.
|
||||
or allows you to specify one explicitly.
|
||||
|
||||
## Manual Schema Specification
|
||||
|
||||
To manually specify a schema you create a Core API `Document`, similar to the
|
||||
example above.
|
||||
|
||||
schema = coreapi.Document(
|
||||
title='Flight Search API',
|
||||
content={
|
||||
...
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
## Automatic Schema Generation
|
||||
|
||||
Automatic schema generation is provided by the `SchemaGenerator` class.
|
||||
|
||||
`SchemaGenerator` processes a list of routed URL pattterns and compiles the
|
||||
appropriately structured Core API Document.
|
||||
|
||||
Basic usage is just to provide the title for your schema and call
|
||||
`get_schema()`:
|
||||
|
||||
generator = schemas.SchemaGenerator(title='Flight Search API')
|
||||
schema = generator.get_schema()
|
||||
|
||||
## Per-View Schema Customisation
|
||||
|
||||
By default, view introspection is performed by an `AutoSchema` instance
|
||||
accessible via the `schema` attribute on `APIView`. This provides the
|
||||
appropriate Core API `Link` object for the view, request method and path:
|
||||
|
||||
auto_schema = view.schema
|
||||
coreapi_link = auto_schema.get_link(...)
|
||||
|
||||
(In compiling the schema, `SchemaGenerator` calls `view.schema.get_link()` for
|
||||
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
|
||||
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.)
|
||||
|
||||
---
|
||||
|
||||
To customise the `Link` generation you may:
|
||||
|
||||
* Instantiate `AutoSchema` on your view with the `manual_fields` kwarg:
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.schemas import AutoSchema
|
||||
|
||||
class CustomView(APIView):
|
||||
...
|
||||
schema = AutoSchema(
|
||||
manual_fields=[
|
||||
coreapi.Field("extra_field", ...),
|
||||
]
|
||||
)
|
||||
|
||||
This allows extension for the most common case without subclassing.
|
||||
|
||||
* Provide an `AutoSchema` subclass with more complex customisation:
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.schemas import AutoSchema
|
||||
|
||||
class CustomSchema(AutoSchema):
|
||||
def get_link(...):
|
||||
# Implement custom introspection here (or in other sub-methods)
|
||||
|
||||
class CustomView(APIView):
|
||||
...
|
||||
schema = CustomSchema()
|
||||
|
||||
This provides complete control over view introspection.
|
||||
|
||||
* Instantiate `ManualSchema` on your view, providing the Core API `Fields` for
|
||||
the view explicitly:
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.schemas import ManualSchema
|
||||
|
||||
class CustomView(APIView):
|
||||
...
|
||||
schema = ManualSchema(fields=[
|
||||
coreapi.Field(
|
||||
"first_field",
|
||||
required=True,
|
||||
location="path",
|
||||
schema=coreschema.String()
|
||||
),
|
||||
coreapi.Field(
|
||||
"second_field",
|
||||
required=True,
|
||||
location="path",
|
||||
schema=coreschema.String()
|
||||
),
|
||||
])
|
||||
|
||||
This allows manually specifying the schema for some views whilst maintaining
|
||||
automatic generation elsewhere.
|
||||
|
||||
You may disable schema generation for a view by setting `schema` to `None`:
|
||||
|
||||
class CustomView(APIView):
|
||||
...
|
||||
schema = None # Will not appear in schema
|
||||
|
||||
---
|
||||
|
||||
**Note**: For full details on `SchemaGenerator` plus the `AutoSchema` and
|
||||
`ManualSchema` descriptors see the [API Reference below](#api-reference).
|
||||
|
||||
---
|
||||
|
||||
# Adding a schema view
|
||||
|
||||
There are a few different ways to add a schema view to your API, depending on
|
||||
exactly what you need.
|
||||
|
||||
## The get_schema_view shortcut
|
||||
|
||||
|
@ -193,6 +346,15 @@ to be exposed in the schema:
|
|||
May be used to specify a `SchemaGenerator` subclass to be passed to the
|
||||
`SchemaView`.
|
||||
|
||||
#### `authentication_classes`
|
||||
|
||||
May be used to specify the list of authentication classes that will apply to the schema endpoint.
|
||||
Defaults to `settings.DEFAULT_AUTHENTICATION_CLASSES`
|
||||
|
||||
#### `permission_classes`
|
||||
|
||||
May be used to specify the list of permission classes that will apply to the schema endpoint.
|
||||
Defaults to `settings.DEFAULT_PERMISSION_CLASSES`
|
||||
|
||||
|
||||
## Using an explicit schema view
|
||||
|
@ -342,38 +504,12 @@ A generic viewset with sections in the class docstring, using multi-line style.
|
|||
|
||||
---
|
||||
|
||||
# Alternate schema formats
|
||||
|
||||
In order to support an alternate schema format, you need to implement a custom renderer
|
||||
class that handles converting a `Document` instance into a bytestring representation.
|
||||
|
||||
If there is a Core API codec package that supports encoding into the format you
|
||||
want to use then implementing the renderer class can be done by using the codec.
|
||||
|
||||
## Example
|
||||
|
||||
For example, the `openapi_codec` package provides support for encoding or decoding
|
||||
to the Open API ("Swagger") format:
|
||||
|
||||
from rest_framework import renderers
|
||||
from openapi_codec import OpenAPICodec
|
||||
|
||||
class SwaggerRenderer(renderers.BaseRenderer):
|
||||
media_type = 'application/openapi+json'
|
||||
format = 'swagger'
|
||||
|
||||
def render(self, data, media_type=None, renderer_context=None):
|
||||
codec = OpenAPICodec()
|
||||
return codec.dump(data)
|
||||
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
## SchemaGenerator
|
||||
|
||||
A class that deals with introspecting your API views, which can be used to
|
||||
generate a schema.
|
||||
A class that walks a list of routed URL patterns, requests the schema for each view,
|
||||
and collates the resulting CoreAPI Document.
|
||||
|
||||
Typically you'll instantiate `SchemaGenerator` with a single argument, like so:
|
||||
|
||||
|
@ -406,39 +542,133 @@ Return a nested dictionary containing all the links that should be included in t
|
|||
This is a good point to override if you want to modify the resulting structure of the generated schema,
|
||||
as you can build a new dictionary with a different layout.
|
||||
|
||||
### get_link(self, path, method, view)
|
||||
|
||||
## AutoSchema
|
||||
|
||||
A class that deals with introspection of individual views for schema generation.
|
||||
|
||||
`AutoSchema` is attached to `APIView` via the `schema` attribute.
|
||||
|
||||
The `AutoSchema` constructor takes a single keyword argument `manual_fields`.
|
||||
|
||||
**`manual_fields`**: a `list` of `coreapi.Field` instances that will be added to
|
||||
the generated fields. Generated fields with a matching `name` will be overwritten.
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = AutoSchema(manual_fields=[
|
||||
coreapi.Field(
|
||||
"my_extra_field",
|
||||
required=True,
|
||||
location="path",
|
||||
schema=coreschema.String()
|
||||
),
|
||||
])
|
||||
|
||||
For more advanced customisation subclass `AutoSchema` to customise schema generation.
|
||||
|
||||
class CustomViewSchema(AutoSchema):
|
||||
"""
|
||||
Overrides `get_link()` to provide Custom Behavior X
|
||||
"""
|
||||
|
||||
def get_link(self, path, method, base_url):
|
||||
link = super().get_link(path, method, base_url)
|
||||
# Do something to customize link here...
|
||||
return link
|
||||
|
||||
class MyView(APIView):
|
||||
schema = CustomViewSchema()
|
||||
|
||||
The following methods are available to override.
|
||||
|
||||
### get_link(self, path, method, base_url)
|
||||
|
||||
Returns a `coreapi.Link` instance corresponding to the given view.
|
||||
|
||||
This is the main entry point.
|
||||
You can override this if you need to provide custom behaviors for particular views.
|
||||
|
||||
### get_description(self, path, method, view)
|
||||
### get_description(self, path, method)
|
||||
|
||||
Returns a string to use as the link description. By default this is based on the
|
||||
view docstring as described in the "Schemas as Documentation" section above.
|
||||
|
||||
### get_encoding(self, path, method, view)
|
||||
### get_encoding(self, path, method)
|
||||
|
||||
Returns a string to indicate the encoding for any request body, when interacting
|
||||
with the given view. Eg. `'application/json'`. May return a blank string for views
|
||||
that do not expect a request body.
|
||||
|
||||
### get_path_fields(self, path, method, view):
|
||||
### get_path_fields(self, path, method):
|
||||
|
||||
Return a list of `coreapi.Link()` instances. One for each path parameter in the URL.
|
||||
|
||||
### get_serializer_fields(self, path, method, view)
|
||||
### get_serializer_fields(self, path, method)
|
||||
|
||||
Return a list of `coreapi.Link()` instances. One for each field in the serializer class used by the view.
|
||||
|
||||
### get_pagination_fields(self, path, method, view
|
||||
### get_pagination_fields(self, path, method)
|
||||
|
||||
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method on any pagination class used by the view.
|
||||
|
||||
### get_filter_fields(self, path, method, view)
|
||||
### get_filter_fields(self, path, method)
|
||||
|
||||
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view.
|
||||
|
||||
### get_manual_fields(self, path, method)
|
||||
|
||||
Return a list of `coreapi.Field()` instances to be added to or replace generated fields. Defaults to (optional) `manual_fields` passed to `AutoSchema` constructor.
|
||||
|
||||
May be overridden to customise manual fields by `path` or `method`. For example, a per-method adjustment may look like this:
|
||||
|
||||
```python
|
||||
def get_manual_fields(self, path, method):
|
||||
"""Example adding per-method fields."""
|
||||
|
||||
extra_fields = []
|
||||
if method=='GET':
|
||||
extra_fields = # ... list of extra fields for GET ...
|
||||
if method=='POST':
|
||||
extra_fields = # ... list of extra fields for POST ...
|
||||
|
||||
manual_fields = super().get_manual_fields()
|
||||
return manual_fields + extra_fields
|
||||
```
|
||||
|
||||
### update_fields(fields, update_with)
|
||||
|
||||
Utility `staticmethod`. Encapsulates logic to add or replace fields from a list
|
||||
by `Field.name`. May be overridden to adjust replacement criteria.
|
||||
|
||||
|
||||
## ManualSchema
|
||||
|
||||
Allows manually providing a list of `coreapi.Field` instances for the schema,
|
||||
plus an optional description.
|
||||
|
||||
class MyView(APIView):
|
||||
schema = ManualSchema(fields=[
|
||||
coreapi.Field(
|
||||
"first_field",
|
||||
required=True,
|
||||
location="path",
|
||||
schema=coreschema.String()
|
||||
),
|
||||
coreapi.Field(
|
||||
"second_field",
|
||||
required=True,
|
||||
location="path",
|
||||
schema=coreschema.String()
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
The `ManualSchema` constructor takes two arguments:
|
||||
|
||||
**`fields`**: A list of `coreapi.Field` instances. Required.
|
||||
|
||||
**`description`**: A string description. Optional.
|
||||
|
||||
---
|
||||
|
||||
## Core API
|
||||
|
@ -572,10 +802,21 @@ Valid only if a `location="body"` field is included on the `Link`.
|
|||
A short description of the meaning and intended usage of the input field.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Third party packages
|
||||
|
||||
## DRF OpenAPI
|
||||
|
||||
[DRF OpenAPI][drf-openapi] renders the schema generated by Django Rest Framework
|
||||
in [OpenAPI][open-api] format.
|
||||
|
||||
|
||||
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
|
||||
[coreapi]: http://www.coreapi.org/
|
||||
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
|
||||
[open-api]: https://openapis.org/
|
||||
[drf-openapi]: https://github.com/limdauto/drf_openapi
|
||||
[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
|
||||
[api-blueprint]: https://apiblueprint.org/
|
||||
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/
|
||||
|
|
|
@ -73,7 +73,7 @@ Deserialization is similar. First we parse a stream into Python native datatypes
|
|||
|
||||
## Saving instances
|
||||
|
||||
If we want to be able to return complete object instances based on the validated data we need to implement one or both of the `.create()` and `update()` methods. For example:
|
||||
If we want to be able to return complete object instances based on the validated data we need to implement one or both of the `.create()` and `.update()` methods. For example:
|
||||
|
||||
class CommentSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField()
|
||||
|
@ -325,7 +325,7 @@ For updates you'll want to think carefully about how to handle updates to relati
|
|||
* Ignore the data and leave the instance as it is.
|
||||
* Raise a validation error.
|
||||
|
||||
Here's an example for an `update()` method on our previous `UserSerializer` class.
|
||||
Here's an example for an `.update()` method on our previous `UserSerializer` class.
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
profile_data = validated_data.pop('profile')
|
||||
|
@ -493,6 +493,8 @@ The names in the `fields` and `exclude` attributes will normally map to model fi
|
|||
|
||||
Alternatively names in the `fields` options can map to properties or methods which take no arguments that exist on the model class.
|
||||
|
||||
Since version 3.3.0, it is **mandatory** to provide one of the attributes `fields` or `exclude`.
|
||||
|
||||
## Specifying nested serialization
|
||||
|
||||
The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option:
|
||||
|
@ -822,9 +824,7 @@ Here's an example of how you might choose to implement multiple updates:
|
|||
# We need to identify elements in the list using their primary key,
|
||||
# so use a writable field here, rather than the default which would be read-only.
|
||||
id = serializers.IntegerField()
|
||||
|
||||
...
|
||||
id = serializers.IntegerField(required=False)
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = BookListSerializer
|
||||
|
@ -993,7 +993,7 @@ The following class is an example of a generic serializer that can handle coerci
|
|||
|
||||
## Overriding serialization and deserialization behavior
|
||||
|
||||
If you need to alter the serialization, deserialization or validation of a serializer class you can do so by overriding the `.to_representation()` or `.to_internal_value()` methods.
|
||||
If you need to alter the serialization or deserialization behavior of a serializer class, you can do so by overriding the `.to_representation()` or `.to_internal_value()` methods.
|
||||
|
||||
Some reasons this might be useful include...
|
||||
|
||||
|
@ -1011,7 +1011,7 @@ Takes the object instance that requires serialization, and should return a primi
|
|||
|
||||
Takes the unvalidated incoming data as input and should return the validated data that will be made available as `serializer.validated_data`. The return value will also be passed to the `.create()` or `.update()` methods if `.save()` is called on the serializer class.
|
||||
|
||||
If any of the validation fails, then the method should raise a `serializers.ValidationError(errors)`. Typically the `errors` argument here will be a dictionary mapping field names to error messages.
|
||||
If any of the validation fails, then the method should raise a `serializers.ValidationError(errors)`. The `errors` argument should be a dictionary mapping field names (or `settings.NON_FIELD_ERRORS_KEY`) to a list of error messages. If you don't need to alter deserialization behavior and instead want to provide object-level validation, it's recommended that you instead override the [`.validate()`](#object-level-validation) method.
|
||||
|
||||
The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.
|
||||
|
||||
|
@ -1155,7 +1155,7 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
|
|||
|
||||
## QueryFields
|
||||
|
||||
[djangorestframework-queryfields][djangorestframework-queryfields] allows API clients to specify which fields will be sent in the response via inclusion/exclusion query parameters.
|
||||
[djangorestframework-queryfields][djangorestframework-queryfields] allows API clients to specify which fields will be sent in the response via inclusion/exclusion query parameters.
|
||||
|
||||
## DRF Writable Nested
|
||||
|
||||
|
@ -1180,4 +1180,4 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested
|
|||
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
|
||||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||
[djangorestframework-queryfields]: http://djangorestframework-queryfields.readthedocs.io/
|
||||
[drf-writable-nested]: http://github.com/Brogency/drf-writable-nested
|
||||
[drf-writable-nested]: http://github.com/beda-software/drf-writable-nested
|
||||
|
|
|
@ -94,6 +94,12 @@ A content negotiation class, that determines how a renderer is selected for the
|
|||
|
||||
Default: `'rest_framework.negotiation.DefaultContentNegotiation'`
|
||||
|
||||
#### DEFAULT_SCHEMA_CLASS
|
||||
|
||||
A view inspector class that will be used for schema generation.
|
||||
|
||||
Default: `'rest_framework.schemas.AutoSchema'`
|
||||
|
||||
---
|
||||
|
||||
## Generic view settings
|
||||
|
@ -362,6 +368,14 @@ The default style is to return minified responses, in line with [Heroku's API de
|
|||
|
||||
Default: `True`
|
||||
|
||||
#### STRICT_JSON
|
||||
|
||||
When set to `True`, JSON rendering and parsing will only observe syntactically valid JSON, raising an exception for the extended float values (`nan`, `inf`, `-inf`) accepted by Python's `json` module. This is the recommended setting, as these values are not generally supported. e.g., neither Javascript's `JSON.Parse` nor PostgreSQL's JSON data type accept these values.
|
||||
|
||||
When set to `False`, JSON rendering and parsing will be permissive. However, these values are still invalid and will need to be specially handled in your code.
|
||||
|
||||
Default: `True`
|
||||
|
||||
#### COERCE_DECIMAL_TO_STRING
|
||||
|
||||
When returning decimal objects in API representations that do not support a native decimal type, it is normally best to return the value as a string. This avoids the loss of precision that occurs with binary floating point implementations.
|
||||
|
|
|
@ -6,7 +6,7 @@ source: status.py
|
|||
>
|
||||
> — [RFC 2324][rfc2324], Hyper Text Coffee Pot Control Protocol
|
||||
|
||||
Using bare status codes in your responses isn't recommended. REST framework includes a set of named constants that you can use to make more code more obvious and readable.
|
||||
Using bare status codes in your responses isn't recommended. REST framework includes a set of named constants that you can use to make your code more obvious and readable.
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
|
|
@ -82,7 +82,11 @@ For example, when forcibly authenticating using a token, you might do something
|
|||
|
||||
user = User.objects.get(username='olivia')
|
||||
request = factory.get('/accounts/django-superstars/')
|
||||
force_authenticate(request, user=user, token=user.token)
|
||||
force_authenticate(request, user=user, token=user.auth_token)
|
||||
|
||||
---
|
||||
|
||||
**Note**: `force_authenticate` directly sets `request.user` to the in-memory `user` instance. If you are re-using the same `user` instance across multiple tests that update the saved `user` state, you may need to call [`refresh_from_db()`][refresh_from_db_docs] between tests.
|
||||
|
||||
---
|
||||
|
||||
|
@ -378,3 +382,4 @@ For example, to add support for using `format='html'` in test requests, you migh
|
|||
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
|
||||
[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
|
||||
|
|
|
@ -68,9 +68,9 @@ Or, if you're using the `@api_view` decorator with function based views.
|
|||
|
||||
## How clients are identified
|
||||
|
||||
The `X-Forwarded-For` and `Remote-Addr` HTTP headers 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` header will be used.
|
||||
The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used.
|
||||
|
||||
If you need to strictly identify unique client IP addresses, you'll need to first configure the number of application proxies that the API runs behind by setting the `NUM_PROXIES` setting. This setting should be an integer of zero or more. If set to non-zero then the client IP will be identified as being the last IP address in the `X-Forwarded-For` header, once any application proxy IP addresses have first been excluded. If set to zero, then the `Remote-Addr` header will always be used as the identifying IP address.
|
||||
If you need to strictly identify unique client IP addresses, you'll need to first configure the number of application proxies that the API runs behind by setting the `NUM_PROXIES` setting. This setting should be an integer of zero or more. If set to non-zero then the client IP will be identified as being the last IP address in the `X-Forwarded-For` header, once any application proxy IP addresses have first been excluded. If set to zero, then the `REMOTE_ADDR` value will always be used as the identifying IP address.
|
||||
|
||||
It is important to understand that if you configure the `NUM_PROXIES` setting, then all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client.
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ It has two required arguments, and a single optional `messages` argument:
|
|||
The validator should be applied to *serializer classes*, like so:
|
||||
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
|
||||
class ExampleSerializer(serializers.Serializer):
|
||||
# ...
|
||||
class Meta:
|
||||
|
|
|
@ -23,6 +23,7 @@ For example:
|
|||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import authentication, permissions
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class ListUsers(APIView):
|
||||
"""
|
||||
|
@ -129,7 +130,7 @@ REST framework also allows you to work with regular function based views. It pr
|
|||
|
||||
## @api_view()
|
||||
|
||||
**Signature:** `@api_view(http_method_names=['GET'], exclude_from_schema=False)`
|
||||
**Signature:** `@api_view(http_method_names=['GET'])`
|
||||
|
||||
The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data:
|
||||
|
||||
|
@ -149,12 +150,6 @@ By default only `GET` methods will be accepted. Other methods will respond with
|
|||
return Response({"message": "Got some data!", "data": request.data})
|
||||
return Response({"message": "Hello, world!"})
|
||||
|
||||
You can also mark an API view as being omitted from any [auto-generated schema][schemas],
|
||||
using the `exclude_from_schema` argument.:
|
||||
|
||||
@api_view(['GET'], exclude_from_schema=True)
|
||||
def api_docs(request):
|
||||
...
|
||||
|
||||
## API policy decorators
|
||||
|
||||
|
@ -183,6 +178,35 @@ The available decorators are:
|
|||
|
||||
Each of these decorators takes a single argument which must be a list or tuple of classes.
|
||||
|
||||
|
||||
## View schema decorator
|
||||
|
||||
To override the default schema generation for function based views you may use
|
||||
the `@schema` decorator. This must come *after* (below) the `@api_view`
|
||||
decorator. For example:
|
||||
|
||||
from rest_framework.decorators import api_view, schema
|
||||
from rest_framework.schemas import AutoSchema
|
||||
|
||||
class CustomAutoSchema(AutoSchema):
|
||||
def get_link(self, path, method, base_url):
|
||||
# override view introspection here...
|
||||
|
||||
@api_view(['GET'])
|
||||
@schema(CustomAutoSchema())
|
||||
def view(request):
|
||||
return Response({"message": "Hello for today! See you tomorrow!"})
|
||||
|
||||
This decorator takes a single `AutoSchema` instance, an `AutoSchema` subclass
|
||||
instance or `ManualSchema` instance as described in the [Schemas documentation][schemas].
|
||||
You may pass `None` in order to exclude the view from schema generation.
|
||||
|
||||
@api_view(['GET'])
|
||||
@schema(None)
|
||||
def view(request):
|
||||
return Response({"message": "Will not appear in schema!"})
|
||||
|
||||
|
||||
[cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html
|
||||
[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html
|
||||
[settings]: settings.md
|
||||
|
|
|
@ -159,6 +159,21 @@ These decorators will route `GET` requests by default, but may also accept other
|
|||
|
||||
The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`
|
||||
|
||||
## Reversing action URLs
|
||||
|
||||
If you need to get the URL of an action, use the `.reverse_action()` method. This is a convenience wrapper for `reverse()`, automatically passing the view's `request` object and prepending the `url_name` with the `.basename` attribute.
|
||||
|
||||
Note that the `basename` is provided by the router during `ViewSet` registration. If you are not using a router, then you must provide the `basename` argument to the `.as_view()` method.
|
||||
|
||||
Using the example from the previous section:
|
||||
|
||||
```python
|
||||
>>> view.reverse_action('set-password', args=['1'])
|
||||
'http://localhost:8000/api/users/1/set_password'
|
||||
```
|
||||
|
||||
The `url_name` argument should match the same argument to the `@list_route` and `@detail_route` decorators. Additionally, this can be used to reverse the default `list` and `detail` routes.
|
||||
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
|
BIN
docs/img/bayer.png
Normal file
BIN
docs/img/bayer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docs/img/drf-openapi.png
Normal file
BIN
docs/img/drf-openapi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
@ -74,12 +74,11 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
||||
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||
<li><a href="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
|
||||
<li><a href="https://micropyramid.com/django-rest-framework-development-services/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/mp-text-logo.png)">MicroPyramid</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
|
||||
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
|
||||
|
||||
---
|
||||
|
||||
|
@ -88,7 +87,7 @@ continued development by **[signing up for a paid plan][funding]**.
|
|||
REST framework requires the following:
|
||||
|
||||
* Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
|
||||
* Django (1.8, 1.9, 1.10, 1.11)
|
||||
* Django (1.10, 1.11, 2.0 alpha)
|
||||
|
||||
The following packages are optional:
|
||||
|
||||
|
@ -121,10 +120,10 @@ 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', namespace='rest_framework'))
|
||||
url(r'^api-auth/', include('rest_framework.urls'))
|
||||
]
|
||||
|
||||
Note that the URL path can be whatever you want, but you must include `'rest_framework.urls'` with the `'rest_framework'` namespace. You may leave out the namespace in Django 1.9+, and REST framework will set it for you.
|
||||
Note that the URL path can be whatever you want.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -248,6 +247,7 @@ General guides to using REST framework.
|
|||
* [3.4 Announcement][3.4-announcement]
|
||||
* [3.5 Announcement][3.5-announcement]
|
||||
* [3.6 Announcement][3.6-announcement]
|
||||
* [3.7 Announcement][3.7-announcement]
|
||||
* [Kickstarter Announcement][kickstarter-announcement]
|
||||
* [Mozilla Grant][mozilla-grant]
|
||||
* [Funding][funding]
|
||||
|
@ -378,6 +378,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[3.4-announcement]: topics/3.4-announcement.md
|
||||
[3.5-announcement]: topics/3.5-announcement.md
|
||||
[3.6-announcement]: topics/3.6-announcement.md
|
||||
[3.7-announcement]: topics/3.7-announcement.md
|
||||
[kickstarter-announcement]: topics/kickstarter-announcement.md
|
||||
[mozilla-grant]: topics/mozilla-grant.md
|
||||
[funding]: topics/funding.md
|
||||
|
|
130
docs/topics/3.7-announcement.md
Normal file
130
docs/topics/3.7-announcement.md
Normal file
|
@ -0,0 +1,130 @@
|
|||
<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.7
|
||||
|
||||
The 3.7 release focuses on improvements to schema generation and the interactive API documentation.
|
||||
|
||||
This release has been made possible by [Bayer](https://www.bayer.com/) who have sponsored the release.
|
||||
|
||||
<a href="https://www.bayer.com/"><img src="/img/bayer.png"/></a>
|
||||
|
||||
---
|
||||
|
||||
## Funding
|
||||
|
||||
If you use REST framework commercially and would like to see this work continue, we strongly encourage you to invest in its continued development by
|
||||
**[signing up for a paid plan][funding]**.
|
||||
|
||||
<ul class="premium-promo promo">
|
||||
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
|
||||
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
|
||||
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
|
||||
<li><a href="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
|
||||
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
|
||||
</ul>
|
||||
<div style="clear: both; padding-bottom: 20px;"></div>
|
||||
|
||||
*As well as our release sponsor, we'd like to say thanks in particular our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
|
||||
|
||||
---
|
||||
|
||||
## Customizing API docs & schema generation.
|
||||
|
||||
The schema generation introduced in 3.5 and the related API docs generation in 3.6 are both hugely powerful features, however they've been somewhat limited in cases where the view introspection isn't able to correctly identify the schema for a particular view.
|
||||
|
||||
In order to try to address this we're now adding the ability for per-view customization of the API schema. The interface that we're adding for this allows either basic manual overrides over which fields should be included on a view, or for more complex programmatic overriding of the schema generation. We believe this release comprehensively addresses some of the existing shortcomings of the schema features.
|
||||
|
||||
Let's take a quick look at using the new functionality...
|
||||
|
||||
The `APIView` class has a `schema` attribute, that is used to control how the Schema for that particular view is generated. The default behaviour is to use the `AutoSchema` class.
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.schemas import AutoSchema
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = AutoSchema() # Included for demonstration only. This is the default behavior.
|
||||
|
||||
We can remove a view from the API schema and docs, like so:
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = None
|
||||
|
||||
If we want to mostly use the default behavior, but additionally include some additional fields on a particular view, we can now do so easily...
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = AutoSchema(manual_fields=[
|
||||
coreapi.Field('search', location='query')
|
||||
])
|
||||
|
||||
To ignore the automatic generation for a particular view, and instead specify the schema explicitly, we use the `ManualSchema` class instead...
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = ManualSchema(fields=[...])
|
||||
|
||||
For more advanced behaviors you can subclass `AutoSchema` to provide for customized schema generation, and apply that to particular views.
|
||||
|
||||
class CustomView(APIView):
|
||||
schema = CustomizedSchemaGeneration()
|
||||
|
||||
For full details on the new functionality, please see the [Schema Documentation][schema-docs].
|
||||
|
||||
---
|
||||
|
||||
## Django 2.0 support
|
||||
|
||||
REST framework 3.7 supports Django versions 1.10, 1.11, and 2.0 alpha.
|
||||
|
||||
---
|
||||
|
||||
## Minor fixes and improvements
|
||||
|
||||
There are a large number of minor fixes and improvements in this release. See the [release notes](release-notes.md) page for a complete listing.
|
||||
|
||||
The number of [open tickets against the project](https://github.com/encode/django-rest-framework/issues) currently at its lowest number in quite some time, and we're continuing to focus on reducing these to a manageable amount.
|
||||
|
||||
---
|
||||
|
||||
## Deprecations
|
||||
|
||||
### `exclude_from_schema`
|
||||
|
||||
Both `APIView.exclude_from_schema` and the `exclude_from_schema` argument to the `@api_view` decorator and now `PendingDeprecation`. They will be moved to deprecated in the 3.8 release, and removed entirely in 3.9.
|
||||
|
||||
For `APIView` you should instead set a `schema = None` attribute on the view class.
|
||||
|
||||
For function based views the `@schema` decorator can be used to exclude the view from the schema, by using `@schema(None)`.
|
||||
|
||||
### `DjangoFilterBackend`
|
||||
|
||||
The `DjangoFilterBackend` was moved to pending deprecation in 3.5, and deprecated in 3.6. It has now been removed from the core framework.
|
||||
|
||||
The functionality remains fully available, but is instead provided in the `django-filter` package.
|
||||
|
||||
---
|
||||
|
||||
## What's next
|
||||
|
||||
We're still planning to work on improving real-time support for REST framework by providing documentation on integrating with Django channels, as well adding support for more easily adding WebSocket support to existing HTTP endpoints.
|
||||
|
||||
This will likely be timed so that any REST framework development here ties in with similar work on [API Star][api-star].
|
||||
|
||||
[funding]: funding.md
|
||||
[schema-docs]: ../api-guide/schemas.md
|
||||
[api-star]: https://github.com/encode/apistar
|
|
@ -392,11 +392,11 @@ Once the API documentation URLs are installed, you'll be able to include both th
|
|||
<!--
|
||||
Load the CoreAPI library and the API schema.
|
||||
|
||||
/static/rest_framework/js/coreapi-0.1.0.js
|
||||
/static/rest_framework/js/coreapi-0.1.1.js
|
||||
/docs/schema.js
|
||||
-->
|
||||
{% load staticfiles %}
|
||||
<script src="{% static 'rest_framework/js/coreapi-0.1.0.js' %}"></script>
|
||||
<script src="{% static 'rest_framework/js/coreapi-0.1.1.js' %}"></script>
|
||||
<script src="{% url 'api-docs:schema-js' %}"></script>
|
||||
|
||||
The `coreapi` library, and the `schema` object will now both be available on the `window` instance.
|
||||
|
@ -408,7 +408,7 @@ The `coreapi` library, and the `schema` object will now both be available on the
|
|||
|
||||
In order to interact with the API you'll need a client instance.
|
||||
|
||||
var client = coreapi.Client()
|
||||
var client = new coreapi.Client()
|
||||
|
||||
Typically you'll also want to provide some authentication credentials when
|
||||
instantiating the client.
|
||||
|
@ -419,11 +419,11 @@ The `SessionAuthentication` class allows session cookies to provide the user
|
|||
authentication. You'll want to provide a standard HTML login flow, to allow
|
||||
the user to login, and then instantiate a client using session authentication:
|
||||
|
||||
let auth = coreapi.auth.SessionAuthentication({
|
||||
let auth = new coreapi.auth.SessionAuthentication({
|
||||
csrfCookieName: 'csrftoken',
|
||||
csrfHeaderName: 'X-CSRFToken'
|
||||
})
|
||||
let client = coreapi.Client({auth: auth})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
|
||||
The authentication scheme will handle including a CSRF header in any outgoing
|
||||
requests for unsafe HTTP methods.
|
||||
|
@ -433,11 +433,11 @@ requests for unsafe HTTP methods.
|
|||
The `TokenAuthentication` class can be used to support REST framework's built-in
|
||||
`TokenAuthentication`, as well as OAuth and JWT schemes.
|
||||
|
||||
let auth = coreapi.auth.TokenAuthentication({
|
||||
let auth = new coreapi.auth.TokenAuthentication({
|
||||
scheme: 'JWT'
|
||||
token: '<token>'
|
||||
})
|
||||
let client = coreapi.Client({auth: auth})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
|
||||
When using TokenAuthentication you'll probably need to implement a login flow
|
||||
using the CoreAPI client.
|
||||
|
@ -448,7 +448,7 @@ request to an "obtain token" endpoint
|
|||
For example, using the "Django REST framework JWT" package
|
||||
|
||||
// Setup some globally accessible state
|
||||
window.client = coreapi.Client()
|
||||
window.client = new coreapi.Client()
|
||||
window.loggedIn = false
|
||||
|
||||
function loginUser(username, password) {
|
||||
|
@ -471,11 +471,11 @@ For example, using the "Django REST framework JWT" package
|
|||
|
||||
The `BasicAuthentication` class can be used to support HTTP Basic Authentication.
|
||||
|
||||
let auth = coreapi.auth.BasicAuthentication({
|
||||
let auth = new coreapi.auth.BasicAuthentication({
|
||||
username: '<username>',
|
||||
password: '<password>'
|
||||
})
|
||||
let client = coreapi.Client({auth: auth})
|
||||
let client = new coreapi.Client({auth: auth})
|
||||
|
||||
## Using the client
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ Prior to version 3.3.0 the semi extension header `X-HTTP-Method-Override` was su
|
|||
For example:
|
||||
|
||||
METHOD_OVERRIDE_HEADER = 'HTTP_X_HTTP_METHOD_OVERRIDE'
|
||||
|
||||
|
||||
class MethodOverrideMiddleware(object):
|
||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||
if request.method != 'POST':
|
||||
|
|
|
@ -34,6 +34,27 @@ This will include two different views:
|
|||
* `/docs/` - The documentation page itself.
|
||||
* `/docs/schema.js` - A JavaScript resource that exposes the API schema.
|
||||
|
||||
---
|
||||
|
||||
**Note**: By default `include_docs_urls` configures the underlying `SchemaView` to generate _public_ schemas.
|
||||
This means that views will not be instantiated with a `request` instance. i.e. Inside the view `self.request` will be `None`.
|
||||
|
||||
To be compatible with this behaviour methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case.
|
||||
|
||||
You may ensure views are given a `request` instance by calling `include_docs_urls` with `public=False`:
|
||||
|
||||
from rest_framework.documentation import include_docs_urls
|
||||
|
||||
urlpatterns = [
|
||||
...
|
||||
# Generate schema with valid `request` instance:
|
||||
url(r'^docs/', include_docs_urls(title='My API title', public=False))
|
||||
]
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Documenting your views
|
||||
|
||||
You can document your views by including docstrings that describe each of the available actions.
|
||||
|
@ -42,7 +63,7 @@ For example:
|
|||
class UserList(generics.ListAPIView):
|
||||
"""
|
||||
Return a list of all the existing users.
|
||||
""""
|
||||
"""
|
||||
|
||||
If a view supports multiple methods, you should split your documentation using `method:` style delimiters.
|
||||
|
||||
|
@ -69,12 +90,69 @@ When using viewsets, you should use the relevant action names as delimiters.
|
|||
Create a new user instance.
|
||||
"""
|
||||
|
||||
|
||||
### `documentation` API Reference
|
||||
|
||||
The `rest_framework.documentation` module provides three helper functions to help configure the interactive API documentation, `include_docs_url` (usage shown above), `get_docs_view` and `get_schemajs_view`.
|
||||
|
||||
`include_docs_url` employs `get_docs_view` and `get_schemajs_view` to generate the url patterns for the documentation page and JavaScript resource that exposes the API schema respectively. They expose the following options for customisation. (`get_docs_view` and `get_schemajs_view` ultimately call `rest_frameworks.schemas.get_schema_view()`, see the Schemas docs for more options there.)
|
||||
|
||||
#### `include_docs_url`
|
||||
|
||||
* `title`: Default `None`. May be used to provide a descriptive title for the schema definition.
|
||||
* `description`: Default `None`. May be used to provide a description for the schema definition.
|
||||
* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema.
|
||||
* `public`: Default `True`. Should the schema be considered _public_? If `True` schema is generated without a `request` instance being passed to views.
|
||||
* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used.
|
||||
* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`.
|
||||
* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`.
|
||||
* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES` May be used to pass custom permission classes to the `SchemaView`.
|
||||
|
||||
#### `get_docs_view`
|
||||
|
||||
* `title`: Default `None`. May be used to provide a descriptive title for the schema definition.
|
||||
* `description`: Default `None`. May be used to provide a description for the schema definition.
|
||||
* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema.
|
||||
* `public`: Default `True`. If `True` schema is generated without a `request` instance being passed to views.
|
||||
* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used.
|
||||
* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`.
|
||||
* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`.
|
||||
* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES` May be used to pass custom permission classes to the `SchemaView`.
|
||||
|
||||
#### `get_schemajs_view`
|
||||
|
||||
* `title`: Default `None`. May be used to provide a descriptive title for the schema definition.
|
||||
* `description`: Default `None`. May be used to provide a description for the schema definition.
|
||||
* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema.
|
||||
* `public`: Default `True`. If `True` schema is generated without a `request` instance being passed to views.
|
||||
* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used.
|
||||
* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`.
|
||||
* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`.
|
||||
* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES` May be used to pass custom permission classes to the `SchemaView`.
|
||||
|
||||
---
|
||||
|
||||
## Third party packages
|
||||
|
||||
There are a number of mature third-party packages for providing API documentation.
|
||||
|
||||
#### DRF OpenAPI
|
||||
|
||||
[DRF OpenAPI][drf-openapi] bridges the gap between OpenAPI specification and tool chain with the schema exposed
|
||||
out-of-the-box by Django Rest Framework. Its goals are:
|
||||
|
||||
* To be dropped into any existing DRF project without any code change necessary.
|
||||
* Provide clear disctinction between request schema and response schema.
|
||||
* Provide a versioning mechanism for each schema. Support defining schema by version range syntax, e.g. >1.0, <=2.0
|
||||
* Support multiple response codes, not just 200
|
||||
* All this information should be bound to view methods, not view classes.
|
||||
|
||||
It also tries to stay current with the maturing schema generation mechanism provided by DRF.
|
||||
|
||||
![Screenshot - DRF OpenAPI][image-drf-openapi]
|
||||
|
||||
---
|
||||
|
||||
#### DRF Docs
|
||||
|
||||
[DRF Docs][drfdocs-repo] allows you to document Web APIs made with Django REST Framework and it is authored by Emmanouil Konstantinidis. It's made to work out of the box and its setup should not take more than a couple of minutes. Complete documentation can be found on the [website][drfdocs-website] while there is also a [demo][drfdocs-demo] available for people to see what it looks like. **Live API Endpoints** allow you to utilize the endpoints from within the documentation in a neat way.
|
||||
|
@ -197,6 +275,8 @@ In this approach, rather than documenting the available API endpoints up front,
|
|||
To implement a hypermedia API you'll need to decide on an appropriate media type for the API, and implement a custom renderer and parser for that media type. The [REST, Hypermedia & HATEOAS][hypermedia-docs] section of the documentation includes pointers to background reading, as well as links to various hypermedia formats.
|
||||
|
||||
[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||
[drf-openapi]: https://github.com/limdauto/drf_openapi/
|
||||
[image-drf-openapi]: ../img/drf-openapi.png
|
||||
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
|
||||
[drfdocs-website]: http://www.drfdocs.com/
|
||||
[drfdocs-demo]: http://demo.drfdocs.com/
|
||||
|
@ -211,4 +291,4 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
|
|||
[image-django-rest-swagger]: ../img/django-rest-swagger.png
|
||||
[image-apiary]: ../img/apiary.png
|
||||
[image-self-describing-api]: ../img/self-describing.png
|
||||
[schemas-examples]: ../api-guide/schemas/#examples
|
||||
[schemas-examples]: ../api-guide/schemas/#example
|
||||
|
|
|
@ -1,386 +1,397 @@
|
|||
<script>
|
||||
// Imperfect, but easier to fit in with the existing docs build.
|
||||
// Hyperlinks should point directly to the "fund." subdomain, but this'll
|
||||
// handle the nav bar links without requiring any docs build changes for the moment.
|
||||
if (window.location.hostname == "www.django-rest-framework.org") {
|
||||
window.location.replace("https://fund.django-rest-framework.org/topics/funding/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.promo li a {
|
||||
float: left;
|
||||
width: 130px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
margin: 10px 30px;
|
||||
padding: 150px 0 0 0;
|
||||
background-position: 0 50%;
|
||||
background-size: 130px auto;
|
||||
background-repeat: no-repeat;
|
||||
font-size: 120%;
|
||||
color: black;
|
||||
}
|
||||
.promo li {
|
||||
list-style: none;
|
||||
}
|
||||
.chart {
|
||||
background-color: #e3e3e3;
|
||||
background: -webkit-linear-gradient(top, #fff 0, #e3e3e3 100%);
|
||||
border: 1px solid #E6E6E6;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 2px 0px rgba(181, 181, 181, 0.3);
|
||||
padding: 40px 0px 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 97%;
|
||||
min-height: 255px;
|
||||
position: relative;
|
||||
top: 37px;
|
||||
margin-bottom: 20px
}
|
||||
.quantity {
|
||||
text-align: center
}
|
||||
.dollar {
|
||||
font-size: 19px;
|
||||
position: relative;
|
||||
top: -18px;
|
||||
}
|
||||
.price {
|
||||
font-size: 49px;
}
|
||||
.period {
|
||||
font-size: 17px;
|
||||
position: relative;
|
||||
top: -8px;
|
||||
margin-left: 4px;
}
|
||||
.plan-name {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: #777;
|
||||
border-bottom: 1px solid #d5d5d5;
|
||||
padding-bottom: 15px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
margin-top: 8px;
}
|
||||
.specs {
|
||||
margin-top: 20px;
min-height: 130px;
|
||||
}
|
||||
.specs.freelancer {
|
||||
min-height: 0px;
|
||||
}
|
||||
.spec {
|
||||
font-size: 15px;
|
||||
color: #474747;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
margin-bottom: 13px;
}
|
||||
.variable {
|
||||
color: #1FBEE7;
|
||||
font-weight: 400;
}
|
||||
form.signup {
|
||||
margin-top: 35px
}
|
||||
.clear-promo {
|
||||
padding-top: 30px
}
|
||||
#main-content h1:first-of-type {
|
||||
margin: 0 0 50px;
|
||||
font-size: 60px;
|
||||
font-weight: 200;
|
||||
text-align: center
|
||||
<script>
|
||||
// Imperfect, but easier to fit in with the existing docs build.
|
||||
// Hyperlinks should point directly to the "fund." subdomain, but this'll
|
||||
// handle the nav bar links without requiring any docs build changes for the moment.
|
||||
if (window.location.hostname == "www.django-rest-framework.org") {
|
||||
window.location.replace("https://fund.django-rest-framework.org/topics/funding/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.promo li a {
|
||||
float: left;
|
||||
width: 130px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
margin: 10px 30px;
|
||||
padding: 150px 0 0 0;
|
||||
background-position: 0 50%;
|
||||
background-size: 130px auto;
|
||||
background-repeat: no-repeat;
|
||||
font-size: 120%;
|
||||
color: black;
|
||||
}
|
||||
.promo li {
|
||||
list-style: none;
|
||||
}
|
||||
.chart {
|
||||
background-color: #e3e3e3;
|
||||
background: -webkit-linear-gradient(top, #fff 0, #e3e3e3 100%);
|
||||
border: 1px solid #E6E6E6;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 2px 0px rgba(181, 181, 181, 0.3);
|
||||
padding: 40px 0px 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 97%;
|
||||
min-height: 255px;
|
||||
position: relative;
|
||||
top: 37px;
|
||||
margin-bottom: 20px
|
||||
}
|
||||
.quantity {
|
||||
text-align: center
|
||||
}
|
||||
.dollar {
|
||||
font-size: 19px;
|
||||
position: relative;
|
||||
top: -18px;
|
||||
}
|
||||
.price {
|
||||
font-size: 49px;
|
||||
}
|
||||
.period {
|
||||
font-size: 17px;
|
||||
position: relative;
|
||||
top: -8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.plan-name {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: #777;
|
||||
border-bottom: 1px solid #d5d5d5;
|
||||
padding-bottom: 15px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.specs {
|
||||
margin-top: 20px;
|
||||
min-height: 130px;
|
||||
}
|
||||
.specs.freelancer {
|
||||
min-height: 0px;
|
||||
}
|
||||
.spec {
|
||||
font-size: 15px;
|
||||
color: #474747;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
.variable {
|
||||
color: #1FBEE7;
|
||||
font-weight: 400;
|
||||
}
|
||||
form.signup {
|
||||
margin-top: 35px
|
||||
}
|
||||
.clear-promo {
|
||||
padding-top: 30px
|
||||
}
|
||||
#main-content h1:first-of-type {
|
||||
margin: 0 0 50px;
|
||||
font-size: 60px;
|
||||
font-weight: 200;
|
||||
text-align: center
|
||||
}
|
||||
#main-content {
|
||||
padding-top: 10px; line-height: 23px
|
||||
}
|
||||
#main-content {
|
||||
padding-top: 10px; line-height: 23px
|
||||
}
|
||||
#main-content li {
|
||||
line-height: 23px
|
||||
}
|
||||
</style>
|
||||
|
||||
# Funding
|
||||
|
||||
If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
|
||||
|
||||
**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
|
||||
|
||||
Signing up for a paid plan will:
|
||||
|
||||
* Directly contribute to faster releases, more features, and higher quality software.
|
||||
* Allow more time to be invested in documentation, issue triage, and community support.
|
||||
* Safeguard the future development of REST framework.
|
||||
|
||||
REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
|
||||
|
||||
---
|
||||
|
||||
## What funding has enabled so far
|
||||
|
||||
* The [3.4](http://www.django-rest-framework.org/topics/3.4-announcement/) and [3.5](http://www.django-rest-framework.org/topics/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
|
||||
* The [3.6](http://www.django-rest-framework.org/topics/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
|
||||
* Tom Christie, the creator of Django REST framework, working on the project full-time.
|
||||
* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
|
||||
* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
|
||||
* Contracting development time for the work on the JavaScript client library and API documentation tooling.
|
||||
|
||||
---
|
||||
|
||||
## What future funding will enable
|
||||
|
||||
* Realtime API support, using WebSockets. This will consist of documentation and support for using REST framework together with Django Channels, plus integrating WebSocket support into the client libraries.
|
||||
* Better authentication defaults, possibly bringing JWT & CORs support into the core package.
|
||||
* Securing the community & operations manager position long-term.
|
||||
* Opening up and securing a part-time position to focus on ticket triage and resolution.
|
||||
* Paying for development time on building API client libraries in a range of programming languages. These would be integrated directly into the upcoming API documentation.
|
||||
|
||||
Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
|
||||
|
||||
---
|
||||
|
||||
## What our sponsors and users say
|
||||
|
||||
> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
|
||||
>
|
||||
> — José Padilla, Django REST framework contributor
|
||||
|
||||
|
||||
|
||||
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
|
||||
>
|
||||
> — Filipe Ximenes, Vinta Software
|
||||
|
||||
|
||||
|
||||
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
|
||||
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
|
||||
>
|
||||
> — Andrew Conti, Django REST framework user
|
||||
|
||||
---
|
||||
|
||||
## Individual plan
|
||||
|
||||
This subscription is recommended for individuals with an interest in seeing REST framework continue to improve.
|
||||
|
||||
If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate plan](#corporate-plans).
|
||||
|
||||
<div class="pricing">
|
||||
<div class="span4">
|
||||
<div class="chart first">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.personal1 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Individual</div>
|
||||
<div class="specs freelancer">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
Credited on the site
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.personal1 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.personal1 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Individual"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both; padding-top: 50px"></div>
|
||||
|
||||
*Billing is monthly and you can cancel at any time.*
|
||||
|
||||
---
|
||||
|
||||
## Corporate plans
|
||||
|
||||
These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
|
||||
|
||||
In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
|
||||
|
||||
Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
|
||||
|
||||
<div class="pricing">
|
||||
<div class="span4">
|
||||
<div class="chart first">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate1 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Basic</div>
|
||||
<div class="specs startup">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Funding page</span> ad placement
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate1 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate1 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Basic"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="chart">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate2 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Professional</div>
|
||||
<div class="specs">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Sidebar</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Priority support</span> for your engineers
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate2 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate2 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Professional"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="chart last">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate3 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Premium</div>
|
||||
<div class="specs">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Homepage</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Sidebar</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Priority support</span> for your engineers
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate3 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate3 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Premium"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="clear: both; padding-top: 50px"></div>
|
||||
|
||||
*Billing is monthly and you can cancel at any time.*
|
||||
|
||||
Once you've signed up, we will contact you via email and arrange your ad placements on the site.
|
||||
|
||||
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
|
||||
|
||||
---
|
||||
|
||||
## Accountability
|
||||
|
||||
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/june-2017) and regularly include financial reports and cost breakdowns.
|
||||
|
||||
<!-- Begin MailChimp Signup Form -->
|
||||
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
|
||||
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
|
||||
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
|
||||
</style>
|
||||
<div id="mc_embed_signup">
|
||||
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
|
||||
<div id="mc_embed_signup_scroll">
|
||||
<h2>Stay up to date, with our monthly progress reports...</h2>
|
||||
<div class="mc-field-group">
|
||||
<label for="mce-EMAIL">Email Address </label>
|
||||
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
|
||||
</div>
|
||||
<div id="mce-responses" class="clear">
|
||||
<div class="response" id="mce-error-response" style="display:none"></div>
|
||||
<div class="response" id="mce-success-response" style="display:none"></div>
|
||||
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
|
||||
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
|
||||
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
|
||||
<!--End mc_embed_signup-->
|
||||
|
||||
---
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
**Q: Can you issue monthly invoices?**
|
||||
A: Yes, we are happy to issue monthly invoices. Please just <a href=mailto:funding@django-rest-framework.org>email us</a> and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
|
||||
|
||||
**Q: Does sponsorship include VAT?**
|
||||
A: Sponsorship is VAT exempt.
|
||||
|
||||
**Q: Do I have to sign up for a certain time period?**
|
||||
A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
|
||||
|
||||
**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
|
||||
A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
|
||||
|
||||
**Q: Are you only looking for corporate sponsors?**
|
||||
A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
|
||||
|
||||
---
|
||||
|
||||
## Our sponsors
|
||||
|
||||
<div id="fundingInclude"></div>
|
||||
|
||||
<script src="https://fund.django-rest-framework.org/funding_include.js"></script>
|
||||
</style>
|
||||
|
||||
# Funding
|
||||
|
||||
If you use REST framework commercially we strongly encourage you to invest in its continued development by signing up for a paid plan.
|
||||
|
||||
**We believe that collaboratively funded software can offer outstanding returns on investment, by encouraging our users to collectively share the cost of development.**
|
||||
|
||||
Signing up for a paid plan will:
|
||||
|
||||
* Directly contribute to faster releases, more features, and higher quality software.
|
||||
* Allow more time to be invested in documentation, issue triage, and community support.
|
||||
* Safeguard the future development of REST framework.
|
||||
|
||||
REST framework continues to be open-source and permissively licensed, but we firmly believe it is in the commercial best-interest for users of the project to invest in its ongoing development.
|
||||
|
||||
---
|
||||
|
||||
## What funding has enabled so far
|
||||
|
||||
* The [3.4](http://www.django-rest-framework.org/topics/3.4-announcement/) and [3.5](http://www.django-rest-framework.org/topics/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
|
||||
* The [3.6](http://www.django-rest-framework.org/topics/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
|
||||
* The recent [3.7 release](http://www.django-rest-framework.org/topics/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
|
||||
* Tom Christie, the creator of Django REST framework, working on the project full-time.
|
||||
* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
|
||||
* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
|
||||
* Contracting development time for the work on the JavaScript client library and API documentation tooling.
|
||||
|
||||
---
|
||||
|
||||
## What future funding will enable
|
||||
|
||||
* Realtime API support, using WebSockets. This will consist of documentation and support for using REST framework together with Django Channels, plus integrating WebSocket support into the client libraries.
|
||||
* Better authentication defaults, possibly bringing JWT & CORs support into the core package.
|
||||
* Securing the community & operations manager position long-term.
|
||||
* Opening up and securing a part-time position to focus on ticket triage and resolution.
|
||||
* Paying for development time on building API client libraries in a range of programming languages. These would be integrated directly into the upcoming API documentation.
|
||||
|
||||
Sign up for a paid plan today, and help ensure that REST framework becomes a sustainable, full-time funded project.
|
||||
|
||||
---
|
||||
|
||||
## What our sponsors and users say
|
||||
|
||||
> As a developer, Django REST framework feels like an obvious and natural extension to all the great things that make up Django and it's community. Getting started is easy while providing simple abstractions which makes it flexible and customizable. Contributing and supporting Django REST framework helps ensure its future and one way or another it also helps Django, and the Python ecosystem.
|
||||
>
|
||||
> — José Padilla, Django REST framework contributor
|
||||
|
||||
|
||||
|
||||
> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it.
|
||||
>
|
||||
> — Filipe Ximenes, Vinta Software
|
||||
|
||||
|
||||
|
||||
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
|
||||
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
|
||||
>
|
||||
> — Andrew Conti, Django REST framework user
|
||||
|
||||
---
|
||||
|
||||
## Individual plan
|
||||
|
||||
This subscription is recommended for individuals with an interest in seeing REST framework continue to improve.
|
||||
|
||||
If you are using REST framework as a full-time employee, consider recommending that your company takes out a [corporate plan](#corporate-plans).
|
||||
|
||||
<div class="pricing">
|
||||
<div class="span4">
|
||||
<div class="chart first">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.personal1 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Individual</div>
|
||||
<div class="specs freelancer">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
Credited on the site
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.personal1 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.personal1 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Individual"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: both; padding-top: 50px"></div>
|
||||
|
||||
*Billing is monthly and you can cancel at any time.*
|
||||
|
||||
---
|
||||
|
||||
## Corporate plans
|
||||
|
||||
These subscriptions are recommended for companies and organizations using REST framework either publicly or privately.
|
||||
|
||||
In exchange for funding you'll also receive advertising space on our site, allowing you to **promote your company or product to many tens of thousands of developers worldwide**.
|
||||
|
||||
Our professional and premium plans also include **priority support**. At any time your engineers can escalate an issue or discussion group thread, and we'll ensure it gets a guaranteed response within the next working day.
|
||||
|
||||
<div class="pricing">
|
||||
<div class="span4">
|
||||
<div class="chart first">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate1 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Basic</div>
|
||||
<div class="specs startup">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Funding page</span> ad placement
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate1 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate1 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Basic"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="chart">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate2 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Professional</div>
|
||||
<div class="specs">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Sidebar</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Priority support</span> for your engineers
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate2 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate2 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Professional"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span4">
|
||||
<div class="chart last">
|
||||
<div class="quantity">
|
||||
<span class="dollar">{{ symbol }}</span>
|
||||
<span class="price">{{ rates.corporate3 }}</span>
|
||||
<span class="period">/month{% if vat %} +VAT{% endif %}</span>
|
||||
</div>
|
||||
<div class="plan-name">Premium</div>
|
||||
<div class="specs">
|
||||
<div class="spec">
|
||||
Support ongoing development
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Homepage</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Sidebar</span> ad placement
|
||||
</div>
|
||||
<div class="spec">
|
||||
<span class="variable">Priority support</span> for your engineers
|
||||
</div>
|
||||
</div>
|
||||
<form class="signup" action="/signup/{{ currency }}-{{ rates.corporate3 }}/" method="POST">
|
||||
<script
|
||||
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
||||
data-key="{{ stripe_public }}"
|
||||
data-amount="{{ stripe_amounts.corporate3 }}"
|
||||
data-name="Django REST framework"
|
||||
data-description="Premium"
|
||||
data-currency="{{ currency }}"
|
||||
data-allow-remember-me=false
|
||||
data-billing-address=true
|
||||
data-label='Sign up'
|
||||
data-panel-label='Sign up - {% verbatim %}{{amount}}{% endverbatim %}/mo'>
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="clear: both; padding-top: 50px"></div>
|
||||
|
||||
*Billing is monthly and you can cancel at any time.*
|
||||
|
||||
Once you've signed up, we will contact you via email and arrange your ad placements on the site.
|
||||
|
||||
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
|
||||
|
||||
---
|
||||
|
||||
## Accountability
|
||||
|
||||
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/november-2017) and regularly include financial reports and cost breakdowns.
|
||||
|
||||
<!-- Begin MailChimp Signup Form -->
|
||||
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
|
||||
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
|
||||
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
|
||||
</style>
|
||||
<div id="mc_embed_signup">
|
||||
<form action="//encode.us13.list-manage.com/subscribe/post?u=b6b66bb5e4c7cb484a85c8dd7&id=e382ef68ef" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
|
||||
<div id="mc_embed_signup_scroll">
|
||||
<h2>Stay up to date, with our monthly progress reports...</h2>
|
||||
<div class="mc-field-group">
|
||||
<label for="mce-EMAIL">Email Address </label>
|
||||
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
|
||||
</div>
|
||||
<div id="mce-responses" class="clear">
|
||||
<div class="response" id="mce-error-response" style="display:none"></div>
|
||||
<div class="response" id="mce-success-response" style="display:none"></div>
|
||||
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
|
||||
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_b6b66bb5e4c7cb484a85c8dd7_e382ef68ef" tabindex="-1" value=""></div>
|
||||
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
|
||||
<!--End mc_embed_signup-->
|
||||
|
||||
---
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
**Q: Can you issue monthly invoices?**
|
||||
A: Yes, we are happy to issue monthly invoices. Please just <a href=mailto:funding@django-rest-framework.org>email us</a> and let us know who to issue the invoice to (name and address) and which email address to send it to each month.
|
||||
|
||||
**Q: Does sponsorship include VAT?**
|
||||
A: Sponsorship is VAT exempt.
|
||||
|
||||
**Q: Do I have to sign up for a certain time period?**
|
||||
A: No, we appreciate your support for any time period that is convenient for you. Also, you can cancel your sponsorship anytime.
|
||||
|
||||
**Q: Can I pay yearly? Can I pay upfront fox X amount of months at a time?**
|
||||
A: We are currently only set up to accept monthly payments. However, if you'd like to support Django REST framework and you can only do yearly/upfront payments, we are happy to work with you and figure out a convenient solution.
|
||||
|
||||
**Q: Are you only looking for corporate sponsors?**
|
||||
A: No, we value individual sponsors just as much as corporate sponsors and appreciate any kind of support.
|
||||
|
||||
---
|
||||
|
||||
## Our sponsors
|
||||
|
||||
<div id="fundingInclude"></div>
|
||||
|
||||
<script src="https://fund.django-rest-framework.org/funding_include.js"></script>
|
||||
|
|
|
@ -40,7 +40,7 @@ Here's an example of a view that returns a list of "Profile" instances, rendered
|
|||
{% endfor %}
|
||||
</ul>
|
||||
</body></html>
|
||||
|
||||
|
||||
## Rendering Forms
|
||||
|
||||
Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
|
||||
|
@ -77,7 +77,7 @@ The following view demonstrates an example of using a serializer in a template f
|
|||
{% load rest_framework %}
|
||||
|
||||
<html><body>
|
||||
|
||||
|
||||
<h1>Profile - {{ profile.name }}</h1>
|
||||
|
||||
<form action="{% url 'profile-detail' pk=profile.pk %}" method="POST">
|
||||
|
|
|
@ -32,7 +32,7 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram
|
|||
[stackoverflow-com]: http://stackoverflow.com/jobs/developer-jobs-using-django
|
||||
[upwork-com]: https://www.upwork.com/o/jobs/browse/skill/django-framework/
|
||||
[technobjobs-co-uk]: https://www.technojobs.co.uk/django-jobs
|
||||
[remoteok-io]: https://remoteok.io/remote-django-jobs
|
||||
[remoteok-io]: https://remoteok.io/remote-django-jobs
|
||||
[remotepython-com]: https://www.remotepython.com/jobs/
|
||||
[drf-funding]: https://fund.django-rest-framework.org/topics/funding/
|
||||
[submit-pr]: https://github.com/encode/django-rest-framework
|
||||
|
|
|
@ -38,8 +38,169 @@ You can determine your currently installed version using `pip freeze`:
|
|||
|
||||
---
|
||||
|
||||
## 3.7.x series
|
||||
|
||||
### 3.7.4
|
||||
|
||||
**Date**: UNRELEASED
|
||||
|
||||
* Extract method for `manual_fields` processing [#5633][gh5633]
|
||||
|
||||
Allows for easier customisation of `manual_fields` processing, for example
|
||||
to provide per-method manual fields. `AutoSchema` adds `get_manual_fields`,
|
||||
as the intended override point, and a utility method `update_fields`, to
|
||||
handle by-name field replacement from a list, which, in general, you are not
|
||||
expected to override.
|
||||
|
||||
Note: `AutoSchema.__init__` now ensures `manual_fields` is a list.
|
||||
Previously may have been stored internally as `None`.
|
||||
|
||||
|
||||
[gh5633]: https://github.com/encode/django-rest-framework/issues/5633
|
||||
|
||||
|
||||
|
||||
### 3.7.3
|
||||
|
||||
**Date**: [6th November 2017][3.7.3-milestone]
|
||||
|
||||
* Fix `AppRegistryNotReady` error from contrib.auth view imports [#5567][gh5567]
|
||||
|
||||
|
||||
### 3.7.2
|
||||
|
||||
**Date**: [6th November 2017][3.7.2-milestone]
|
||||
|
||||
* Fixed Django 2.1 compatibility due to removal of django.contrib.auth.login()/logout() views. [#5510][gh5510]
|
||||
* Add missing import for TextLexer. [#5512][gh5512]
|
||||
* Adding examples and documentation for caching [#5514][gh5514]
|
||||
* Include date and date-time format for schema generation [#5511][gh5511]
|
||||
* Use triple backticks for markdown code blocks [#5513][gh5513]
|
||||
* Interactive docs - make bottom sidebar items sticky [#5516][gh5516]
|
||||
* Clarify pagination system check [#5524][gh5524]
|
||||
* Stop JSONBoundField mangling invalid JSON [#5527][gh5527]
|
||||
* Have JSONField render as textarea in Browsable API [#5530][gh5530]
|
||||
* Schema: Exclude OPTIONS/HEAD for ViewSet actions [#5532][gh5532]
|
||||
* Fix ordering for dotted sources [#5533][gh5533]
|
||||
* Fix: Fields with `allow_null=True` should imply a default serialization value [#5518][gh5518]
|
||||
* Ensure Location header is strictly a 'str', not subclass. [#5544][gh5544]
|
||||
* Add import to example in api-guide/parsers [#5547][gh5547]
|
||||
* Catch OverflowError for "out of range" datetimes [#5546][gh5546]
|
||||
* Add djangorestframework-rapidjson to third party packages [#5549][gh5549]
|
||||
* Increase test coverage for `drf_create_token` command [#5550][gh5550]
|
||||
* Add trove classifier for Python 3.6 support. [#5555][gh5555]
|
||||
* Add pip cache support to the Travis CI configuration [#5556][gh5556]
|
||||
* Rename [`wheel`] section to [`bdist_wheel`] as the former is legacy [#5557][gh5557]
|
||||
* Fix invalid escape sequence deprecation warnings [#5560][gh5560]
|
||||
* Add interactive docs error template [#5548][gh5548]
|
||||
* Add rounding parameter to DecimalField [#5562][gh5562]
|
||||
* Fix all BytesWarning caught during tests [#5561][gh5561]
|
||||
* Use dict and set literals instead of calls to dict() and set() [#5559][gh5559]
|
||||
* Change ImageField validation pattern, use validators from DjangoImageField [#5539][gh5539]
|
||||
* Fix processing unicode symbols in query_string by Python 2 [#5552][gh5552]
|
||||
|
||||
|
||||
### 3.7.1
|
||||
|
||||
**Date**: [16th October 2017][3.7.1-milestone]
|
||||
|
||||
* Fix Interactive documentation always uses false for boolean fields in requests [#5492][gh5492]
|
||||
* Improve compatibility with Django 2.0 alpha. [#5500][gh5500] [#5503][gh5503]
|
||||
* Improved handling of schema naming collisions [#5486][gh5486]
|
||||
* Added additional docs and tests around providing a default value for dotted `source` fields [#5489][gh5489]
|
||||
|
||||
|
||||
### 3.7.0
|
||||
|
||||
**Date**: [6th October 2017][3.7.0-milestone]
|
||||
|
||||
* Fix `DjangoModelPermissions` to ensure user authentication before calling the view's `get_queryset()` method. As a side effect, this changes the order of the HTTP method permissions and authentication checks, and 405 responses will only be returned when authenticated. If you want to replicate the old behavior, see the PR for details. [#5376][gh5376]
|
||||
* 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]
|
||||
|
||||
**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.
|
||||
|
||||
* Removed DjangoFilterBackend inline with deprecation policy. Use `django_filters.rest_framework.FilterSet` and/or `django_filters.rest_framework.DjangoFilterBackend` instead. [#5273][gh5273]
|
||||
* Don't strip microseconds from `time` when encoding. Makes consistent with `datetime`.
|
||||
**BC Change**: Previously only milliseconds were encoded. [#5440][gh5440]
|
||||
* Added `STRICT_JSON` setting (default `True`) to raise exception for the extended float values (`nan`, `inf`, `-inf`) accepted by Python's `json` module.
|
||||
**BC Change**: Previously these values would converted to corresponding strings. Set `STRICT_JSON` to `False` to restore the previous behaviour. [#5265][gh5265]
|
||||
* Add support for `page_size` parameter in CursorPaginator class [#5250][gh5250]
|
||||
* Make `DEFAULT_PAGINATION_CLASS` `None` by default.
|
||||
**BC Change**: If your were **just** setting `PAGE_SIZE` to enable pagination you will need to add `DEFAULT_PAGINATION_CLASS`.
|
||||
The previous default was `rest_framework.pagination.PageNumberPagination`. There is a system check warning to catch this case. You may silence that if you are setting pagination class on a per-view basis. [#5170][gh5170]
|
||||
* Catch `APIException` from `get_serializer_fields` in schema generation. [#5443][gh5443]
|
||||
* Allow custom authentication and permission classes when using `include_docs_urls` [#5448][gh5448]
|
||||
* Defer translated string evaluation on validators. [#5452][gh5452]
|
||||
* Added default value for 'detail' param into 'ValidationError' exception [#5342][gh5342]
|
||||
* Adjust schema get_filter_fields rules to match framework [#5454][gh5454]
|
||||
* Updated test matrix to add Django 2.0 and drop Django 1.8 & 1.9
|
||||
**BC Change**: This removes Django 1.8 and Django 1.9 from Django REST Framework supported versions. [#5457][gh5457]
|
||||
* Fixed a deprecation warning in serializers.ModelField [#5058][gh5058]
|
||||
* Added a more explicit error message when `get_queryset` returned `None` [#5348][gh5348]
|
||||
* Fix docs for Response `data` description [#5361][gh5361]
|
||||
* Fix __pycache__/.pyc excludes when packaging [#5373][gh5373]
|
||||
* Fix default value handling for dotted sources [#5375][gh5375]
|
||||
* Ensure content_type is set when passing empty body to RequestFactory [#5351][gh5351]
|
||||
* Fix ErrorDetail Documentation [#5380][gh5380]
|
||||
* Allow optional content in the generic content form [#5372][gh5372]
|
||||
* Updated supported values for the NullBooleanField [#5387][gh5387]
|
||||
* Fix ModelSerializer custom named fields with source on model [#5388][gh5388]
|
||||
* Fixed the MultipleFieldLookupMixin documentation example to properly check for object level permission [#5398][gh5398]
|
||||
* Update get_object() example in permissions.md [#5401][gh5401]
|
||||
* Fix authtoken management command [#5415][gh5415]
|
||||
* Fix schema generation markdown [#5421][gh5421]
|
||||
* Allow `ChoiceField.choices` to be set dynamically [#5426][gh5426]
|
||||
* Add the project layout to the quickstart [#5434][gh5434]
|
||||
* Reuse 'apply_markdown' function in 'render_markdown' templatetag [#5469][gh5469]
|
||||
* Added links to `drf-openapi` package in docs [#5470][gh5470]
|
||||
* Added docstrings code highlighting with pygments [#5462][gh5462]
|
||||
* Fixed documentation rendering for views named `data` [#5472][gh5472]
|
||||
* Docs: Clarified 'to_internal_value()' validation behavior [#5466][gh5466]
|
||||
* Fix missing six.text_type() call on APIException.__str__ [#5476][gh5476]
|
||||
* Document documentation.py [#5478][gh5478]
|
||||
* Fix naming collisions in Schema Generation [#5464][gh5464]
|
||||
* Call Django's authenticate function with the request object [#5295][gh5295]
|
||||
* Update coreapi JS to 0.1.1 [#5479][gh5479]
|
||||
* Have `is_list_view` recognise RetrieveModel… views [#5480][gh5480]
|
||||
* Remove Django 1.8 & 1.9 compatibility code [#5481][gh5481]
|
||||
* Remove deprecated schema code from DefaultRouter [#5482][gh5482]
|
||||
* Refactor schema generation to allow per-view customisation.
|
||||
**BC Change**: `SchemaGenerator.get_serializer_fields` has been refactored as `AutoSchema.get_serializer_fields` and drops the `view` argument [#5354][gh5354]
|
||||
|
||||
## 3.6.x series
|
||||
|
||||
### 3.6.4
|
||||
|
||||
**Date**: [21st August 2017][3.6.4-milestone]
|
||||
|
||||
* Ignore any invalidly formed query parameters for OrderingFilter. [#5131][gh5131]
|
||||
* Improve memory footprint when reading large JSON requests. [#5147][gh5147]
|
||||
* Fix schema generation for pagination. [#5161][gh5161]
|
||||
* Fix exception when `HTML_CUTOFF` is set to `None`. [#5174][gh5174]
|
||||
* Fix browsable API not supporting `multipart/form-data` correctly. [#5176][gh5176]
|
||||
* Fixed `test_hyperlinked_related_lookup_url_encoded_exists`. [#5179][gh5179]
|
||||
* Make sure max_length is in FileField kwargs. [#5186][gh5186]
|
||||
* Fix `list_route` & `detail_route` with kwargs contains curly bracket in `url_path` [#5187][gh5187]
|
||||
* Add Django manage command to create a DRF user Token. [#5188][gh5188]
|
||||
* Ensure API documentation templates do not check for user authentication [#5162][gh5162]
|
||||
* Fix special case where OneToOneField is also primary key. [#5192][gh5192]
|
||||
* Added aria-label and a new region for accessibility purposes in base.html [#5196][gh5196]
|
||||
* Quote nested API parameters in api.js. [#5214][gh5214]
|
||||
* Set ViewSet args/kwargs/request before dispatch. [#5229][gh5229]
|
||||
* Added unicode support to SlugField. [#5231][gh5231]
|
||||
* Fix HiddenField appears in Raw Data form initial content. [#5259][gh5259]
|
||||
* Raise validation error on invalid timezone parsing. [#5261][gh5261]
|
||||
* Fix SearchFilter to-many behavior/performance. [#5264][gh5264]
|
||||
* Simplified chained comparisons and minor code fixes. [#5276][gh5276]
|
||||
* RemoteUserAuthentication, docs, and tests. [#5306][gh5306]
|
||||
* Revert "Cached the field's root and context property" [#5313][gh5313]
|
||||
* Fix introspection of list field in schema. [#5326][gh5326]
|
||||
* Fix interactive docs for multiple nested and extra methods. [#5334][gh5334]
|
||||
* Fix/remove undefined template var "schema" [#5346][gh5346]
|
||||
|
||||
### 3.6.3
|
||||
|
||||
**Date**: [12th May 2017][3.6.3-milestone]
|
||||
|
@ -73,7 +234,7 @@ You can determine your currently installed version using `pip freeze`:
|
|||
|
||||
* Support for Safari & IE in API docs. ([#4959][gh4959], [#4961][gh4961])
|
||||
* Add missing `mark_safe` in API docs template tags. ([#4952][gh4952], [#4953][gh4953])
|
||||
* Add missing glyicon fonts. ([#4950][gh4950], [#4951][gh4951])
|
||||
* Add missing glyphicon fonts. ([#4950][gh4950], [#4951][gh4951])
|
||||
* Fix One-to-one fields in API docs. ([#4955][gh4955], [#4956][gh4956])
|
||||
* Test clean ups. ([#4949][gh4949])
|
||||
|
||||
|
@ -81,7 +242,7 @@ You can determine your currently installed version using `pip freeze`:
|
|||
|
||||
**Date**: [9th March 2017][3.6.1-milestone]
|
||||
|
||||
* Ensure `markdown` dependancy is optional. ([#4947][gh4947])
|
||||
* Ensure `markdown` dependency is optional. ([#4947][gh4947])
|
||||
|
||||
### 3.6.0
|
||||
|
||||
|
@ -206,7 +367,7 @@ See the [release announcement][3.6-release].
|
|||
|
||||
**Date**: [5th August 2016][3.4.3-milestone]
|
||||
|
||||
* Include fallaback for users of older TemplateHTMLRenderer internal API. ([#4361][gh4361])
|
||||
* Include fallback for users of older TemplateHTMLRenderer internal API. ([#4361][gh4361])
|
||||
|
||||
### 3.4.2
|
||||
|
||||
|
@ -305,7 +466,7 @@ See the [release announcement][3.6-release].
|
|||
* ListSerializer doesn't handle unique_together constraints. ([#3970][gh3970])
|
||||
* Add missing migration file. ([#3968][gh3968])
|
||||
* `OrderingFilter` should call `get_serializer_class()` to determine default fields. ([#3964][gh3964])
|
||||
* Remove old django checks from tests and compat. ([#3953][gh3953])
|
||||
* Remove old Django checks from tests and compat. ([#3953][gh3953])
|
||||
* Support callable as the value of `initial` for any `serializer.Field`. ([#3943][gh3943])
|
||||
* Prevented unnecessary distinct() call in SearchFilter. ([#3938][gh3938])
|
||||
* Fix None UUID ForeignKey serialization. ([#3936][gh3936])
|
||||
|
@ -489,7 +650,7 @@ See the [release announcement][3.6-release].
|
|||
* Fix behavior of `allow_blank=False` when used with `trim_whitespace=True`. ([#2712][gh2712])
|
||||
* Fix issue with some field combinations incorrectly mapping to an invalid `allow_blank` argument. ([#3011][gh3011])
|
||||
* Fix for output representations with prefetches and modified querysets. ([#2704][gh2704], [#2727][gh2727])
|
||||
* Fix assertion error when CursorPagination is provided with certains invalid query parameters. (#2920)[gh2920].
|
||||
* Fix assertion error when CursorPagination is provided with certain invalid query parameters. (#2920)[gh2920].
|
||||
* Fix `UnicodeDecodeError` when invalid characters included in header with `TokenAuthentication`. ([#2928][gh2928])
|
||||
* Fix transaction rollbacks with `@non_atomic_requests` decorator. ([#3016][gh3016])
|
||||
* Fix duplicate results issue with Oracle databases using `SearchFilter`. ([#2935][gh2935])
|
||||
|
@ -529,7 +690,7 @@ See the [release announcement][3.6-release].
|
|||
* Use default reason phrases from HTTP standard. ([#2764][gh2764], [#2763][gh2763])
|
||||
* Raise error when `ModelSerializer` used with abstract model. ([#2757][gh2757], [#2630][gh2630])
|
||||
* Handle reversal of non-API view_name in `HyperLinkedRelatedField` ([#2724][gh2724], [#2711][gh2711])
|
||||
* Dont require pk strictly for related fields. ([#2745][gh2745], [#2754][gh2754])
|
||||
* Don't require pk strictly for related fields. ([#2745][gh2745], [#2754][gh2754])
|
||||
* Metadata detects null boolean field type. ([#2762][gh2762])
|
||||
* Proper handling of depth in nested serializers. ([#2798][gh2798])
|
||||
* Display viewset without paginator. ([#2807][gh2807])
|
||||
|
@ -716,6 +877,12 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
|||
[3.6.1-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.1+Release%22
|
||||
[3.6.2-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.2+Release%22
|
||||
[3.6.3-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.3+Release%22
|
||||
[3.6.4-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.6.4+Release%22
|
||||
[3.7.0-milestone]: https://github.com/encode/django-rest-framework/issues?q=milestone%3A%223.7.0+Release%22
|
||||
[3.7.1-milestone]: https://github.com/encode/django-rest-framework/milestone/58?closed=1
|
||||
[3.7.2-milestone]: https://github.com/encode/django-rest-framework/milestone/59?closed=1
|
||||
[3.7.3-milestone]: https://github.com/encode/django-rest-framework/milestone/60?closed=1
|
||||
|
||||
|
||||
<!-- 3.0.1 -->
|
||||
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
|
||||
|
@ -1326,8 +1493,8 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
|||
[gh4955]: https://github.com/encode/django-rest-framework/issues/4955
|
||||
[gh4956]: https://github.com/encode/django-rest-framework/issues/4956
|
||||
[gh4949]: https://github.com/encode/django-rest-framework/issues/4949
|
||||
<!-- 3.6.3 -->
|
||||
|
||||
<!-- 3.6.3 -->
|
||||
[gh5126]: https://github.com/encode/django-rest-framework/issues/5126
|
||||
[gh5085]: https://github.com/encode/django-rest-framework/issues/5085
|
||||
[gh4437]: https://github.com/encode/django-rest-framework/issues/4437
|
||||
|
@ -1360,3 +1527,116 @@ For older release notes, [please see the version 2.x documentation][old-release-
|
|||
[gh4968]: https://github.com/encode/django-rest-framework/issues/4968
|
||||
[gh5089]: https://github.com/encode/django-rest-framework/issues/5089
|
||||
[gh5117]: https://github.com/encode/django-rest-framework/issues/5117
|
||||
|
||||
<!-- 3.6.4 -->
|
||||
[gh5346]: https://github.com/encode/django-rest-framework/issues/5346
|
||||
[gh5334]: https://github.com/encode/django-rest-framework/issues/5334
|
||||
[gh5326]: https://github.com/encode/django-rest-framework/issues/5326
|
||||
[gh5313]: https://github.com/encode/django-rest-framework/issues/5313
|
||||
[gh5306]: https://github.com/encode/django-rest-framework/issues/5306
|
||||
[gh5276]: https://github.com/encode/django-rest-framework/issues/5276
|
||||
[gh5264]: https://github.com/encode/django-rest-framework/issues/5264
|
||||
[gh5261]: https://github.com/encode/django-rest-framework/issues/5261
|
||||
[gh5259]: https://github.com/encode/django-rest-framework/issues/5259
|
||||
[gh5231]: https://github.com/encode/django-rest-framework/issues/5231
|
||||
[gh5229]: https://github.com/encode/django-rest-framework/issues/5229
|
||||
[gh5214]: https://github.com/encode/django-rest-framework/issues/5214
|
||||
[gh5196]: https://github.com/encode/django-rest-framework/issues/5196
|
||||
[gh5192]: https://github.com/encode/django-rest-framework/issues/5192
|
||||
[gh5162]: https://github.com/encode/django-rest-framework/issues/5162
|
||||
[gh5188]: https://github.com/encode/django-rest-framework/issues/5188
|
||||
[gh5187]: https://github.com/encode/django-rest-framework/issues/5187
|
||||
[gh5186]: https://github.com/encode/django-rest-framework/issues/5186
|
||||
[gh5179]: https://github.com/encode/django-rest-framework/issues/5179
|
||||
[gh5176]: https://github.com/encode/django-rest-framework/issues/5176
|
||||
[gh5174]: https://github.com/encode/django-rest-framework/issues/5174
|
||||
[gh5161]: https://github.com/encode/django-rest-framework/issues/5161
|
||||
[gh5147]: https://github.com/encode/django-rest-framework/issues/5147
|
||||
[gh5131]: https://github.com/encode/django-rest-framework/issues/5131
|
||||
|
||||
<!-- 3.7.0 -->
|
||||
[gh5481]: https://github.com/encode/django-rest-framework/issues/5481
|
||||
[gh5480]: https://github.com/encode/django-rest-framework/issues/5480
|
||||
[gh5479]: https://github.com/encode/django-rest-framework/issues/5479
|
||||
[gh5295]: https://github.com/encode/django-rest-framework/issues/5295
|
||||
[gh5464]: https://github.com/encode/django-rest-framework/issues/5464
|
||||
[gh5478]: https://github.com/encode/django-rest-framework/issues/5478
|
||||
[gh5476]: https://github.com/encode/django-rest-framework/issues/5476
|
||||
[gh5466]: https://github.com/encode/django-rest-framework/issues/5466
|
||||
[gh5472]: https://github.com/encode/django-rest-framework/issues/5472
|
||||
[gh5462]: https://github.com/encode/django-rest-framework/issues/5462
|
||||
[gh5470]: https://github.com/encode/django-rest-framework/issues/5470
|
||||
[gh5469]: https://github.com/encode/django-rest-framework/issues/5469
|
||||
[gh5435]: https://github.com/encode/django-rest-framework/issues/5435
|
||||
[gh5434]: https://github.com/encode/django-rest-framework/issues/5434
|
||||
[gh5426]: https://github.com/encode/django-rest-framework/issues/5426
|
||||
[gh5421]: https://github.com/encode/django-rest-framework/issues/5421
|
||||
[gh5415]: https://github.com/encode/django-rest-framework/issues/5415
|
||||
[gh5401]: https://github.com/encode/django-rest-framework/issues/5401
|
||||
[gh5398]: https://github.com/encode/django-rest-framework/issues/5398
|
||||
[gh5388]: https://github.com/encode/django-rest-framework/issues/5388
|
||||
[gh5387]: https://github.com/encode/django-rest-framework/issues/5387
|
||||
[gh5372]: https://github.com/encode/django-rest-framework/issues/5372
|
||||
[gh5380]: https://github.com/encode/django-rest-framework/issues/5380
|
||||
[gh5351]: https://github.com/encode/django-rest-framework/issues/5351
|
||||
[gh5375]: https://github.com/encode/django-rest-framework/issues/5375
|
||||
[gh5373]: https://github.com/encode/django-rest-framework/issues/5373
|
||||
[gh5361]: https://github.com/encode/django-rest-framework/issues/5361
|
||||
[gh5348]: https://github.com/encode/django-rest-framework/issues/5348
|
||||
[gh5058]: https://github.com/encode/django-rest-framework/issues/5058
|
||||
[gh5457]: https://github.com/encode/django-rest-framework/issues/5457
|
||||
[gh5376]: https://github.com/encode/django-rest-framework/issues/5376
|
||||
[gh5422]: https://github.com/encode/django-rest-framework/issues/5422
|
||||
[gh5408]: https://github.com/encode/django-rest-framework/issues/5408
|
||||
[gh3732]: https://github.com/encode/django-rest-framework/issues/3732
|
||||
[djangodocs-set-timezone]: https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/#default-time-zone-and-current-time-zone
|
||||
[gh5273]: https://github.com/encode/django-rest-framework/issues/5273
|
||||
[gh5440]: https://github.com/encode/django-rest-framework/issues/5440
|
||||
[gh5265]: https://github.com/encode/django-rest-framework/issues/5265
|
||||
[gh5250]: https://github.com/encode/django-rest-framework/issues/5250
|
||||
[gh5170]: https://github.com/encode/django-rest-framework/issues/5170
|
||||
[gh5443]: https://github.com/encode/django-rest-framework/issues/5443
|
||||
[gh5448]: https://github.com/encode/django-rest-framework/issues/5448
|
||||
[gh5452]: https://github.com/encode/django-rest-framework/issues/5452
|
||||
[gh5342]: https://github.com/encode/django-rest-framework/issues/5342
|
||||
[gh5454]: https://github.com/encode/django-rest-framework/issues/5454
|
||||
[gh5482]: https://github.com/encode/django-rest-framework/issues/5482
|
||||
|
||||
<!-- 3.7.1 -->
|
||||
[gh5489]: https://github.com/encode/django-rest-framework/issues/5489
|
||||
[gh5486]: https://github.com/encode/django-rest-framework/issues/5486
|
||||
[gh5503]: https://github.com/encode/django-rest-framework/issues/5503
|
||||
[gh5500]: https://github.com/encode/django-rest-framework/issues/5500
|
||||
[gh5492]: https://github.com/encode/django-rest-framework/issues/5492
|
||||
|
||||
<!-- 3.7.2 -->
|
||||
[gh5552]: https://github.com/encode/django-rest-framework/issues/5552
|
||||
[gh5539]: https://github.com/encode/django-rest-framework/issues/5539
|
||||
[gh5559]: https://github.com/encode/django-rest-framework/issues/5559
|
||||
[gh5561]: https://github.com/encode/django-rest-framework/issues/5561
|
||||
[gh5562]: https://github.com/encode/django-rest-framework/issues/5562
|
||||
[gh5548]: https://github.com/encode/django-rest-framework/issues/5548
|
||||
[gh5560]: https://github.com/encode/django-rest-framework/issues/5560
|
||||
[gh5557]: https://github.com/encode/django-rest-framework/issues/5557
|
||||
[gh5556]: https://github.com/encode/django-rest-framework/issues/5556
|
||||
[gh5555]: https://github.com/encode/django-rest-framework/issues/5555
|
||||
[gh5550]: https://github.com/encode/django-rest-framework/issues/5550
|
||||
[gh5549]: https://github.com/encode/django-rest-framework/issues/5549
|
||||
[gh5546]: https://github.com/encode/django-rest-framework/issues/5546
|
||||
[gh5547]: https://github.com/encode/django-rest-framework/issues/5547
|
||||
[gh5544]: https://github.com/encode/django-rest-framework/issues/5544
|
||||
[gh5518]: https://github.com/encode/django-rest-framework/issues/5518
|
||||
[gh5533]: https://github.com/encode/django-rest-framework/issues/5533
|
||||
[gh5532]: https://github.com/encode/django-rest-framework/issues/5532
|
||||
[gh5530]: https://github.com/encode/django-rest-framework/issues/5530
|
||||
[gh5527]: https://github.com/encode/django-rest-framework/issues/5527
|
||||
[gh5524]: https://github.com/encode/django-rest-framework/issues/5524
|
||||
[gh5516]: https://github.com/encode/django-rest-framework/issues/5516
|
||||
[gh5513]: https://github.com/encode/django-rest-framework/issues/5513
|
||||
[gh5511]: https://github.com/encode/django-rest-framework/issues/5511
|
||||
[gh5514]: https://github.com/encode/django-rest-framework/issues/5514
|
||||
[gh5512]: https://github.com/encode/django-rest-framework/issues/5512
|
||||
[gh5510]: https://github.com/encode/django-rest-framework/issues/5510
|
||||
|
||||
<!-- 3.7.3 -->
|
||||
[gh5567]: https://github.com/encode/django-rest-framework/issues/5567
|
||||
|
|
|
@ -239,6 +239,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [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.
|
||||
* [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.
|
||||
|
||||
### Filtering
|
||||
|
||||
|
@ -306,6 +307,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
|
||||
[drf_ujson]: https://github.com/gizmag/drf-ujson-renderer
|
||||
[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
|
||||
|
|
|
@ -48,8 +48,6 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
|
|||
'snippets.apps.SnippetsConfig',
|
||||
)
|
||||
|
||||
Please note that if you're using Django <1.9, you need to replace `snippets.apps.SnippetsConfig` with `snippets`.
|
||||
|
||||
Okay, we're ready to roll.
|
||||
|
||||
## Creating a model to work with
|
||||
|
|
|
@ -35,7 +35,7 @@ The wrappers also provide behaviour such as returning `405 Method Not Allowed` r
|
|||
|
||||
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` anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly.
|
||||
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.
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
|
@ -47,7 +47,7 @@ We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and de
|
|||
@api_view(['GET', 'POST'])
|
||||
def snippet_list(request):
|
||||
"""
|
||||
List all snippets, or create a new snippet.
|
||||
List all code snippets, or create a new snippet.
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
snippets = Snippet.objects.all()
|
||||
|
@ -68,7 +68,7 @@ Here is the view for an individual snippet, in the `views.py` module.
|
|||
@api_view(['GET', 'PUT', 'DELETE'])
|
||||
def snippet_detail(request, pk):
|
||||
"""
|
||||
Retrieve, update or delete a snippet instance.
|
||||
Retrieve, update or delete a code snippet.
|
||||
"""
|
||||
try:
|
||||
snippet = Snippet.objects.get(pk=pk)
|
||||
|
@ -106,7 +106,7 @@ and
|
|||
|
||||
def snippet_detail(request, pk, format=None):
|
||||
|
||||
Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
||||
Now update the `snippets/urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
|
|
@ -62,7 +62,7 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
|
|||
|
||||
That's looking good. Again, it's still pretty similar to the function based view right now.
|
||||
|
||||
We'll also need to refactor our `urls.py` slightly now that we're using class-based views.
|
||||
We'll also need to refactor our `snippets/urls.py` slightly now that we're using class-based views.
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
|
|
@ -43,7 +43,7 @@ And now we can add a `.save()` method to our model class:
|
|||
When that's all done we'll need to update our database tables.
|
||||
Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
|
||||
|
||||
rm -f tmp.db db.sqlite3
|
||||
rm -f db.sqlite3
|
||||
rm -r snippets/migrations
|
||||
python manage.py makemigrations snippets
|
||||
python manage.py migrate
|
||||
|
@ -142,11 +142,10 @@ Add the following import at the top of the file:
|
|||
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^api-auth/', include('rest_framework.urls',
|
||||
namespace='rest_framework')),
|
||||
url(r'^api-auth/', include('rest_framework.urls'),
|
||||
]
|
||||
|
||||
The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace. In Django 1.9+, REST framework will set the namespace, so you may leave it out.
|
||||
The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use.
|
||||
|
||||
Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.
|
||||
|
||||
|
@ -206,11 +205,11 @@ If we try to create a snippet without authenticating, we'll get an error:
|
|||
|
||||
We can make a successful request by including the username and password of one of the users we created earlier.
|
||||
|
||||
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
|
||||
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"owner": "tom",
|
||||
"owner": "admin",
|
||||
"title": "foo",
|
||||
"code": "print 789",
|
||||
"linenos": false,
|
||||
|
|
|
@ -130,23 +130,18 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file
|
|||
name='user-detail')
|
||||
])
|
||||
|
||||
# Login and logout views for the browsable API
|
||||
urlpatterns += [
|
||||
url(r'^api-auth/', include('rest_framework.urls',
|
||||
namespace='rest_framework')),
|
||||
]
|
||||
|
||||
## Adding pagination
|
||||
|
||||
The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.
|
||||
|
||||
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
|
||||
We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
|
||||
Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings.
|
||||
Note that settings in REST framework are all namespaced into a single dictionary setting, named `REST_FRAMEWORK`, which helps keep them well separated from your other project settings.
|
||||
|
||||
We could also customize the pagination style if we needed too, but in this case we'll just stick with the default.
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ Here we've used the `ReadOnlyModelViewSet` class to automatically provide the de
|
|||
Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class.
|
||||
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.response import Response
|
||||
|
||||
class SnippetViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
|
@ -60,7 +61,7 @@ The URLs for custom actions by default depend on the method name itself. If you
|
|||
The handler methods only get bound to the actions when we define the URLConf.
|
||||
To see what's going on under the hood let's first explicitly create a set of views from our ViewSets.
|
||||
|
||||
In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views.
|
||||
In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concrete views.
|
||||
|
||||
from snippets.views import SnippetViewSet, UserViewSet, api_root
|
||||
from rest_framework import renderers
|
||||
|
@ -102,11 +103,11 @@ Now that we've bound our resources into concrete views, we can register the view
|
|||
|
||||
Because we're using `ViewSet` classes rather than `View` classes, we actually don't need to design the URL conf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using a `Router` class. All we need to do is register the appropriate view sets with a router, and let it do the rest.
|
||||
|
||||
Here's our re-wired `urls.py` file.
|
||||
Here's our re-wired `snippets/urls.py` file.
|
||||
|
||||
from django.conf.urls import url, include
|
||||
from snippets import views
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from snippets import views
|
||||
|
||||
# Create a router and register our viewsets with it.
|
||||
router = DefaultRouter()
|
||||
|
@ -114,10 +115,8 @@ Here's our re-wired `urls.py` file.
|
|||
router.register(r'users', views.UserViewSet)
|
||||
|
||||
# The API URLs are now determined automatically by the router.
|
||||
# Additionally, we include the login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
url(r'^', include(router.urls)),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||
url(r'^', include(router.urls))
|
||||
]
|
||||
|
||||
Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself.
|
||||
|
|
|
@ -36,15 +36,15 @@ API schema.
|
|||
We can now include a schema for our API, by including an autogenerated schema
|
||||
view in our URL configuration.
|
||||
|
||||
```
|
||||
from rest_framework.schemas import get_schema_view
|
||||
```python
|
||||
from rest_framework.schemas import get_schema_view
|
||||
|
||||
schema_view = get_schema_view(title='Pastebin API')
|
||||
schema_view = get_schema_view(title='Pastebin API')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^schema/$', schema_view),
|
||||
...
|
||||
]
|
||||
urlpatterns = [
|
||||
url(r'^schema/$', schema_view),
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
If you visit the API root endpoint in a browser you should now see `corejson`
|
||||
|
|
|
@ -24,13 +24,37 @@ Create a new Django project named `tutorial`, then start a new app called `quick
|
|||
django-admin.py startapp quickstart
|
||||
cd ..
|
||||
|
||||
The project layout should look like:
|
||||
|
||||
$ pwd
|
||||
<some path>/tutorial
|
||||
$ find .
|
||||
.
|
||||
./manage.py
|
||||
./tutorial
|
||||
./tutorial/__init__.py
|
||||
./tutorial/quickstart
|
||||
./tutorial/quickstart/__init__.py
|
||||
./tutorial/quickstart/admin.py
|
||||
./tutorial/quickstart/apps.py
|
||||
./tutorial/quickstart/migrations
|
||||
./tutorial/quickstart/migrations/__init__.py
|
||||
./tutorial/quickstart/models.py
|
||||
./tutorial/quickstart/tests.py
|
||||
./tutorial/quickstart/views.py
|
||||
./tutorial/settings.py
|
||||
./tutorial/urls.py
|
||||
./tutorial/wsgi.py
|
||||
|
||||
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external module (topic goes outside the scope of the quickstart).
|
||||
|
||||
Now sync your database for the first time:
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
We'll also create an initial user named `admin` with a password of `password123`. We'll authenticate as that user later in our example.
|
||||
|
||||
python manage.py createsuperuser
|
||||
python manage.py createsuperuser --email admin@example.com --username admin
|
||||
|
||||
Once you've set up a database and initial user created and ready to go, open up the app's directory and we'll get coding...
|
||||
|
||||
|
@ -110,20 +134,13 @@ Finally, we're including default login and logout views for use with the browsab
|
|||
|
||||
## Settings
|
||||
|
||||
We'd also like to set a few global settings. We'd like to turn on pagination, and we want our API to only be accessible to admin users. The settings module will be in `tutorial/settings.py`
|
||||
Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py`
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'rest_framework',
|
||||
)
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAdminUser',
|
||||
],
|
||||
'PAGE_SIZE': 10
|
||||
}
|
||||
|
||||
Okay, we're done.
|
||||
|
||||
---
|
||||
|
|
|
@ -70,6 +70,7 @@ pages:
|
|||
- '3.4 Announcement': 'topics/3.4-announcement.md'
|
||||
- '3.5 Announcement': 'topics/3.5-announcement.md'
|
||||
- '3.6 Announcement': 'topics/3.6-announcement.md'
|
||||
- '3.7 Announcement': 'topics/3.7-announcement.md'
|
||||
- 'Kickstarter Announcement': 'topics/kickstarter-announcement.md'
|
||||
- 'Mozilla Grant': 'topics/mozilla-grant.md'
|
||||
- 'Funding': 'topics/funding.md'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# PEP8 code linting, which we run on all commits.
|
||||
flake8==2.4.0
|
||||
pep8==1.5.7
|
||||
flake8==3.5.0
|
||||
flake8-tidy-imports==1.1.0
|
||||
pycodestyle==2.3.1
|
||||
|
||||
# Sort and lint imports
|
||||
isort==4.2.5
|
||||
isort==4.2.15
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# MkDocs to build our documentation.
|
||||
mkdocs==0.16.2
|
||||
mkdocs==0.16.3
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Optional packages which may be used with REST framework.
|
||||
pytz==2017.2
|
||||
markdown==2.6.4
|
||||
django-guardian==1.4.8
|
||||
django-filter==1.0.4
|
||||
coreapi==2.2.4
|
||||
django-guardian==1.4.9
|
||||
django-filter==1.1.0
|
||||
coreapi==2.3.1
|
||||
coreschema==0.0.4
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
# Wheel for PyPI installs.
|
||||
wheel==0.29.0
|
||||
wheel==0.30.0
|
||||
|
||||
# Twine for secured PyPI uploads.
|
||||
twine==1.6.5
|
||||
twine==1.9.1
|
||||
|
||||
# Transifex client for managing translation resources.
|
||||
transifex-client==0.11
|
||||
|
||||
# Pandoc to have a nice pypi page
|
||||
pypandoc
|
||||
|
||||
# readme_renderer to check readme syntax
|
||||
readme_renderer
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# PyTest for running the tests.
|
||||
pytest==3.0.5
|
||||
pytest==3.2.5
|
||||
pytest-django==3.1.2
|
||||
pytest-cov==2.4.0
|
||||
pytest-cov==2.5.1
|
||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
|||
"""
|
||||
|
||||
__title__ = 'Django REST framework'
|
||||
__version__ = '3.6.3'
|
||||
__version__ = '3.7.3'
|
||||
__author__ = 'Tom Christie'
|
||||
__license__ = 'BSD 2-Clause'
|
||||
__copyright__ = 'Copyright 2011-2017 Tom Christie'
|
||||
|
@ -21,3 +21,5 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
|
|||
|
||||
# Default datetime input and output formats
|
||||
ISO_8601 = 'iso-8601'
|
||||
|
||||
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
|
||||
|
|
10
rest_framework/apps.py
Normal file
10
rest_framework/apps.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RestFrameworkConfig(AppConfig):
|
||||
name = 'rest_framework'
|
||||
verbose_name = "Django REST framework"
|
||||
|
||||
def ready(self):
|
||||
# Add System checks
|
||||
from .checks import pagination_system_check # NOQA
|
|
@ -6,12 +6,13 @@ from __future__ import unicode_literals
|
|||
import base64
|
||||
import binascii
|
||||
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.middleware.csrf import CsrfViewMiddleware
|
||||
from django.utils.six import text_type
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import HTTP_HEADER_ENCODING, exceptions
|
||||
from rest_framework.compat import authenticate
|
||||
|
||||
|
||||
def get_authorization_header(request):
|
||||
|
@ -83,17 +84,18 @@ class BasicAuthentication(BaseAuthentication):
|
|||
raise exceptions.AuthenticationFailed(msg)
|
||||
|
||||
userid, password = auth_parts[0], auth_parts[2]
|
||||
return self.authenticate_credentials(userid, password)
|
||||
return self.authenticate_credentials(userid, password, request)
|
||||
|
||||
def authenticate_credentials(self, userid, password):
|
||||
def authenticate_credentials(self, userid, password, request=None):
|
||||
"""
|
||||
Authenticate the userid and password against username and password.
|
||||
Authenticate the userid and password against username and password
|
||||
with optional request for context.
|
||||
"""
|
||||
credentials = {
|
||||
get_user_model().USERNAME_FIELD: userid,
|
||||
'password': password
|
||||
}
|
||||
user = authenticate(**credentials)
|
||||
user = authenticate(request=request, **credentials)
|
||||
|
||||
if user is None:
|
||||
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
|
||||
|
@ -201,3 +203,24 @@ class TokenAuthentication(BaseAuthentication):
|
|||
|
||||
def authenticate_header(self, request):
|
||||
return self.keyword
|
||||
|
||||
|
||||
class RemoteUserAuthentication(BaseAuthentication):
|
||||
"""
|
||||
REMOTE_USER authentication.
|
||||
|
||||
To use this, set up your web server to perform authentication, which will
|
||||
set the REMOTE_USER environment variable. You will need to have
|
||||
'django.contrib.auth.backends.RemoteUserBackend in your
|
||||
AUTHENTICATION_BACKENDS setting
|
||||
"""
|
||||
|
||||
# Name of request header to grab username from. This will be the key as
|
||||
# used in the request.META dictionary, i.e. the normalization of headers to
|
||||
# all uppercase and the addition of "HTTP_" prefix apply.
|
||||
header = "REMOTE_USER"
|
||||
|
||||
def authenticate(self, request):
|
||||
user = authenticate(remote_user=request.META.get(self.header))
|
||||
if user and user.is_active:
|
||||
return (user, None)
|
||||
|
|
0
rest_framework/authtoken/management/__init__.py
Normal file
0
rest_framework/authtoken/management/__init__.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Create DRF Token for a given user'
|
||||
|
||||
def create_user_token(self, username, reset_token):
|
||||
user = UserModel._default_manager.get_by_natural_key(username)
|
||||
|
||||
if reset_token:
|
||||
Token.objects.filter(user=user).delete()
|
||||
|
||||
token = Token.objects.get_or_create(user=user)
|
||||
return token[0]
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('username', type=str)
|
||||
|
||||
parser.add_argument(
|
||||
'-r',
|
||||
'--reset',
|
||||
action='store_true',
|
||||
dest='reset_token',
|
||||
default=False,
|
||||
help='Reset existing User token and create a new one',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
username = options['username']
|
||||
reset_token = options['reset_token']
|
||||
|
||||
try:
|
||||
token = self.create_user_token(username, reset_token)
|
||||
except UserModel.DoesNotExist:
|
||||
raise CommandError(
|
||||
'Cannot create the Token: user {0} does not exist'.format(
|
||||
username)
|
||||
)
|
||||
self.stdout.write(
|
||||
'Generated token {0} for user {1}'.format(token.key, username))
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth import authenticate
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.compat import authenticate
|
||||
|
||||
|
||||
class AuthTokenSerializer(serializers.Serializer):
|
||||
|
@ -17,16 +17,13 @@ class AuthTokenSerializer(serializers.Serializer):
|
|||
password = attrs.get('password')
|
||||
|
||||
if username and password:
|
||||
user = authenticate(username=username, password=password)
|
||||
user = authenticate(request=self.context.get('request'),
|
||||
username=username, password=password)
|
||||
|
||||
if user:
|
||||
# From Django 1.10 onwards the `authenticate` call simply
|
||||
# returns `None` for is_active=False users.
|
||||
# (Assuming the default `ModelBackend` authentication backend.)
|
||||
if not user.is_active:
|
||||
msg = _('User account is disabled.')
|
||||
raise serializers.ValidationError(msg, code='authorization')
|
||||
else:
|
||||
# The authenticate call simply returns None for is_active=False
|
||||
# users. (Assuming the default ModelBackend authentication
|
||||
# backend.)
|
||||
if not user:
|
||||
msg = _('Unable to log in with provided credentials.')
|
||||
raise serializers.ValidationError(msg, code='authorization')
|
||||
else:
|
||||
|
|
|
@ -13,7 +13,8 @@ class ObtainAuthToken(APIView):
|
|||
serializer_class = AuthTokenSerializer
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
serializer = self.serializer_class(data=request.data,
|
||||
context={'request': request})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
user = serializer.validated_data['user']
|
||||
token, created = Token.objects.get_or_create(user=user)
|
||||
|
|
21
rest_framework/checks.py
Normal file
21
rest_framework/checks.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from django.core.checks import Tags, Warning, register
|
||||
|
||||
|
||||
@register(Tags.compatibility)
|
||||
def pagination_system_check(app_configs, **kwargs):
|
||||
errors = []
|
||||
# Use of default page size setting requires a default Paginator class
|
||||
from rest_framework.settings import api_settings
|
||||
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,"
|
||||
"without specifying also a DEFAULT_PAGINATION_CLASS.",
|
||||
hint="The default for DEFAULT_PAGINATION_CLASS is None. "
|
||||
"In previous versions this was PageNumberPagination. "
|
||||
"If you wish to define PAGE_SIZE globally whilst defining "
|
||||
"pagination_class on a per-view basis you may silence this check.",
|
||||
id="rest_framework.W001"
|
||||
)
|
||||
)
|
||||
return errors
|
|
@ -3,7 +3,6 @@ The `compat` module provides support for backwards compatibility with older
|
|||
versions of Django/Python, and compatibility wrappers around optional packages.
|
||||
"""
|
||||
|
||||
# flake8: noqa
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import inspect
|
||||
|
@ -11,27 +10,43 @@ import inspect
|
|||
import django
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import connection, models, transaction
|
||||
from django.template import Context, RequestContext, Template
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
from django.views.generic import View
|
||||
|
||||
|
||||
try:
|
||||
from django.urls import (
|
||||
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
|
||||
from django.urls import ( # noqa
|
||||
URLPattern,
|
||||
URLResolver,
|
||||
)
|
||||
except ImportError:
|
||||
from django.core.urlresolvers import ( # Will be removed in Django 2.0
|
||||
NoReverseMatch, RegexURLPattern, RegexURLResolver, ResolverMatch, Resolver404, get_script_prefix, reverse, reverse_lazy, resolve
|
||||
# Will be removed in Django 2.0
|
||||
from django.urls import ( # noqa
|
||||
RegexURLPattern as URLPattern,
|
||||
RegexURLResolver as URLResolver,
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import urlparse # Python 2.x
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
def get_regex_pattern(urlpattern):
|
||||
if hasattr(urlpattern, 'pattern'):
|
||||
# Django 2.0
|
||||
return urlpattern.pattern.regex.pattern
|
||||
else:
|
||||
# Django < 2.0
|
||||
return urlpattern.regex.pattern
|
||||
|
||||
|
||||
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_repr(instance):
|
||||
|
@ -58,14 +73,6 @@ def unicode_http_header(value):
|
|||
return value
|
||||
|
||||
|
||||
def total_seconds(timedelta):
|
||||
# TimeDelta.total_seconds() is only available in Python 2.7
|
||||
if hasattr(timedelta, 'total_seconds'):
|
||||
return timedelta.total_seconds()
|
||||
else:
|
||||
return (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0)
|
||||
|
||||
|
||||
def distinct(queryset, base):
|
||||
if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle":
|
||||
# distinct analogue for Oracle users
|
||||
|
@ -73,36 +80,6 @@ def distinct(queryset, base):
|
|||
return queryset.distinct()
|
||||
|
||||
|
||||
# Obtaining manager instances and names from model options differs after 1.10.
|
||||
def get_names_and_managers(options):
|
||||
if django.VERSION >= (1, 10):
|
||||
# Django 1.10 onwards provides a `.managers` property on the Options.
|
||||
return [
|
||||
(manager.name, manager)
|
||||
for manager
|
||||
in options.managers
|
||||
]
|
||||
# For Django 1.8 and 1.9, use the three-tuple information provided
|
||||
# by .concrete_managers and .abstract_managers
|
||||
return [
|
||||
(manager_info[1], manager_info[2])
|
||||
for manager_info
|
||||
in (options.concrete_managers + options.abstract_managers)
|
||||
]
|
||||
|
||||
|
||||
# field.rel is deprecated from 1.9 onwards
|
||||
def get_remote_field(field, **kwargs):
|
||||
if 'default' in kwargs:
|
||||
if django.VERSION < (1, 9):
|
||||
return getattr(field, 'rel', kwargs['default'])
|
||||
return getattr(field, 'remote_field', kwargs['default'])
|
||||
|
||||
if django.VERSION < (1, 9):
|
||||
return field.rel
|
||||
return field.remote_field
|
||||
|
||||
|
||||
def _resolve_model(obj):
|
||||
"""
|
||||
Resolve supplied `obj` to a Django model class.
|
||||
|
@ -127,44 +104,13 @@ def _resolve_model(obj):
|
|||
raise ValueError("{0} is not a Django model".format(obj))
|
||||
|
||||
|
||||
def is_authenticated(user):
|
||||
if django.VERSION < (1, 10):
|
||||
return user.is_authenticated()
|
||||
return user.is_authenticated
|
||||
|
||||
|
||||
def is_anonymous(user):
|
||||
if django.VERSION < (1, 10):
|
||||
return user.is_anonymous()
|
||||
return user.is_anonymous
|
||||
|
||||
|
||||
def get_related_model(field):
|
||||
if django.VERSION < (1, 9):
|
||||
return _resolve_model(field.rel.to)
|
||||
return field.remote_field.model
|
||||
|
||||
|
||||
def value_from_object(field, obj):
|
||||
if django.VERSION < (1, 9):
|
||||
return field._get_val_from_obj(obj)
|
||||
return field.value_from_object(obj)
|
||||
|
||||
|
||||
# contrib.postgres only supported from 1.8 onwards.
|
||||
# django.contrib.postgres requires psycopg2
|
||||
try:
|
||||
from django.contrib.postgres import fields as postgres_fields
|
||||
except ImportError:
|
||||
postgres_fields = None
|
||||
|
||||
|
||||
# JSONField is only supported from 1.9 onwards
|
||||
try:
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
except ImportError:
|
||||
JSONField = None
|
||||
|
||||
|
||||
# coreapi is optional (Note that uritemplate is a dependency of coreapi)
|
||||
try:
|
||||
import coreapi
|
||||
|
@ -182,13 +128,6 @@ except ImportError:
|
|||
coreschema = None
|
||||
|
||||
|
||||
# django-filter is optional
|
||||
try:
|
||||
import django_filters
|
||||
except ImportError:
|
||||
django_filters = None
|
||||
|
||||
|
||||
# django-crispy-forms is optional
|
||||
try:
|
||||
import crispy_forms
|
||||
|
@ -208,7 +147,7 @@ except ImportError:
|
|||
guardian = None
|
||||
try:
|
||||
if 'guardian' in settings.INSTALLED_APPS:
|
||||
import guardian
|
||||
import guardian # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
@ -246,6 +185,7 @@ try:
|
|||
md = markdown.Markdown(
|
||||
extensions=extensions, extension_configs=extension_configs
|
||||
)
|
||||
md_filter_add_syntax_highlight(md)
|
||||
return md.convert(text)
|
||||
except ImportError:
|
||||
apply_markdown = None
|
||||
|
@ -254,7 +194,7 @@ except ImportError:
|
|||
|
||||
try:
|
||||
import pygments
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.lexers import get_lexer_by_name, TextLexer
|
||||
from pygments.formatters import HtmlFormatter
|
||||
|
||||
def pygments_highlight(text, lang, style):
|
||||
|
@ -275,9 +215,42 @@ except ImportError:
|
|||
def pygments_css(style):
|
||||
return None
|
||||
|
||||
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
|
||||
|
||||
class CodeBlockPreprocessor(Preprocessor):
|
||||
pattern = re.compile(
|
||||
r'^\s*``` *([^\n]+)\n(.+?)^\s*```', re.M | re.S)
|
||||
|
||||
formatter = HtmlFormatter()
|
||||
|
||||
def run(self, lines):
|
||||
def repl(m):
|
||||
try:
|
||||
lexer = get_lexer_by_name(m.group(1))
|
||||
except (ValueError, NameError):
|
||||
lexer = TextLexer()
|
||||
code = m.group(2).replace('\t', ' ')
|
||||
code = pygments.highlight(code, lexer, self.formatter)
|
||||
code = code.replace('\n\n', '\n \n').replace('\n', '<br />').replace('\\@', '@')
|
||||
return '\n\n%s\n\n' % code
|
||||
ret = self.pattern.sub(repl, "\n".join(lines))
|
||||
return ret.split("\n")
|
||||
|
||||
def md_filter_add_syntax_highlight(md):
|
||||
md.preprocessors.add('highlight', CodeBlockPreprocessor(), "_begin")
|
||||
return True
|
||||
else:
|
||||
def md_filter_add_syntax_highlight(md):
|
||||
return False
|
||||
|
||||
# pytz is required from Django 1.11. Remove when dropping Django 1.10 support.
|
||||
try:
|
||||
import pytz
|
||||
import pytz # noqa
|
||||
from pytz.exceptions import InvalidTimeError
|
||||
except ImportError:
|
||||
InvalidTimeError = Exception
|
||||
|
@ -294,63 +267,39 @@ else:
|
|||
LONG_SEPARATORS = (b', ', b': ')
|
||||
INDENT_SEPARATORS = (b',', b': ')
|
||||
|
||||
try:
|
||||
# DecimalValidator is unavailable in Django < 1.9
|
||||
from django.core.validators import DecimalValidator
|
||||
except ImportError:
|
||||
DecimalValidator = None
|
||||
|
||||
|
||||
def set_rollback():
|
||||
if hasattr(transaction, 'set_rollback'):
|
||||
if connection.settings_dict.get('ATOMIC_REQUESTS', False):
|
||||
# If running in >=1.6 then mark a rollback as required,
|
||||
# and allow it to be handled by Django.
|
||||
if connection.in_atomic_block:
|
||||
transaction.set_rollback(True)
|
||||
elif transaction.is_managed():
|
||||
# Otherwise handle it explicitly if in managed mode.
|
||||
if transaction.is_dirty():
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
else:
|
||||
# transaction not managed
|
||||
pass
|
||||
|
||||
|
||||
def template_render(template, context=None, request=None):
|
||||
class CustomValidatorMessage(object):
|
||||
"""
|
||||
Passing Context or RequestContext to Template.render is deprecated in 1.9+,
|
||||
see https://github.com/django/django/pull/3883 and
|
||||
https://github.com/django/django/blob/1.9/django/template/backends/django.py#L82-L84
|
||||
We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`.
|
||||
https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297
|
||||
|
||||
:param template: Template instance
|
||||
:param context: dict
|
||||
:param request: Request instance
|
||||
:return: rendered template as SafeText instance
|
||||
Ref: https://github.com/encode/django-rest-framework/pull/5452
|
||||
"""
|
||||
if isinstance(template, Template):
|
||||
if request:
|
||||
context = RequestContext(request, context)
|
||||
else:
|
||||
context = Context(context)
|
||||
return template.render(context)
|
||||
# backends template, e.g. django.template.backends.django.Template
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.message = kwargs.pop('message', self.message)
|
||||
super(CustomValidatorMessage, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator):
|
||||
pass
|
||||
|
||||
|
||||
class MaxValueValidator(CustomValidatorMessage, validators.MaxValueValidator):
|
||||
pass
|
||||
|
||||
|
||||
class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
|
||||
pass
|
||||
|
||||
|
||||
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
|
||||
pass
|
||||
|
||||
|
||||
def authenticate(request=None, **credentials):
|
||||
from django.contrib.auth import authenticate
|
||||
if django.VERSION < (1, 11):
|
||||
return authenticate(**credentials)
|
||||
else:
|
||||
return template.render(context, request=request)
|
||||
|
||||
|
||||
def set_many(instance, field, value):
|
||||
if django.VERSION < (1, 10):
|
||||
setattr(instance, field, value)
|
||||
else:
|
||||
field = getattr(instance, field)
|
||||
field.set(value)
|
||||
|
||||
|
||||
def include(module, namespace=None, app_name=None):
|
||||
from django.conf.urls import include
|
||||
if django.VERSION < (1,9):
|
||||
return include(module, namespace, app_name)
|
||||
else:
|
||||
return include((module, app_name), namespace)
|
||||
return authenticate(request=request, **credentials)
|
||||
|
|
|
@ -9,6 +9,7 @@ used to annotate methods on viewsets that should be included by routers.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import types
|
||||
import warnings
|
||||
|
||||
from django.utils import six
|
||||
|
||||
|
@ -45,7 +46,7 @@ def api_view(http_method_names=None, exclude_from_schema=False):
|
|||
assert isinstance(http_method_names, (list, tuple)), \
|
||||
'@api_view expected a list of strings, received %s' % type(http_method_names).__name__
|
||||
|
||||
allowed_methods = set(http_method_names) | set(('options',))
|
||||
allowed_methods = set(http_method_names) | {'options'}
|
||||
WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods]
|
||||
|
||||
def handler(self, *args, **kwargs):
|
||||
|
@ -72,7 +73,17 @@ def api_view(http_method_names=None, exclude_from_schema=False):
|
|||
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
|
||||
APIView.permission_classes)
|
||||
|
||||
WrappedAPIView.exclude_from_schema = exclude_from_schema
|
||||
WrappedAPIView.schema = getattr(func, 'schema',
|
||||
APIView.schema)
|
||||
|
||||
if exclude_from_schema:
|
||||
warnings.warn(
|
||||
"The `exclude_from_schema` argument to `api_view` is pending deprecation. "
|
||||
"Use the `schema` decorator instead, passing `None`.",
|
||||
PendingDeprecationWarning
|
||||
)
|
||||
WrappedAPIView.exclude_from_schema = exclude_from_schema
|
||||
|
||||
return WrappedAPIView.as_view()
|
||||
return decorator
|
||||
|
||||
|
@ -112,6 +123,13 @@ def permission_classes(permission_classes):
|
|||
return decorator
|
||||
|
||||
|
||||
def schema(view_inspector):
|
||||
def decorator(func):
|
||||
func.schema = view_inspector
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
def detail_route(methods=None, **kwargs):
|
||||
"""
|
||||
Used to mark a method on a ViewSet that should be routed for detail requests.
|
||||
|
|
|
@ -4,12 +4,16 @@ from rest_framework.renderers import (
|
|||
CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
|
||||
)
|
||||
from rest_framework.schemas import SchemaGenerator, get_schema_view
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
|
||||
def get_docs_view(
|
||||
title=None, description=None, schema_url=None, public=True,
|
||||
patterns=None, generator_class=SchemaGenerator,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
|
||||
renderer_classes=None):
|
||||
|
||||
if renderer_classes is None:
|
||||
renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
|
||||
|
||||
|
@ -21,12 +25,16 @@ def get_docs_view(
|
|||
public=public,
|
||||
patterns=patterns,
|
||||
generator_class=generator_class,
|
||||
authentication_classes=authentication_classes,
|
||||
permission_classes=permission_classes,
|
||||
)
|
||||
|
||||
|
||||
def get_schemajs_view(
|
||||
title=None, description=None, schema_url=None, public=True,
|
||||
patterns=None, generator_class=SchemaGenerator):
|
||||
patterns=None, generator_class=SchemaGenerator,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
renderer_classes = [SchemaJSRenderer]
|
||||
|
||||
return get_schema_view(
|
||||
|
@ -37,13 +45,18 @@ def get_schemajs_view(
|
|||
public=public,
|
||||
patterns=patterns,
|
||||
generator_class=generator_class,
|
||||
authentication_classes=authentication_classes,
|
||||
permission_classes=permission_classes,
|
||||
)
|
||||
|
||||
|
||||
def include_docs_urls(
|
||||
title=None, description=None, schema_url=None, public=True,
|
||||
patterns=None, generator_class=SchemaGenerator,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
|
||||
renderer_classes=None):
|
||||
|
||||
docs_view = get_docs_view(
|
||||
title=title,
|
||||
description=description,
|
||||
|
@ -51,6 +64,8 @@ def include_docs_urls(
|
|||
public=public,
|
||||
patterns=patterns,
|
||||
generator_class=generator_class,
|
||||
authentication_classes=authentication_classes,
|
||||
permission_classes=permission_classes,
|
||||
renderer_classes=renderer_classes
|
||||
)
|
||||
schema_js_view = get_schemajs_view(
|
||||
|
@ -60,9 +75,11 @@ def include_docs_urls(
|
|||
public=public,
|
||||
patterns=patterns,
|
||||
generator_class=generator_class,
|
||||
authentication_classes=authentication_classes,
|
||||
permission_classes=permission_classes,
|
||||
)
|
||||
urls = [
|
||||
url(r'^$', docs_view, name='docs-index'),
|
||||
url(r'^schema.js$', schema_js_view, name='schema-js')
|
||||
]
|
||||
return include(urls, namespace='api-docs')
|
||||
return include((urls, 'api-docs'), namespace='api-docs')
|
||||
|
|
|
@ -64,7 +64,7 @@ def _get_full_details(detail):
|
|||
|
||||
class ErrorDetail(six.text_type):
|
||||
"""
|
||||
A string-like object that can additionally
|
||||
A string-like object that can additionally have a code.
|
||||
"""
|
||||
code = None
|
||||
|
||||
|
@ -92,7 +92,7 @@ class APIException(Exception):
|
|||
self.detail = _get_error_details(detail, code)
|
||||
|
||||
def __str__(self):
|
||||
return self.detail
|
||||
return six.text_type(self.detail)
|
||||
|
||||
def get_codes(self):
|
||||
"""
|
||||
|
@ -123,22 +123,19 @@ class ValidationError(APIException):
|
|||
default_detail = _('Invalid input.')
|
||||
default_code = 'invalid'
|
||||
|
||||
def __init__(self, detail, code=None):
|
||||
def __init__(self, detail=None, code=None):
|
||||
if detail is None:
|
||||
detail = self.default_detail
|
||||
if code is None:
|
||||
code = self.default_code
|
||||
|
||||
# For validation failures, we may collect may errors together, so the
|
||||
# details should always be coerced to a list if not already.
|
||||
# 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):
|
||||
detail = [detail]
|
||||
|
||||
self.detail = _get_error_details(detail, code)
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.detail)
|
||||
|
||||
|
||||
class ParseError(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
|
|
|
@ -4,8 +4,8 @@ import collections
|
|||
import copy
|
||||
import datetime
|
||||
import decimal
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
|
@ -14,8 +14,7 @@ from django.conf import settings
|
|||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.validators import (
|
||||
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
|
||||
MinValueValidator, RegexValidator, URLValidator, ip_address_validators
|
||||
EmailValidator, RegexValidator, URLValidator, ip_address_validators
|
||||
)
|
||||
from django.forms import FilePathField as DjangoFilePathField
|
||||
from django.forms import ImageField as DjangoImageField
|
||||
|
@ -26,19 +25,19 @@ from django.utils.dateparse import (
|
|||
from django.utils.duration import duration_string
|
||||
from django.utils.encoding import is_protected_type, smart_text
|
||||
from django.utils.formats import localize_input, sanitize_separators
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.ipv6 import clean_ipv6_address
|
||||
from django.utils.timezone import utc
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework import ISO_8601
|
||||
from rest_framework.compat import (
|
||||
InvalidTimeError, get_remote_field, unicode_repr, unicode_to_repr,
|
||||
value_from_object
|
||||
InvalidTimeError, MaxLengthValidator, MaxValueValidator,
|
||||
MinLengthValidator, MinValueValidator, unicode_repr, unicode_to_repr
|
||||
)
|
||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.utils import html, humanize_datetime, representation
|
||||
from rest_framework.utils import html, humanize_datetime, json, representation
|
||||
|
||||
|
||||
class empty:
|
||||
|
@ -56,7 +55,7 @@ if six.PY3:
|
|||
"""
|
||||
True if the object is a callable that takes no arguments.
|
||||
"""
|
||||
if not (inspect.isfunction(obj) or inspect.ismethod(obj)):
|
||||
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
|
||||
return False
|
||||
|
||||
sig = inspect.signature(obj)
|
||||
|
@ -94,9 +93,6 @@ def get_attribute(instance, attrs):
|
|||
Also accepts either attribute lookup on objects or dictionary lookups.
|
||||
"""
|
||||
for attr in attrs:
|
||||
if instance is None:
|
||||
# Break out early if we get `None` at any point in a nested lookup.
|
||||
return None
|
||||
try:
|
||||
if isinstance(instance, collections.Mapping):
|
||||
instance = instance[attr]
|
||||
|
@ -143,7 +139,7 @@ def to_choices_dict(choices):
|
|||
|
||||
to_choices_dict([1]) -> {1: 1}
|
||||
to_choices_dict([(1, '1st'), (2, '2nd')]) -> {1: '1st', 2: '2nd'}
|
||||
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2nd'}}
|
||||
to_choices_dict([('Group', ((1, '1st'), 2))]) -> {'Group': {1: '1st', 2: '2'}}
|
||||
"""
|
||||
# Allow single, paired or grouped choices style:
|
||||
# choices = [1, 2, 3]
|
||||
|
@ -448,6 +444,8 @@ class Field(object):
|
|||
return self.get_default()
|
||||
if not self.required:
|
||||
raise SkipField()
|
||||
if self.allow_null:
|
||||
return None
|
||||
msg = (
|
||||
'Got {exc_type} when attempting to get a value for field '
|
||||
'`{field}` on serializer `{serializer}`.\nThe serializer '
|
||||
|
@ -586,7 +584,7 @@ class Field(object):
|
|||
message_string = msg.format(**kwargs)
|
||||
raise ValidationError(message_string, code=key)
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def root(self):
|
||||
"""
|
||||
Returns the top-level serializer for this field.
|
||||
|
@ -596,7 +594,7 @@ class Field(object):
|
|||
root = root.parent
|
||||
return root
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def context(self):
|
||||
"""
|
||||
Returns the context as passed to the root serializer on initialization.
|
||||
|
@ -692,8 +690,22 @@ class NullBooleanField(Field):
|
|||
'invalid': _('"{input}" is not a valid boolean.')
|
||||
}
|
||||
initial = None
|
||||
TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
|
||||
FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
|
||||
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 = {'n', 'N', 'null', 'Null', 'NULL', '', None}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -702,12 +714,15 @@ class NullBooleanField(Field):
|
|||
super(NullBooleanField, self).__init__(**kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if data in self.TRUE_VALUES:
|
||||
return True
|
||||
elif data in self.FALSE_VALUES:
|
||||
return False
|
||||
elif data in self.NULL_VALUES:
|
||||
return None
|
||||
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):
|
||||
|
@ -738,11 +753,17 @@ class CharField(Field):
|
|||
self.min_length = kwargs.pop('min_length', None)
|
||||
super(CharField, self).__init__(**kwargs)
|
||||
if self.max_length is not None:
|
||||
message = self.error_messages['max_length'].format(max_length=self.max_length)
|
||||
self.validators.append(MaxLengthValidator(self.max_length, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['max_length'].format,
|
||||
six.text_type)(max_length=self.max_length)
|
||||
self.validators.append(
|
||||
MaxLengthValidator(self.max_length, message=message))
|
||||
if self.min_length is not None:
|
||||
message = self.error_messages['min_length'].format(min_length=self.min_length)
|
||||
self.validators.append(MinLengthValidator(self.min_length, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['min_length'].format,
|
||||
six.text_type)(min_length=self.min_length)
|
||||
self.validators.append(
|
||||
MinLengthValidator(self.min_length, message=message))
|
||||
|
||||
def run_validation(self, data=empty):
|
||||
# Test for the empty string here so that it does not get validated,
|
||||
|
@ -897,11 +918,17 @@ class IntegerField(Field):
|
|||
self.min_value = kwargs.pop('min_value', None)
|
||||
super(IntegerField, self).__init__(**kwargs)
|
||||
if self.max_value is not None:
|
||||
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
||||
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['max_value'].format,
|
||||
six.text_type)(max_value=self.max_value)
|
||||
self.validators.append(
|
||||
MaxValueValidator(self.max_value, message=message))
|
||||
if self.min_value is not None:
|
||||
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
||||
self.validators.append(MinValueValidator(self.min_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['min_value'].format,
|
||||
six.text_type)(min_value=self.min_value)
|
||||
self.validators.append(
|
||||
MinValueValidator(self.min_value, message=message))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH:
|
||||
|
@ -931,11 +958,17 @@ class FloatField(Field):
|
|||
self.min_value = kwargs.pop('min_value', None)
|
||||
super(FloatField, self).__init__(**kwargs)
|
||||
if self.max_value is not None:
|
||||
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
||||
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['max_value'].format,
|
||||
six.text_type)(max_value=self.max_value)
|
||||
self.validators.append(
|
||||
MaxValueValidator(self.max_value, message=message))
|
||||
if self.min_value is not None:
|
||||
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
||||
self.validators.append(MinValueValidator(self.min_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['min_value'].format,
|
||||
six.text_type)(min_value=self.min_value)
|
||||
self.validators.append(
|
||||
MinValueValidator(self.min_value, message=message))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
||||
|
@ -964,7 +997,7 @@ class DecimalField(Field):
|
|||
MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs.
|
||||
|
||||
def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None,
|
||||
localize=False, **kwargs):
|
||||
localize=False, rounding=None, **kwargs):
|
||||
self.max_digits = max_digits
|
||||
self.decimal_places = decimal_places
|
||||
self.localize = localize
|
||||
|
@ -984,11 +1017,23 @@ class DecimalField(Field):
|
|||
super(DecimalField, self).__init__(**kwargs)
|
||||
|
||||
if self.max_value is not None:
|
||||
message = self.error_messages['max_value'].format(max_value=self.max_value)
|
||||
self.validators.append(MaxValueValidator(self.max_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['max_value'].format,
|
||||
six.text_type)(max_value=self.max_value)
|
||||
self.validators.append(
|
||||
MaxValueValidator(self.max_value, message=message))
|
||||
if self.min_value is not None:
|
||||
message = self.error_messages['min_value'].format(min_value=self.min_value)
|
||||
self.validators.append(MinValueValidator(self.min_value, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['min_value'].format,
|
||||
six.text_type)(min_value=self.min_value)
|
||||
self.validators.append(
|
||||
MinValueValidator(self.min_value, message=message))
|
||||
|
||||
if rounding is not None:
|
||||
valid_roundings = [v for k, v in vars(decimal).items() if k.startswith('ROUND_')]
|
||||
assert rounding in valid_roundings, (
|
||||
'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
|
||||
self.rounding = rounding
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""
|
||||
|
@ -1082,6 +1127,7 @@ class DecimalField(Field):
|
|||
context.prec = self.max_digits
|
||||
return value.quantize(
|
||||
decimal.Decimal('.1') ** self.decimal_places,
|
||||
rounding=self.rounding,
|
||||
context=context
|
||||
)
|
||||
|
||||
|
@ -1092,7 +1138,8 @@ class DateTimeField(Field):
|
|||
default_error_messages = {
|
||||
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
|
||||
'date': _('Expected a datetime but got a date.'),
|
||||
'make_aware': _('Invalid datetime for the timezone "{timezone}".')
|
||||
'make_aware': _('Invalid datetime for the timezone "{timezone}".'),
|
||||
'overflow': _('Datetime value out of range.')
|
||||
}
|
||||
datetime_parser = datetime.datetime.strptime
|
||||
|
||||
|
@ -1112,7 +1159,12 @@ class DateTimeField(Field):
|
|||
"""
|
||||
field_timezone = getattr(self, 'timezone', self.default_timezone())
|
||||
|
||||
if (field_timezone is not None) and not timezone.is_aware(value):
|
||||
if field_timezone is not None:
|
||||
if timezone.is_aware(value):
|
||||
try:
|
||||
return value.astimezone(field_timezone)
|
||||
except OverflowError:
|
||||
self.fail('overflow')
|
||||
try:
|
||||
return timezone.make_aware(value, field_timezone)
|
||||
except InvalidTimeError:
|
||||
|
@ -1122,7 +1174,7 @@ class DateTimeField(Field):
|
|||
return value
|
||||
|
||||
def default_timezone(self):
|
||||
return timezone.get_default_timezone() if settings.USE_TZ else None
|
||||
return timezone.get_current_timezone() if settings.USE_TZ else None
|
||||
|
||||
def to_internal_value(self, value):
|
||||
input_formats = getattr(self, 'input_formats', api_settings.DATETIME_INPUT_FORMATS)
|
||||
|
@ -1137,18 +1189,16 @@ class DateTimeField(Field):
|
|||
if input_format.lower() == ISO_8601:
|
||||
try:
|
||||
parsed = parse_datetime(value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
if parsed is not None:
|
||||
return self.enforce_timezone(parsed)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
parsed = self.datetime_parser(value, input_format)
|
||||
return self.enforce_timezone(parsed)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
return self.enforce_timezone(parsed)
|
||||
|
||||
humanized_format = humanize_datetime.datetime_formats(input_formats)
|
||||
self.fail('invalid', format=humanized_format)
|
||||
|
@ -1163,6 +1213,7 @@ class DateTimeField(Field):
|
|||
return value
|
||||
|
||||
if output_format.lower() == ISO_8601:
|
||||
value = self.enforce_timezone(value)
|
||||
value = value.isoformat()
|
||||
if value.endswith('+00:00'):
|
||||
value = value[:-6] + 'Z'
|
||||
|
@ -1326,18 +1377,10 @@ class ChoiceField(Field):
|
|||
html_cutoff_text = _('More than {count} items...')
|
||||
|
||||
def __init__(self, choices, **kwargs):
|
||||
self.grouped_choices = to_choices_dict(choices)
|
||||
self.choices = flatten_choices_dict(self.grouped_choices)
|
||||
self.choices = choices
|
||||
self.html_cutoff = kwargs.pop('html_cutoff', self.html_cutoff)
|
||||
self.html_cutoff_text = kwargs.pop('html_cutoff_text', self.html_cutoff_text)
|
||||
|
||||
# Map the string representation of choices to the underlying value.
|
||||
# Allows us to deal with eg. integer choices while supporting either
|
||||
# integer or string input, but still get the correct datatype out.
|
||||
self.choice_strings_to_values = {
|
||||
six.text_type(key): key for key in self.choices.keys()
|
||||
}
|
||||
|
||||
self.allow_blank = kwargs.pop('allow_blank', False)
|
||||
|
||||
super(ChoiceField, self).__init__(**kwargs)
|
||||
|
@ -1366,6 +1409,22 @@ class ChoiceField(Field):
|
|||
cutoff_text=self.html_cutoff_text
|
||||
)
|
||||
|
||||
def _get_choices(self):
|
||||
return self._choices
|
||||
|
||||
def _set_choices(self, choices):
|
||||
self.grouped_choices = to_choices_dict(choices)
|
||||
self._choices = flatten_choices_dict(self.grouped_choices)
|
||||
|
||||
# Map the string representation of choices to the underlying value.
|
||||
# Allows us to deal with eg. integer choices while supporting either
|
||||
# integer or string input, but still get the correct datatype out.
|
||||
self.choice_strings_to_values = {
|
||||
six.text_type(key): key for key in self.choices.keys()
|
||||
}
|
||||
|
||||
choices = property(_get_choices, _set_choices)
|
||||
|
||||
|
||||
class MultipleChoiceField(ChoiceField):
|
||||
default_error_messages = {
|
||||
|
@ -1494,8 +1553,7 @@ class ImageField(FileField):
|
|||
file_object = super(ImageField, self).to_internal_value(data)
|
||||
django_field = self._DjangoImageField()
|
||||
django_field.error_messages = self.error_messages
|
||||
django_field.to_python(file_object)
|
||||
return file_object
|
||||
return django_field.clean(file_object)
|
||||
|
||||
|
||||
# Composite field types...
|
||||
|
@ -1776,13 +1834,16 @@ class ModelField(Field):
|
|||
max_length = kwargs.pop('max_length', None)
|
||||
super(ModelField, self).__init__(**kwargs)
|
||||
if max_length is not None:
|
||||
message = self.error_messages['max_length'].format(max_length=max_length)
|
||||
self.validators.append(MaxLengthValidator(max_length, message=message))
|
||||
message = lazy(
|
||||
self.error_messages['max_length'].format,
|
||||
six.text_type)(max_length=self.max_length)
|
||||
self.validators.append(
|
||||
MaxLengthValidator(self.max_length, message=message))
|
||||
|
||||
def to_internal_value(self, data):
|
||||
rel = get_remote_field(self.model_field, default=None)
|
||||
rel = self.model_field.remote_field
|
||||
if rel is not None:
|
||||
return rel.to._meta.get_field(rel.field_name).to_python(data)
|
||||
return rel.model._meta.get_field(rel.field_name).to_python(data)
|
||||
return self.model_field.to_python(data)
|
||||
|
||||
def get_attribute(self, obj):
|
||||
|
@ -1791,7 +1852,7 @@ class ModelField(Field):
|
|||
return obj
|
||||
|
||||
def to_representation(self, obj):
|
||||
value = value_from_object(self.model_field, obj)
|
||||
value = self.model_field.value_from_object(obj)
|
||||
if is_protected_type(value):
|
||||
return value
|
||||
return self.model_field.value_to_string(obj)
|
||||
|
|
|
@ -5,7 +5,6 @@ returned by list views.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import operator
|
||||
import warnings
|
||||
from functools import reduce
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
@ -17,9 +16,7 @@ from django.utils import six
|
|||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.compat import (
|
||||
coreapi, coreschema, distinct, django_filters, guardian, template_render
|
||||
)
|
||||
from rest_framework.compat import coreapi, coreschema, distinct, guardian
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
|
||||
|
@ -40,44 +37,6 @@ class BaseFilterBackend(object):
|
|||
return []
|
||||
|
||||
|
||||
if django_filters:
|
||||
from django_filters.rest_framework.filterset import FilterSet as DFFilterSet
|
||||
|
||||
class FilterSet(DFFilterSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(
|
||||
"The built in 'rest_framework.filters.FilterSet' is deprecated. "
|
||||
"You should use 'django_filters.rest_framework.FilterSet' instead.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
return super(FilterSet, self).__init__(*args, **kwargs)
|
||||
|
||||
DFBase = django_filters.rest_framework.DjangoFilterBackend
|
||||
|
||||
else:
|
||||
def FilterSet():
|
||||
assert False, 'django-filter must be installed to use the `FilterSet` class'
|
||||
|
||||
DFBase = BaseFilterBackend
|
||||
|
||||
|
||||
class DjangoFilterBackend(DFBase):
|
||||
"""
|
||||
A filter backend that uses django-filter.
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
|
||||
assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required'
|
||||
|
||||
warnings.warn(
|
||||
"The built in 'rest_framework.filters.DjangoFilterBackend' is deprecated. "
|
||||
"You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
return super(DjangoFilterBackend, cls).__new__(cls, *args, **kwargs)
|
||||
|
||||
|
||||
class SearchFilter(BaseFilterBackend):
|
||||
# The URL query parameter used for the search.
|
||||
search_param = api_settings.SEARCH_PARAM
|
||||
|
@ -140,12 +99,14 @@ class SearchFilter(BaseFilterBackend):
|
|||
]
|
||||
|
||||
base = queryset
|
||||
conditions = []
|
||||
for search_term in search_terms:
|
||||
queries = [
|
||||
models.Q(**{orm_lookup: search_term})
|
||||
for orm_lookup in orm_lookups
|
||||
]
|
||||
queryset = queryset.filter(reduce(operator.or_, queries))
|
||||
conditions.append(reduce(operator.or_, queries))
|
||||
queryset = queryset.filter(reduce(operator.and_, conditions))
|
||||
|
||||
if self.must_call_distinct(queryset, search_fields):
|
||||
# Filtering against a many-to-many field requires us to
|
||||
|
@ -166,7 +127,7 @@ class SearchFilter(BaseFilterBackend):
|
|||
'term': term
|
||||
}
|
||||
template = loader.get_template(self.template)
|
||||
return template_render(template, context)
|
||||
return template.render(context)
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
|
||||
|
@ -238,7 +199,7 @@ class OrderingFilter(BaseFilterBackend):
|
|||
raise ImproperlyConfigured(msg % self.__class__.__name__)
|
||||
|
||||
return [
|
||||
(field.source or field_name, field.label)
|
||||
(field.source.replace('.', '__') or field_name, field.label)
|
||||
for field_name, field in serializer_class(context=context).fields.items()
|
||||
if not getattr(field, 'write_only', False) and not field.source == '*'
|
||||
]
|
||||
|
@ -297,7 +258,7 @@ class OrderingFilter(BaseFilterBackend):
|
|||
def to_html(self, request, queryset, view):
|
||||
template = loader.get_template(self.template)
|
||||
context = self.get_template_context(request, queryset, view)
|
||||
return template_render(template, context)
|
||||
return template.render(context)
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
|
||||
|
|
Binary file not shown.
|
@ -3,15 +3,17 @@
|
|||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Bashar Al-Abdulhadi, 2016
|
||||
# Eyad Toma <d.eyad.t@gmail.com>, 2015
|
||||
# Andrew Ayoub <andrew.ayoub@connectads.com>, 2017
|
||||
# aymen chaieb <chaieb.aymen1992@gmail.com>, 2017
|
||||
# Bashar Al-Abdulhadi, 2016-2017
|
||||
# Eyad Toma <d.eyad.t@gmail.com>, 2015,2017
|
||||
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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-10-18 09:51+0000\n"
|
||||
"Last-Translator: Andrew Ayoub <andrew.ayoub@connectads.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"
|
||||
|
@ -54,7 +56,7 @@ msgstr ""
|
|||
|
||||
#: authentication.py:195
|
||||
msgid "Invalid token."
|
||||
msgstr "رمز غير صحيح"
|
||||
msgstr "رمز غير صحيح."
|
||||
|
||||
#: authtoken/apps.py:7
|
||||
msgid "Auth Token"
|
||||
|
@ -126,7 +128,7 @@ msgstr "غير موجود."
|
|||
|
||||
#: exceptions.py:109
|
||||
msgid "Method \"{method}\" not allowed."
|
||||
msgstr ""
|
||||
msgstr "طلب غير مسموح به"
|
||||
|
||||
#: exceptions.py:120
|
||||
msgid "Could not satisfy the request Accept header."
|
||||
|
@ -189,7 +191,7 @@ msgstr ""
|
|||
|
||||
#: fields.py:796
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr ""
|
||||
msgstr "برجاء إدخال عنوان IPV4 أو IPV6 صحيح"
|
||||
|
||||
#: fields.py:821
|
||||
msgid "A valid integer is required."
|
||||
|
@ -205,7 +207,7 @@ msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}."
|
|||
|
||||
#: fields.py:824 fields.py:859 fields.py:896
|
||||
msgid "String value too large."
|
||||
msgstr ""
|
||||
msgstr "القيمه اكبر من المسموح"
|
||||
|
||||
#: fields.py:856 fields.py:890
|
||||
msgid "A valid number is required."
|
||||
|
@ -232,7 +234,7 @@ msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن
|
|||
|
||||
#: fields.py:1026
|
||||
msgid "Expected a datetime but got a date."
|
||||
msgstr ""
|
||||
msgstr "متوقع تاريخ و وقت و وجد تاريخ فقط"
|
||||
|
||||
#: fields.py:1103
|
||||
msgid "Date has wrong format. Use one of these formats instead: {format}."
|
||||
|
@ -240,7 +242,7 @@ msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم و
|
|||
|
||||
#: fields.py:1104
|
||||
msgid "Expected a date but got a datetime."
|
||||
msgstr ""
|
||||
msgstr "متوقع تاريخ فقط و وجد تاريخ ووقت"
|
||||
|
||||
#: fields.py:1170
|
||||
msgid "Time has wrong format. Use one of these formats instead: {format}."
|
||||
|
@ -248,7 +250,7 @@ msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واح
|
|||
|
||||
#: fields.py:1232
|
||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr ""
|
||||
msgstr "صيغة المده غير صحيحه, برجاء إستخدام أحد هذه الصيغ {format}"
|
||||
|
||||
#: fields.py:1251 fields.py:1300
|
||||
msgid "\"{input}\" is not a valid choice."
|
||||
|
@ -256,7 +258,7 @@ msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة."
|
|||
|
||||
#: fields.py:1254 relations.py:71 relations.py:441
|
||||
msgid "More than {count} items..."
|
||||
msgstr ""
|
||||
msgstr "أكثر من {count} عنصر..."
|
||||
|
||||
#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524
|
||||
msgid "Expected a list of items but got type \"{input_type}\"."
|
||||
|
@ -316,15 +318,15 @@ msgstr "أرسل"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "تصاعدي"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "تنازلي"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr "صفحة غير صحيحة"
|
||||
msgstr "صفحة غير صحيحة."
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -382,13 +384,13 @@ msgstr "الترتيب"
|
|||
|
||||
#: templates/rest_framework/filters/search.html:2
|
||||
msgid "Search"
|
||||
msgstr "البحث"
|
||||
msgstr "بحث"
|
||||
|
||||
#: templates/rest_framework/horizontal/radio.html:2
|
||||
#: templates/rest_framework/inline/radio.html:2
|
||||
#: templates/rest_framework/vertical/radio.html:2
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
msgstr "لا شيء"
|
||||
|
||||
#: templates/rest_framework/horizontal/select_multiple.html:2
|
||||
#: templates/rest_framework/inline/select_multiple.html:2
|
||||
|
@ -398,7 +400,7 @@ msgstr ""
|
|||
|
||||
#: validators.py:43
|
||||
msgid "This field must be unique."
|
||||
msgstr ""
|
||||
msgstr "هذا الحقل يجب أن يكون فريد"
|
||||
|
||||
#: validators.py:97
|
||||
msgid "The fields {field_names} must make a unique set."
|
||||
|
@ -438,4 +440,4 @@ msgstr ""
|
|||
|
||||
#: views.py:88
|
||||
msgid "Permission denied."
|
||||
msgstr ""
|
||||
msgstr "ليس لديك صلاحية."
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Catalan (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -10,7 +10,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Czech (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/cs/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -3,15 +3,15 @@
|
|||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Mads Jensen <mje@inducks.org>, 2015-2016
|
||||
# Mads Jensen <mje@inducks.org>, 2015-2017
|
||||
# Mikkel Munch Mortensen <3xm@detfalskested.dk>, 2015
|
||||
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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Mads Jensen <mje@inducks.org>\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -62,31 +62,31 @@ msgstr ""
|
|||
|
||||
#: authtoken/models.py:15
|
||||
msgid "Key"
|
||||
msgstr ""
|
||||
msgstr "Nøgle"
|
||||
|
||||
#: authtoken/models.py:18
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
msgstr "Bruger"
|
||||
|
||||
#: authtoken/models.py:20
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Oprettet"
|
||||
|
||||
#: authtoken/models.py:29
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
msgstr "Token"
|
||||
|
||||
#: authtoken/models.py:30
|
||||
msgid "Tokens"
|
||||
msgstr ""
|
||||
msgstr "Tokens"
|
||||
|
||||
#: authtoken/serializers.py:8
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "Brugernavn"
|
||||
|
||||
#: authtoken/serializers.py:9
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Kodeord"
|
||||
|
||||
#: authtoken/serializers.py:20
|
||||
msgid "User account is disabled."
|
||||
|
@ -316,15 +316,15 @@ msgstr "Indsend."
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "stigende"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "faldende"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr ""
|
||||
msgstr "Ugyldig side"
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -426,7 +426,7 @@ msgstr "Ugyldig version i URL-stien."
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "Ugyldig version in URLen. Den stemmer ikke overens med nogen versionsnumre."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -4,6 +4,8 @@
|
|||
#
|
||||
# Translators:
|
||||
# Fabian Büchler <fabian@buechler.io>, 2015
|
||||
# datKater <imperator.katz@gmail.com>, 2017
|
||||
# Lukas Bischofberger <me@worx.li>, 2017
|
||||
# Mads Jensen <mje@inducks.org>, 2015
|
||||
# Niklas P <contact@niklasplessing.net>, 2015-2016
|
||||
# Thomas Tanner, 2015
|
||||
|
@ -14,8 +16,8 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Lukas Bischofberger <me@worx.li>\n"
|
||||
"Language-Team: German (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -74,7 +76,7 @@ msgstr "Benutzer"
|
|||
|
||||
#: authtoken/models.py:20
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Erzeugt"
|
||||
|
||||
#: authtoken/models.py:29
|
||||
msgid "Token"
|
||||
|
@ -98,7 +100,7 @@ msgstr "Benutzerkonto ist gesperrt."
|
|||
|
||||
#: authtoken/serializers.py:23
|
||||
msgid "Unable to log in with provided credentials."
|
||||
msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden."
|
||||
msgstr "Die angegebenen Zugangsdaten stimmen nicht."
|
||||
|
||||
#: authtoken/serializers.py:26
|
||||
msgid "Must include \"username\" and \"password\"."
|
||||
|
@ -122,7 +124,7 @@ msgstr "Anmeldedaten fehlen."
|
|||
|
||||
#: exceptions.py:99
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "Sie sind nicht berechtigt, diese Aktion durchzuführen."
|
||||
msgstr "Sie sind nicht berechtigt diese Aktion durchzuführen."
|
||||
|
||||
#: exceptions.py:104 views.py:81
|
||||
msgid "Not found."
|
||||
|
@ -151,7 +153,7 @@ msgstr "Dieses Feld ist erforderlich."
|
|||
|
||||
#: fields.py:270
|
||||
msgid "This field may not be null."
|
||||
msgstr "Dieses Feld darf nicht Null sein."
|
||||
msgstr "Dieses Feld darf nicht null sein."
|
||||
|
||||
#: fields.py:608 fields.py:639
|
||||
msgid "\"{input}\" is not a valid boolean."
|
||||
|
@ -193,7 +195,7 @@ msgstr "\"{value}\" ist keine gültige UUID."
|
|||
|
||||
#: fields.py:796
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr "Geben Sie eine gültige UPv4 oder IPv6 Adresse an"
|
||||
msgstr "Geben Sie eine gültige IPv4 oder IPv6 Adresse an"
|
||||
|
||||
#: fields.py:821
|
||||
msgid "A valid integer is required."
|
||||
|
@ -272,7 +274,7 @@ msgstr "Diese Auswahl darf nicht leer sein"
|
|||
|
||||
#: fields.py:1339
|
||||
msgid "\"{input}\" is not a valid path choice."
|
||||
msgstr "\"{input}\" ist ein ungültiger Pfad Wahl."
|
||||
msgstr "\"{input}\" ist ein ungültiger Pfad."
|
||||
|
||||
#: fields.py:1358
|
||||
msgid "No file was submitted."
|
||||
|
@ -308,7 +310,7 @@ msgstr "Diese Liste darf nicht leer sein."
|
|||
|
||||
#: fields.py:1502
|
||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||
msgstr "Erwarte ein Dictionary mit Elementen, erhielt aber den Typ \"{input_type}\"."
|
||||
msgstr "Erwartete ein Dictionary mit Elementen, erhielt aber den Typ \"{input_type}\"."
|
||||
|
||||
#: fields.py:1549
|
||||
msgid "Value must be valid JSON."
|
||||
|
@ -320,15 +322,15 @@ msgstr "Abschicken"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "Aufsteigend"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "Absteigend"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr ""
|
||||
msgstr "Ungültige Seite."
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -430,7 +432,7 @@ msgstr "Ungültige Version im URL Pfad."
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "Ungültige Version im URL-Pfad. Entspricht keinem Versions-Namensraum."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -9,7 +9,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Greek (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/el/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-09-21 21:11+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: English (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/en/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -6,6 +6,7 @@
|
|||
# Ernesto Rico-Schmidt <e.rico.schmidt@gmail.com>, 2015
|
||||
# José Padilla <jpadilla@webapplicate.com>, 2015
|
||||
# Miguel Gonzalez <migonzalvar@gmail.com>, 2015
|
||||
# Miguel Gonzalez <migonzalvar@gmail.com>, 2016
|
||||
# Miguel Gonzalez <migonzalvar@gmail.com>, 2015-2016
|
||||
# Sergio Infante <rsinfante@gmail.com>, 2015
|
||||
msgid ""
|
||||
|
@ -13,8 +14,8 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Miguel Gonzalez <migonzalvar@gmail.com>\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -319,11 +320,11 @@ msgstr "Enviar"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "ascendiente"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "descendiente"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
|
@ -429,7 +430,7 @@ msgstr "Versión inválida en la ruta de la URL."
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "La versión especificada en la ruta de la URL no es válida. No coincide con ninguna del espacio de nombres de versiones."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -9,7 +9,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Estonian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/et/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -4,14 +4,14 @@
|
|||
#
|
||||
# Translators:
|
||||
# Aarni Koskela, 2015
|
||||
# Aarni Koskela, 2015
|
||||
# Aarni Koskela, 2015-2016
|
||||
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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Aarni Koskela\n"
|
||||
"Language-Team: Finnish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fi/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -58,35 +58,35 @@ msgstr "Epäkelpo Token."
|
|||
|
||||
#: authtoken/apps.py:7
|
||||
msgid "Auth Token"
|
||||
msgstr ""
|
||||
msgstr "Autentikaatiotunniste"
|
||||
|
||||
#: authtoken/models.py:15
|
||||
msgid "Key"
|
||||
msgstr ""
|
||||
msgstr "Avain"
|
||||
|
||||
#: authtoken/models.py:18
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
msgstr "Käyttäjä"
|
||||
|
||||
#: authtoken/models.py:20
|
||||
msgid "Created"
|
||||
msgstr ""
|
||||
msgstr "Luotu"
|
||||
|
||||
#: authtoken/models.py:29
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
msgstr "Tunniste"
|
||||
|
||||
#: authtoken/models.py:30
|
||||
msgid "Tokens"
|
||||
msgstr ""
|
||||
msgstr "Tunnisteet"
|
||||
|
||||
#: authtoken/serializers.py:8
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgstr "Käyttäjänimi"
|
||||
|
||||
#: authtoken/serializers.py:9
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Salasana"
|
||||
|
||||
#: authtoken/serializers.py:20
|
||||
msgid "User account is disabled."
|
||||
|
@ -316,15 +316,15 @@ msgstr "Lähetä"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "nouseva"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "laskeva"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr ""
|
||||
msgstr "Epäkelpo sivu."
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -426,7 +426,7 @@ msgstr "Epäkelpo versio URL-polussa."
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "URL-polun versio ei täsmää mihinkään versionimiavaruuteen."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -12,8 +12,8 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Xavier Ordoquy <xordoquy@linovia.com>\n"
|
||||
"Language-Team: French (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -318,11 +318,11 @@ msgstr "Envoyer"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "croissant"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "décroissant"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
|
@ -428,7 +428,7 @@ msgstr "Version non valide dans l'URL."
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "Version invalide dans l'URL. Ne correspond à aucune version de l'espace de nommage."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -9,7 +9,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Hungarian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/hu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -12,7 +12,7 @@ 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: 2016-07-12 15:14+0000\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"Language-Team: Italian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
Binary file not shown.
|
@ -4,13 +4,14 @@
|
|||
#
|
||||
# Translators:
|
||||
# Hiroaki Nakamura <hnakamur@gmail.com>, 2016
|
||||
# Kouichi Nishizawa <kouichi.nishizawa@gmail.com>, 2017
|
||||
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: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-08-03 14:58+0000\n"
|
||||
"Last-Translator: Kouichi Nishizawa <kouichi.nishizawa@gmail.com>\n"
|
||||
"Language-Team: Japanese (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -315,15 +316,15 @@ msgstr "提出"
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "昇順"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "降順"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr ""
|
||||
msgstr "不正なページです。"
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -425,7 +426,7 @@ msgstr "URLパス内のバージョンが不正です。"
|
|||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "不正なバージョンのURLのパスです。どのバージョンの名前空間にも一致しません。"
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
|
Binary file not shown.
|
@ -3,14 +3,16 @@
|
|||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# GarakdongBigBoy <novelview9@gmail.com>, 2017
|
||||
# Joon Hwan 김준환 <xncbf12@gmail.com>, 2017
|
||||
# SUN CHOI <best2378@gmail.com>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django REST framework\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-07-12 16:13+0100\n"
|
||||
"PO-Revision-Date: 2016-07-12 15:14+0000\n"
|
||||
"Last-Translator: Thomas Christie <tom@tomchristie.com>\n"
|
||||
"PO-Revision-Date: 2017-09-28 09:41+0000\n"
|
||||
"Last-Translator: GarakdongBigBoy <novelview9@gmail.com>\n"
|
||||
"Language-Team: Korean (Korea) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ko_KR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -44,7 +46,7 @@ msgstr "토큰 헤더가 유효하지 않습니다. 인증데이터(credentials)
|
|||
|
||||
#: authentication.py:179
|
||||
msgid "Invalid token header. Token string should not contain spaces."
|
||||
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)를 포함하지 않아야 합니다."
|
||||
msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)을 포함하지 않아야 합니다."
|
||||
|
||||
#: authentication.py:185
|
||||
msgid ""
|
||||
|
@ -117,7 +119,7 @@ msgstr "자격 인증데이터(authentication credentials)가 제공되지 않
|
|||
|
||||
#: exceptions.py:99
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "이 작업을 "
|
||||
msgstr "이 작업을 수행할 권한(permission)이 없습니다."
|
||||
|
||||
#: exceptions.py:104 views.py:81
|
||||
msgid "Not found."
|
||||
|
@ -142,11 +144,11 @@ msgstr "요청이 지연(throttled)되었습니다."
|
|||
#: fields.py:269 relations.py:206 relations.py:239 validators.py:98
|
||||
#: validators.py:181
|
||||
msgid "This field is required."
|
||||
msgstr "이 항목을 채워주십시오."
|
||||
msgstr "이 필드는 필수 항목입니다."
|
||||
|
||||
#: fields.py:270
|
||||
msgid "This field may not be null."
|
||||
msgstr "이 칸은 null일 수 없습니다."
|
||||
msgstr "이 필드는 null일 수 없습니다."
|
||||
|
||||
#: fields.py:608 fields.py:639
|
||||
msgid "\"{input}\" is not a valid boolean."
|
||||
|
@ -154,15 +156,15 @@ msgstr "\"{input}\"이 유효하지 않은 부울(boolean)입니다."
|
|||
|
||||
#: fields.py:674
|
||||
msgid "This field may not be blank."
|
||||
msgstr "이 칸은 blank일 수 없습니다."
|
||||
msgstr "이 필드는 blank일 수 없습니다."
|
||||
|
||||
#: fields.py:675 fields.py:1675
|
||||
msgid "Ensure this field has no more than {max_length} characters."
|
||||
msgstr "이 칸이 글자 수가 {max_length} 이하인지 확인하십시오."
|
||||
msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하십시오."
|
||||
|
||||
#: fields.py:676
|
||||
msgid "Ensure this field has at least {min_length} characters."
|
||||
msgstr "이 칸이 글자 수가 적어도 {min_length} 이상인지 확인하십시오."
|
||||
msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하십시오."
|
||||
|
||||
#: fields.py:713
|
||||
msgid "Enter a valid email address."
|
||||
|
@ -247,7 +249,7 @@ msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를
|
|||
|
||||
#: fields.py:1232
|
||||
msgid "Duration has wrong format. Use one of these formats instead: {format}."
|
||||
msgstr ""
|
||||
msgstr "Duration의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}."
|
||||
|
||||
#: fields.py:1251 fields.py:1300
|
||||
msgid "\"{input}\" is not a valid choice."
|
||||
|
@ -263,11 +265,11 @@ msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습
|
|||
|
||||
#: fields.py:1302
|
||||
msgid "This selection may not be empty."
|
||||
msgstr ""
|
||||
msgstr "이 선택 항목은 비워 둘 수 없습니다."
|
||||
|
||||
#: fields.py:1339
|
||||
msgid "\"{input}\" is not a valid path choice."
|
||||
msgstr ""
|
||||
msgstr "\"{input}\"이 유효하지 않은 경로 선택입니다."
|
||||
|
||||
#: fields.py:1358
|
||||
msgid "No file was submitted."
|
||||
|
@ -284,7 +286,7 @@ msgstr "파일명을 알 수 없습니다."
|
|||
|
||||
#: fields.py:1361
|
||||
msgid "The submitted file is empty."
|
||||
msgstr "제출된 파일이 비어있습니다."
|
||||
msgstr "제출한 파일이 비어있습니다."
|
||||
|
||||
#: fields.py:1362
|
||||
msgid ""
|
||||
|
@ -299,7 +301,7 @@ msgstr "유효한 이미지 파일을 업로드 하십시오. 업로드 하신
|
|||
|
||||
#: fields.py:1449 relations.py:438 serializers.py:525
|
||||
msgid "This list may not be empty."
|
||||
msgstr ""
|
||||
msgstr "이 리스트는 비워 둘 수 없습니다."
|
||||
|
||||
#: fields.py:1502
|
||||
msgid "Expected a dictionary of items but got type \"{input_type}\"."
|
||||
|
@ -307,7 +309,7 @@ msgstr "아이템 딕셔너리가 예상되었으나 \"{input_type}\" 타입을
|
|||
|
||||
#: fields.py:1549
|
||||
msgid "Value must be valid JSON."
|
||||
msgstr ""
|
||||
msgstr "Value 는 유효한 JSON형식이어야 합니다."
|
||||
|
||||
#: filters.py:36 templates/rest_framework/filters/django_filter.html:5
|
||||
msgid "Submit"
|
||||
|
@ -315,15 +317,15 @@ msgstr ""
|
|||
|
||||
#: filters.py:336
|
||||
msgid "ascending"
|
||||
msgstr ""
|
||||
msgstr "오름차순"
|
||||
|
||||
#: filters.py:337
|
||||
msgid "descending"
|
||||
msgstr ""
|
||||
msgstr "내림차순"
|
||||
|
||||
#: pagination.py:193
|
||||
msgid "Invalid page."
|
||||
msgstr ""
|
||||
msgstr "페이지가 유효하지 않습니다."
|
||||
|
||||
#: pagination.py:427
|
||||
msgid "Invalid cursor"
|
||||
|
@ -381,7 +383,7 @@ msgstr ""
|
|||
|
||||
#: templates/rest_framework/filters/search.html:2
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
msgstr "검색"
|
||||
|
||||
#: templates/rest_framework/horizontal/radio.html:2
|
||||
#: templates/rest_framework/inline/radio.html:2
|
||||
|
@ -397,35 +399,35 @@ msgstr "선택할 아이템이 없습니다."
|
|||
|
||||
#: validators.py:43
|
||||
msgid "This field must be unique."
|
||||
msgstr "이 칸은 반드시 고유해야 합니다."
|
||||
msgstr "이 필드는 반드시 고유(unique)해야 합니다."
|
||||
|
||||
#: validators.py:97
|
||||
msgid "The fields {field_names} must make a unique set."
|
||||
msgstr ""
|
||||
msgstr "필드 {field_names} 는 반드시 고유(unique)해야 합니다."
|
||||
|
||||
#: validators.py:245
|
||||
msgid "This field must be unique for the \"{date_field}\" date."
|
||||
msgstr ""
|
||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 날짜를 갖습니다."
|
||||
|
||||
#: validators.py:260
|
||||
msgid "This field must be unique for the \"{date_field}\" month."
|
||||
msgstr ""
|
||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 월을 갖습니다. "
|
||||
|
||||
#: validators.py:273
|
||||
msgid "This field must be unique for the \"{date_field}\" year."
|
||||
msgstr ""
|
||||
msgstr "이 필드는 고유(unique)한 \"{date_field}\" 년을 갖습니다. "
|
||||
|
||||
#: versioning.py:42
|
||||
msgid "Invalid version in \"Accept\" header."
|
||||
msgstr "\"Accept\" header내 버전이 유효하지 않습니다."
|
||||
msgstr "\"Accept\" 헤더(header)의 버전이 유효하지 않습니다."
|
||||
|
||||
#: versioning.py:73
|
||||
msgid "Invalid version in URL path."
|
||||
msgstr "URL path내 버전이 유효하지 않습니다."
|
||||
msgstr "URL path의 버전이 유효하지 않습니다."
|
||||
|
||||
#: versioning.py:115
|
||||
msgid "Invalid version in URL path. Does not match any version namespace."
|
||||
msgstr ""
|
||||
msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임 스페이스와 일치하지 않습니다."
|
||||
|
||||
#: versioning.py:147
|
||||
msgid "Invalid version in hostname."
|
||||
|
@ -437,4 +439,4 @@ msgstr "쿼리 파라메터내 버전이 유효하지 않습니다."
|
|||
|
||||
#: views.py:88
|
||||
msgid "Permission denied."
|
||||
msgstr ""
|
||||
msgstr "사용 권한이 거부되었습니다."
|
||||
|
|
BIN
rest_framework/locale/lv/LC_MESSAGES/django.mo
Normal file
BIN
rest_framework/locale/lv/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user