Merge branch 'master' into version-3-6-3

This commit is contained in:
Tom Christie 2017-05-12 17:03:32 +01:00
commit a99f12f1c6
72 changed files with 1345 additions and 859 deletions

View File

@ -35,7 +35,6 @@ matrix:
allow_failures: allow_failures:
- env: DJANGO=master - env: DJANGO=master
- env: DJANGO=1.11
install: install:
- pip install tox tox-travis - pip install tox tox-travis

View File

@ -50,7 +50,7 @@ Getting involved in triaging incoming issues is a good way to start contributing
To start developing on Django REST framework, clone the repo: To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git git clone git@github.com:encode/django-rest-framework.git
Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles. Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
@ -198,10 +198,10 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[code-of-conduct]: https://www.djangoproject.com/conduct/ [code-of-conduct]: https://www.djangoproject.com/conduct/
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[so-filter]: http://stackexchange.com/filters/66475/rest-framework [so-filter]: http://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open [issues]: https://github.com/encode/django-rest-framework/issues?state=open
[pep-8]: http://www.python.org/dev/peps/pep-0008/ [pep-8]: http://www.python.org/dev/peps/pep-0008/
[pull-requests]: https://help.github.com/articles/using-pull-requests [pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: https://tox.readthedocs.io/en/latest/ [tox]: https://tox.readthedocs.io/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics [markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [docs]: https://github.com/encode/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/ [mou]: http://mouapp.com/

View File

@ -1,4 +1,4 @@
*Note*: Before submitting this pull request, please review our [contributing guidelines](https://github.com/tomchristie/django-rest-framework/blob/master/CONTRIBUTING.md#pull-requests). *Note*: Before submitting this pull request, please review our [contributing guidelines](https://github.com/encode/django-rest-framework/blob/master/CONTRIBUTING.md#pull-requests).
## Description ## Description

View File

@ -21,12 +21,12 @@ 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.* *Every single sign-up makes a significant impact towards making that possible.*
<p align="center"> <p align="center">
<a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a> <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/tomchristie/django-rest-framework/master/docs/img/premium/sentry-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/tomchristie/django-rest-framework/master/docs/img/premium/stream-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/tomchristie/django-rest-framework/master/docs/img/premium/machinalis-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/tomchristie/django-rest-framework/master/docs/img/premium/rollbar-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/tomchristie/django-rest-framework/master/docs/img/premium/micropyramid-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> </p>
*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/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
@ -53,8 +53,8 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements # Requirements
* Python (2.7, 3.2, 3.3, 3.4, 3.5) * Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
* Django (1.8, 1.9, 1.10) * Django (1.8, 1.9, 1.10, 1.11)
# Installation # Installation
@ -176,10 +176,10 @@ If you believe you've found something in Django REST framework which has securit
Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure.
[build-status-image]: https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master [build-status-image]: https://secure.travis-ci.org/encode/django-rest-framework.svg?branch=master
[travis]: http://travis-ci.org/tomchristie/django-rest-framework?branch=master [travis]: http://travis-ci.org/encode/django-rest-framework?branch=master
[coverage-status-image]: https://img.shields.io/codecov/c/github/tomchristie/django-rest-framework/master.svg [coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/master.svg
[codecov]: http://codecov.io/github/tomchristie/django-rest-framework?branch=master [codecov]: http://codecov.io/github/encode/django-rest-framework?branch=master
[pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg [pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg
[pypi]: https://pypi.python.org/pypi/djangorestframework [pypi]: https://pypi.python.org/pypi/djangorestframework
[twitter]: https://twitter.com/_tomchristie [twitter]: https://twitter.com/_tomchristie

View File

@ -356,6 +356,10 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[Django-rest-knox][django-rest-knox] library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into). [Django-rest-knox][django-rest-knox] library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into).
## drfpasswordless
[drfpasswordless][drfpasswordless] adds (Medium, Square Cash inspired) passwordless support to Django REST Framework's own TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number.
[cite]: http://jacobian.org/writing/rest-worst-practices/ [cite]: http://jacobian.org/writing/rest-worst-practices/
[http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
[http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 [http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
@ -396,3 +400,4 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[django-rest-auth]: https://github.com/Tivix/django-rest-auth [django-rest-auth]: https://github.com/Tivix/django-rest-auth
[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 [django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2
[django-rest-knox]: https://github.com/James1345/django-rest-knox [django-rest-knox]: https://github.com/James1345/django-rest-knox
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless

View File

@ -142,7 +142,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
The `django-filter` library includes a `DjangoFilterBackend` class which The `django-filter` library includes a `DjangoFilterBackend` class which
supports highly customizable field filtering for REST framework. supports highly customizable field filtering for REST framework.
To use `DjangoFilterBackend`, first install `django-filter`. To use `DjangoFilterBackend`, first install `django-filter`. Then add `django_filters` to Django's `INSTALLED_APPS`
pip install django-filter pip install django-filter
@ -160,16 +160,6 @@ Or add the filter backend to an individual View or ViewSet.
... ...
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
If you are using the browsable API or admin API you may also want to install `django-crispy-forms`, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML.
pip install django-crispy-forms
With crispy forms installed and added to Django's `INSTALLED_APPS`, the browsable API will present a filtering control for `DjangoFilterBackend`, like so:
![Django Filter](../img/django-filter.png)
#### Specifying filter fields
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against. If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
class ProductList(generics.ListAPIView): class ProductList(generics.ListAPIView):
@ -182,80 +172,10 @@ This will automatically create a `FilterSet` class for the given fields, and wil
http://example.com/api/products?category=clothing&in_stock=True http://example.com/api/products?category=clothing&in_stock=True
#### Specifying a FilterSet For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view.
You can read more about `FilterSet`s in the [django-filter documentation][django-filter-docs].
It's also recommended that you read the section on [DRF integration][django-filter-drf-docs].
For more advanced filtering requirements you can specify a `FilterSet` class that should be used by the view. For example:
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import generics
class ProductFilter(django_filters.rest_framework.FilterSet):
min_price = django_filters.NumberFilter(name="price", lookup_expr='gte')
max_price = django_filters.NumberFilter(name="price", lookup_expr='lte')
class Meta:
model = Product
fields = ['category', 'in_stock', 'min_price', 'max_price']
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filter_class = ProductFilter
Which will allow you to make requests such as:
http://example.com/api/products?category=clothing&max_price=10.00
You can also span relationships using `django-filter`, let's assume that each
product has foreign key to `Manufacturer` model, so we create filter that
filters using `Manufacturer` name. For example:
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import generics
class ProductFilter(django_filters.rest_framework.FilterSet):
class Meta:
model = Product
fields = ['category', 'in_stock', 'manufacturer__name']
This enables us to make queries like:
http://example.com/api/products?manufacturer__name=foo
This is nice, but it exposes the Django's double underscore convention as part of the API. If you instead want to explicitly name the filter argument you can instead explicitly include it on the `FilterSet` class:
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import generics
class ProductFilter(django_filters.rest_framework.FilterSet):
manufacturer = django_filters.CharFilter(name="manufacturer__name")
class Meta:
model = Product
fields = ['category', 'in_stock', 'manufacturer']
And now you can execute:
http://example.com/api/products?manufacturer=foo
For more details on using filter sets see the [django-filter documentation][django-filter-docs].
---
**Hints & Tips**
* By default filtering is not enabled. If you want to use `DjangoFilterBackend` remember to make sure it is installed by using the `'DEFAULT_FILTER_BACKENDS'` setting.
* When using boolean fields, you should use the values `True` and `False` in the URL query parameters, rather than `0`, `1`, `true` or `false`. (The allowed boolean values are currently hardwired in Django's [NullBooleanSelect implementation][nullbooleanselect].)
* `django-filter` supports filtering across relationships, using Django's double-underscore syntax.
---
## SearchFilter ## SearchFilter
@ -432,8 +352,11 @@ The method should return a rendered HTML string.
## Pagination & schemas ## Pagination & schemas
You can also make the filter controls available to the schema autogeneration You can also make the filter controls available to the schema autogeneration
that REST framework provides, by implementing a `get_schema_fields()` method, that REST framework provides, by implementing a `get_schema_fields()` method. This method should have the following signature:
which should return a list of `coreapi.Field` instances.
`get_schema_fields(self, view)`
The method should return a list of `coreapi.Field` instances.
# Third party packages # Third party packages
@ -458,6 +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 [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]: https://github.com/alex/django-filter
[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html [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
[guardian]: https://django-guardian.readthedocs.io/ [guardian]: https://django-guardian.readthedocs.io/
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html [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 [view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models

View File

@ -279,8 +279,11 @@ API responses for list endpoints will now include a `Link` header, instead of in
## Pagination & schemas ## Pagination & schemas
You can also make the pagination controls available to the schema autogeneration You can also make the pagination controls available to the schema autogeneration
that REST framework provides, by implementing a `get_schema_fields()` method, that REST framework provides, by implementing a `get_schema_fields()` method. This method should have the following signature:
which should return a list of `coreapi.Field` instances.
`get_schema_fields(self, view)`
The method should return a list of `coreapi.Field` instances.
--- ---

View File

@ -270,7 +270,7 @@ Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a
lookup_field = 'username' lookup_field = 'username'
@detail_route() @detail_route()
def group_names(self, request): def group_names(self, request, pk=None):
""" """
Returns a list of all the group names that the given Returns a list of all the group names that the given
user belongs to. user belongs to.

View File

@ -117,7 +117,7 @@ The simplest way to include a schema in your project is to use the
Once the view has been added, you'll be able to make API requests to retrieve Once the view has been added, you'll be able to make API requests to retrieve
the auto-generated schema definition. the auto-generated schema definition.
$ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json $ http http://127.0.0.1:8000/ Accept:application/coreapi+json
HTTP/1.0 200 OK HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS Allow: GET, HEAD, OPTIONS
Content-Type: application/vnd.coreapi+json Content-Type: application/vnd.coreapi+json
@ -170,6 +170,22 @@ May be used to pass the set of renderer classes that can be used to render the A
renderer_classes=[CoreJSONRenderer, APIBlueprintRenderer] renderer_classes=[CoreJSONRenderer, APIBlueprintRenderer]
) )
#### `patterns`
List of url patterns to limit the schema introspection to. If you only want the `myproject.api` urls
to be exposed in the schema:
schema_url_patterns = [
url(r'^api/', include('myproject.api.urls')),
]
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
patterns=schema_url_patterns,
)
## Using an explicit schema view ## Using an explicit schema view
If you need a little more control than the `get_schema_view()` shortcut gives you, If you need a little more control than the `get_schema_view()` shortcut gives you,

View File

@ -162,7 +162,7 @@ The `credentials` method is appropriate for testing APIs that require authentica
#### .force_authenticate(user=None, token=None) #### .force_authenticate(user=None, token=None)
Sometimes you may want to bypass authentication, and simple force all requests by the test client to be automatically treated as authenticated. Sometimes you may want to bypass authentication entirely and force all requests by the test client to be automatically treated as authenticated.
This can be a useful shortcut if you're testing the API but don't want to have to construct valid authentication credentials in order to make test requests. This can be a useful shortcut if you're testing the API but don't want to have to construct valid authentication credentials in order to make test requests.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -18,10 +18,10 @@
</style> </style>
<p class="badges" height=20px> <p class="badges" height=20px>
<iframe src="http://ghbtns.com/github-btn.html?user=tomchristie&amp;repo=django-rest-framework&amp;type=watch&amp;count=true" class="github-star-button" allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe> <iframe src="http://ghbtns.com/github-btn.html?user=encode&amp;repo=django-rest-framework&amp;type=watch&amp;count=true" class="github-star-button" allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
<a href="http://travis-ci.org/tomchristie/django-rest-framework?branch=master"> <a href="http://travis-ci.org/encode/django-rest-framework?branch=master">
<img src="https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master" class="status-badge"> <img src="https://secure.travis-ci.org/encode/django-rest-framework.svg?branch=master" class="status-badge">
</a> </a>
<a href="https://pypi.python.org/pypi/djangorestframework"> <a href="https://pypi.python.org/pypi/djangorestframework">
@ -87,8 +87,8 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following: REST framework requires the following:
* Python (2.7, 3.2, 3.3, 3.4, 3.5) * Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
* Django (1.8, 1.9, 1.10) * Django (1.8, 1.9, 1.10, 1.11)
The following packages are optional: The following packages are optional:
@ -108,7 +108,7 @@ Install using `pip`, including any optional packages you want...
...or clone the project from github. ...or clone the project from github.
git clone git@github.com:tomchristie/django-rest-framework.git git clone git@github.com:encode/django-rest-framework.git
Add `'rest_framework'` to your `INSTALLED_APPS` setting. Add `'rest_framework'` to your `INSTALLED_APPS` setting.
@ -310,8 +310,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[markdown]: http://pypi.python.org/pypi/Markdown/ [markdown]: http://pypi.python.org/pypi/Markdown/
[django-filter]: http://pypi.python.org/pypi/django-filter [django-filter]: http://pypi.python.org/pypi/django-filter
[django-crispy-forms]: https://github.com/maraujop/django-crispy-forms [django-crispy-forms]: https://github.com/maraujop/django-crispy-forms
[django-guardian]: https://github.com/lukaszb/django-guardian [django-guardian]: https://github.com/django-guardian/django-guardian
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [0.4]: https://github.com/encode/django-rest-framework/tree/0.4.X
[image]: img/quickstart.png [image]: img/quickstart.png
[index]: . [index]: .
[oauth1-section]: api-guide/authentication/#django-rest-framework-oauth [oauth1-section]: api-guide/authentication/#django-rest-framework-oauth

View File

@ -155,5 +155,5 @@ From version 2.2 onwards, serializers with hyperlinked relationships *always* re
[mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[django-rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs [django-rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs
[marcgibbons]: https://github.com/marcgibbons/ [marcgibbons]: https://github.com/marcgibbons/
[issues]: https://github.com/tomchristie/django-rest-framework/issues [issues]: https://github.com/encode/django-rest-framework/issues
[564]: https://github.com/tomchristie/django-rest-framework/issues/564 [564]: https://github.com/encode/django-rest-framework/issues/564

View File

@ -167,6 +167,6 @@ Once again, many thanks to all the generous [backers and sponsors][kickstarter-s
[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions [view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions
[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified [client-ip-identification]: ../api-guide/throttling#how-clients-are-identified
[2-3-announcement]: 2.3-announcement [2-3-announcement]: 2.3-announcement
[github-labels]: https://github.com/tomchristie/django-rest-framework/issues [github-labels]: https://github.com/encode/django-rest-framework/issues
[github-milestones]: https://github.com/tomchristie/django-rest-framework/milestones [github-milestones]: https://github.com/encode/django-rest-framework/milestones
[kickstarter-sponsors]: kickstarter-announcement#sponsors [kickstarter-sponsors]: kickstarter-announcement#sponsors

View File

@ -957,9 +957,9 @@ The 3.1 release is planned to address improvements in the following components:
The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API. The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API.
You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/tomchristie/django-rest-framework/milestones). You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/encode/django-rest-framework/milestones).
[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3 [kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors [sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py [mixins.py]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files [django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files

View File

@ -8,7 +8,7 @@ This interface is intended to act as a more user-friendly interface to the API.
We've also fixed a huge number of issues, and made numerous cleanups and improvements. We've also fixed a huge number of issues, and made numerous cleanups and improvements.
Over the course of the 3.1.x series we've [resolved nearly 600 tickets](https://github.com/tomchristie/django-rest-framework/issues?utf8=%E2%9C%93&q=closed%3A%3E2015-03-05) on our GitHub issue tracker. This means we're currently running at a rate of **closing around 100 issues or pull requests per month**. Over the course of the 3.1.x series we've [resolved nearly 600 tickets](https://github.com/encode/django-rest-framework/issues?utf8=%E2%9C%93&q=closed%3A%3E2015-03-05) on our GitHub issue tracker. This means we're currently running at a rate of **closing around 100 issues or pull requests per month**.
None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors) and finding out who's hiring. None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors) and finding out who's hiring.

View File

@ -53,6 +53,6 @@ The following pagination view attributes and settings have been moved into attri
The `ModelSerializer` and `HyperlinkedModelSerializer` classes should now include either a `fields` or `exclude` option, although the `fields = '__all__'` shortcut may be used. Failing to include either of these two options is currently pending deprecation, and will be removed entirely in the 3.5 release. This behavior brings `ModelSerializer` more closely in line with Django's `ModelForm` behavior. The `ModelSerializer` and `HyperlinkedModelSerializer` classes should now include either a `fields` or `exclude` option, although the `fields = '__all__'` shortcut may be used. Failing to include either of these two options is currently pending deprecation, and will be removed entirely in the 3.5 release. This behavior brings `ModelSerializer` more closely in line with Django's `ModelForm` behavior.
[forms-api]: html-and-forms.md [forms-api]: html-and-forms.md
[ajax-form]: https://github.com/tomchristie/ajax-form [ajax-form]: https://github.com/encode/ajax-form
[jsonfield]: ../../api-guide/fields#jsonfield [jsonfield]: ../../api-guide/fields#jsonfield
[django-supported-versions]: https://www.djangoproject.com/download/#supported-versions [django-supported-versions]: https://www.djangoproject.com/download/#supported-versions

View File

@ -188,7 +188,7 @@ The full set of itemized release notes [are available here][release-notes].
[tut-7]: ../../tutorial/7-schemas-and-client-libraries/ [tut-7]: ../../tutorial/7-schemas-and-client-libraries/
[schema-generation]: ../../api-guide/schemas/ [schema-generation]: ../../api-guide/schemas/
[api-clients]: api-clients.md [api-clients]: api-clients.md
[milestone]: https://github.com/tomchristie/django-rest-framework/milestone/35 [milestone]: https://github.com/encode/django-rest-framework/milestone/35
[release-notes]: release-notes#34 [release-notes]: release-notes#34
[metadata]: ../../api-guide/metadata/#custom-metadata-classes [metadata]: ../../api-guide/metadata/#custom-metadata-classes
[gh3751]: https://github.com/tomchristie/django-rest-framework/issues/3751 [gh3751]: https://github.com/encode/django-rest-framework/issues/3751

View File

@ -261,6 +261,6 @@ in version 3.3 and raised a deprecation warning in 3.4. Its usage is now mandato
[schema-generation-api]: ../api-guide/schemas/#schemagenerator [schema-generation-api]: ../api-guide/schemas/#schemagenerator
[schema-docs]: ../api-guide/schemas/#schemas-as-documentation [schema-docs]: ../api-guide/schemas/#schemas-as-documentation
[schema-view]: ../api-guide/schemas/#the-get_schema_view-shortcut [schema-view]: ../api-guide/schemas/#the-get_schema_view-shortcut
[django-rest-raml]: https://github.com/tomchristie/django-rest-raml [django-rest-raml]: https://github.com/encode/django-rest-raml
[raml-image]: ../img/raml.png [raml-image]: ../img/raml.png
[raml-codec]: https://github.com/core-api/python-raml-codec [raml-codec]: https://github.com/core-api/python-raml-codec

View File

@ -146,8 +146,6 @@ An alternative, but more complex option would be to replace the input with an au
There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light], that you may want to refer to. Note that you will not be able to simply include these components as standard widgets, but will need to write the HTML template explicitly. This is because REST framework 3.0 no longer supports the `widget` keyword argument since it now uses templated HTML generation. There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light], that you may want to refer to. Note that you will not be able to simply include these components as standard widgets, but will need to write the HTML template explicitly. This is because REST framework 3.0 no longer supports the `widget` keyword argument since it now uses templated HTML generation.
Better support for autocomplete inputs is planned in future versions.
--- ---
[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead [cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead

View File

@ -81,7 +81,7 @@ was later [dropped from the spec][html5]. There remains
as well as how to support content types other than form-encoded data. as well as how to support content types other than form-encoded data.
[cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260 [cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
[ajax-form]: https://github.com/tomchristie/ajax-form [ajax-form]: https://github.com/encode/ajax-form
[rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work [rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
[html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24 [html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24
[put_delete]: http://amundsen.com/examples/put-delete-forms/ [put_delete]: http://amundsen.com/examples/put-delete-forms/

View File

@ -50,7 +50,7 @@ Getting involved in triaging incoming issues is a good way to start contributing
To start developing on Django REST framework, clone the repo: To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git git clone git@github.com:encode/django-rest-framework.git
Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles. Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
@ -202,11 +202,11 @@ If you want to draw attention to a note or warning, use a pair of enclosing line
[code-of-conduct]: https://www.djangoproject.com/conduct/ [code-of-conduct]: https://www.djangoproject.com/conduct/
[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[so-filter]: http://stackexchange.com/filters/66475/rest-framework [so-filter]: http://stackexchange.com/filters/66475/rest-framework
[issues]: https://github.com/tomchristie/django-rest-framework/issues?state=open [issues]: https://github.com/encode/django-rest-framework/issues?state=open
[pep-8]: http://www.python.org/dev/peps/pep-0008/ [pep-8]: http://www.python.org/dev/peps/pep-0008/
[travis-status]: ../img/travis-status.png [travis-status]: ../img/travis-status.png
[pull-requests]: https://help.github.com/articles/using-pull-requests [pull-requests]: https://help.github.com/articles/using-pull-requests
[tox]: https://tox.readthedocs.io/en/latest/ [tox]: https://tox.readthedocs.io/en/latest/
[markdown]: http://daringfireball.net/projects/markdown/basics [markdown]: http://daringfireball.net/projects/markdown/basics
[docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [docs]: https://github.com/encode/django-rest-framework/tree/master/docs
[mou]: http://mouapp.com/ [mou]: http://mouapp.com/

View File

@ -329,7 +329,7 @@ For further enquires please contact <a href=mailto:funding@django-rest-framework
## Accountability ## Accountability
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/february-2017) and regularly include financial reports and cost breakdowns. In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](http://www.encode.io/reports/march-2017) and regularly include financial reports and cost breakdowns.
<!-- Begin MailChimp Signup Form --> <!-- Begin MailChimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css"> <link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">

View File

@ -81,7 +81,7 @@ If you're translating a new language you'll need to translate the existing REST
4. Edit the `django.po` file you've just copied, translating all the error messages. 4. Edit the `django.po` file you've just copied, translating all the error messages.
5. Run `manage.py compilemessages -l pt_BR` to make the translations 5. Run `manage.py compilemessages -l pt_BR` to make the translations
available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`. available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`.
6. Restart your development server to see the changes take effect. 6. Restart your development server to see the changes take effect.
@ -106,7 +106,7 @@ For API clients the most appropriate of these will typically be to use the `Acce
[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation [django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation
[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling [custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ [transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[django-po-source]: https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po [django-po-source]: https://raw.githubusercontent.com/encode/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference [django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference
[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS [django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS
[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name [django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name

View File

@ -35,5 +35,5 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram
[remoteok-io]: https://remoteok.io/remote-django-jobs [remoteok-io]: https://remoteok.io/remote-django-jobs
[remotepython-com]: https://www.remotepython.com/jobs/ [remotepython-com]: https://www.remotepython.com/jobs/
[drf-funding]: https://fund.django-rest-framework.org/topics/funding/ [drf-funding]: https://fund.django-rest-framework.org/topics/funding/
[submit-pr]: https://github.com/tomchristie/django-rest-framework [submit-pr]: https://github.com/encode/django-rest-framework
[anna-email]: mailto:anna@django-rest-framework.org [anna-email]: mailto:anna@django-rest-framework.org

View File

@ -17,9 +17,9 @@ We have a quarterly maintenance cycle where new members may join the maintenance
#### Current team #### Current team
The [maintenance team for Q4 2015](https://github.com/tomchristie/django-rest-framework/issues/2190): The [maintenance team for Q4 2015](https://github.com/encode/django-rest-framework/issues/2190):
* [@tomchristie](https://github.com/tomchristie/) * [@tomchristie](https://github.com/encode/)
* [@xordoquy](https://github.com/xordoquy/) (Release manager.) * [@xordoquy](https://github.com/xordoquy/) (Release manager.)
* [@carltongibson](https://github.com/carltongibson/) * [@carltongibson](https://github.com/carltongibson/)
* [@kevin-brown](https://github.com/kevin-brown/) * [@kevin-brown](https://github.com/kevin-brown/)
@ -104,9 +104,9 @@ The following template should be used for the description of the issue, and serv
Checklist: Checklist:
- [ ] Create pull request for [release notes](https://github.com/tomchristie/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/tomchristie/django-rest-framework/milestones/***). - [ ] Create pull request for [release notes](https://github.com/encode/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/encode/django-rest-framework/milestones/***).
- [ ] Update the translations from [transifex](http://www.django-rest-framework.org/topics/project-management/#translations). - [ ] Update the translations from [transifex](http://www.django-rest-framework.org/topics/project-management/#translations).
- [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/__init__.py). - [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py).
- [ ] Confirm with @tomchristie that release is finalized and ready to go. - [ ] Confirm with @tomchristie that release is finalized and ready to go.
- [ ] Ensure that release date is included in pull request. - [ ] Ensure that release date is included in pull request.
- [ ] Merge the release pull request. - [ ] Merge the release pull request.
@ -197,10 +197,10 @@ The following issues still need to be addressed:
* Document ownership and management of the security mailing list. * Document ownership and management of the security mailing list.
[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor [bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel [un-triaged]: https://github.com/encode/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ [transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[transifex-client]: https://pypi.python.org/pypi/transifex-client [transifex-client]: https://pypi.python.org/pypi/transifex-client
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations [translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
[github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162 [github-org]: https://github.com/encode/django-rest-framework/issues/2162
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework [mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework

File diff suppressed because it is too large Load Diff

View File

@ -190,6 +190,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. * [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. * [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF. * [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
* [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers.
### Permissions ### Permissions
@ -260,7 +261,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [drf-haystack][drf-haystack] - Haystack search for Django Rest Framework * [drf-haystack][drf-haystack] - Haystack search for Django Rest Framework
* [django-rest-framework-version-transforms][django-rest-framework-version-transforms] - Enables the use of delta transformations for versioning of DRF resource representations. * [django-rest-framework-version-transforms][django-rest-framework-version-transforms] - Enables the use of delta transformations for versioning of DRF resource representations.
* [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM. * [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM.
* [djangorest-alchemy][djangorest-alchemy] - SQLAlchemy support for REST framework.
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
@ -271,10 +272,10 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[pypi-register]: https://pypi.python.org/pypi?%3Aaction=register_form [pypi-register]: https://pypi.python.org/pypi?%3Aaction=register_form
[semver]: http://semver.org/ [semver]: http://semver.org/
[tox-docs]: https://tox.readthedocs.io/en/latest/ [tox-docs]: https://tox.readthedocs.io/en/latest/
[drf-compat]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/compat.py [drf-compat]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/compat.py
[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/ [rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
[drf-create-pr]: https://github.com/tomchristie/django-rest-framework/compare [drf-create-pr]: https://github.com/encode/django-rest-framework/compare
[drf-create-issue]: https://github.com/tomchristie/django-rest-framework/issues/new [drf-create-issue]: https://github.com/encode/django-rest-framework/issues/new
[authentication]: ../api-guide/authentication.md [authentication]: ../api-guide/authentication.md
[permissions]: ../api-guide/permissions.md [permissions]: ../api-guide/permissions.md
[third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages [third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages
@ -330,3 +331,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth [drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions [drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields [djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields
[drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless
[djangorest-alchemy]: https://github.com/dealertrack/djangorest-alchemy

View File

@ -2,6 +2,17 @@
There are a wide range of resources available for learning and using Django REST framework. We try to keep a comprehensive list available here. There are a wide range of resources available for learning and using Django REST framework. We try to keep a comprehensive list available here.
## Books
<div class="book-covers">
<a class="book-cover" href="https://hellowebapp.com/order/">
<img src="../../img/books/hwa-cover.png"/>
</a>
<a class="book-cover" href="https://www.twoscoopspress.com/products/two-scoops-of-django-1-11">
<img src="../../img/books/tsd-cover.png"/>
</a>
</div>
## Tutorials ## Tutorials
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework] * [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
@ -56,10 +67,6 @@ There are a wide range of resources available for learning and using Django REST
* [New Django Admin with DRF and EmberJS... What are the News?][new-django-admin-with-drf-and-emberjs] * [New Django Admin with DRF and EmberJS... What are the News?][new-django-admin-with-drf-and-emberjs]
* [Blog posts about Django REST Framework][medium-django-rest-framework] * [Blog posts about Django REST Framework][medium-django-rest-framework]
## Books
* [Hello Web App: Intermediate Concepts, Chapter 10][hello-web-app-intermediate]
### Documentations ### Documentations
* [Classy Django REST Framework][cdrf.co] * [Classy Django REST Framework][cdrf.co]
* [DRF-schema-adapter][drf-schema] * [DRF-schema-adapter][drf-schema]
@ -95,7 +102,6 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[drf-schema]: http://drf-schema-adapter.readthedocs.io/en/latest/ [drf-schema]: http://drf-schema-adapter.readthedocs.io/en/latest/
[creating-a-production-ready-api-with-python-and-drf-part1]: https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/ [creating-a-production-ready-api-with-python-and-drf-part1]: https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/
[creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/ [creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/
[hello-web-app-intermediate]: https://hellowebapp.com/order/
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ [django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk [full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww [drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww
@ -106,5 +112,5 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[drf-an-intro]: https://realpython.com/blog/python/django-rest-framework-quick-start/ [drf-an-intro]: https://realpython.com/blog/python/django-rest-framework-quick-start/
[drf-tutorial]: https://tests4geeks.com/django-rest-framework-tutorial/ [drf-tutorial]: https://tests4geeks.com/django-rest-framework-tutorial/
[building-a-restful-api-with-drf]: http://agiliq.com/blog/2014/12/building-a-restful-api-with-django-rest-framework/ [building-a-restful-api-with-drf]: http://agiliq.com/blog/2014/12/building-a-restful-api-with-django-rest-framework/
[submit-pr]: https://github.com/tomchristie/django-rest-framework [submit-pr]: https://github.com/encode/django-rest-framework
[anna-email]: mailto:anna@django-rest-framework.org [anna-email]: mailto:anna@django-rest-framework.org

View File

@ -310,7 +310,7 @@ Quit out of the shell...
Validating models... Validating models...
0 errors found 0 errors found
Django version 1.8.3, using settings 'tutorial.settings' Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/ Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C. Quit the server with CONTROL-C.
@ -373,7 +373,7 @@ Our API views don't do anything particularly special at the moment, beyond servi
We'll see how we can start to improve things in [part 2 of the tutorial][tut-2]. We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[quickstart]: quickstart.md [quickstart]: quickstart.md
[repo]: https://github.com/tomchristie/rest-framework-tutorial [repo]: https://github.com/encode/rest-framework-tutorial
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[virtualenv]: http://www.virtualenv.org/en/latest/index.html [virtualenv]: http://www.virtualenv.org/en/latest/index.html
[tut-2]: 2-requests-and-responses.md [tut-2]: 2-requests-and-responses.md

View File

@ -36,14 +36,16 @@ API schema.
We can now include a schema for our API, by including an autogenerated schema We can now include a schema for our API, by including an autogenerated schema
view in our URL configuration. view in our URL configuration.
```
from rest_framework.schemas import get_schema_view 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 = [ urlpatterns = [
       url(r'^schema/$', schema_view),    url(r'^schema/$', schema_view),
... ...
] ]
```
If you visit the API root endpoint in a browser you should now see `corejson` If you visit the API root endpoint in a browser you should now see `corejson`
representation become available as an option. representation become available as an option.
@ -221,8 +223,8 @@ We've reached the end of our tutorial. If you want to get more involved in the
[coreapi]: http://www.coreapi.org [coreapi]: http://www.coreapi.org
[corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding [corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding
[openapi]: https://openapis.org/ [openapi]: https://openapis.org/
[repo]: https://github.com/tomchristie/rest-framework-tutorial [repo]: https://github.com/encode/rest-framework-tutorial
[sandbox]: http://restframework.herokuapp.com/ [sandbox]: http://restframework.herokuapp.com/
[github]: https://github.com/tomchristie/django-rest-framework [github]: https://github.com/encode/django-rest-framework
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[twitter]: https://twitter.com/_tomchristie [twitter]: https://twitter.com/_tomchristie

View File

@ -417,3 +417,8 @@ ul.sponsor {
.toclink { .toclink {
color: #333; color: #333;
} }
.book-cover img {
margin: 0 !important;
display: inline-block !important;
}

View File

@ -114,7 +114,7 @@
{% block content %} {% block content %}
{% if page.meta.source %} {% if page.meta.source %}
{% for filename in page.meta.source %} {% for filename in page.meta.source %}
<a class="github" href="https://github.com/tomchristie/django-rest-framework/tree/master/rest_framework/{{ filename }}"> <a class="github" href="https://github.com/encode/django-rest-framework/tree/master/rest_framework/{{ filename }}">
<span class="label label-info">{{ filename }}</span> <span class="label label-info">{{ filename }}</span>
</a> </a>
{% endfor %} {% endfor %}

View File

@ -1,7 +1,7 @@
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a> <a class="repo-link btn btn-primary btn-small" href="https://github.com/encode/django-rest-framework/tree/master">GitHub</a>
<a class="repo-link btn btn-inverse btn-small {% if not page.next_page %}disabled{% endif %}" rel="prev" {% if page.next_page %}href="{{ page.next_page.url }}"{% endif %}> <a class="repo-link btn btn-inverse btn-small {% if not page.next_page %}disabled{% endif %}" rel="prev" {% if page.next_page %}href="{{ page.next_page.url }}"{% endif %}>
Next <i class="icon-arrow-right icon-white"></i> Next <i class="icon-arrow-right icon-white"></i>
</a> </a>

View File

@ -2,7 +2,7 @@ site_name: Django REST framework
site_url: http://www.django-rest-framework.org/ site_url: http://www.django-rest-framework.org/
site_description: Django REST framework - Web APIs for Django site_description: Django REST framework - Web APIs for Django
repo_url: https://github.com/tomchristie/django-rest-framework repo_url: https://github.com/encode/django-rest-framework
theme_dir: docs_theme theme_dir: docs_theme

View File

@ -1,6 +1,6 @@
# Optional packages which may be used with REST framework. # Optional packages which may be used with REST framework.
markdown==2.6.4 markdown==2.6.4
django-guardian==1.4.6 django-guardian==1.4.8
django-filter==1.0.0 django-filter==1.0.2
coreapi==2.2.4 coreapi==2.2.4
coreschema==0.0.4 coreschema==0.0.4

View File

@ -24,7 +24,7 @@ class Token(models.Model):
# https://code.djangoproject.com/ticket/19422 # https://code.djangoproject.com/ticket/19422
# #
# Also see corresponding ticket: # Also see corresponding ticket:
# https://github.com/tomchristie/django-rest-framework/issues/705 # https://github.com/encode/django-rest-framework/issues/705
abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS
verbose_name = _("Token") verbose_name = _("Token")
verbose_name_plural = _("Tokens") verbose_name_plural = _("Tokens")

View File

@ -275,6 +275,14 @@ except ImportError:
def pygments_css(style): def pygments_css(style):
return None return None
try:
import pytz
from pytz.exceptions import InvalidTimeError
except ImportError:
InvalidTimeError = Exception
# `separators` argument to `json.dumps()` differs between 2.x and 3.x # `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767 # See: http://bugs.python.org/issue22767
if six.PY3: if six.PY3:
@ -339,6 +347,7 @@ def set_many(instance, field, value):
field = getattr(instance, field) field = getattr(instance, field)
field.set(value) field.set(value)
def include(module, namespace=None, app_name=None): def include(module, namespace=None, app_name=None):
from django.conf.urls import include from django.conf.urls import include
if django.VERSION < (1,9): if django.VERSION < (1,9):

View File

@ -3,10 +3,12 @@ from django.conf.urls import include, url
from rest_framework.renderers import ( from rest_framework.renderers import (
CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
) )
from rest_framework.schemas import get_schema_view from rest_framework.schemas import SchemaGenerator, get_schema_view
def get_docs_view(title=None, description=None, schema_url=None, public=True): def get_docs_view(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
renderer_classes = [DocumentationRenderer, CoreJSONRenderer] renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
return get_schema_view( return get_schema_view(
@ -14,11 +16,15 @@ def get_docs_view(title=None, description=None, schema_url=None, public=True):
url=schema_url, url=schema_url,
description=description, description=description,
renderer_classes=renderer_classes, renderer_classes=renderer_classes,
public=public public=public,
patterns=patterns,
generator_class=generator_class,
) )
def get_schemajs_view(title=None, description=None, schema_url=None, public=True): def get_schemajs_view(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
renderer_classes = [SchemaJSRenderer] renderer_classes = [SchemaJSRenderer]
return get_schema_view( return get_schema_view(
@ -26,22 +32,30 @@ def get_schemajs_view(title=None, description=None, schema_url=None, public=True
url=schema_url, url=schema_url,
description=description, description=description,
renderer_classes=renderer_classes, renderer_classes=renderer_classes,
public=public public=public,
patterns=patterns,
generator_class=generator_class,
) )
def include_docs_urls(title=None, description=None, schema_url=None, public=True): def include_docs_urls(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
docs_view = get_docs_view( docs_view = get_docs_view(
title=title, title=title,
description=description, description=description,
schema_url=schema_url, schema_url=schema_url,
public=public public=public,
patterns=patterns,
generator_class=generator_class,
) )
schema_js_view = get_schemajs_view( schema_js_view = get_schemajs_view(
title=title, title=title,
description=description, description=description,
schema_url=schema_url, schema_url=schema_url,
public=public public=public,
patterns=patterns,
generator_class=generator_class,
) )
urls = [ urls = [
url(r'^$', docs_view, name='docs-index'), url(r'^$', docs_view, name='docs-index'),

View File

@ -33,7 +33,8 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601 from rest_framework import ISO_8601
from rest_framework.compat import ( from rest_framework.compat import (
get_remote_field, unicode_repr, unicode_to_repr, value_from_object InvalidTimeError, get_remote_field, unicode_repr, unicode_to_repr,
value_from_object
) )
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -618,8 +619,8 @@ class Field(object):
originally created with, rather than copying the complete state. originally created with, rather than copying the complete state.
""" """
# Treat regexes and validators as immutable. # Treat regexes and validators as immutable.
# See https://github.com/tomchristie/django-rest-framework/issues/1954 # See https://github.com/encode/django-rest-framework/issues/1954
# and https://github.com/tomchristie/django-rest-framework/pull/4489 # and https://github.com/encode/django-rest-framework/pull/4489
args = [ args = [
copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item copy.deepcopy(item) if not isinstance(item, REGEX_TYPE) else item
for item in self._args for item in self._args
@ -649,6 +650,7 @@ class BooleanField(Field):
initial = False initial = False
TRUE_VALUES = { TRUE_VALUES = {
't', 'T', 't', 'T',
'y', 'Y', 'yes', 'YES',
'true', 'True', 'TRUE', 'true', 'True', 'TRUE',
'on', 'On', 'ON', 'on', 'On', 'ON',
'1', 1, '1', 1,
@ -656,6 +658,7 @@ class BooleanField(Field):
} }
FALSE_VALUES = { FALSE_VALUES = {
'f', 'F', 'f', 'F',
'n', 'N', 'no', 'NO',
'false', 'False', 'FALSE', 'false', 'False', 'FALSE',
'off', 'Off', 'OFF', 'off', 'Off', 'OFF',
'0', 0, 0.0, '0', 0, 0.0,
@ -1085,6 +1088,7 @@ class DateTimeField(Field):
default_error_messages = { default_error_messages = {
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'), 'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
'date': _('Expected a datetime but got a date.'), 'date': _('Expected a datetime but got a date.'),
'make_aware': _('Invalid datetime for the timezone "{timezone}".')
} }
datetime_parser = datetime.datetime.strptime datetime_parser = datetime.datetime.strptime
@ -1105,7 +1109,10 @@ class DateTimeField(Field):
field_timezone = getattr(self, 'timezone', self.default_timezone()) 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) and not timezone.is_aware(value):
return timezone.make_aware(value, field_timezone) try:
return timezone.make_aware(value, field_timezone)
except InvalidTimeError:
self.fail('make_aware', timezone=field_timezone)
elif (field_timezone is None) and timezone.is_aware(value): elif (field_timezone is None) and timezone.is_aware(value):
return timezone.make_naive(value, utc) return timezone.make_naive(value, utc)
return value return value

View File

@ -50,12 +50,17 @@ if django_filters:
DeprecationWarning DeprecationWarning
) )
return super(FilterSet, self).__init__(*args, **kwargs) return super(FilterSet, self).__init__(*args, **kwargs)
DFBase = django_filters.rest_framework.DjangoFilterBackend
else: else:
def FilterSet(): def FilterSet():
assert False, 'django-filter must be installed to use the `FilterSet` class' assert False, 'django-filter must be installed to use the `FilterSet` class'
DFBase = BaseFilterBackend
class DjangoFilterBackend(BaseFilterBackend):
class DjangoFilterBackend(DFBase):
""" """
A filter backend that uses django-filter. A filter backend that uses django-filter.
""" """
@ -69,9 +74,7 @@ class DjangoFilterBackend(BaseFilterBackend):
DeprecationWarning DeprecationWarning
) )
from django_filters.rest_framework import DjangoFilterBackend return super(DjangoFilterBackend, cls).__new__(cls, *args, **kwargs)
return DjangoFilterBackend(*args, **kwargs)
class SearchFilter(BaseFilterBackend): class SearchFilter(BaseFilterBackend):
@ -323,7 +326,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
# We want to defer this import until run-time, rather than import-time. # We want to defer this import until run-time, rather than import-time.
# See https://github.com/tomchristie/django-rest-framework/issues/4608 # See https://github.com/encode/django-rest-framework/issues/4608
# (Also see #1624 for why we need to make this import explicitly) # (Also see #1624 for why we need to make this import explicitly)
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user

View File

@ -3,6 +3,7 @@ Generic views that provide commonly needed behaviour.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 as _get_object_or_404 from django.shortcuts import get_object_or_404 as _get_object_or_404
@ -18,7 +19,7 @@ def get_object_or_404(queryset, *filter_args, **filter_kwargs):
""" """
try: try:
return _get_object_or_404(queryset, *filter_args, **filter_kwargs) return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
except (TypeError, ValueError): except (TypeError, ValueError, ValidationError):
raise Http404 raise Http404

View File

@ -332,12 +332,12 @@ class LimitOffsetPagination(BasePagination):
template = 'rest_framework/pagination/numbers.html' template = 'rest_framework/pagination/numbers.html'
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
self.count = _get_count(queryset)
self.limit = self.get_limit(request) self.limit = self.get_limit(request)
if self.limit is None: if self.limit is None:
return None return None
self.offset = self.get_offset(request) self.offset = self.get_offset(request)
self.count = _get_count(queryset)
self.request = request self.request = request
if self.count > self.limit and self.template is not None: if self.count > self.limit and self.template is not None:
self.display_page_controls = True self.display_page_controls = True

View File

@ -7,7 +7,9 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db.models import Manager from django.db.models import Manager
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils import six from django.utils import six
from django.utils.encoding import python_2_unicode_compatible, smart_text from django.utils.encoding import (
python_2_unicode_compatible, smart_text, uri_to_iri
)
from django.utils.six.moves.urllib import parse as urlparse from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -324,6 +326,8 @@ class HyperlinkedRelatedField(RelatedField):
if data.startswith(prefix): if data.startswith(prefix):
data = '/' + data[len(prefix):] data = '/' + data[len(prefix):]
data = uri_to_iri(data)
try: try:
match = resolve(data) match = resolve(data)
except Resolver404: except Resolver404:

View File

@ -316,6 +316,7 @@ class DefaultRouter(SimpleRouter):
default_schema_renderers = None default_schema_renderers = None
APIRootView = APIRootView APIRootView = APIRootView
APISchemaView = SchemaView APISchemaView = SchemaView
SchemaGenerator = SchemaGenerator
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'schema_title' in kwargs: if 'schema_title' in kwargs:
@ -342,7 +343,7 @@ class DefaultRouter(SimpleRouter):
""" """
Return a schema root view. Return a schema root view.
""" """
schema_generator = SchemaGenerator( schema_generator = self.SchemaGenerator(
title=self.schema_title, title=self.schema_title,
url=self.schema_url, url=self.schema_url,
patterns=api_urls patterns=api_urls

View File

@ -246,7 +246,9 @@ class EndpointInspector(object):
Return a list of the valid HTTP methods for this endpoint. Return a list of the valid HTTP methods for this endpoint.
""" """
if hasattr(callback, 'actions'): if hasattr(callback, 'actions'):
return [method.upper() for method in callback.actions.keys()] actions = set(callback.actions.keys())
http_method_names = set(callback.cls.http_method_names)
return [method.upper() for method in actions & http_method_names]
return [ return [
method for method in method for method in
@ -604,7 +606,7 @@ class SchemaGenerator(object):
return [] return []
pagination = getattr(view, 'pagination_class', None) pagination = getattr(view, 'pagination_class', None)
if not pagination or not pagination.page_size: if not pagination or not getattr(pagination, 'page_size', None):
return [] return []
paginator = view.pagination_class() paginator = view.pagination_class()
@ -694,11 +696,16 @@ class SchemaView(APIView):
return Response(schema) return Response(schema)
def get_schema_view(title=None, url=None, description=None, urlconf=None, renderer_classes=None, public=False): def get_schema_view(
title=None, url=None, description=None, urlconf=None, renderer_classes=None,
public=False, patterns=None, generator_class=SchemaGenerator):
""" """
Return a schema view. Return a schema view.
""" """
generator = SchemaGenerator(title=title, url=url, description=description, urlconf=urlconf) generator = generator_class(
title=title, url=url, description=description,
urlconf=urlconf, patterns=patterns,
)
return SchemaView.as_view( return SchemaView.as_view(
renderer_classes=renderer_classes, renderer_classes=renderer_classes,
schema_generator=generator, schema_generator=generator,

View File

@ -38,7 +38,8 @@ from rest_framework.utils.field_mapping import (
get_relation_kwargs, get_url_kwargs get_relation_kwargs, get_url_kwargs
) )
from rest_framework.utils.serializer_helpers import ( from rest_framework.utils.serializer_helpers import (
BindingDict, BoundField, NestedBoundField, ReturnDict, ReturnList BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
ReturnList
) )
from rest_framework.validators import ( from rest_framework.validators import (
UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator, UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
@ -521,6 +522,8 @@ class Serializer(BaseSerializer):
error = self.errors.get(key) if hasattr(self, '_errors') else None error = self.errors.get(key) if hasattr(self, '_errors') else None
if isinstance(field, Serializer): if isinstance(field, Serializer):
return NestedBoundField(field, value, error) return NestedBoundField(field, value, error)
if isinstance(field, JSONField):
return JSONBoundField(field, value, error)
return BoundField(field, value, error) return BoundField(field, value, error)
# Include a backlink to the serializer class on return objects. # Include a backlink to the serializer class on return objects.
@ -562,6 +565,10 @@ class ListSerializer(BaseSerializer):
super(ListSerializer, self).__init__(*args, **kwargs) super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self) self.child.bind(field_name='', parent=self)
def bind(self, field_name, parent):
super(ListSerializer, self).bind(field_name, parent)
self.partial = self.parent.partial
def get_initial(self): def get_initial(self):
if hasattr(self, 'initial_data'): if hasattr(self, 'initial_data'):
return self.to_representation(self.initial_data) return self.to_representation(self.initial_data)
@ -613,6 +620,9 @@ class ListSerializer(BaseSerializer):
}, code='not_a_list') }, code='not_a_list')
if not self.allow_empty and len(data) == 0: if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
message = self.error_messages['empty'] message = self.error_messages['empty']
raise ValidationError({ raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message] api_settings.NON_FIELD_ERRORS_KEY: [message]

View File

@ -9,7 +9,8 @@ REST_FRAMEWORK = {
) )
'DEFAULT_PARSER_CLASSES': ( 'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser', 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.TemplateHTMLRenderer', 'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
) )
} }

View File

@ -24,11 +24,12 @@ def get_breadcrumbs(url, request=None):
# Check if this is a REST framework view, # Check if this is a REST framework view,
# and if so add it to the breadcrumbs # and if so add it to the breadcrumbs
cls = getattr(view, 'cls', None) cls = getattr(view, 'cls', None)
initkwargs = getattr(view, 'initkwargs', {})
if cls is not None and issubclass(cls, APIView): if cls is not None and issubclass(cls, APIView):
# Don't list the same view twice in a row. # Don't list the same view twice in a row.
# Probably an optional trailing slash. # Probably an optional trailing slash.
if not seen or seen[-1] != view: if not seen or seen[-1] != view:
c = cls() c = cls(**initkwargs)
c.suffix = getattr(view, 'suffix', None) c.suffix = getattr(view, 'suffix', None)
name = c.get_view_name() name = c.get_view_name()
insert_url = preserve_builtin_query_params(prefix + url, request) insert_url = preserve_builtin_query_params(prefix + url, request)

View File

@ -8,7 +8,7 @@ from django.core import validators
from django.db import models from django.db import models
from django.utils.text import capfirst from django.utils.text import capfirst
from rest_framework.compat import DecimalValidator from rest_framework.compat import DecimalValidator, JSONField
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
NUMERIC_FIELD_TYPES = ( NUMERIC_FIELD_TYPES = (
@ -88,7 +88,7 @@ def get_field_kwargs(field_name, model_field):
if decimal_places is not None: if decimal_places is not None:
kwargs['decimal_places'] = decimal_places kwargs['decimal_places'] = decimal_places
if isinstance(model_field, models.TextField): if isinstance(model_field, models.TextField) or (JSONField and isinstance(model_field, JSONField)):
kwargs['style'] = {'base_template': 'textarea.html'} kwargs['style'] = {'base_template': 'textarea.html'}
if isinstance(model_field, models.AutoField) or not model_field.editable: if isinstance(model_field, models.AutoField) or not model_field.editable:
@ -123,18 +123,70 @@ def get_field_kwargs(field_name, model_field):
kwargs['allow_folders'] = model_field.allow_folders kwargs['allow_folders'] = model_field.allow_folders
if model_field.choices: if model_field.choices:
# If this model field contains choices, then return early.
# Further keyword arguments are not valid.
kwargs['choices'] = model_field.choices kwargs['choices'] = model_field.choices
return kwargs else:
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
max_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxValueValidator)
]
# Our decimal validation is handled in the field code, not validator code. # Ensure that min_value is passed explicitly as a keyword arg,
# (In Django 1.9+ this differs from previous style) # rather than as a validator.
if isinstance(model_field, models.DecimalField) and DecimalValidator: min_value = next((
validator_kwarg = [ validator.limit_value for validator in validator_kwarg
validator for validator in validator_kwarg if isinstance(validator, validators.MinValueValidator)
if not isinstance(validator, DecimalValidator) ), None)
] if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinValueValidator)
]
# URLField does not need to include the URLValidator argument,
# as it is explicitly added in.
if isinstance(model_field, models.URLField):
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.URLValidator)
]
# EmailField does not need to include the validate_email argument,
# as it is explicitly added in.
if isinstance(model_field, models.EmailField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_email
]
# SlugField do not need to include the 'validate_slug' argument,
if isinstance(model_field, models.SlugField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_slug
]
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
if isinstance(model_field, models.GenericIPAddressField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_ipv46_address
]
# Our decimal validation is handled in the field code, not validator code.
# (In Django 1.9+ this differs from previous style)
if isinstance(model_field, models.DecimalField) and DecimalValidator:
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, DecimalValidator)
]
# Ensure that max_length is passed explicitly as a keyword arg, # Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator. # rather than as a validator.
@ -160,62 +212,6 @@ def get_field_kwargs(field_name, model_field):
if not isinstance(validator, validators.MinLengthValidator) if not isinstance(validator, validators.MinLengthValidator)
] ]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
max_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MaxValueValidator)
), None)
if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['max_value'] = max_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MaxValueValidator)
]
# Ensure that max_value is passed explicitly as a keyword arg,
# rather than as a validator.
min_value = next((
validator.limit_value for validator in validator_kwarg
if isinstance(validator, validators.MinValueValidator)
), None)
if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
kwargs['min_value'] = min_value
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.MinValueValidator)
]
# URLField does not need to include the URLValidator argument,
# as it is explicitly added in.
if isinstance(model_field, models.URLField):
validator_kwarg = [
validator for validator in validator_kwarg
if not isinstance(validator, validators.URLValidator)
]
# EmailField does not need to include the validate_email argument,
# as it is explicitly added in.
if isinstance(model_field, models.EmailField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_email
]
# SlugField do not need to include the 'validate_slug' argument,
if isinstance(model_field, models.SlugField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_slug
]
# IPAddressField do not need to include the 'validate_ipv46_address' argument,
if isinstance(model_field, models.GenericIPAddressField):
validator_kwarg = [
validator for validator in validator_kwarg
if validator is not validators.validate_ipv46_address
]
if getattr(model_field, 'unique', False): if getattr(model_field, 'unique', False):
unique_error_message = model_field.error_messages.get('unique', None) unique_error_message = model_field.error_messages.get('unique', None)
if unique_error_message: if unique_error_message:

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import collections import collections
import json
from collections import OrderedDict from collections import OrderedDict
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -82,6 +83,16 @@ class BoundField(object):
return self.__class__(self._field, value, self.errors, self._prefix) return self.__class__(self._field, value, self.errors, self._prefix)
class JSONBoundField(BoundField):
def as_form_field(self):
value = self.value
try:
value = json.dumps(self.value, sort_keys=True, indent=4)
except TypeError:
pass
return self.__class__(self._field, value, self.errors, self._prefix)
class NestedBoundField(BoundField): class NestedBoundField(BoundField):
""" """
This `BoundField` additionally implements __iter__ and __getitem__ This `BoundField` additionally implements __iter__ and __getitem__
@ -101,7 +112,7 @@ class NestedBoundField(BoundField):
def __getitem__(self, key): def __getitem__(self, key):
field = self.fields[key] field = self.fields[key]
value = self.value.get(key) if self.value else None value = self.value.get(key) if self.value else None
error = self.errors.get(key) if self.errors else None error = self.errors.get(key) if isinstance(self.errors, dict) else None
if hasattr(field, 'fields'): if hasattr(field, 'fields'):
return NestedBoundField(field, value, error, prefix=self.name + '.') return NestedBoundField(field, value, error, prefix=self.name + '.')
return BoundField(field, value, error, prefix=self.name + '.') return BoundField(field, value, error, prefix=self.name + '.')

View File

@ -18,7 +18,7 @@ from rest_framework.utils.representation import smart_repr
# Robust filter and exist implementations. Ensures that queryset.exists() for # Robust filter and exist implementations. Ensures that queryset.exists() for
# an invalid value returns `False`, rather than raising an error. # an invalid value returns `False`, rather than raising an error.
# Refs https://github.com/tomchristie/django-rest-framework/issues/3381 # Refs https://github.com/encode/django-rest-framework/issues/3381
def qs_exists(queryset): def qs_exists(queryset):
try: try:

View File

@ -9,6 +9,7 @@ from django.db import models
from django.http import Http404 from django.http import Http404
from django.http.response import HttpResponseBase from django.http.response import HttpResponseBase
from django.utils import six from django.utils import six
from django.utils.cache import cc_delim_re, patch_vary_headers
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -290,7 +291,7 @@ class APIView(View):
""" """
Returns the exception handler that this view uses. Returns the exception handler that this view uses.
""" """
return api_settings.EXCEPTION_HANDLER return self.settings.EXCEPTION_HANDLER
# API policy implementation methods # API policy implementation methods
@ -414,6 +415,11 @@ class APIView(View):
response.accepted_media_type = request.accepted_media_type response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context() response.renderer_context = self.get_renderer_context()
# Add new vary headers to the response instead of overwriting.
vary_headers = self.headers.pop('Vary', None)
if vary_headers is not None:
patch_vary_headers(response, cc_delim_re.split(vary_headers))
for key, value in self.headers.items(): for key, value in self.headers.items():
response[key] = value response[key] = value

View File

@ -95,6 +95,7 @@ setup(
'Framework :: Django :: 1.8', 'Framework :: Django :: 1.8',
'Framework :: Django :: 1.9', 'Framework :: Django :: 1.9',
'Framework :: Django :: 1.10', 'Framework :: Django :: 1.10',
'Framework :: Django :: 1.11',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',

View File

@ -44,7 +44,7 @@ class TestManyPostView(TestCase):
POST request to a view that returns a list of objects should POST request to a view that returns a list of objects should
still successfully return the browsable API with a rendered form. still successfully return the browsable API with a rendered form.
Regression test for https://github.com/tomchristie/django-rest-framework/pull/3164 Regression test for https://github.com/encode/django-rest-framework/pull/3164
""" """
data = {} data = {}
request = factory.post('/', data, format='json') request = factory.post('/', data, format='json')

View File

@ -127,7 +127,7 @@ class BasicAuthTests(TestCase):
def test_regression_handle_bad_base64_basic_auth_header(self): def test_regression_handle_bad_base64_basic_auth_header(self):
"""Ensure POSTing JSON over basic auth with incorrectly padded Base64 string is handled correctly""" """Ensure POSTing JSON over basic auth with incorrectly padded Base64 string is handled correctly"""
# regression test for issue in 'rest_framework.authentication.BasicAuthentication.authenticate' # regression test for issue in 'rest_framework.authentication.BasicAuthentication.authenticate'
# https://github.com/tomchristie/django-rest-framework/issues/4089 # https://github.com/encode/django-rest-framework/issues/4089
auth = 'Basic =a=' auth = 'Basic =a='
response = self.csrf_client.post( response = self.csrf_client.post(
'/basic/', '/basic/',
@ -185,7 +185,7 @@ class SessionAuthTests(TestCase):
""" """
Ensure the login template renders for a basic GET. Ensure the login template renders for a basic GET.
cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810) cf. [#1810](https://github.com/encode/django-rest-framework/pull/1810)
""" """
response = self.csrf_client.get('/auth/login/') response = self.csrf_client.get('/auth/login/')
content = response.content.decode('utf8') content = response.content.decode('utf8')

View File

@ -96,7 +96,7 @@ class TestViewNamesAndDescriptions(TestCase):
Ensure a view may have a docstring that is actually a lazily evaluated Ensure a view may have a docstring that is actually a lazily evaluated
class that can be converted to a string. class that can be converted to a string.
See: https://github.com/tomchristie/django-rest-framework/issues/1708 See: https://github.com/encode/django-rest-framework/issues/1708
""" """
# use a mock object instead of gettext_lazy to ensure that we can't end # use a mock object instead of gettext_lazy to ensure that we can't end
# up with a test case string in our l10n catalog # up with a test case string in our l10n catalog

View File

@ -12,7 +12,7 @@ from django.utils import six
from django.utils.timezone import utc from django.utils.timezone import utc
import rest_framework import rest_framework
from rest_framework import serializers from rest_framework import compat, serializers
from rest_framework.fields import is_simple_callable from rest_framework.fields import is_simple_callable
try: try:
@ -1205,6 +1205,30 @@ class TestNaiveDateTimeField(FieldValues):
field = serializers.DateTimeField(default_timezone=None) field = serializers.DateTimeField(default_timezone=None)
class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues):
"""
Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.
Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and
from 2017-11-05T02:00:00 to 2017-11-05T01:00:00 in 2017.
"""
valid_inputs = {}
invalid_inputs = {
'2017-03-12T02:30:00': ['Invalid datetime for the timezone "America/New_York".'],
'2017-11-05T01:30:00': ['Invalid datetime for the timezone "America/New_York".']
}
outputs = {}
class MockTimezone:
@staticmethod
def localize(value, is_dst):
raise compat.InvalidTimeError()
def __str__(self):
return 'America/New_York'
field = serializers.DateTimeField(default_timezone=MockTimezone())
class TestTimeField(FieldValues): class TestTimeField(FieldValues):
""" """
Valid and invalid values for `TimeField`. Valid and invalid values for `TimeField`.

View File

@ -201,6 +201,21 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
assert response.data == self.data assert response.data == self.data
assert len(w) == 0 assert len(w) == 0
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_backend_mro(self):
class CustomBackend(filters.DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
assert False, "custom filter_queryset should run"
class DFFilterFieldsRootView(FilterFieldsRootView):
filter_backends = (CustomBackend,)
view = DFFilterFieldsRootView.as_view()
request = factory.get('/')
with pytest.raises(AssertionError, message="custom filter_queryset should run"):
view(request).render()
@unittest.skipUnless(django_filters, 'django-filter not installed') @unittest.skipUnless(django_filters, 'django-filter not installed')
def test_get_filtered_fields_root_view(self): def test_get_filtered_fields_root_view(self):
""" """

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import pytest import pytest
from django.db import models from django.db import models
from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
@ -10,7 +11,8 @@ from rest_framework import generics, renderers, serializers, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from tests.models import ( from tests.models import (
BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel,
UUIDForeignKeyTarget
) )
factory = APIRequestFactory() factory = APIRequestFactory()
@ -398,7 +400,7 @@ class TestCreateModelWithAutoNowAddField(TestCase):
""" """
Regression test for #285 Regression test for #285
https://github.com/tomchristie/django-rest-framework/issues/285 https://github.com/encode/django-rest-framework/issues/285
""" """
data = {'email': 'foobar@example.com', 'content': 'foobar'} data = {'email': 'foobar@example.com', 'content': 'foobar'}
request = factory.post('/', data, format='json') request = factory.post('/', data, format='json')
@ -647,3 +649,19 @@ class ApiViewsTests(TestCase):
view.delete('test request', 'test arg', test_kwarg='test') view.delete('test request', 'test arg', test_kwarg='test')
assert view.called is True assert view.called is True
assert view.call_args == data assert view.call_args == data
class GetObjectOr404Tests(TestCase):
def setUp(self):
super(GetObjectOr404Tests, self).setUp()
self.uuid_object = UUIDForeignKeyTarget.objects.create(name='bar')
def test_get_object_or_404_with_valid_uuid(self):
obj = generics.get_object_or_404(
UUIDForeignKeyTarget, pk=self.uuid_object.pk
)
assert obj == self.uuid_object
def test_get_object_or_404_with_invalid_string_for_uuid(self):
with pytest.raises(Http404):
generics.get_object_or_404(UUIDForeignKeyTarget, pk='not-a-uuid')

View File

@ -99,6 +99,15 @@ class Issue3674ChildModel(models.Model):
value = models.CharField(primary_key=True, max_length=64) value = models.CharField(primary_key=True, max_length=64)
class UniqueChoiceModel(models.Model):
CHOICES = (
('choice1', 'choice 1'),
('choice2', 'choice 1'),
)
name = models.CharField(max_length=254, unique=True, choices=CHOICES)
class TestModelSerializer(TestCase): class TestModelSerializer(TestCase):
def test_create_method(self): def test_create_method(self):
class TestSerializer(serializers.ModelSerializer): class TestSerializer(serializers.ModelSerializer):
@ -1080,3 +1089,16 @@ class Issue4897TestCase(TestCase):
with pytest.raises(AssertionError) as cm: with pytest.raises(AssertionError) as cm:
TestSerializer(obj).fields TestSerializer(obj).fields
cm.match(r'readonly_fields') cm.match(r'readonly_fields')
class Test5004UniqueChoiceField(TestCase):
def test_unique_choice_field(self):
class TestUniqueChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = UniqueChoiceModel
fields = '__all__'
UniqueChoiceModel.objects.create(name='choice1')
serializer = TestUniqueChoiceSerializer(data={'name': 'choice1'})
assert not serializer.is_valid()
assert serializer.errors == {'name': ['unique choice model with this name already exists.']}

View File

@ -43,7 +43,7 @@ class TestPrefetchRelatedUpdates(TestCase):
def test_prefetch_related_excluding_instance_from_original_queryset(self): def test_prefetch_related_excluding_instance_from_original_queryset(self):
""" """
Regression test for https://github.com/tomchristie/django-rest-framework/issues/4661 Regression test for https://github.com/encode/django-rest-framework/issues/4661
""" """
view = UserUpdate.as_view() view = UserUpdate.as_view()
pk = self.user.pk pk = self.user.pk

View File

@ -1,7 +1,9 @@
import uuid import uuid
import pytest import pytest
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from rest_framework import serializers from rest_framework import serializers
@ -87,10 +89,21 @@ class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
assert representation == self.instance.pk.int assert representation == self.instance.pk.int
@override_settings(ROOT_URLCONF=[
url(r'^example/(?P<name>.+)/$', lambda: None, name='example'),
])
class TestHyperlinkedRelatedField(APISimpleTestCase): class TestHyperlinkedRelatedField(APISimpleTestCase):
def setUp(self): def setUp(self):
self.queryset = MockQueryset([
MockObject(pk=1, name='foobar'),
MockObject(pk=2, name='baz qux'),
])
self.field = serializers.HyperlinkedRelatedField( self.field = serializers.HyperlinkedRelatedField(
view_name='example', read_only=True) view_name='example',
lookup_field='name',
lookup_url_kwarg='name',
queryset=self.queryset,
)
self.field.reverse = mock_reverse self.field.reverse = mock_reverse
self.field._context = {'request': True} self.field._context = {'request': True}
@ -98,6 +111,20 @@ class TestHyperlinkedRelatedField(APISimpleTestCase):
representation = self.field.to_representation(MockObject(pk='')) representation = self.field.to_representation(MockObject(pk=''))
assert representation is None assert representation is None
def test_hyperlinked_related_lookup_exists(self):
instance = self.field.to_internal_value('http://example.org/example/foobar/')
assert instance is self.queryset.items[0]
def test_hyperlinked_related_lookup_url_encoded_exists(self):
instance = self.field.to_internal_value('http://example.org/example/baz%20qux/')
assert instance is self.queryset.items[1]
def test_hyperlinked_related_lookup_does_not_exist(self):
with pytest.raises(serializers.ValidationError) as excinfo:
self.field.to_internal_value('http://example.org/example/doesnotexist/')
msg = excinfo.value.detail[0]
assert msg == 'Invalid hyperlink - Object does not exist.'
class TestHyperlinkedIdentityField(APISimpleTestCase): class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self): def setUp(self):

View File

@ -335,7 +335,7 @@ class PKForeignKeyTests(TestCase):
""" """
Regression test for #1072 Regression test for #1072
https://github.com/tomchristie/django-rest-framework/issues/1072 https://github.com/encode/django-rest-framework/issues/1072
""" """
serializer = NullableForeignKeySourceSerializer() serializer = NullableForeignKeySourceSerializer()
assert serializer.data['target'] is None assert serializer.data['target'] is None

View File

@ -242,7 +242,7 @@ class RendererEndToEndTests(TestCase):
""" """
Regression test for #1196 Regression test for #1196
https://github.com/tomchristie/django-rest-framework/issues/1196 https://github.com/encode/django-rest-framework/issues/1196
""" """
resp = self.client.get('/empty') resp = self.client.get('/empty')
self.assertEqual(resp.get('Content-Type', None), None) self.assertEqual(resp.get('Content-Type', None), None)

View File

@ -156,6 +156,7 @@ class TestCustomLookupFields(TestCase):
""" """
def setUp(self): def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar') RouterTestModel.objects.create(uuid='123', text='foo bar')
RouterTestModel.objects.create(uuid='a b', text='baz qux')
def test_custom_lookup_field_route(self): def test_custom_lookup_field_route(self):
detail_route = notes_router.urls[-1] detail_route = notes_router.urls[-1]
@ -164,12 +165,19 @@ class TestCustomLookupFields(TestCase):
def test_retrieve_lookup_field_list_view(self): def test_retrieve_lookup_field_list_view(self):
response = self.client.get('/example/notes/') response = self.client.get('/example/notes/')
assert response.data == [{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}] assert response.data == [
{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"},
{"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"},
]
def test_retrieve_lookup_field_detail_view(self): def test_retrieve_lookup_field_detail_view(self):
response = self.client.get('/example/notes/123/') response = self.client.get('/example/notes/123/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"} assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
def test_retrieve_lookup_field_url_encoded_detail_view_(self):
response = self.client.get('/example/notes/a%20b/')
assert response.data == {"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"}
class TestLookupValueRegex(TestCase): class TestLookupValueRegex(TestCase):
""" """
@ -211,6 +219,10 @@ class TestLookupUrlKwargs(TestCase):
response = self.client.get('/example2/notes/fo/') response = self.client.get('/example2/notes/fo/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"} assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
def test_retrieve_lookup_url_encoded_kwarg_detail_view(self):
response = self.client.get('/example2/notes/foo%20bar/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
class TestTrailingSlashIncluded(TestCase): class TestTrailingSlashIncluded(TestCase):
def setUp(self): def setUp(self):

View File

@ -246,6 +246,11 @@ class PermissionDeniedExampleViewSet(ExampleViewSet):
permission_classes = [DenyAllUsingPermissionDenied] permission_classes = [DenyAllUsingPermissionDenied]
class MethodLimitedViewSet(ExampleViewSet):
permission_classes = []
http_method_names = ['get', 'head', 'options']
class ExampleListView(APIView): class ExampleListView(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly] permission_classes = [permissions.IsAuthenticatedOrReadOnly]
@ -368,6 +373,61 @@ class TestSchemaGeneratorNotAtRoot(TestCase):
assert schema == expected assert schema == expected
@unittest.skipUnless(coreapi, 'coreapi is not installed')
class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase):
def setUp(self):
router = DefaultRouter()
router.register('example1', MethodLimitedViewSet, base_name='example1')
self.patterns = [
url(r'^', include(router.urls))
]
def test_schema_for_regular_views(self):
"""
Ensure that schema generation works for ViewSet classes
with method limitation by Django CBV's http_method_names attribute
"""
generator = SchemaGenerator(title='Example API', patterns=self.patterns)
request = factory.get('/example1/')
schema = generator.get_schema(Request(request))
expected = coreapi.Document(
url='http://testserver/example1/',
title='Example API',
content={
'example1': {
'list': coreapi.Link(
url='/example1/',
action='get',
fields=[
coreapi.Field('page', required=False, location='query', schema=coreschema.Integer(title='Page', description='A page number within the paginated result set.')),
coreapi.Field('page_size', required=False, location='query', schema=coreschema.Integer(title='Page size', description='Number of results to return per page.')),
coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.'))
]
),
'custom_list_action': coreapi.Link(
url='/example1/custom_list_action/',
action='get'
),
'custom_list_action_multiple_methods': {
'read': coreapi.Link(
url='/example1/custom_list_action_multiple_methods/',
action='get'
)
},
'read': coreapi.Link(
url='/example1/{id}/',
action='get',
fields=[
coreapi.Field('id', required=True, location='path', schema=coreschema.String())
]
)
}
}
)
assert schema == expected
@unittest.skipUnless(coreapi, 'coreapi is not installed') @unittest.skipUnless(coreapi, 'coreapi is not installed')
class TestSchemaGeneratorWithRestrictedViewSets(TestCase): class TestSchemaGeneratorWithRestrictedViewSets(TestCase):
def setUp(self): def setUp(self):

View File

@ -318,3 +318,217 @@ class TestSerializerPartialUsage:
assert serializer.is_valid() assert serializer.is_valid()
assert serializer.validated_data == {} assert serializer.validated_data == {}
assert serializer.errors == {} assert serializer.errors == {}
def test_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
serializer = ListSerializer(instance, data=[], partial=True, many=True)
assert serializer.is_valid()
assert serializer.validated_data == []
assert serializer.errors == []
def test_update_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
input_data = [{'update_field': 31}, {'update_field': 41}]
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=input_data, partial=True, many=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == []
def test_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=[], allow_empty=False, partial=True, many=True)
assert not serializer.is_valid()
assert serializer.validated_data == []
assert len(serializer.errors) == 1
assert serializer.errors['non_field_errors'][0] == 'This list may not be empty.'
def test_update_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
input_data = [{'update_field': 31}, {'update_field': 41}]
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=input_data, allow_empty=False, partial=True, many=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == []
def test_as_field_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
serializer = Serializer(instance, data={}, partial=True)
assert serializer.is_valid()
assert serializer.validated_data == {}
assert serializer.errors == {}
def test_udate_as_field_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
input_data_1 = {'extra_field': 2}
input_data_2 = {
'list_field': [
{'update_field': 31},
{'update_field': 41},
]
}
# data_1
serializer = Serializer(instance, data=input_data_1, partial=True)
assert serializer.is_valid()
assert len(serializer.validated_data) == 1
assert serializer.validated_data['extra_field'] == 2
assert serializer.errors == {}
# data_2
serializer = Serializer(instance, data=input_data_2, partial=True)
assert serializer.is_valid()
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
for index, data in enumerate(serializer.validated_data['list_field']):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == {}
def test_as_field_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True, allow_empty=False)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
serializer = Serializer(instance, data={}, partial=True)
assert serializer.is_valid()
assert serializer.validated_data == {}
assert serializer.errors == {}
def test_update_as_field_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True, allow_empty=False)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
input_data_1 = {'extra_field': 2}
input_data_2 = {
'list_field': [
{'update_field': 31},
{'update_field': 41},
]
}
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
# data_1
serializer = Serializer(instance, data=input_data_1, partial=True)
assert serializer.is_valid()
assert serializer.errors == {}
# data_2
serializer = Serializer(instance, data=input_data_2, partial=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data['list_field']):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == {}

View File

@ -8,7 +8,7 @@ from django.test import TestCase
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings from rest_framework.settings import APISettings, api_settings
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from rest_framework.views import APIView from rest_framework.views import APIView
@ -45,6 +45,19 @@ class ErrorView(APIView):
raise Exception raise Exception
def custom_handler(exc, context):
if isinstance(exc, SyntaxError):
return Response({'error': 'SyntaxError'}, status=400)
return Response({'error': 'UnknownError'}, status=500)
class OverridenSettingsView(APIView):
settings = APISettings({'EXCEPTION_HANDLER': custom_handler})
def get(self, request, *args, **kwargs):
raise SyntaxError('request is invalid syntax')
@api_view(['GET']) @api_view(['GET'])
def error_view(request): def error_view(request):
raise Exception raise Exception
@ -118,3 +131,14 @@ class TestCustomExceptionHandler(TestCase):
expected = 'Error!' expected = 'Error!'
assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == expected assert response.data == expected
class TestCustomSettings(TestCase):
def setUp(self):
self.view = OverridenSettingsView.as_view()
def test_get_exception_handler(self):
request = factory.get('/', content_type='application/json')
response = self.view(request)
assert response.status_code == 400
assert response.data == {'error': 'SyntaxError'}

View File

@ -26,7 +26,7 @@ deps =
django18: Django>=1.8,<1.9 django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10 django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11 django110: Django>=1.10,<1.11
django111: Django>=1.11rc1,<2.0 django111: Django>=1.11,<2.0
djangomaster: https://github.com/django/django/archive/master.tar.gz djangomaster: https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt -rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt -rrequirements/requirements-optionals.txt