Merge branch 'master' into localizednumberfields

This commit is contained in:
kgeorgy 2017-02-27 10:27:13 +01:00 committed by GitHub
commit f64c41e786
135 changed files with 3232 additions and 1332 deletions

View File

@ -1,44 +1,51 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
sudo: false
env:
- TOX_ENV=py27-lint
- TOX_ENV=py27-docs
- TOX_ENV=py35-django19
- TOX_ENV=py34-django19
- TOX_ENV=py27-django19
- TOX_ENV=py35-django18
- TOX_ENV=py34-django18
- TOX_ENV=py33-django18
- TOX_ENV=py27-django18
- TOX_ENV=py27-django110
- TOX_ENV=py35-django110
- TOX_ENV=py34-django110
- TOX_ENV=py27-djangomaster
- TOX_ENV=py34-djangomaster
- TOX_ENV=py35-djangomaster
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=1.10
- DJANGO=1.11
- DJANGO=master
matrix:
fast_finish: true
include:
- python: "3.6"
env: DJANGO=master
- python: "3.6"
env: DJANGO=1.11
- python: "3.3"
env: DJANGO=1.8
- python: "2.7"
env: TOXENV="lint"
- python: "2.7"
env: TOXENV="docs"
exclude:
- python: "2.7"
env: DJANGO=master
- python: "3.4"
env: DJANGO=master
allow_failures:
- env: TOX_ENV=py27-djangomaster
- env: TOX_ENV=py34-djangomaster
- env: TOX_ENV=py35-djangomaster
- env: DJANGO=master
- env: DJANGO=1.11
install:
# Virtualenv < 14 is required to keep the Python 3.2 builds running.
- pip install tox "virtualenv<14"
- pip install tox tox-travis
script:
- tox -e $TOX_ENV
- tox
after_success:
- pip install codecov
- codecov -e TOX_ENV
- codecov -e TOXENV,DJANGO
notifications:
email: false

View File

@ -1,6 +1,6 @@
# License
Copyright (c) 2011-2016, Tom Christie
Copyright (c) 2011-2017, Tom Christie
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -23,11 +23,13 @@ The initial aim is to provide a single full-time position on REST framework.
<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="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://getstream.io/?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="http://www.machinalis.com/#services"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/machinalis-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://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://rollbar.com/"><img src="https://raw.githubusercontent.com/tomchristie/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>
</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), and [Machinalis](http://www.machinalis.com/#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/).*
---

View File

@ -363,7 +363,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth]: http://oauth.net/2/
[permission]: permissions.md
[throttling]: throttling.md
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/

View File

@ -47,7 +47,7 @@ Any example validation error might look like this:
You can implement custom exception handling by creating a handler function that converts exceptions raised in your API views into response objects. This allows you to control the style of error responses used by your API.
The function must take a pair of arguments, this first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
The function must take a pair of arguments, the first is the exception to be handled, and the second is a dictionary containing any extra context such as the view currently being handled. The exception handler function should either return a `Response` object, or return `None` if the exception cannot be handled. If the handler returns `None` then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
For example, you might want to ensure that all error responses include the HTTP status code in the body of the response, like so:
@ -119,7 +119,7 @@ The available attributes and methods are:
* `.detail` - Return the textual description of the error.
* `.get_codes()` - Return the code identifier of the error.
* `.full_details()` - Return both the textual description and the code identifier.
* `.get_full_details()` - Return both the textual description and the code identifier.
In most cases the error detail will be a simple item:
@ -127,7 +127,7 @@ In most cases the error detail will be a simple item:
You do not have permission to perform this action.
>>> print(exc.get_codes())
permission_denied
>>> print(exc.full_details())
>>> print(exc.get_full_details())
{'message':'You do not have permission to perform this action.','code':'permission_denied'}
In the case of validation errors the error detail will be either a list or

View File

@ -261,7 +261,7 @@ Corresponds to `django.db.models.fields.DecimalField`.
**Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)`
- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places.
- `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`.
- `decimal_places` The number of decimal places to store with the number.
- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`.
- `max_value` Validate that the number provided is no greater than this value.
@ -303,8 +303,6 @@ Format strings may either be [Python strftime formats][strftime] which explicitl
When a value of `None` is used for the format `datetime` objects will be returned by `to_representation` and the final output representation will determined by the renderer class.
In the case of JSON this means the default datetime representation uses the [ECMA 262 date time string specification][ecma262]. This is a subset of ISO 8601 which uses millisecond precision, and includes the 'Z' suffix for the UTC timezone, for example: `2013-01-29T12:34:56.123Z`.
#### `auto_now` and `auto_now_add` model fields.
When using `ModelSerializer` or `HyperlinkedModelSerializer`, note that any model fields with `auto_now=True` or `auto_now_add=True` will use serializer fields that are `read_only=True` by default.
@ -434,9 +432,11 @@ Requires either the `Pillow` package or `PIL` package. The `Pillow` package is
A field class that validates a list of objects.
**Signature**: `ListField(child)`
**Signature**: `ListField(child, min_length=None, max_length=None)`
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.
- `min_length` - Validates that the list contains no fewer than this number of elements.
- `max_length` - Validates that the list contains no more than this number of elements.
For example, to validate a list of integers you might use something like the following:
@ -626,7 +626,6 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
def to_internal_value(self, data):
if not isinstance(data, six.text_type):
msg = 'Incorrect type. Expected a string, but got %s'
self.fail('incorrect_type', input_type=type(data).__name__)
if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
@ -666,12 +665,12 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog
The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreField` to support [django-hstore][django-hstore] `DictionaryField` model field.
[cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data
[cite]: https://docs.djangoproject.com/en/stable/ref/forms/api/#django.forms.Form.cleaned_data
[html-and-forms]: ../topics/html-and-forms.md
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
[strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/stable/ref/forms/widgets/
[iso8601]: http://www.w3.org/TR/NOTE-datetime
[drf-compound-fields]: https://drf-compound-fields.readthedocs.io
[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields

View File

@ -89,24 +89,24 @@ Generic filters can also present themselves as HTML controls in the browsable AP
## Setting filter backends
The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example.
The default filter backends may be set globally, using the `DEFAULT_FILTER_BACKENDS` setting. For example.
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
You can also set the filter backends on a per-view, or per-viewset basis,
using the `GenericAPIView` class-based views.
import django_filters.rest_framework
from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import filters
from rest_framework import generics
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
## Filtering and object lookups
@ -139,12 +139,27 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
## DjangoFilterBackend
The `DjangoFilterBackend` class supports highly customizable field filtering, using the [django-filter package][django-filter].
The `django-filter` library includes a `DjangoFilterBackend` class which
supports highly customizable field filtering for REST framework.
To use REST framework's `DjangoFilterBackend`, first install `django-filter`.
To use `DjangoFilterBackend`, first install `django-filter`.
pip install django-filter
You should now either add the filter backend to your settings:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
Or add the filter backend to an individual View or ViewSet.
from django_filters.rest_framework import DjangoFilterBackend
class UserListView(generics.ListAPIView):
...
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
@ -174,10 +189,9 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
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:
@ -187,7 +201,7 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
filter_class = ProductFilter
@ -199,12 +213,12 @@ 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 filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
class ProductFilter(django_filters.rest_framework.FilterSet):
class Meta:
model = Product
fields = ['category', 'in_stock', 'manufacturer__name']
@ -218,10 +232,9 @@ This is nice, but it exposes the Django's double underscore convention as part o
import django_filters
from myapp.models import Product
from myapp.serializers import ProductSerializer
from rest_framework import filters
from rest_framework import generics
class ProductFilter(filters.FilterSet):
class ProductFilter(django_filters.rest_framework.FilterSet):
manufacturer = django_filters.CharFilter(name="manufacturer__name")
class Meta:
@ -442,16 +455,15 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
[drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values. A beautiful python package `Voluptuous` is being used for validations on the incoming query parameters. The best part about voluptuous is you can define your own validations as per your query params requirements.
[cite]: https://docs.djangoproject.com/en/dev/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-docs]: https://django-filter.readthedocs.io/en/latest/index.html
[guardian]: https://django-guardian.readthedocs.io/
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
[django-url-filter]: https://github.com/miki725/django-url-filter
[drf-url-filter]: https://github.com/manjitkumar/drf-url-filters

View File

@ -382,7 +382,7 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views
[cite]: https://docs.djangoproject.com/en/stable/ref/class-based-views/#base-vs-generic-views
[GenericAPIView]: #genericapiview
[ListModelMixin]: #listmodelmixin
[CreateModelMixin]: #createmodelmixin

View File

@ -104,6 +104,18 @@ Then configure your settings to use this custom class:
'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata'
}
# Third party packages
The following third party packages provide additional metadata implementations.
## DRF-schema-adapter
[drf-schema-adapter][drf-schema-adapter] is a set of tools that makes it easier to provide schema information to frontend frameworks and libraries. It provides a metadata mixin as well as 2 metadata classes and several adapters suitable to generate [json-schema][json-schema] as well as schema information readable by various libraries.
You can also write your own adapter to work with your specific frontend.
If you wish to do so, it also provides an exporter that can export those schema information to json files.
[cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7
[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
[json-schema]: http://json-schema.org/
[drf-schema-adapter]: https://github.com/drf-forms/drf-schema-adapter

View File

@ -321,9 +321,14 @@ The following third party packages are also available.
The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` mixin class][paginate-by-max-mixin] that allows your API clients to specify `?page_size=max` to obtain the maximum allowed page size.
[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
## drf-proxy-pagination
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter.
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/
[link-header]: ../img/link-header-pagination.png
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
[drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination
[disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api

View File

@ -224,7 +224,7 @@ Modify your REST framework settings.
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
[upload-handlers]: https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#upload-handlers
[upload-handlers]: https://docs.djangoproject.com/en/stable/topics/http/file-uploads/#upload-handlers
[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
[yaml]: http://www.yaml.org/

View File

@ -164,7 +164,7 @@ As with `DjangoModelPermissions`, this permission must only be applied to views
Note that `DjangoObjectPermissions` **does not** require the `django-guardian` package, and should support other object-level backends equally well.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoModelPermissions` and setting the `.perms_map` property. Refer to the source code for details.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoObjectPermissions` and setting the `.perms_map` property. Refer to the source code for details.
---
@ -259,18 +259,22 @@ The [REST Condition][rest-condition] package is another extension for building c
## DRY Rest Permissions
The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrive per user.
The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to define different permissions for individual default and custom actions. This package is made for apps with permissions that are derived from relationships defined in the app's data model. It also supports permission checks being returned to a client app through the API's serializer. Additionally it supports adding permissions to the default and custom list actions to restrict the data they retrieve per user.
## Django Rest Framework Roles
The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users.
## Django Rest Framework API Key
The [Django Rest Framework API Key][django-rest-framework-api-key] package allows you to ensure that every request made to the server requires an API key header. You can generate one from the django admin interface.
[cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
[authentication]: authentication.md
[throttling]: throttling.md
[filtering]: filtering.md
[contribauth]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions
[contribauth]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#handling-object-permissions
[guardian]: https://github.com/lukaszb/django-guardian
[get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user
[2.2-announcement]: ../topics/2.2-announcement.md
@ -280,3 +284,4 @@ The [Django Rest Framework Roles][django-rest-framework-roles] package makes it
[rest-condition]: https://github.com/caxap/rest_condition
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles
[django-rest-framework-api-key]: https://github.com/manosim/django-rest-framework-api-key

View File

@ -39,7 +39,7 @@ In order to explain the various types of relational fields, we'll use a couple o
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
@ -484,7 +484,7 @@ Note that reverse relationships are not automatically included by the `ModelSeri
You'll normally want to ensure that you've set an appropriate `related_name` argument on the relationship, that you can use as the field name. For example:
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks')
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
...
If you have not set a related name for the reverse relationship, you'll need to use the automatically generated related name in the `fields` argument. For example:
@ -505,10 +505,10 @@ For example, given the following model for a tag, which has a generic relationsh
"""
Tags arbitrary model instances using a generic relation.
See: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
"""
tag_name = models.SlugField()
content_type = models.ForeignKey(ContentType)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
tagged_object = GenericForeignKey('content_type', 'object_id')
@ -593,9 +593,9 @@ The [drf-nested-routers package][drf-nested-routers] provides routers and relati
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
[cite]: http://lwn.net/Articles/193245/
[reverse-relationships]: https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: http://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
[2.2-announcement]: ../topics/2.2-announcement.md
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations

View File

@ -123,6 +123,8 @@ You can use `TemplateHTMLRenderer` either to return regular HTML pages using RES
If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers.
See the [_HTML & Forms_ Topic Page][html-and-forms] for further examples of `TemplateHTMLRenderer` usage.
**.media_type**: `text/html`
**.format**: `'.html'`
@ -476,7 +478,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/#the-rendering-process
[conneg]: content-negotiation.md
[html-and-forms]: ../topics/html-and-forms.md
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
@ -485,7 +487,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[application/vnd.github+json]: http://developer.github.com/v3/media/
[application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
[django-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/
[cors]: http://www.w3.org/TR/cors/
[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/

View File

@ -91,5 +91,5 @@ As with any other `TemplateResponse`, this method is called to render the serial
You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/
[statuscodes]: status-codes.md

View File

@ -51,5 +51,5 @@ As with the `reverse` function, you should **include the request as a keyword ar
api_root = reverse_lazy('api-root', request=request)
[cite]: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
[reverse]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy

View File

@ -118,6 +118,26 @@ The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/change-password/$` Name: `'user-change-password'`
In the case you do not want to use the default name generated for your custom action, you can use the url_name parameter to customize it.
For example, if you want to change the name of our custom action to `'user-change-password'`, you could write:
from myapp.permissions import IsAdminOrIsSelf
from rest_framework.decorators import detail_route
class UserViewSet(ModelViewSet):
...
@detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_name='change-password')
def set_password(self, request, pk=None):
...
The above example would now generate the following URL pattern:
* URL pattern: `^users/{pk}/set_password/$` Name: `'user-change-password'`
You can also use url_path and url_name parameters together to obtain extra control on URL generation for custom views.
For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
# API Guide

View File

@ -145,6 +145,18 @@ May be used to pass a canonical URL for the schema.
url='https://www.example.org/api/'
)
#### `urlconf`
A string representing the import path to the URL conf that you want
to generate an API schema for. This defaults to the value of Django's
ROOT_URLCONF setting.
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
urlconf='myproject.urls'
)
#### `renderer_classes`
May be used to pass the set of renderer classes that can be used to render the API root endpoint.
@ -281,8 +293,8 @@ A generic view with sections in the class docstring, using single-line style.
class UserList(generics.ListCreateAPIView):
"""
get: Create a new user.
post: List all the users.
get: List all the users.
post: Create a new user.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@ -344,12 +356,12 @@ Typically you'll instantiate `SchemaGenerator` with a single argument, like so:
Arguments:
* `title` - The name of the API. **required**
* `title` **required** - The name of the API.
* `url` - The root URL of the API schema. This option is not required unless the schema is included under path prefix.
* `patterns` - A list of URLs to inspect when generating the schema. Defaults to the project's URL conf.
* `urlconf` - A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`.
### get_schema()
### get_schema(self, request)
Returns a `coreapi.Document` instance that represents the API schema.
@ -359,9 +371,48 @@ Returns a `coreapi.Document` instance that represents the API schema.
generator = schemas.SchemaGenerator(title='Bookings API')
return Response(generator.get_schema())
Arguments:
The `request` argument is optional, and may be used if you want to apply per-user
permissions to the resulting schema generation.
* `request` - The incoming request. Optionally used if you want to apply per-user permissions to the schema-generation.
### get_links(self, request)
Return a nested dictionary containing all the links that should be included in the API schema.
This is a good point to override if you want to modify the resulting structure of the generated schema,
as you can build a new dictionary with a different layout.
### get_link(self, path, method, view)
Returns a `coreapi.Link` instance corresponding to the given view.
You can override this if you need to provide custom behaviors for particular views.
### get_description(self, path, method, view)
Returns a string to use as the link description. By default this is based on the
view docstring as described in the "Schemas as Documentation" section above.
### get_encoding(self, path, method, view)
Returns a string to indicate the encoding for any request body, when interacting
with the given view. Eg. `'application/json'`. May return a blank string for views
that do not expect a request body.
### get_path_fields(self, path, method, view):
Return a list of `coreapi.Link()` instances. One for each path parameter in the URL.
### get_serializer_fields(self, path, method, view)
Return a list of `coreapi.Link()` instances. One for each field in the serializer class used by the view.
### get_pagination_fields(self, path, method, view
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method on any pagination class used by the view.
### get_filter_fields(self, path, method, view)
Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view.
---
@ -502,5 +553,5 @@ A short description of the meaning and intended usage of the input field.
[open-api]: https://openapis.org/
[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[static-files]: https://docs.djangoproject.com/en/dev/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/dev/topics/http/urls/#named-groups
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/stable/topics/http/urls/#named-groups

View File

@ -578,16 +578,6 @@ Alternative representations include serializing using hyperlinks, serializing co
For full details see the [serializer relations][relations] documentation.
## Inheritance of the 'Meta' class
The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behavior as with Django's `Model` and `ModelForm` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
## Customizing field mappings
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
@ -710,7 +700,7 @@ You can override a URL field view name and lookup field by using either, or both
model = Account
fields = ('account_url', 'account_name', 'users', 'created')
extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'}
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
'users': {'lookup_field': 'username'}
}
@ -1025,6 +1015,40 @@ If any of the validation fails, then the method should raise a `serializers.Vali
The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.
## Serializer Inheritance
Similar to Django forms, you can extend and reuse serializers through inheritance. This allows you to declare a common set of fields or methods on a parent class that can then be used in a number of serializers. For example,
class MyBaseSerializer(Serializer):
my_field = serializers.CharField()
def validate_my_field(self):
...
class MySerializer(MyBaseSerializer):
...
Like Django's `Model` and `ModelForm` classes, the inner `Meta` class on serializers does not implicitly inherit from it's parents' inner `Meta` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
Additionally, the following caveats apply to serializer inheritance:
* Normal Python name resolution rules apply. If you have multiple base classes that declare a `Meta` inner class, only the first one will be used. This means the childs `Meta`, if it exists, otherwise the `Meta` of the first parent, etc.
* Its possible to declaratively remove a `Field` inherited from a parent class by setting the name to be `None` on the subclass.
class MyBaseSerializer(ModelSerializer):
my_field = serializers.CharField()
class MySerializer(MyBaseSerializer):
my_field = None
However, you can only use this technique to opt out from a field defined declaratively by a parent class; it wont prevent the `ModelSerializer` from generating a default field. To opt-out from default fields, see [Specifying which fields to include](#specifying-which-fields-to-include).
## Dynamically modifying fields
Once a serializer has been initialized, the dictionary of fields that are set on the serializer may be accessed using the `.fields` attribute. Accessing and modifying this attribute allows you to dynamically modify the serializer.
@ -1076,8 +1100,6 @@ This API included the `.get_field()`, `.get_pk_field()` and other methods.
Because the serializers have been fundamentally redesigned with 3.0 this API no longer exists. You can still modify the fields that get created but you'll need to refer to the source code, and be aware that if the changes you make are against private bits of API then they may be subject to change.
A new interface for controlling this behavior is currently planned for REST framework 3.1.
---
# Third party packages
@ -1112,13 +1134,36 @@ The [dynamic-rest][dynamic-rest] package extends the ModelSerializer and ModelVi
The [drf-dynamic-fields][drf-dynamic-fields] package provides a mixin to dynamically limit the fields per serializer to a subset specified by an URL parameter.
## DRF FlexFields
The [drf-flex-fields][drf-flex-fields] package extends the ModelSerializer and ModelViewSet to provide commonly used functionality for dynamically setting fields and expanding primitive fields to nested models, both from URL parameters and your serializer class definitions.
## Serializer Extensions
The [django-rest-framework-serializer-extensions][drf-serializer-extensions]
package provides a collection of tools to DRY up your serializers, by allowing
fields to be defined on a per-view/request basis. Fields can be whitelisted,
blacklisted and child serializers can be optionally expanded.
## HTML JSON Forms
The [html-json-forms][html-json-forms] package provides an algorithm and serializer for processing `<form>` submissions per the (inactive) [HTML JSON Form specification][json-form-spec]. The serializer facilitates processing of arbitrarily nested JSON structures within HTML. For example, `<input name="items[0][id]" value="5">` will be interpreted as `{"items": [{"id": "5"}]}`.
## DRF-Base64
[DRF-Base64][drf-base64] provides a set of field and model serializers that handles the upload of base64-encoded files.
## QueryFields
[djangorestframework-queryfields][djangorestframework-queryfields] allows API clients to specify which fields will be sent in the response via inclusion/exclusion query parameters.
## DRF Writable Nested
The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows to create/update models with nested related data.
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
[relations]: relations.md
[model-managers]: https://docs.djangoproject.com/en/dev/topics/db/managers/
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
[encapsulation-blogpost]: http://www.dabapps.com/blog/django-models-and-encapsulation/
[django-rest-marshmallow]: http://tomchristie.github.io/django-rest-marshmallow/
[marshmallow]: https://marshmallow.readthedocs.io/en/latest/
@ -1129,5 +1174,10 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
[django-hstore]: https://github.com/djangonauts/django-hstore
[dynamic-rest]: https://github.com/AltSchool/dynamic-rest
[html-json-forms]: https://github.com/wq/html-json-forms
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
[json-form-spec]: https://www.w3.org/TR/html-json-forms/
[drf-dynamic-fields]: https://github.com/dbrgn/drf-dynamic-fields
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: http://djangorestframework-queryfields.readthedocs.io/
[drf-writable-nested]: http://github.com/Brogency/drf-writable-nested

View File

@ -241,7 +241,7 @@ Default:
If set, this maps the `'pk'` identifier in the URL conf onto the actual field
name when generating a schema path parameter. Typically this will be `'id'`.
This gives a more suitable representation as "primary key" is an implementation
detail, wheras "identifier" is a more general concept.
detail, whereas "identifier" is a more general concept.
Default: `True`
@ -456,7 +456,7 @@ An integer of 0 or more, that may be used to specify the number of application p
Default: `None`
[cite]: http://www.python.org/dev/peps/pep-0020/
[cite]: https://www.python.org/dev/peps/pep-0020/
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
[strftime]: http://docs.python.org/2/library/time.html#time.strftime
[strftime]: https://docs.python.org/3/library/time.html#time.strftime

View File

@ -187,16 +187,28 @@ As usual CSRF validation will only apply to any session authenticated views. Th
# RequestsClient
REST framework also includes a client for interacting with your application
using the popular Python library, `requests`.
using the popular Python library, `requests`. This may be useful if:
* You are expecting to interface with the API primarily from another Python service,
and want to test the service at the same level as the client will see.
* You want to write tests in such a way that they can also be run against a staging or
live environment. (See "Live tests" below.)
This exposes exactly the same interface as if you were using a requests session
directly.
client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200
Note that the requests client requires you to pass fully qualified URLs.
## `RequestsClient` and working with the database
The `RequestsClient` class is useful if you want to write tests that solely interact with the service interface. This is a little stricter than using the standard Django test client, as it means that all interactions should be via the API.
If you're using `RequestsClient` you'll want to ensure that test setup, and results assertions are performed as regular API calls, rather than interacting with the database models directly. For example, rather than checking that `Customer.objects.count() == 3` you would list the customers endpoint, and ensure that it contains three records.
## Headers & Authentication
Custom headers and authentication credentials can be provided in the same way
@ -251,9 +263,8 @@ The CoreAPIClient allows you to interact with your API using the Python
`coreapi` client library.
# Fetch the API schema
url = reverse('schema')
client = CoreAPIClient()
schema = client.get(url)
schema = client.get('http://testserver/schema/')
# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
@ -364,6 +375,6 @@ For example, to add support for using `format='html'` in test requests, you migh
}
[cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper
[client]: https://docs.djangoproject.com/en/dev/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.client.RequestFactory
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
[configuration]: #configuration

View File

@ -193,5 +193,5 @@ The following is an example of a rate throttle, that will randomly throttle 1 in
[cite]: https://dev.twitter.com/docs/error-codes-responses
[permissions]: permissions.md
[identifing-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
[cache-setting]: https://docs.djangoproject.com/en/dev/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache

View File

@ -20,7 +20,7 @@ With `ModelForm` the validation is performed partially on the form, and partiall
* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate.
* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behavior being called on the model instance.
When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using a `Serializer` classes instead, then you need to define the validation rules explicitly.
When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using `Serializer` classes instead, then you need to define the validation rules explicitly.
#### Example
@ -153,13 +153,13 @@ The field will not be writable to the user, but the default value will still be
#### Using with a hidden date field.
If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns it's default value to the `validated_data` in the serializer.
If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns its default value to the `validated_data` in the serializer.
published = serializers.HiddenField(default=timezone.now)
---
**Note**: The `UniqueFor<Range>Validation` classes always imposes an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
**Note**: The `UniqueFor<Range>Validation` classes impose an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input.
---
@ -272,6 +272,12 @@ A validator may be any callable that raises a `serializers.ValidationError` on f
if value % 2 != 0:
raise serializers.ValidationError('This field must be an even number.')
#### Field-level validation
You can specify custom field-level validation by adding `.validate_<field_name>` methods
to your `Serializer` subclass. This is documented in the
[Serializer docs](http://www.django-rest-framework.org/api-guide/serializers/#field-level-validation)
## Class-based
To write a class-based validator, use the `__call__` method. Class-based validators are useful as they allow you to parameterize and reuse behavior.
@ -294,4 +300,4 @@ In some advanced cases you might want a validator to be passed the serializer fi
# In `__call__` we can then use that information to modify the validation behavior.
self.is_update = serializer_field.parent.instance is not None
[cite]: https://docs.djangoproject.com/en/dev/ref/validators/
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/

View File

@ -73,6 +73,8 @@ The following methods are used by REST framework to instantiate the various plug
### .get_content_negotiator(self)
### .get_exception_handler(self)
## API policy implementation methods
The following methods are called before dispatching to the handler method.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/img/raml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -73,12 +73,14 @@ The initial aim is to provide a single full-time position on REST framework.
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="http://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
<li><a href="https://micropyramid.com/django-rest-framework-development-services/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/mp-text-logo.png)">MicroPyramid</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](http://www.machinalis.com/#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/).*
---
@ -236,7 +238,8 @@ General guides to using REST framework.
* [Browser enhancements][browser-enhancements]
* [The Browsable API][browsableapi]
* [REST, Hypermedia & HATEOAS][rest-hypermedia-hateoas]
* [Third Party Resources][third-party-resources]
* [Third Party Packages][third-party-packages]
* [Tutorials and Resources][tutorials-and-resources]
* [Contributing to REST framework][contributing]
* [Project management][project-management]
* [3.0 Announcement][3.0-announcement]
@ -244,10 +247,12 @@ General guides to using REST framework.
* [3.2 Announcement][3.2-announcement]
* [3.3 Announcement][3.3-announcement]
* [3.4 Announcement][3.4-announcement]
* [3.5 Announcement][3.5-announcement]
* [Kickstarter Announcement][kickstarter-announcement]
* [Mozilla Grant][mozilla-grant]
* [Funding][funding]
* [Release Notes][release-notes]
* [Jobs][jobs]
## Development
@ -259,7 +264,7 @@ Framework.
For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, search [the IRC archives][botbot], or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
[Paid support is available][paid-support] from [DabApps][dabapps], and can include work on REST framework core, or support with building your REST framework API. Please [contact DabApps][contact-dabapps] if you'd like to discuss commercial support options.
For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter.
@ -364,16 +369,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[rest-hypermedia-hateoas]: topics/rest-hypermedia-hateoas.md
[contributing]: topics/contributing.md
[project-management]: topics/project-management.md
[third-party-resources]: topics/third-party-resources.md
[third-party-packages]: topics/third-party-packages.md
[tutorials-and-resources]: topics/tutorials-and-resources.md
[3.0-announcement]: topics/3.0-announcement.md
[3.1-announcement]: topics/3.1-announcement.md
[3.2-announcement]: topics/3.2-announcement.md
[3.3-announcement]: topics/3.3-announcement.md
[3.4-announcement]: topics/3.4-announcement.md
[3.5-announcement]: topics/3.5-announcement.md
[kickstarter-announcement]: topics/kickstarter-announcement.md
[mozilla-grant]: topics/mozilla-grant.md
[funding]: topics/funding.md
[release-notes]: topics/release-notes.md
[jobs]: topics/jobs.md
[tox]: http://testrun.org/tox/latest/

View File

@ -147,10 +147,10 @@ When using a serializer with a `HyperlinkedRelatedField` or `HyperlinkedIdentity
From version 2.2 onwards, serializers with hyperlinked relationships *always* require a `'request'` key to be supplied in the context dictionary. The implicit behavior will continue to function, but its use will raise a `PendingDeprecationWarning`.
[xordoquy]: https://github.com/xordoquy
[django-python-3]: https://docs.djangoproject.com/en/dev/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/dev/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/dev/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-python-3]: https://docs.djangoproject.com/en/stable/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/stable/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/stable/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[credits]: http://www.django-rest-framework.org/topics/credits
[mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[django-rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs

View File

@ -162,7 +162,7 @@ The next planned release will be 3.0, featuring an improved and simplified seria
Once again, many thanks to all the generous [backers and sponsors][kickstarter-sponsors] who've helped make this possible!
[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases
[lts-releases]: https://docs.djangoproject.com/en/stable/internals/release-process/#long-term-support-lts-releases
[2-4-release-notes]: release-notes#240
[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions
[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified

View File

@ -870,7 +870,7 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi
#### File fields as URLs
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development).
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/stable/howto/static-files/#serving-uploaded-files-in-development).
You can revert this behavior, and display filenames in the representation by using the `UPLOADED_FILES_USE_URL` settings key:
@ -894,11 +894,11 @@ If the request is omitted from the context, the returned URLs will be of the for
The custom `X-Throttle-Wait-Second` header has now been dropped in favor of the standard `Retry-After` header. You can revert this behavior if needed by writing a custom exception handler for your application.
#### Date and time objects as ISO-8859-1 strings in serializer data.
#### Date and time objects as ISO-8601 strings in serializer data.
Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as `Date`, `Time` and `DateTime` objects, and later coerced to strings by the renderer.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8859-1'` will result in native objects being returned in serializer data.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8601'` will result in native objects being returned in serializer data.
REST_FRAMEWORK = {
# Return native `Date` and `Time` objects in `serializer.data`
@ -962,4 +962,4 @@ You can follow development on the GitHub site, where we use [milestones to indic
[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[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
[django-localization]: https://docs.djangoproject.com/en/dev/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

@ -153,7 +153,7 @@ For more information, see the documentation on [customizing field mappings][cust
We've now moved a number of packages out of the core of REST framework, and into separately installable packages. If you're currently using these you don't need to worry, you simply need to `pip install` the new packages, and change any import paths.
We're making this change in order to help distribute the maintainance workload, and keep better focus of the core essentials of the framework.
We're making this change in order to help distribute the maintenance workload, and keep better focus of the core essentials of the framework.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.

View File

@ -89,7 +89,7 @@ Name | Support | PyPI pa
---------------------------------|-------------------------------------|--------------------------------
[Core JSON][core-json] | Schema generation & client support. | Built-in support in `coreapi`.
[Swagger / OpenAPI][swagger] | Schema generation & client support. | The `openapi-codec` package.
[JSON Hyper-Schema][hyperschema] | Currrently client support only. | The `hyperschema-codec` package.
[JSON Hyper-Schema][hyperschema] | Currently client support only. | The `hyperschema-codec` package.
[API Blueprint][api-blueprint] | Not yet available. | Not yet available.
---

View File

@ -0,0 +1,266 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.5
The 3.5 release is the second in a planned series that is addressing schema
generation, hypermedia support, API client libraries, and finally realtime support.
---
## Funding
The 3.5 release would not have been possible without our [collaborative funding model][funding].
If you use REST framework commercially and would like to see this work continue,
we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="http://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [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), and [Machinalis](http://www.machinalis.com/#services).*
---
## Improved schema generation
Docstrings on views are now pulled through into schema definitions, allowing
you to [use the schema definition to document your&nbsp;API][schema-docs].
There is now also a shortcut function, `get_schema_view()`, which makes it easier to
[adding schema views][schema-view] to your API.
For example, to include a swagger schema to your API, you would do the following:
* Run `pip install django-rest-swagger`.
* Add `'rest_framework_swagger'` to your `INSTALLED_APPS` setting.
* Include the schema view in your URL conf:
```py
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
schema_view = get_schema_view(
title='Example API',
renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]
)
urlpatterns = [
url(r'^swagger/$', schema_view),
...
]
```
There have been a large number of fixes to the schema generation. These should
resolve issues for anyone using the latest version of the `django-rest-swagger`
package.
Some of these changes do affect the resulting schema structure,
so if you're already using schema generation you should make sure to review
[the deprecation notes](#deprecations), particularly if you're currently using
a dynamic client library to interact with your API.
Finally, we're also now exposing the schema generation as a
[publicly documented API][schema-generation-api], allowing you to more easily
override the behaviour.
## Requests test client
You can now test your project using the `requests` library.
This exposes exactly the same interface as if you were using a standard
requests session instance.
client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200
Rather than sending any HTTP requests to the network, this interface will
coerce all outgoing requests into WSGI, and call into your application directly.
## Core API client
You can also now test your project by interacting with it using the `coreapi`
client library.
# Fetch the API schema
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')
# Create a new organisation
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)
# Ensure that the organisation exists in the listing
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])
Again, this will call directly into the application using the WSGI interface,
rather than making actual network calls.
This is a good option if you are planning for clients to mainly interact with
your API using the `coreapi` client library, or some other auto-generated client.
## Live tests
One interesting aspect of both the `requests` client and the `coreapi` client
is that they allow you to write tests in such a way that they can also be made
to run against a live service.
By switching the WSGI based client instances to actual instances of `requests.Session`
or `coreapi.Client` you can have the test cases make actual network calls.
Being able to write test cases that can exercise your staging or production
environment is a powerful tool. However in order to do this, you'll need to pay
close attention to how you handle setup and teardown to ensure a strict isolation
of test data from other live or staging data.
## RAML support
We now have preliminary support for [RAML documentation generation][django-rest-raml].
![RAML Example][raml-image]
Further work on the encoding and documentation generation is planned, in order to
make features such as the 'Try it now' support available at a later date.
This work also now means that you can use the Core API client libraries to interact
with APIs that expose a RAML specification. The [RAML codec][raml-codec] gives some examples of
interacting with the Spotify API in this way.
## Validation codes
Exceptions raised by REST framework now include short code identifiers.
When used together with our customizable error handling, this now allows you to
modify the style of API error messages.
As an example, this allows for the following style of error responses:
{
"message": "You do not have permission to perform this action.",
"code": "permission_denied"
}
This is particularly useful with validation errors, which use appropriate
codes to identify differing kinds of failure...
{
"name": {"message": "This field is required.", "code": "required"},
"age": {"message": "A valid integer is required.", "code": "invalid"}
}
## Client upload & download support
The Python `coreapi` client library and the Core API command line tool both
now fully support file [uploads][uploads] and [downloads][downloads].
---
## Deprecations
### Generating schemas from Router
The router arguments for generating a schema view, such as `schema_title`,
are now pending deprecation.
Instead of using `DefaultRouter(schema_title='Example API')`, you should use
the `get_schema_view()` function, and include the view in your URL conf.
Make sure to include the view before your router urls. For example:
from rest_framework.schemas import get_schema_view
from my_project.routers import router
schema_view = get_schema_view(title='Example API')
urlpatterns = [
url('^$', schema_view),
url(r'^', include(router.urls)),
]
### Schema path representations
The `'pk'` identifier in schema paths is now mapped onto the actually model field
name by default. This will typically be `'id'`.
This gives a better external representation for schemas, with less implementation
detail being exposed. It also reflects the behaviour of using a ModelSerializer
class with `fields = '__all__'`.
You can revert to the previous behaviour by setting `'SCHEMA_COERCE_PATH_PK': False`
in the REST framework settings.
### Schema action name representations
The internal `retrieve()` and `destroy()` method names are now coerced to an
external representation of `read` and `delete`.
You can revert to the previous behaviour by setting `'SCHEMA_COERCE_METHOD_NAMES': {}`
in the REST framework settings.
### DjangoFilterBackend
The functionality of the built-in `DjangoFilterBackend` is now completely
included by the `django-filter` package.
You should change your imports and REST framework filter settings as follows:
* `rest_framework.filters.DjangoFilterBackend` becomes `django_filters.rest_framework.DjangoFilterBackend`.
* `rest_framework.filters.FilterSet` becomes `django_filters.rest_framework.FilterSet`.
The existing imports will continue to work but are now pending deprecation.
### CoreJSON media type
The media type for `CoreJSON` is now `application/json+coreapi`, rather than
the previous `application/vnd.json+coreapi`. This brings it more into line with
other custom media types, such as those used by Swagger and RAML.
The clients currently accept either media type. The old style-media type will
be deprecated at a later date.
### ModelSerializer 'fields' and 'exclude'
ModelSerializer and HyperlinkedModelSerializer must include either a fields
option, or an exclude option. The `fields = '__all__'` shortcut may be used to
explicitly include all fields.
Failing to set either `fields` or `exclude` raised a pending deprecation warning
in version 3.3 and raised a deprecation warning in 3.4. Its usage is now mandatory.
---
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[funding]: funding.md
[uploads]: http://core-api.github.io/python-client/api-guide/utils/#file
[downloads]: http://core-api.github.io/python-client/api-guide/codecs/#downloadcodec
[schema-generation-api]: ../api-guide/schemas/#schemagenerator
[schema-docs]: ../api-guide/schemas/#schemas-as-documentation
[schema-view]: ../api-guide/schemas/#the-get_schema_view-shortcut
[django-rest-raml]: https://github.com/tomchristie/django-rest-raml
[raml-image]: ../img/raml.png
[raml-codec]: https://github.com/core-api/python-raml-codec

View File

@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response
[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[cors]: http://www.w3.org/TR/cors/
[ottoyiu]: https://github.com/ottoyiu/
[django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/

View File

@ -217,7 +217,7 @@ credentials, headers and bookmarks:
# Python client library
The `coreapi` Python package allows you to programatically interact with any
The `coreapi` Python package allows you to programmatically interact with any
API that exposes a supported schema format.
## Getting started
@ -257,7 +257,7 @@ Codecs are responsible for encoding or decoding Documents.
The decoding process is used by a client to take a bytestring of an API schema
definition, and returning the Core API `Document` that represents that interface.
A codec should be associated with a particular media type, such as **TODO**.
A codec should be associated with a particular media type, such as `'application/coreapi+json'`.
This media type is used by the server in the response `Content-Type` header,
in order to indicate what kind of data is being returned in the response.

View File

@ -38,6 +38,34 @@ Both this package and DRF docs are fully documented, well supported, and come hi
---
### DRF AutoDocs
Oleksander Mashianovs' [DRF Auto Docs][drfautodocs-repo] automated api renderer.
Collects almost all the code you written into documentation effortlessly.
Supports:
* functional view docs
* tree-like structure
* Docstrings:
* markdown
* preserve space & newlines
* formatting with nice syntax
* Fields:
* choices rendering
* help_text (to specify SerializerMethodField output, etc)
* smart read_only/required rendering
* Endpoint properties:
* filter_backends
* authentication_classes
* permission_classes
* extra url params(GET params)
![whole structure](http://joxi.ru/52aBGNI4k3oyA0.jpg)
---
#### Apiary
There are various other online tools and services for providing API documentation. One notable service is [Apiary][apiary]. With Apiary, you describe your API using a simple markdown-like syntax. The generated documentation includes API interaction, a mock server for testing & prototyping, and various other tools.
@ -77,7 +105,7 @@ If the python `markdown` library is installed, then [markdown syntax][markdown]
[ref]: http://example.com/activating-accounts
"""
Note that one constraint of using viewsets is that any documentation be used for all generated views, so for example, you cannot have differing documentation for the generated list view and detail view.
Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
#### The `OPTIONS` method
@ -109,6 +137,7 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
[drfdocs-website]: http://www.drfdocs.com/
[drfdocs-demo]: http://demo.drfdocs.com/
[drfautodocs-repo]: https://github.com/iMakedonsky/drf-autodocs
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
[swagger]: https://developers.helloreverb.com/swagger/
[rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs
@ -119,3 +148,4 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[image-django-rest-swagger]: ../img/django-rest-swagger.png
[image-apiary]: ../img/apiary.png
[image-self-describing-api]: ../img/self-describing.png
[schemas-examples]: api-guide/schemas/#examples

View File

@ -308,7 +308,7 @@ Our professional and premium plans also include **priority support**. At any tim
Once you've signed up I'll contact you via email and arrange your ad placements on the site.
For further enquires please contact <a href=mailto:tom@tomchristie.com>tom@tomchristie.com</a>.
For further enquires please contact <a href=mailto:funding@django-rest-framework.org>funding@django-rest-framework.org</a>.
---

View File

@ -108,7 +108,7 @@ Let's take a look at how to render each of the three available template packs. F
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(
max_length=100,
style={'placeholder': 'Email'}
style={'placeholder': 'Email', 'autofocus': True}
)
password = serializers.CharField(
max_length=100,
@ -209,7 +209,7 @@ The complete list of `base_template` options and their associated style options
base_template | Valid field types | Additional style options
----|----|----
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label
input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus
textarea.html | `CharField` | rows, placeholder, hide_label
select.html | `ChoiceField` or relational field types | hide_label
radio.html | `ChoiceField` or relational field types | inline, hide_label

39
docs/topics/jobs.md Normal file
View File

@ -0,0 +1,39 @@
# Jobs
Looking for a new Django REST Framework related role? On this site we provide a list of job resources that may be helpful. It's also worth checking out if any of [our sponsors are hiring][drf-funding].
## Places to Look for Django REST Framework Jobs
* [https://www.djangoproject.com/community/jobs/][djangoproject-website]
* [https://www.python.org/jobs/][python-org-jobs]
* [https://djangogigs.com][django-gigs-com]
* [https://djangojobs.net/jobs/][django-jobs-net]
* [http://djangojobbers.com][django-jobbers-com]
* [https://www.indeed.com/q-Django-jobs.html][indeed-com]
* [http://stackoverflow.com/jobs/developer-jobs-using-django][stackoverflow-com]
* [https://www.upwork.com/o/jobs/browse/skill/django-framework/][upwork-com]
* [https://www.technojobs.co.uk/django-jobs][technobjobs-co-uk]
* [https://remoteok.io/remote-django-jobs][remoteok-io]
* [https://www.remotepython.com/jobs/][remotepython-com]
Know of any other great resources for Django REST Framework jobs that are missing in our list? Please [submit a pull request][submit-pr] or [email us][anna-email].
Wonder how else you can help? One of the best ways you can help Django REST Framework is to ask interviewers if their company is signed up for [REST Framework sponsorship][drf-funding] yet.
[djangoproject-website]: https://www.djangoproject.com/community/jobs/
[python-org-jobs]: https://www.python.org/jobs/
[django-gigs-com]: https://djangogigs.com
[django-jobs-net]: https://djangojobs.net/jobs/
[django-jobbers-com]: http://djangojobbers.com
[indeed-com]: https://www.indeed.com/q-Django-jobs.html
[stackoverflow-com]: http://stackoverflow.com/jobs/developer-jobs-using-django
[upwork-com]: https://www.upwork.com/o/jobs/browse/skill/django-framework/
[technobjobs-co-uk]: https://www.technojobs.co.uk/django-jobs
[remoteok-io]: https://remoteok.io/remote-django-jobs
[remotepython-com]: https://www.remotepython.com/jobs/
[drf-funding]: https://fund.django-rest-framework.org/topics/funding/
[submit-pr]: https://github.com/tomchristie/django-rest-framework
[anna-email]: mailto:anna@django-rest-framework.org

View File

@ -102,7 +102,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
### Silver sponsors
The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank&nbsp;you to individuals who have choosen to privately support the project at this level.
The serious financial contribution that our silver sponsors have made is very much appreciated. I'd like to say a particular thank&nbsp;you to individuals who have chosen to privately support the project at this level.
<ul class="sponsor silver">
<li><a href="http://www.imtapps.com/" rel="nofollow" style="background-image:url(../../img/sponsors/3-imt_computer_services.png);">IMT Computer Services</a></li>

View File

@ -38,6 +38,63 @@ You can determine your currently installed version using `pip freeze`:
---
## 3.5.x series
### 3.5.4
**Date**: [10th February 2017][3.5.4-milestone]
* Add max_length and min_length arguments for ListField. ([#4877][gh4877])
* Add per-view custom exception handler support. ([#4753][gh4753])
* Support disabling of declared fields on serializer subclasses. ([#4764][gh4764])
* Support custom view names on `@list_route` and `@detail_route` endpoints. ([#4821][gh4821])
* Correct labels for fields in login template when custom user model is used. ([#4841][gh4841])
* Whitespace fixes for descriptions generated from docstrings. ([#4759][gh4759], [#4869][gh4869], [#4870][gh4870])
* Better error reporting when schemas are returned by views without a schema renderer. ([#4790][gh4790])
* Fix for returned response of `PUT` requests when `prefetch_related` is used. ([#4661][gh4661], [#4668][gh4668])
* Fix for breadcrumb view names. ([#4750][gh4750])
* Fix for RequestsClient ensuring fully qualified URLs. ([#4678][gh4678])
* Fix for incorrect behavior of writable-nested fields check in some cases. ([#4634][gh4634], [#4669][gh4669])
* Resolve Django deprecation warnings. ([#4712][gh4712])
* Various cleanup of test cases.
### 3.5.3
**Date**: [7th November 2016][3.5.3-milestone]
* Don't raise incorrect FilterSet deprecation warnings. ([#4660][gh4660], [#4643][gh4643], [#4644][gh4644])
* Schema generation should not raise 404 when a view permission class does. ([#4645][gh4645], [#4646][gh4646])
* Add `autofocus` support for input controls. ([#4650][gh4650])
### 3.5.2
**Date**: [1st November 2016][3.5.2-milestone]
* Restore exception tracebacks in Python 2.7. ([#4631][gh4631], [#4638][gh4638])
* Properly display dicts in the admin console. ([#4532][gh4532], [#4636][gh4636])
* Fix is_simple_callable with variable args, kwargs. ([#4622][gh4622], [#4602][gh4602])
* Support 'on'/'off' literals with BooleanField. ([#4640][gh4640], [#4624][gh4624])
* Enable cursor pagination of value querysets. ([#4569][gh4569])
* Fix support of get_full_details() for Throttled exceptions. ([#4627][gh4627])
* Fix FilterSet proxy. ([#4620][gh4620])
* Make serializer fields import explicit. ([#4628][gh4628])
* Drop redundant requests adapter. ([#4639][gh4639])
### 3.5.1
**Date**: [21st October 2016][3.5.1-milestone]
* Make `rest_framework/compat.py` imports. ([#4612][gh4612], [#4608][gh4608], [#4601][gh4601])
* Fix bug in schema base path generation. ([#4611][gh4611], [#4605][gh4605])
* Fix broken case of ListSerializer with single item. ([#4609][gh4609], [#4606][gh4606])
* Remove bare `raise` for Python 3.5 compat. ([#4600][gh4600])
### 3.5.0
**Date**: [20th October 2016][3.5.0-milestone]
---
## 3.4.x series
### 3.4.7
@ -189,7 +246,7 @@ You can determine your currently installed version using `pip freeze`:
* Fixed use of deprecated Query.aggregates. ([#4003][gh4003])
* Fix blank lines around docstrings. ([#4002][gh4002])
* Fixed admin pagination when limit is 0. ([#3990][gh3990])
* OrderingFilter adjustements. ([#3983][gh3983])
* OrderingFilter adjustments. ([#3983][gh3983])
* Non-required serializer related fields. ([#3976][gh3976])
* Using safer calling way of "@api_view" in tutorial. ([#3971][gh3971])
* ListSerializer doesn't handle unique_together constraints. ([#3970][gh3970])
@ -559,7 +616,7 @@ For older release notes, [please see the version 2.x documentation][old-release-
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[deprecation-policy]: #deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[defusedxml-announce]: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
[743]: https://github.com/tomchristie/django-rest-framework/pull/743
[staticfiles14]: https://docs.djangoproject.com/en/1.4/howto/static-files/#with-a-template-tag
@ -596,6 +653,11 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.4.5-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.5+Release%22
[3.4.6-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.6+Release%22
[3.4.7-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.4.7+Release%22
[3.5.0-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.0+Release%22
[3.5.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.1+Release%22
[3.5.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.2+Release%22
[3.5.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.3+Release%22
[3.5.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.5.4+Release%22
<!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
@ -1137,3 +1199,58 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh4465]: https://github.com/tomchristie/django-rest-framework/issues/4465
[gh4462]: https://github.com/tomchristie/django-rest-framework/issues/4462
[gh4458]: https://github.com/tomchristie/django-rest-framework/issues/4458
<!-- 3.5.1 -->
[gh4612]: https://github.com/tomchristie/django-rest-framework/issues/4612
[gh4608]: https://github.com/tomchristie/django-rest-framework/issues/4608
[gh4601]: https://github.com/tomchristie/django-rest-framework/issues/4601
[gh4611]: https://github.com/tomchristie/django-rest-framework/issues/4611
[gh4605]: https://github.com/tomchristie/django-rest-framework/issues/4605
[gh4609]: https://github.com/tomchristie/django-rest-framework/issues/4609
[gh4606]: https://github.com/tomchristie/django-rest-framework/issues/4606
[gh4600]: https://github.com/tomchristie/django-rest-framework/issues/4600
<!-- 3.5.2 -->
[gh4631]: https://github.com/tomchristie/django-rest-framework/issues/4631
[gh4638]: https://github.com/tomchristie/django-rest-framework/issues/4638
[gh4532]: https://github.com/tomchristie/django-rest-framework/issues/4532
[gh4636]: https://github.com/tomchristie/django-rest-framework/issues/4636
[gh4622]: https://github.com/tomchristie/django-rest-framework/issues/4622
[gh4602]: https://github.com/tomchristie/django-rest-framework/issues/4602
[gh4640]: https://github.com/tomchristie/django-rest-framework/issues/4640
[gh4624]: https://github.com/tomchristie/django-rest-framework/issues/4624
[gh4569]: https://github.com/tomchristie/django-rest-framework/issues/4569
[gh4627]: https://github.com/tomchristie/django-rest-framework/issues/4627
[gh4620]: https://github.com/tomchristie/django-rest-framework/issues/4620
[gh4628]: https://github.com/tomchristie/django-rest-framework/issues/4628
[gh4639]: https://github.com/tomchristie/django-rest-framework/issues/4639
<!-- 3.5.3 -->
[gh4660]: https://github.com/tomchristie/django-rest-framework/issues/4660
[gh4643]: https://github.com/tomchristie/django-rest-framework/issues/4643
[gh4644]: https://github.com/tomchristie/django-rest-framework/issues/4644
[gh4645]: https://github.com/tomchristie/django-rest-framework/issues/4645
[gh4646]: https://github.com/tomchristie/django-rest-framework/issues/4646
[gh4650]: https://github.com/tomchristie/django-rest-framework/issues/4650
<!-- 3.5.4 -->
[gh4877]: https://github.com/tomchristie/django-rest-framework/issues/4877
[gh4753]: https://github.com/tomchristie/django-rest-framework/issues/4753
[gh4764]: https://github.com/tomchristie/django-rest-framework/issues/4764
[gh4821]: https://github.com/tomchristie/django-rest-framework/issues/4821
[gh4841]: https://github.com/tomchristie/django-rest-framework/issues/4841
[gh4759]: https://github.com/tomchristie/django-rest-framework/issues/4759
[gh4869]: https://github.com/tomchristie/django-rest-framework/issues/4869
[gh4870]: https://github.com/tomchristie/django-rest-framework/issues/4870
[gh4790]: https://github.com/tomchristie/django-rest-framework/issues/4790
[gh4661]: https://github.com/tomchristie/django-rest-framework/issues/4661
[gh4668]: https://github.com/tomchristie/django-rest-framework/issues/4668
[gh4750]: https://github.com/tomchristie/django-rest-framework/issues/4750
[gh4678]: https://github.com/tomchristie/django-rest-framework/issues/4678
[gh4634]: https://github.com/tomchristie/django-rest-framework/issues/4634
[gh4669]: https://github.com/tomchristie/django-rest-framework/issues/4669
[gh4712]: https://github.com/tomchristie/django-rest-framework/issues/4712

View File

@ -1,4 +1,4 @@
# Third Party Resources
# Third Party Packages
> Software ecosystems […] establish a community that further accelerates the sharing of knowledge, content, issues, expertise and skills.
>
@ -165,7 +165,7 @@ We suggest adding your package to the [REST Framework][rest-framework-grid] grid
#### Adding to the Django REST framework docs
Create a [Pull Request][drf-create-pr] or [Issue][drf-create-issue] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Resources][third-party-resources] section.
Create a [Pull Request][drf-create-pr] or [Issue][drf-create-issue] on GitHub, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Packages][third-party-packages] section.
#### Announce on the discussion group.
@ -189,6 +189,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
* [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.
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
### Permissions
@ -203,7 +204,10 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-gis][djangorestframework-gis] - Geographic add-ons
* [djangorestframework-hstore][djangorestframework-hstore] - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature.
* [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec.
* [html-json-forms][html-json-forms]: Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [html-json-forms][html-json-forms] - Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [django-rest-framework-serializer-extensions][drf-serializer-extensions] -
Enables black/whitelisting fields, and conditionally expanding child serializers on a per-view/request basis.
* [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response.
### Serializer fields
@ -251,38 +255,12 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js
* [django-versatileimagefield][django-versatileimagefield] - Provides a drop-in replacement for Django's stock `ImageField` that makes it easy to serve images in multiple sizes/renditions from a single field. For DRF-specific implementation docs, [click here][django-versatileimagefield-drf-docs].
* [drf-tracking][drf-tracking] - Utilities to track requests to DRF API views.
* [drf_tweaks][drf_tweaks] - Serializers with one-step validation (and more), pagination without counts and other tweaks.
* [django-rest-framework-braces][django-rest-framework-braces] - Collection of utilities for working with Django Rest Framework. The most notable ones are [FormSerializer](https://django-rest-framework-braces.readthedocs.io/en/latest/overview.html#formserializer) and [SerializerForm](https://django-rest-framework-braces.readthedocs.io/en/latest/overview.html#serializerform), which are adapters between DRF serializers and Django forms.
* [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-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.
## Other Resources
### Tutorials
* [Beginner's Guide to the Django Rest Framework][beginners-guide-to-the-django-rest-framework]
* [Getting Started with Django Rest Framework and AngularJS][getting-started-with-django-rest-framework-and-angularjs]
* [End to end web app with Django-Rest-Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs]
* [Start Your API - django-rest-framework part 1][start-your-api-django-rest-framework-part-1]
* [Permissions & Authentication - django-rest-framework part 2][permissions-authentication-django-rest-framework-part-2]
* [ViewSets and Routers - django-rest-framework part 3][viewsets-and-routers-django-rest-framework-part-3]
* [Django Rest Framework User Endpoint][django-rest-framework-user-endpoint]
* [Check credentials using Django Rest Framework][check-credentials-using-django-rest-framework]
* [Django REST Framework course][django-rest-framework-course]
### Videos
* [Ember and Django Part 1 (Video)][ember-and-django-part 1-video]
* [Django Rest Framework Part 1 (Video)][django-rest-framework-part-1-video]
### Articles
* [Web API performance: profiling Django REST framework][web-api-performance-profiling-django-rest-framework]
* [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework]
* [Blog posts about Django REST framework][medium-django-rest-framework]
### Documentations
* [Classy Django REST Framework][cdrf.co]
[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
@ -299,7 +277,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf-create-issue]: https://github.com/tomchristie/django-rest-framework/issues/new
[authentication]: ../api-guide/authentication.md
[permissions]: ../api-guide/permissions.md
[third-party-resources]: ../topics/third-party-resources/#existing-third-party-packages
[third-party-packages]: ../topics/third-party-packages/#existing-third-party-packages
[discussion-group]: https://groups.google.com/forum/#!forum/django-rest-framework
[djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
@ -332,22 +310,9 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
[drf-extensions]: https://github.com/chibisov/drf-extensions
[ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/
[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/
[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/
[django-rest-framework-user-endpoint]: http://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/
[check-credentials-using-django-rest-framework]: http://richardtier.com/2014/03/06/110/
[ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1
[django-rest-framework-part-1-video]: http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1
[web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/
[django-rest-auth]: https://github.com/Tivix/django-rest-auth/
[django-versatileimagefield]: https://github.com/WGBH/django-versatileimagefield
[django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html
[cdrf.co]:http://www.cdrf.co
[drf-tracking]: https://github.com/aschn/drf-tracking
[django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
@ -361,5 +326,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[django-rest-messaging]: https://github.com/raphaelgyory/django-rest-messaging
[django-rest-messaging-centrifugo]: https://github.com/raphaelgyory/django-rest-messaging-centrifugo
[django-rest-messaging-js]: https://github.com/raphaelgyory/django-rest-messaging-js
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[drf_tweaks]: https://github.com/ArabellaTech/drf_tweaks
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields

View File

@ -0,0 +1,109 @@
# Tutorials and Resources
There are a wide range of resources available for learning and using Django REST framework. We try to keep a comprehensive list available here.
## Tutorials
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
* [Django REST Framework - An Introduction][drf-an-intro]
* [Django REST Framework Tutorial][drf-tutorial]
* [Django REST Framework Course][django-rest-framework-course]
* [Building a RESTful API with Django REST Framework][building-a-restful-api-with-drf]
* [Getting Started with Django REST Framework and AngularJS][getting-started-with-django-rest-framework-and-angularjs]
* [End to End Web App with Django REST Framework & AngularJS][end-to-end-web-app-with-django-rest-framework-angularjs]
* [Start Your API - Django REST Framework Part 1][start-your-api-django-rest-framework-part-1]
* [Permissions & Authentication - Django REST Framework Part 2][permissions-authentication-django-rest-framework-part-2]
* [ViewSets and Routers - Django REST Framework Part 3][viewsets-and-routers-django-rest-framework-part-3]
* [Django REST Framework User Endpoint][django-rest-framework-user-endpoint]
* [Check Credentials Using Django REST Framework][check-credentials-using-django-rest-framework]
* [Creating a Production Ready API with Python and Django REST Framework Part 1][creating-a-production-ready-api-with-python-and-drf-part1]
* [Creating a Production Ready API with Python and Django REST Framework Part 2][creating-a-production-ready-api-with-python-and-drf-part2]
## Videos
### Talks
* [How to Make a Full Fledged REST API with Django OAuth Toolkit][full-fledged-rest-api-with-django-oauth-tookit]
* [Django REST API - So Easy You Can Learn It in 25 Minutes][django-rest-api-so-easy]
* [Tom Christie about Django Rest Framework at Django: Under The Hood][django-under-hood-2014]
* [Django REST Framework: Schemas, Hypermedia & Client Libraries][pycon-uk-2016]
### Tutorials
* [Django REST Framework Part 1][django-rest-framework-part-1-video]
* [Django REST Framework in Your PJ's!][drf-in-your-pjs]
* [Building a REST API Using Django & Django REST Framework][building-a-rest-api-using-django-and-drf]
* [Blog API with Django REST Framework][blog-api-with-drf]
* [Ember and Django Part 1][ember-and-django-part 1-video]
* [Django REST Framework Image Upload Tutorial (with AngularJS)][drf-image-upload-tutorial-with-angularjs]
* [Django REST Framework Tutorials][drf-tutorials]
## Articles
* [Web API performance: Profiling Django REST Framework][web-api-performance-profiling-django-rest-framework]
* [API Development with Django and Django REST Framework][api-development-with-django-and-django-rest-framework]
* [Integrating Pandas, Django REST Framework and Bokeh][integrating-pandas-drf-and-bokeh]
* [Controlling Uncertainty on Web Applications and APIs][controlling-uncertainty-on-web-apps-and-apis]
* [Full Text Search in Django REST Framework with Database Backends][full-text-search-in-drf]
* [OAuth2 Authentication with Django REST Framework and Custom Third-Party OAuth2 Backends][oauth2-authentication-with-drf]
* [Nested Resources with Django REST Framework][nested-resources-with-drf]
* [Image Fields with Django REST Framework][image-fields-with-drf]
* [Chatbot Using Django REST Framework + api.ai + SlackPart 1/3][chatbot-using-drf-part1]
* [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]
## Books
* [Hello Web App: Intermediate Concepts, Chapter 10][hello-web-app-intermediate]
### Documentations
* [Classy Django REST Framework][cdrf.co]
* [DRF-schema-adapter][drf-schema]
Want your Django REST Framework talk/tutorial/article to be added to our website? Or know of a resource that's not yet included here? Please [submit a pull request][submit-pr] or [email us][mailto:anna@django-rest-framework.org]!
[beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
[getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
[end-to-end-web-app-with-django-rest-framework-angularjs]: http://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/
[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/
[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/
[django-rest-framework-user-endpoint]: http://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/
[check-credentials-using-django-rest-framework]: http://richardtier.com/2014/03/06/110/
[ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1
[django-rest-framework-part-1-video]: http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1
[web-api-performance-profiling-django-rest-framework]: http://dabapps.com/blog/api-performance-profiling-django-rest-framework/
[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/
[cdrf.co]:http://www.cdrf.co
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg
[django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E
[integrating-pandas-drf-and-bokeh]: http://machinalis.com/blog/pandas-django-rest-framework-bokeh/
[controlling-uncertainty-on-web-apps-and-apis]: http://machinalis.com/blog/controlling-uncertainty-on-web-applications-and-apis/
[full-text-search-in-drf]: http://machinalis.com/blog/full-text-search-on-django-rest-framework/
[oauth2-authentication-with-drf]: http://machinalis.com/blog/oauth2-authentication/
[nested-resources-with-drf]: http://machinalis.com/blog/nested-resources-with-django/
[image-fields-with-drf]: http://machinalis.com/blog/image-fields-with-django-rest-framework/
[chatbot-using-drf-part1]: https://chatbotslife.com/chatbot-using-django-rest-framework-api-ai-slack-part-1-3-69c7e38b7b1e#.g2aceuncf
[new-django-admin-with-drf-and-emberjs]: https://blog.levit.be/new-django-admin-with-emberjs-what-are-the-news/
[drf-schema]: 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-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
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww
[building-a-rest-api-using-django-and-drf]: https://www.youtube.com/watch?v=PwssEec3IRw
[drf-tutorials]: https://www.youtube.com/watch?v=axRCBgbOJp8&list=PLJtp8Jm8EDzjgVg9vVyIUMoGyqtegj7FH
[drf-image-upload-tutorial-with-angularjs]: https://www.youtube.com/watch?v=hMiNTCIY7dw&list=PLUe5s-xycYk_X0vDjYBmKuIya2a2myF8O
[blog-api-with-drf]: https://www.youtube.com/watch?v=XMu0T6L2KRQ&list=PLEsfXFp6DpzTOcOVdZF-th7BS_GYGguAS
[drf-an-intro]: https://realpython.com/blog/python/django-rest-framework-quick-start/
[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/
[submit-pr]: https://github.com/tomchristie/django-rest-framework

View File

@ -48,6 +48,8 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
'snippets.apps.SnippetsConfig',
)
Please note that if you're using Django <1.9, you need to replace `snippets.apps.SnippetsConfig` with `snippets`.
Okay, we're ready to roll.
## Creating a model to work with
@ -259,12 +261,12 @@ Note that because we want to be able to POST to this view from clients that won'
We'll also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the snippet.
@csrf_exempt
def snippet_detail(request, id):
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(id=id)
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
@ -291,7 +293,7 @@ Finally we need to wire these views up. Create the `snippets/urls.py` file:
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<id>[0-9]+)/$', views.snippet_detail),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.

View File

@ -66,12 +66,12 @@ Our instance view is an improvement over the previous example. It's a little mo
Here is the view for an individual snippet, in the `views.py` module.
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, id):
def snippet_detail(request, pk):
"""
Retrieve, update or delete a snippet instance.
"""
try:
snippet = Snippet.objects.get(id=id)
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
@ -104,7 +104,7 @@ Start by adding a `format` keyword argument to both of the views, like so.
and
def snippet_detail(request, id, format=None):
def snippet_detail(request, pk, format=None):
Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
@ -114,7 +114,7 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<id>[0-9]+)$', views.snippet_detail),
url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)

View File

@ -36,33 +36,33 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, id):
def get_object(self, pk):
try:
return Snippet.objects.get(id=id)
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, id, format=None):
snippet = self.get_object(id)
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, id, format=None):
snippet = self.get_object(id)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id, format=None):
snippet = self.get_object(id)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
That's looking good. Again, it's still pretty similar to the function based view right now.
We'll also need to refactor our `urls.py` slightly now we're using class-based views.
We'll also need to refactor our `urls.py` slightly now that we're using class-based views.
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
@ -70,7 +70,7 @@ We'll also need to refactor our `urls.py` slightly now we're using class-based v
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<id>[0-9]+)/$', views.SnippetDetail.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)

View File

@ -14,7 +14,7 @@ First, let's add a couple of fields. One of those fields will be used to repres
Add the following two fields to the `Snippet` model in `models.py`.
owner = models.ForeignKey('auth.User', related_name='snippets')
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
We'd also need to make sure that when the model is saved, that we populate the highlighted field, using the `pygments` code highlighting library.
@ -88,7 +88,7 @@ Make sure to also import the `UserSerializer` class
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `urls.py`.
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<id>[0-9]+)/$', views.UserDetail.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
## Associating Snippets with Users

View File

@ -48,7 +48,7 @@ We'll add a url pattern for our new API root in `snippets/urls.py`:
And then add a url pattern for the snippet highlights:
url(r'^snippets/(?P<id>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
## Hyperlinking our API
@ -116,16 +116,16 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<id>[0-9]+)/$',
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<id>[0-9]+)/highlight/$',
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<id>[0-9]+)/$',
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])

View File

@ -51,7 +51,7 @@ This time we've used the `ModelViewSet` class in order to get the complete set o
Notice that we've also used the `@detail_route` decorator to create a custom action, named `highlight`. This decorator can be used to add any custom endpoints that don't fit into the standard `create`/`update`/`delete` style.
Custom actions which use the `@detail_route` decorator will respond to `GET` requests. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
Custom actions which use the `@detail_route` decorator will respond to `GET` requests by default. We can use the `methods` argument if we wanted an action that responded to `POST` requests.
The URLs for custom actions by default depend on the method name itself. If you want to change the way url should be constructed, you can include url_path as a decorator keyword argument.
@ -92,10 +92,10 @@ Now that we've bound our resources into concrete views, we can register the view
urlpatterns = format_suffix_patterns([
url(r'^$', api_root),
url(r'^snippets/$', snippet_list, name='snippet-list'),
url(r'^snippets/(?P<id>[0-9]+)/$', snippet_detail, name='snippet-detail'),
url(r'^snippets/(?P<id>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
url(r'^users/$', user_list, name='user-list'),
url(r'^users/(?P<id>[0-9]+)/$', user_detail, name='user-detail')
url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
])
## Using Routers

View File

@ -53,10 +53,10 @@ representation become available as an option.
We can also request the schema from the command line, by specifying the desired
content type in the `Accept` header.
$ http http://127.0.0.1:8000/schema/ Accept:application/vnd.coreapi+json
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/vnd.coreapi+json
Content-Type: application/coreapi+json
{
"_meta": {

View File

@ -118,7 +118,9 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}

View File

@ -58,7 +58,8 @@ pages:
- 'Browser Enhancements': 'topics/browser-enhancements.md'
- 'The Browsable API': 'topics/browsable-api.md'
- 'REST, Hypermedia & HATEOAS': 'topics/rest-hypermedia-hateoas.md'
- 'Third Party Resources': 'topics/third-party-resources.md'
- 'Third Party Packages': 'topics/third-party-packages.md'
- 'Tutorials and Resources': 'topics/tutorials-and-resources.md'
- 'Contributing to REST framework': 'topics/contributing.md'
- 'Project management': 'topics/project-management.md'
- '3.0 Announcement': 'topics/3.0-announcement.md'
@ -66,7 +67,9 @@ pages:
- '3.2 Announcement': 'topics/3.2-announcement.md'
- '3.3 Announcement': 'topics/3.3-announcement.md'
- '3.4 Announcement': 'topics/3.4-announcement.md'
- '3.5 Announcement': 'topics/3.5-announcement.md'
- 'Kickstarter Announcement': 'topics/kickstarter-announcement.md'
- 'Mozilla Grant': 'topics/mozilla-grant.md'
- 'Funding': 'topics/funding.md'
- 'Release Notes': 'topics/release-notes.md'
- 'Jobs': 'topics/jobs.md'

View File

@ -1,5 +1,5 @@
# Optional packages which may be used with REST framework.
markdown==2.6.4
django-guardian==1.4.6
django-filter==0.14.0
django-filter==1.0.0
coreapi==2.0.8

View File

@ -1,4 +1,4 @@
# PyTest for running the tests.
pytest==2.9.1
pytest-django==2.9.1
pytest-cov==1.8.1
pytest==3.0.5
pytest-django==3.1.2
pytest-cov==2.4.0

View File

@ -1,4 +1,4 @@
"""
r"""
______ _____ _____ _____ __
| ___ \ ___/ ___|_ _| / _| | |
| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
__version__ = '3.5.0'
__version__ = '3.5.4'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2016 Tom Christie'

View File

@ -17,11 +17,6 @@ from django.template import Context, RequestContext, Template
from django.utils import six
from django.views.generic import View
try:
import importlib # Available in Python 3.1+
except ImportError:
from django.utils import importlib # Will be removed in Django 1.9
try:
from django.urls import (
@ -170,6 +165,16 @@ except ImportError:
JSONField = None
# coreapi is optional (Note that uritemplate is a dependency of coreapi)
try:
import coreapi
import uritemplate
except (ImportError, SyntaxError):
# SyntaxError is possible under python 3.2
coreapi = None
uritemplate = None
# django-filter is optional
try:
import django_filters
@ -184,16 +189,6 @@ except ImportError:
crispy_forms = None
# coreapi is optional (Note that uritemplate is a dependency of coreapi)
try:
import coreapi
import uritemplate
except (ImportError, SyntaxError):
# SyntaxError is possible under python 3.2
coreapi = None
uritemplate = None
# requests is optional
try:
import requests
@ -207,7 +202,6 @@ guardian = None
try:
if 'guardian' in settings.INSTALLED_APPS:
import guardian
import guardian.shortcuts # Fixes #1624
except ImportError:
pass
@ -313,3 +307,10 @@ def set_many(instance, field, value):
else:
field = getattr(instance, field)
field.set(value)
def include(module, namespace=None, app_name=None):
from django.conf.urls import include
if django.VERSION < (1,9):
return include(module, namespace, app_name)
else:
return include((module, app_name), namespace)

View File

@ -210,14 +210,14 @@ class Throttled(APIException):
default_code = 'throttled'
def __init__(self, wait=None, detail=None, code=None):
if detail is None:
detail = force_text(self.default_detail)
if wait is not None:
wait = math.ceil(wait)
detail = ' '.join((
detail,
force_text(ungettext(self.extra_detail_singular.format(wait=wait),
self.extra_detail_plural.format(wait=wait),
wait))))
self.wait = wait
super(Throttled, self).__init__(detail, code)
if wait is None:
self.wait = None
else:
self.wait = math.ceil(wait)
self.detail += ' ' + force_text(ungettext(
self.extra_detail_singular.format(wait=self.wait),
self.extra_detail_plural.format(wait=self.wait),
self.wait
))

View File

@ -30,6 +30,7 @@ from django.utils.formats import (
)
from django.utils.functional import cached_property
from django.utils.ipv6 import clean_ipv6_address
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
@ -56,12 +57,17 @@ if six.PY3:
"""
True if the object is a callable that takes no arguments.
"""
if not callable(obj):
if not (inspect.isfunction(obj) or inspect.ismethod(obj)):
return False
sig = inspect.signature(obj)
params = sig.parameters.values()
return all(param.default != param.empty for param in params)
return all(
param.kind == param.VAR_POSITIONAL or
param.kind == param.VAR_KEYWORD or
param.default != param.empty
for param in params
)
else:
def is_simple_callable(obj):
@ -146,7 +152,7 @@ def to_choices_dict(choices):
# choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
ret = OrderedDict()
for choice in choices:
if (not isinstance(choice, (list, tuple))):
if not isinstance(choice, (list, tuple)):
# single choice
ret[choice] = choice
else:
@ -641,8 +647,20 @@ class BooleanField(Field):
}
default_empty_html = False
initial = False
TRUE_VALUES = {'t', 'T', 'true', 'True', 'TRUE', '1', 1, True}
FALSE_VALUES = {'f', 'F', 'false', 'False', 'FALSE', '0', 0, 0.0, False}
TRUE_VALUES = {
't', 'T',
'true', 'True', 'TRUE',
'on', 'On', 'ON',
'1', 1,
True
}
FALSE_VALUES = {
'f', 'F',
'false', 'False', 'FALSE',
'off', 'Off', 'OFF',
'0', 0, 0.0,
False
}
def __init__(self, **kwargs):
assert 'allow_null' not in kwargs, '`allow_null` is not a valid option. Use `NullBooleanField` instead.'
@ -1093,7 +1111,7 @@ class DateTimeField(Field):
if (field_timezone is not None) and not timezone.is_aware(value):
return timezone.make_aware(value, field_timezone)
elif (field_timezone is None) and timezone.is_aware(value):
return timezone.make_naive(value, timezone.UTC())
return timezone.make_naive(value, utc)
return value
def default_timezone(self):
@ -1434,11 +1452,11 @@ class FileField(Field):
return data
def to_representation(self, value):
use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
if not value:
return None
use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
if use_url:
if not getattr(value, 'url', None):
# If the file has not been saved it may not have a URL.
@ -1493,12 +1511,16 @@ class ListField(Field):
initial = []
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
'empty': _('This list may not be empty.')
'empty': _('This list may not be empty.'),
'min_length': _('Ensure this field has at least {min_length} elements.'),
'max_length': _('Ensure this field has no more than {max_length} elements.')
}
def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
self.allow_empty = kwargs.pop('allow_empty', True)
self.max_length = kwargs.pop('max_length', None)
self.min_length = kwargs.pop('min_length', None)
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
assert self.child.source is None, (
@ -1508,6 +1530,12 @@ class ListField(Field):
super(ListField, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
if self.max_length is not None:
message = self.error_messages['max_length'].format(max_length=self.max_length)
self.validators.append(MaxLengthValidator(self.max_length, message=message))
if self.min_length is not None:
message = self.error_messages['min_length'].format(min_length=self.min_length)
self.validators.append(MinLengthValidator(self.min_length, message=message))
def get_value(self, dictionary):
if self.field_name not in dictionary:
@ -1603,7 +1631,7 @@ class JSONField(Field):
def get_value(self, dictionary):
if html.is_html_input(dictionary) and self.field_name in dictionary:
# When HTML form input is used, mark up the input
# as being a JSON string, rather than a JSON primative.
# as being a JSON string, rather than a JSON primitive.
class JSONString(six.text_type):
def __new__(self, value):
ret = six.text_type.__new__(self, value)
@ -1641,7 +1669,7 @@ class ReadOnlyField(Field):
A read-only field that simply returns the field value.
If the field is a method with no parameters, the method will be called
and it's return value used as the representation.
and its return value used as the representation.
For example, the following would call `get_expiry_date()` on the object:

View File

@ -5,9 +5,9 @@ returned by list views.
from __future__ import unicode_literals
import operator
import warnings
from functools import reduce
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.constants import LOOKUP_SEP
@ -16,50 +16,10 @@ from django.utils import six
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import (
coreapi, crispy_forms, distinct, django_filters, guardian, template_render
coreapi, distinct, django_filters, guardian, template_render
)
from rest_framework.settings import api_settings
if 'crispy_forms' in settings.INSTALLED_APPS and crispy_forms and django_filters:
# If django-crispy-forms is installed, use it to get a bootstrap3 rendering
# of the DjangoFilterBackend controls when displayed as HTML.
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
layout_components = list(self.form.fields.keys()) + [
Submit('', _('Submit'), css_class='btn-default'),
]
helper = FormHelper()
helper.form_method = 'GET'
helper.template_pack = 'bootstrap3'
helper.layout = Layout(*layout_components)
self.form.helper = helper
filter_template = 'rest_framework/filters/django_filter_crispyforms.html'
elif django_filters:
# If django-crispy-forms is not installed, use the standard
# 'form.as_p' rendering when DjangoFilterBackend is displayed as HTML.
class FilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(FilterSet, self).__init__(*args, **kwargs)
for field in self.form.fields.values():
field.help_text = None
filter_template = 'rest_framework/filters/django_filter.html'
else:
FilterSet = None
filter_template = None
class BaseFilterBackend(object):
"""
@ -77,78 +37,39 @@ class BaseFilterBackend(object):
return []
if django_filters:
from django_filters.rest_framework.filterset import FilterSet as DFFilterSet
class FilterSet(DFFilterSet):
def __init__(self, *args, **kwargs):
warnings.warn(
"The built in 'rest_framework.filters.FilterSet' is pending deprecation. "
"You should use 'django_filters.rest_framework.FilterSet' instead.",
PendingDeprecationWarning
)
return super(FilterSet, self).__init__(*args, **kwargs)
else:
def FilterSet():
assert False, 'django-filter must be installed to use the `FilterSet` class'
class DjangoFilterBackend(BaseFilterBackend):
"""
A filter backend that uses django-filter.
"""
default_filter_set = FilterSet
template = filter_template
def __init__(self):
def __new__(cls, *args, **kwargs):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required'
def get_filter_class(self, view, queryset=None):
"""
Return the django-filters `FilterSet` used to filter the queryset.
"""
filter_class = getattr(view, 'filter_class', None)
filter_fields = getattr(view, 'filter_fields', None)
warnings.warn(
"The built in 'rest_framework.filters.DjangoFilterBackend' is pending deprecation. "
"You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.",
PendingDeprecationWarning
)
if filter_class:
filter_model = filter_class.Meta.model
from django_filters.rest_framework import DjangoFilterBackend
assert issubclass(queryset.model, filter_model), \
'FilterSet model %s does not match queryset model %s' % \
(filter_model, queryset.model)
return filter_class
if filter_fields:
class AutoFilterSet(self.default_filter_set):
class Meta:
model = queryset.model
fields = filter_fields
return AutoFilterSet
return None
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset).qs
return queryset
def to_html(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if not filter_class:
return None
filter_instance = filter_class(request.query_params, queryset=queryset)
context = {
'filter': filter_instance
}
template = loader.get_template(self.template)
return template_render(template, context)
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
filter_class = getattr(view, 'filter_class', None)
if filter_class:
return [
coreapi.Field(name=field_name, required=False, location='query')
for field_name in filter_class().filters.keys()
]
filter_fields = getattr(view, 'filter_fields', None)
if filter_fields:
return [
coreapi.Field(name=field_name, required=False, location='query')
for field_name in filter_fields
]
return []
return DjangoFilterBackend(*args, **kwargs)
class SearchFilter(BaseFilterBackend):
@ -222,7 +143,7 @@ class SearchFilter(BaseFilterBackend):
# Filtering against a many-to-many field requires us to
# call queryset.distinct() in order to avoid duplicate items
# in the resulting queryset.
# We try to avoid this is possible, for performance reasons.
# We try to avoid this if possible, for performance reasons.
queryset = distinct(queryset, base)
return queryset
@ -373,6 +294,11 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
perm_format = '%(app_label)s.view_%(model_name)s'
def filter_queryset(self, request, queryset, view):
# We want to defer this import until run-time, rather than import-time.
# See https://github.com/tomchristie/django-rest-framework/issues/4608
# (Also see #1624 for why we need to make this import explicitly)
from guardian.shortcuts import get_objects_for_user
extra = {}
user = request.user
model_cls = queryset.model
@ -386,4 +312,4 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
extra = {'accept_global_perms': False}
else:
extra = {}
return guardian.shortcuts.get_objects_for_user(user, permission, queryset, **extra)
return get_objects_for_user(user, permission, queryset, **extra)

View File

@ -71,9 +71,8 @@ class UpdateModelMixin(object):
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# refresh the instance from the database.
instance = self.get_object()
serializer = self.get_serializer(instance)
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)

View File

@ -131,7 +131,7 @@ def _reverse_ordering(ordering_tuple):
ordering and return a new tuple, eg. `('created', '-uuid')`.
"""
def invert(x):
return x[1:] if (x.startswith('-')) else '-' + x
return x[1:] if x.startswith('-') else '-' + x
return tuple([invert(item) for item in ordering_tuple])
@ -711,7 +711,11 @@ class CursorPagination(BasePagination):
return replace_query_param(self.base_url, self.cursor_query_param, encoded)
def _get_position_from_instance(self, instance, ordering):
attr = getattr(instance, ordering[0].lstrip('-'))
field_name = ordering[0].lstrip('-')
if isinstance(instance, dict):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
return six.text_type(attr)
def get_paginated_response(self, data):

View File

@ -503,7 +503,7 @@ class ManyRelatedField(Field):
return []
relationship = get_attribute(instance, self.source_attrs)
return relationship.all() if (hasattr(relationship, 'all')) else relationship
return relationship.all() if hasattr(relationship, 'all') else relationship
def to_representation(self, iterable):
return [

View File

@ -228,7 +228,7 @@ class StaticHTMLRenderer(TemplateHTMLRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
renderer_context = renderer_context or {}
response = renderer_context['response']
response = renderer_context.get('response')
if response and response.exception:
request = renderer_context['request']
@ -540,7 +540,7 @@ class BrowsableAPIRenderer(BaseRenderer):
# If possible, serialize the initial content for the generic form
default_parser = view.parser_classes[0]
renderer_class = getattr(default_parser, 'renderer_class', None)
if (hasattr(view, 'get_serializer') and renderer_class):
if hasattr(view, 'get_serializer') and renderer_class:
# View has a serializer defined and parser class has a
# corresponding renderer that can be used to render the data.
@ -598,7 +598,7 @@ class BrowsableAPIRenderer(BaseRenderer):
paginator = getattr(view, 'paginator', None)
if isinstance(data, list):
pass
elif (paginator is not None and data is not None):
elif paginator is not None and data is not None:
try:
paginator.get_results(data)
except (TypeError, KeyError):
@ -738,7 +738,7 @@ class AdminRenderer(BrowsableAPIRenderer):
ret = template_render(template, context, request=renderer_context['request'])
# Creation and deletion should use redirects in the admin style.
if (response.status_code == status.HTTP_201_CREATED) and ('Location' in response):
if response.status_code == status.HTTP_201_CREATED and 'Location' in response:
response.status_code = status.HTTP_303_SEE_OTHER
response['Location'] = request.build_absolute_uri()
ret = ''
@ -764,7 +764,7 @@ class AdminRenderer(BrowsableAPIRenderer):
)
paginator = getattr(context['view'], 'paginator', None)
if (paginator is not None and data is not None):
if paginator is not None and data is not None:
try:
results = paginator.get_results(data)
except (TypeError, KeyError):

View File

@ -152,7 +152,7 @@ class Request(object):
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if (force_user is not None or force_token is not None):
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
@ -353,10 +353,9 @@ class Request(object):
def _not_authenticated(self):
"""
Return a three-tuple of (authenticator, user, authtoken), representing
an unauthenticated request.
Set authenticator, user & authtoken representing an unauthenticated request.
By default this will be (None, AnonymousUser, None).
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None

View File

@ -179,10 +179,11 @@ class SimpleRouter(BaseRouter):
initkwargs = route.initkwargs.copy()
initkwargs.update(method_kwargs)
url_path = initkwargs.pop("url_path", None) or methodname
url_name = initkwargs.pop("url_name", None) or url_path
ret.append(Route(
url=replace_methodname(route.url, url_path),
mapping={httpmethod: methodname for httpmethod in httpmethods},
name=replace_methodname(route.name, url_path),
name=replace_methodname(route.name, url_name),
initkwargs=initkwargs,
))

View File

@ -1,10 +1,11 @@
import os
import re
from collections import OrderedDict
from importlib import import_module
from django.conf import settings
from django.contrib.admindocs.views import simplify_regex
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils import six
from django.utils.encoding import force_text, smart_text
@ -37,6 +38,18 @@ types_lookup = ClassLookupDict({
})
def common_path(paths):
split_paths = [path.strip('/').split('/') for path in paths]
s1 = min(split_paths)
s2 = max(split_paths)
common = s1
for i, c in enumerate(s1):
if c != s2[i]:
common = s1[:i]
break
return '/' + '/'.join(common)
def get_pk_name(model):
meta = model._meta.concrete_model._meta
return _get_pk(meta).name
@ -250,6 +263,8 @@ class SchemaGenerator(object):
view_endpoints.append((path, method, view))
# Only generate the path prefix for paths that will be included
if not paths:
return None
prefix = self.determine_path_prefix(paths)
for path, method, view in view_endpoints:
@ -292,7 +307,7 @@ class SchemaGenerator(object):
# one URL that doesn't have a path prefix.
return '/'
prefixes.append('/' + prefix + '/')
return os.path.commonprefix(prefixes)
return common_path(prefixes)
def create_view(self, callback, method, request=None):
"""
@ -328,7 +343,7 @@ class SchemaGenerator(object):
try:
view.check_permissions(view.request)
except exceptions.APIException:
except (exceptions.APIException, Http404, PermissionDenied):
return False
return True
@ -399,7 +414,7 @@ class SchemaGenerator(object):
current_section, seperator, lead = line.partition(':')
sections[current_section] = lead.strip()
else:
sections[current_section] += line + '\n'
sections[current_section] += '\n' + line
header = getattr(view, 'action', method.lower())
if header in sections:
@ -556,11 +571,11 @@ class SchemaGenerator(object):
return named_path_components + [action]
def get_schema_view(title=None, url=None, renderer_classes=None):
def get_schema_view(title=None, url=None, urlconf=None, renderer_classes=None):
"""
Return a schema view.
"""
generator = SchemaGenerator(title=title, url=url)
generator = SchemaGenerator(title=title, url=url, urlconf=urlconf)
if renderer_classes is None:
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
rclasses = [renderers.CoreJSONRenderer, renderers.BrowsableAPIRenderer]

View File

@ -12,18 +12,27 @@ response content is handled by parsers and renderers.
"""
from __future__ import unicode_literals
import copy
import inspect
import traceback
from collections import Mapping, OrderedDict
from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models import DurationField as ModelDurationField
from django.db.models.fields import Field as DjangoModelField
from django.db.models.fields import FieldDoesNotExist
from django.utils import six, timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import JSONField as ModelJSONField
from rest_framework.compat import postgres_fields, set_many, unicode_to_repr
from rest_framework.utils import model_meta
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail, set_value
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
from rest_framework.utils.field_mapping import (
ClassLookupDict, get_field_kwargs, get_nested_relation_kwargs,
get_relation_kwargs, get_url_kwargs
@ -42,9 +51,23 @@ from rest_framework.validators import (
#
# This helps keep the separation between model fields, form fields, and
# serializer fields more explicit.
from rest_framework.fields import ( # NOQA # isort:skip
BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
HiddenField, IPAddressField, ImageField, IntegerField, JSONField, ListField,
ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField, RegexField,
SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
)
from rest_framework.relations import ( # NOQA # isort:skip
HyperlinkedIdentityField, HyperlinkedRelatedField, ManyRelatedField,
PrimaryKeyRelatedField, RelatedField, SlugRelatedField, StringRelatedField,
)
from rest_framework.fields import * # NOQA # isort:skip
from rest_framework.relations import * # NOQA # isort:skip
# Non-field imports, but public API
from rest_framework.fields import ( # NOQA # isort:skip
CreateOnlyDefault, CurrentUserDefault, SkipField, empty
)
from rest_framework.relations import Hyperlink, PKOnlyObject # NOQA # isort:skip
# We assume that 'validators' are intended for the child serializer,
# rather than the parent serializer.
@ -282,7 +305,11 @@ class SerializerMetaclass(type):
# in order to maintain the correct order of fields.
for base in reversed(bases):
if hasattr(base, '_declared_fields'):
fields = list(base._declared_fields.items()) + fields
fields = [
(field_name, obj) for field_name, obj
in base._declared_fields.items()
if field_name not in attrs
] + fields
return OrderedDict(fields)
@ -299,11 +326,11 @@ def as_serializer_error(exc):
else:
detail = exc.detail
if isinstance(detail, dict):
if isinstance(detail, Mapping):
# If errors may be a dict we use the standard {key: list of values}.
# Here we ensure that all the values are *lists* of errors.
return {
key: value if isinstance(value, (list, dict)) else [value]
key: value if isinstance(value, (list, Mapping)) else [value]
for key, value in detail.items()
}
elif isinstance(detail, list):
@ -415,7 +442,7 @@ class Serializer(BaseSerializer):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, dict):
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
@ -507,7 +534,7 @@ class Serializer(BaseSerializer):
@property
def errors(self):
ret = super(Serializer, self).errors
if isinstance(ret, list) and len(ret) == 1 and ret[0].code == 'null':
if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
# Edge case. Provide a more descriptive error than
# "this field may not be null", when no data is passed.
detail = ErrorDetail('No data provided', code='null')
@ -705,7 +732,7 @@ class ListSerializer(BaseSerializer):
@property
def errors(self):
ret = super(ListSerializer, self).errors
if isinstance(ret, list) and len(ret) == 1 and ret[0].code == 'null':
if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null':
# Edge case. Provide a more descriptive error than
# "this field may not be null", when no data is passed.
detail = ErrorDetail('No data provided', code='null')
@ -746,7 +773,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
isinstance(field, BaseSerializer) and
(field.source in validated_data) and
isinstance(validated_data[field.source], (list, dict))
for key, field in serializer.fields.items()
for field in serializer._writable_fields
), (
'The `.{method_name}()` method does not support writable nested '
'fields by default.\nWrite an explicit `.{method_name}()` method for '
@ -1150,7 +1177,7 @@ class ModelSerializer(Serializer):
if postgres_fields and isinstance(model_field, postgres_fields.ArrayField):
# Populate the `child` argument on `ListField` instances generated
# for the PostgrSQL specfic `ArrayField`.
# for the PostgreSQL specific `ArrayField`.
child_model_field = model_field.base_field
child_field_class, child_field_kwargs = self.build_standard_field(
'child', child_model_field
@ -1263,6 +1290,15 @@ class ModelSerializer(Serializer):
kwargs['read_only'] = True
extra_kwargs[field_name] = kwargs
else:
# Guard against the possible misspelling `readonly_fields` (used
# by the Django admin and others).
assert not hasattr(self.Meta, 'readonly_fields'), (
'Serializer `%s.%s` has field `readonly_fields`; '
'the correct spelling for the option is `read_only_fields`.' %
(self.__class__.__module__, self.__class__.__name__)
)
return extra_kwargs
def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs):

View File

@ -18,13 +18,13 @@ REST framework settings, checking for user settings first, then falling
back to the defaults.
"""
from __future__ import unicode_literals
from importlib import import_module
from django.conf import settings
from django.test.signals import setting_changed
from django.utils import six
from rest_framework import ISO_8601
from rest_framework.compat import importlib
DEFAULTS = {
# Base API policies
@ -179,7 +179,7 @@ def import_from_string(val, setting_name):
# Nod to tastypie's use of importlib.
parts = val.split('.')
module_path, class_name = '.'.join(parts[:-1]), parts[-1]
module = importlib.import_module(module_path)
module = import_module(module_path)
return getattr(module, class_name)
except (ImportError, AttributeError) as e:
msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)

View File

@ -0,0 +1,11 @@
{% load rest_framework %}
<table class="table table-striped">
<tbody>
{% for key, value in value.items %}
<tr>
<th>{{ key|format_value }}</th>
<td>{{ value|format_value }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -6,7 +6,7 @@
{% endif %}
<div class="col-sm-10">
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %}>
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %} {% if style.autofocus and style.input_type != "hidden" %}autofocus{% endif %}>
{% if field.errors %}
{% for error in field.errors %}

View File

@ -5,5 +5,5 @@
</label>
{% endif %}
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %}>
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %} {% if style.autofocus and style.input_type != "hidden" %}autofocus{% endif %}>
</div>

View File

@ -21,7 +21,7 @@
<div id="div_id_username" class="clearfix control-group {% if form.username.errors %}error{% endif %}">
<div class="form-group">
<label for="id_username">Username:</label>
<label for="id_username">{{ form.username.label }}:</label>
<input type="text" name="username" maxlength="100"
autocapitalize="off"
autocorrect="off" class="form-control textinput textInput"
@ -37,7 +37,7 @@
<div id="div_id_password" class="clearfix control-group {% if form.password.errors %}error{% endif %}">
<div class="form-group">
<label for="id_password">Password:</label>
<label for="id_password">{{ form.password.label }}:</label>
<input type="password" name="password" maxlength="100" autocapitalize="off" autocorrect="off" class="form-control textinput textInput" id="id_password" required>
{% if form.password.errors %}
<p class="text-error">

View File

@ -3,7 +3,7 @@
<label {% if style.hide_label %}class="sr-only"{% endif %}>{{ field.label }}</label>
{% endif %}
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %}>
<input name="{{ field.name }}" {% if style.input_type != "file" %}class="form-control"{% endif %} type="{{ style.input_type }}" {% if style.placeholder %}placeholder="{{ style.placeholder }}"{% endif %} {% if field.value %}value="{{ field.value }}"{% endif %} {% if style.autofocus and style.input_type != "hidden" %}autofocus{% endif %}>
{% if field.errors %}
{% for error in field.errors %}

View File

@ -106,15 +106,6 @@ if requests is not None:
def close(self):
pass
class NoExternalRequestsAdapter(requests.adapters.HTTPAdapter):
def send(self, request, *args, **kwargs):
msg = (
'RequestsClient refusing to make an outgoing network request '
'to "%s". Only "testserver" or hostnames in your ALLOWED_HOSTS '
'setting are valid.' % request.url
)
raise RuntimeError(msg)
class RequestsClient(requests.Session):
def __init__(self, *args, **kwargs):
super(RequestsClient, self).__init__(*args, **kwargs)
@ -123,7 +114,7 @@ if requests is not None:
self.mount('https://', adapter)
def request(self, method, url, *args, **kwargs):
if ':' not in url:
if not url.startswith('http'):
raise ValueError('Missing "http:" or "https:". Use a fully qualified URL, eg "http://testserver%s"' % url)
return super(RequestsClient, self).request(method, url, *args, **kwargs)

View File

@ -1,8 +1,8 @@
from __future__ import unicode_literals
from django.conf.urls import include, url
from django.conf.urls import url
from rest_framework.compat import RegexURLResolver
from rest_framework.compat import RegexURLResolver, include
from rest_framework.settings import api_settings

View File

@ -28,7 +28,9 @@ def get_breadcrumbs(url, request=None):
# Don't list the same view twice in a row.
# Probably an optional trailing slash.
if not seen or seen[-1] != view:
name = cls().get_view_name()
c = cls()
c.suffix = getattr(view, 'suffix', None)
name = c.get_view_name()
insert_url = preserve_builtin_query_params(prefix + url, request)
breadcrumbs_list.insert(0, (name, insert_url))
seen.append(view)

View File

@ -55,6 +55,11 @@ class JSONEncoder(json.JSONEncoder):
elif hasattr(obj, 'tolist'):
# Numpy arrays and array scalars.
return obj.tolist()
elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)):
raise RuntimeError(
'Cannot return a coreapi object from a JSON view. '
'You should be using a schema renderer instead for this view.'
)
elif hasattr(obj, '__getitem__'):
try:
return dict(obj)
@ -62,9 +67,4 @@ class JSONEncoder(json.JSONEncoder):
pass
elif hasattr(obj, '__iter__'):
return tuple(item for item in obj)
elif (coreapi is not None) and isinstance(obj, (coreapi.Document, coreapi.Error)):
raise RuntimeError(
'Cannot return a coreapi object from a JSON view. '
'You should be using a schema renderer instead for this view.'
)
return super(JSONEncoder, self).default(obj)

View File

@ -32,23 +32,18 @@ def dedent(content):
unindented text on the initial line.
"""
content = force_text(content)
whitespace_counts = [
len(line) - len(line.lstrip(' '))
for line in content.splitlines()[1:] if line.lstrip()
]
tab_counts = [
len(line) - len(line.lstrip('\t'))
for line in content.splitlines()[1:] if line.lstrip()
]
lines = [line for line in content.splitlines()[1:] if line.lstrip()]
# unindent the content if needed
if whitespace_counts:
whitespace_pattern = '^' + (' ' * min(whitespace_counts))
content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
elif tab_counts:
whitespace_pattern = '^' + ('\t' * min(whitespace_counts))
content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
if lines:
whitespace_counts = min([len(line) - len(line.lstrip(' ')) for line in lines])
tab_counts = min([len(line) - len(line.lstrip('\t')) for line in lines])
if whitespace_counts:
whitespace_pattern = '^' + (' ' * whitespace_counts)
content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
elif tab_counts:
whitespace_pattern = '^' + ('\t' * tab_counts)
content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
return content.strip()

View File

@ -49,10 +49,8 @@ def order_by_precedence(media_type_lst):
@python_2_unicode_compatible
class _MediaType(object):
def __init__(self, media_type_str):
if media_type_str is None:
media_type_str = ''
self.orig = media_type_str
self.full_type, self.params = parse_header(media_type_str.encode(HTTP_HEADER_ENCODING))
self.orig = '' if (media_type_str is None) else media_type_str
self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING))
self.main_type, sep, self.sub_type = self.full_type.partition('/')
def match(self, other):

View File

@ -117,7 +117,7 @@ class NamespaceVersioning(BaseVersioning):
def determine_version(self, request, *args, **kwargs):
resolver_match = getattr(request, 'resolver_match', None)
if (resolver_match is None or not resolver_match.namespace):
if resolver_match is None or not resolver_match.namespace:
return self.default_version
# Allow for possibly nested namespaces.

View File

@ -286,6 +286,12 @@ class APIView(View):
self._negotiator = self.content_negotiation_class()
return self._negotiator
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return api_settings.EXCEPTION_HANDLER
# API policy implementation methods
def perform_content_negotiation(self, request, force=False):
@ -428,7 +434,7 @@ class APIView(View):
else:
exc.status_code = status.HTTP_403_FORBIDDEN
exception_handler = self.settings.EXCEPTION_HANDLER
exception_handler = self.get_exception_handler()
context = self.get_exception_handler_context()
response = exception_handler(exc, context)

View File

View File

@ -61,9 +61,6 @@ if sys.argv[-1] == 'publish':
import pypandoc
except ImportError:
print("pypandoc not installed.\nUse `pip install pypandoc`.\nExiting.")
if os.system("pip freeze | grep wheel"):
print("wheel not installed.\nUse `pip install wheel`.\nExiting.")
sys.exit()
if os.system("pip freeze | grep twine"):
print("twine not installed.\nUse `pip install twine`.\nExiting.")
sys.exit()
@ -95,11 +92,19 @@ setup(
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 1.8',
'Framework :: Django :: 1.9',
'Framework :: Django :: 1.10',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Internet :: WWW/HTTP',
]
)

View File

@ -26,16 +26,19 @@ class DropdownWithAuthTests(TestCase):
def test_name_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, 'john')
content = response.content.decode('utf8')
assert 'john' in content
def test_logout_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, '>Log out<')
content = response.content.decode('utf8')
assert '>Log out<' in content
def test_login_shown_when_logged_out(self):
response = self.client.get('/')
self.assertContains(response, '>Log in<')
content = response.content.decode('utf8')
assert '>Log in<' in content
@override_settings(ROOT_URLCONF='tests.browsable_api.no_auth_urls')
@ -58,13 +61,16 @@ class NoDropdownWithoutAuthTests(TestCase):
def test_name_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, 'john')
content = response.content.decode('utf8')
assert 'john' in content
def test_dropdown_not_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertNotContains(response, '<li class="dropdown">')
content = response.content.decode('utf8')
assert '<li class="dropdown">' not in content
def test_dropdown_not_shown_when_logged_out(self):
response = self.client.get('/')
self.assertNotContains(response, '<li class="dropdown">')
content = response.content.decode('utf8')
assert '<li class="dropdown">' not in content

View File

@ -35,8 +35,8 @@ class DropdownWithAuthTests(TestCase):
@override_settings(ROOT_URLCONF='tests.browsable_api.test_browsable_nested_api')
def test_login(self):
response = self.client.get('/api/')
self.assertEqual(200, response.status_code)
assert 200 == response.status_code
content = response.content.decode('utf-8')
self.assertIn('form action="/api/"', content)
self.assertIn('input name="nested.one"', content)
self.assertIn('input name="nested.two"', content)
assert 'form action="/api/"' in content
assert 'input name="nested.one"' in content
assert 'input name="nested.two"' in content

View File

@ -50,5 +50,5 @@ class TestManyPostView(TestCase):
request = factory.post('/', data, format='json')
with self.assertNumQueries(1):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 3)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 3

View File

@ -67,8 +67,8 @@ class DBTransactionTests(TestCase):
with self.assertNumQueries(1):
response = self.view(request)
self.assertFalse(transaction.get_rollback())
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert not transaction.get_rollback()
assert response.status_code == status.HTTP_200_OK
assert BasicModel.objects.count() == 1
@ -98,7 +98,7 @@ class DBTransactionErrorTests(TestCase):
# 3 - release savepoint
with transaction.atomic():
self.assertRaises(Exception, self.view, request)
self.assertFalse(transaction.get_rollback())
assert not transaction.get_rollback()
assert BasicModel.objects.count() == 1
@ -128,9 +128,8 @@ class DBTransactionAPIExceptionTests(TestCase):
# 4 - release savepoint (django>=1.8 only)
with transaction.atomic():
response = self.view(request)
self.assertTrue(transaction.get_rollback())
self.assertEqual(response.status_code,
status.HTTP_500_INTERNAL_SERVER_ERROR)
assert transaction.get_rollback()
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
assert BasicModel.objects.count() == 0
@ -151,5 +150,4 @@ class NonAtomicDBTransactionAPIExceptionTests(TransactionTestCase):
# without checking connection.in_atomic_block view raises 500
# due attempt to rollback without transaction
self.assertEqual(response.status_code,
status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import base64
import pytest
from django.conf.urls import include, url
from django.contrib.auth.models import User
from django.db import models
@ -106,7 +107,7 @@ class BasicAuthTests(TestCase):
{'example': 'example'},
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_json_passing_basic_auth(self):
"""Ensure POSTing form over basic auth with correct credentials passes and does not require CSRF"""
@ -121,7 +122,7 @@ class BasicAuthTests(TestCase):
format='json',
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_regression_handle_bad_base64_basic_auth_header(self):
"""Ensure POSTing JSON over basic auth with incorrectly padded Base64 string is handled correctly"""
@ -134,12 +135,12 @@ class BasicAuthTests(TestCase):
format='json',
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_form_failing_basic_auth(self):
"""Ensure POSTing form over basic auth without correct credentials fails"""
response = self.csrf_client.post('/basic/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_failing_basic_auth(self):
"""Ensure POSTing json over basic auth without correct credentials fails"""
@ -148,8 +149,20 @@ class BasicAuthTests(TestCase):
{'example': 'example'},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(response['WWW-Authenticate'], 'Basic realm="api"')
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response['WWW-Authenticate'] == 'Basic realm="api"'
def test_fail_post_if_credentials_are_missing(self):
response = self.csrf_client.post(
'/basic/', {'example': 'example'}, HTTP_AUTHORIZATION='Basic ')
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_if_credentials_contain_spaces(self):
response = self.csrf_client.post(
'/basic/', {'example': 'example'},
HTTP_AUTHORIZATION='Basic foo bar'
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -175,9 +188,8 @@ class SessionAuthTests(TestCase):
cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810)
"""
response = self.csrf_client.get('/auth/login/')
self.assertContains(
response, '<label for="id_username">Username:</label>'
)
content = response.content.decode('utf8')
assert '<label for="id_username">Username:</label>' in content
def test_post_form_session_auth_failing_csrf(self):
"""
@ -185,7 +197,7 @@ class SessionAuthTests(TestCase):
"""
self.csrf_client.login(username=self.username, password=self.password)
response = self.csrf_client.post('/session/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_post_form_session_auth_passing(self):
"""
@ -198,7 +210,7 @@ class SessionAuthTests(TestCase):
response = self.non_csrf_client.post(
'/session/', {'example': 'example'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_put_form_session_auth_passing(self):
"""
@ -211,14 +223,14 @@ class SessionAuthTests(TestCase):
response = self.non_csrf_client.put(
'/session/', {'example': 'example'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_form_session_auth_failing(self):
"""
Ensure POSTing form over session authentication without logged in user fails.
"""
response = self.csrf_client.post('/session/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
class BaseTokenAuthTests(object):
@ -248,7 +260,18 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_fail_authentication_if_user_is_not_active(self):
user = User.objects.create_user('foo', 'bar', 'baz')
user.is_active = False
user.save()
self.model.objects.create(key='foobar_token', user=user)
response = self.csrf_client.post(
self.path, {'example': 'example'},
HTTP_AUTHORIZATION=self.header_prefix + 'foobar_token'
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_form_passing_nonexistent_token_auth(self):
# use a nonexistent token key
@ -256,7 +279,20 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_if_token_is_missing(self):
response = self.csrf_client.post(
self.path, {'example': 'example'},
HTTP_AUTHORIZATION=self.header_prefix)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_if_token_contains_spaces(self):
response = self.csrf_client.post(
self.path, {'example': 'example'},
HTTP_AUTHORIZATION=self.header_prefix + 'foo bar'
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_form_passing_invalid_token_auth(self):
# add an 'invalid' unicode character
@ -264,7 +300,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_passing_token_auth(self):
"""
@ -276,7 +312,7 @@ class BaseTokenAuthTests(object):
self.path, {'example': 'example'},
format='json', HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_json_makes_one_db_query(self):
"""
@ -298,7 +334,7 @@ class BaseTokenAuthTests(object):
Ensure POSTing form over token auth without correct credentials fails
"""
response = self.csrf_client.post(self.path, {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_failing_token_auth(self):
"""
@ -307,7 +343,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, format='json'
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -319,13 +355,13 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
"""Ensure creating a token with no key will auto-assign a key"""
self.token.delete()
token = self.model.objects.create(user=self.user)
self.assertTrue(bool(token.key))
assert bool(token.key)
def test_generate_key_returns_string(self):
"""Ensure generate_key returns a string"""
token = self.model()
key = token.generate_key()
self.assertTrue(isinstance(key, six.string_types))
assert isinstance(key, six.string_types)
def test_token_login_json(self):
"""Ensure token login view using JSON POST works."""
@ -335,8 +371,8 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
{'username': self.username, 'password': self.password},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['token'], self.key)
assert response.status_code == status.HTTP_200_OK
assert response.data['token'] == self.key
def test_token_login_json_bad_creds(self):
"""
@ -349,22 +385,24 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
{'username': self.username, 'password': "badpass"},
format='json'
)
self.assertEqual(response.status_code, 400)
assert response.status_code == 400
def test_token_login_json_missing_fields(self):
"""Ensure token login view using JSON POST fails if missing fields."""
client = APIClient(enforce_csrf_checks=True)
response = client.post('/auth-token/',
{'username': self.username}, format='json')
self.assertEqual(response.status_code, 400)
assert response.status_code == 400
def test_token_login_form(self):
"""Ensure token login view using form POST works."""
client = APIClient(enforce_csrf_checks=True)
response = client.post('/auth-token/',
{'username': self.username, 'password': self.password})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['token'], self.key)
response = client.post(
'/auth-token/',
{'username': self.username, 'password': self.password}
)
assert response.status_code == status.HTTP_200_OK
assert response.data['token'] == self.key
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -397,8 +435,8 @@ class IncorrectCredentialsTests(TestCase):
permission_classes=()
)
response = view(request)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.data, {'detail': 'Bad credentials'})
assert response.status_code == status.HTTP_403_FORBIDDEN
assert response.data == {'detail': 'Bad credentials'}
class FailingAuthAccessedInRenderer(TestCase):
@ -435,7 +473,7 @@ class FailingAuthAccessedInRenderer(TestCase):
request = factory.get('/')
response = self.view(request)
content = response.render().content
self.assertEqual(content, b'not authenticated')
assert content == b'not authenticated'
class NoAuthenticationClassesTests(TestCase):
@ -458,6 +496,30 @@ class NoAuthenticationClassesTests(TestCase):
permission_classes=(DummyPermission,),
)
response = view(request)
self.assertEqual(response.status_code,
status.HTTP_403_FORBIDDEN)
self.assertEqual(response.data, {'detail': 'Dummy permission message'})
assert response.status_code == status.HTTP_403_FORBIDDEN
assert response.data == {'detail': 'Dummy permission message'}
class BasicAuthenticationUnitTests(TestCase):
def test_base_authentication_abstract_method(self):
with pytest.raises(NotImplementedError):
BaseAuthentication().authenticate({})
def test_basic_authentication_raises_error_if_user_not_found(self):
auth = BasicAuthentication()
with pytest.raises(exceptions.AuthenticationFailed):
auth.authenticate_credentials('invalid id', 'invalid password')
def test_basic_authentication_raises_error_if_user_not_active(self):
from rest_framework import authentication
class MockUser(object):
is_active = False
old_authenticate = authentication.authenticate
authentication.authenticate = lambda **kwargs: MockUser()
auth = authentication.BasicAuthentication()
with pytest.raises(exceptions.AuthenticationFailed) as error:
auth.authenticate_credentials('foo', 'bar')
assert 'User inactive or deleted.' in str(error)
authentication.authenticate = old_authenticate

29
tests/test_authtoken.py Normal file
View File

@ -0,0 +1,29 @@
import pytest
from django.contrib.admin import site
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.authtoken.admin import TokenAdmin
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.exceptions import ValidationError
class AuthTokenTests(TestCase):
def setUp(self):
self.site = site
self.user = User.objects.create_user(username='test_user')
self.token = Token.objects.create(key='test token', user=self.user)
def test_model_admin_displayed_fields(self):
mock_request = object()
token_admin = TokenAdmin(self.token, self.site)
assert token_admin.get_fields(mock_request) == ('user',)
def test_token_string_representation(self):
assert str(self.token) == 'test token'
def test_validate_raise_error_if_no_credentials_provided(self):
with pytest.raises(ValidationError):
AuthTokenSerializer().validate({})

View File

@ -45,6 +45,15 @@ class TestSimpleBoundField:
assert serializer['amount'].errors is None
assert serializer['amount'].name == 'amount'
def test_delete_field(self):
class ExampleSerializer(serializers.Serializer):
text = serializers.CharField(max_length=100)
amount = serializers.IntegerField()
serializer = ExampleSerializer()
del serializer.fields['text']
assert 'text' not in serializer.fields.keys()
def test_as_form_fields(self):
class ExampleSerializer(serializers.Serializer):
bool_field = serializers.BooleanField()

67
tests/test_compat.py Normal file
View File

@ -0,0 +1,67 @@
from django.test import TestCase
from rest_framework import compat
class CompatTests(TestCase):
def setUp(self):
self.original_django_version = compat.django.VERSION
self.original_transaction = compat.transaction
def tearDown(self):
compat.django.VERSION = self.original_django_version
compat.transaction = self.original_transaction
def test_total_seconds(self):
class MockTimedelta(object):
days = 1
seconds = 1
microseconds = 100
timedelta = MockTimedelta()
expected = (timedelta.days * 86400.0) + float(timedelta.seconds) + (timedelta.microseconds / 1000000.0)
assert compat.total_seconds(timedelta) == expected
def test_get_remote_field_with_old_django_version(self):
class MockField(object):
rel = 'example_rel'
compat.django.VERSION = (1, 8)
assert compat.get_remote_field(MockField(), default='default_value') == 'example_rel'
assert compat.get_remote_field(object(), default='default_value') == 'default_value'
def test_get_remote_field_with_new_django_version(self):
class MockField(object):
remote_field = 'example_remote_field'
compat.django.VERSION = (1, 10)
assert compat.get_remote_field(MockField(), default='default_value') == 'example_remote_field'
assert compat.get_remote_field(object(), default='default_value') == 'default_value'
def test_set_rollback_for_transaction_in_managed_mode(self):
class MockTransaction(object):
called_rollback = False
called_leave_transaction_management = False
def is_managed(self):
return True
def is_dirty(self):
return True
def rollback(self):
self.called_rollback = True
def leave_transaction_management(self):
self.called_leave_transaction_management = True
dirty_mock_transaction = MockTransaction()
compat.transaction = dirty_mock_transaction
compat.set_rollback()
assert dirty_mock_transaction.called_rollback is True
assert dirty_mock_transaction.called_leave_transaction_management is True
clean_mock_transaction = MockTransaction()
clean_mock_transaction.is_dirty = lambda: False
compat.transaction = clean_mock_transaction
compat.set_rollback()
assert clean_mock_transaction.called_rollback is False
assert clean_mock_transaction.called_leave_transaction_management is True

View File

@ -56,11 +56,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_calling_put_method(self):
@ -70,11 +70,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.put('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_calling_patch_method(self):
@ -84,11 +84,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.patch('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_renderer_classes(self):
@ -99,16 +99,15 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertTrue(isinstance(response.accepted_renderer, JSONRenderer))
assert isinstance(response.accepted_renderer, JSONRenderer)
def test_parser_classes(self):
@api_view(['GET'])
@parser_classes([JSONParser])
def view(request):
self.assertEqual(len(request.parsers), 1)
self.assertTrue(isinstance(request.parsers[0],
JSONParser))
assert len(request.parsers) == 1
assert isinstance(request.parsers[0], JSONParser)
return Response({})
request = self.factory.get('/')
@ -119,9 +118,8 @@ class DecoratorTestCase(TestCase):
@api_view(['GET'])
@authentication_classes([BasicAuthentication])
def view(request):
self.assertEqual(len(request.authenticators), 1)
self.assertTrue(isinstance(request.authenticators[0],
BasicAuthentication))
assert len(request.authenticators) == 1
assert isinstance(request.authenticators[0], BasicAuthentication)
return Response({})
request = self.factory.get('/')
@ -136,7 +134,7 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_throttle_classes(self):
class OncePerDayUserThrottle(UserRateThrottle):
@ -149,7 +147,7 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
response = view(request)
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS

View File

@ -60,7 +60,7 @@ class TestViewNamesAndDescriptions(TestCase):
"""
class MockView(APIView):
pass
self.assertEqual(MockView().get_view_name(), 'Mock')
assert MockView().get_view_name() == 'Mock'
def test_view_description_uses_docstring(self):
"""Ensure view descriptions are based on the docstring."""
@ -80,7 +80,7 @@ class TestViewNamesAndDescriptions(TestCase):
# hash style header #"""
self.assertEqual(MockView().get_view_description(), DESCRIPTION)
assert MockView().get_view_description() == DESCRIPTION
def test_view_description_can_be_empty(self):
"""
@ -89,7 +89,7 @@ class TestViewNamesAndDescriptions(TestCase):
"""
class MockView(APIView):
pass
self.assertEqual(MockView().get_view_description(), '')
assert MockView().get_view_description() == ''
def test_view_description_can_be_promise(self):
"""
@ -111,7 +111,7 @@ class TestViewNamesAndDescriptions(TestCase):
class MockView(APIView):
__doc__ = MockLazyStr("a gettext string")
self.assertEqual(MockView().get_view_description(), 'a gettext string')
assert MockView().get_view_description() == 'a gettext string'
def test_markdown(self):
"""
@ -120,8 +120,12 @@ class TestViewNamesAndDescriptions(TestCase):
if apply_markdown:
gte_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_gte_21
lt_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_lt_21
self.assertTrue(gte_21_match or lt_21_match)
assert gte_21_match or lt_21_match
def test_dedent_tabs():
assert dedent("\tfirst string\n\n\tsecond string") == 'first string\n\n\tsecond string'
result = 'first string\n\nsecond string'
assert dedent(" first string\n\n second string") == result
assert dedent("first string\n\n second string") == result
assert dedent("\tfirst string\n\n\tsecond string") == result
assert dedent("first string\n\n\tsecond string") == result

View File

@ -1,12 +1,20 @@
from datetime import date, datetime, timedelta, tzinfo
from datetime import date, datetime, timedelta
from decimal import Decimal
from uuid import uuid4
import pytest
from django.test import TestCase
from django.utils.timezone import utc
from rest_framework.compat import coreapi
from rest_framework.utils.encoders import JSONEncoder
class MockList(object):
def tolist(self):
return [1, 2, 3]
class JSONEncoderTests(TestCase):
"""
Tests the JSONEncoder method
@ -20,43 +28,31 @@ class JSONEncoderTests(TestCase):
Tests encoding a decimal
"""
d = Decimal(3.14)
self.assertEqual(d, float(d))
assert self.encoder.default(d) == float(d)
def test_encode_datetime(self):
"""
Tests encoding a datetime object
"""
current_time = datetime.now()
self.assertEqual(self.encoder.default(current_time), current_time.isoformat())
assert self.encoder.default(current_time) == current_time.isoformat()
current_time_utc = current_time.replace(tzinfo=utc)
assert self.encoder.default(current_time_utc) == current_time.isoformat() + 'Z'
def test_encode_time(self):
"""
Tests encoding a timezone
"""
current_time = datetime.now().time()
self.assertEqual(self.encoder.default(current_time), current_time.isoformat()[:12])
assert self.encoder.default(current_time) == current_time.isoformat()[:12]
def test_encode_time_tz(self):
"""
Tests encoding a timezone aware timestamp
"""
class UTC(tzinfo):
"""
Class extending tzinfo to mimic UTC time
"""
def utcoffset(self, dt):
return timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return timedelta(0)
current_time = datetime.now().time()
current_time = current_time.replace(tzinfo=UTC())
with self.assertRaises(ValueError):
current_time = current_time.replace(tzinfo=utc)
with pytest.raises(ValueError):
self.encoder.default(current_time)
def test_encode_date(self):
@ -64,18 +60,35 @@ class JSONEncoderTests(TestCase):
Tests encoding a date object
"""
current_date = date.today()
self.assertEqual(self.encoder.default(current_date), current_date.isoformat())
assert self.encoder.default(current_date) == current_date.isoformat()
def test_encode_timedelta(self):
"""
Tests encoding a timedelta object
"""
delta = timedelta(hours=1)
self.assertEqual(self.encoder.default(delta), str(delta.total_seconds()))
assert self.encoder.default(delta) == str(delta.total_seconds())
def test_encode_uuid(self):
"""
Tests encoding a UUID object
"""
unique_id = uuid4()
self.assertEqual(self.encoder.default(unique_id), str(unique_id))
assert self.encoder.default(unique_id) == str(unique_id)
def test_encode_coreapi_raises_error(self):
"""
Tests encoding a coreapi objects raises proper error
"""
with pytest.raises(RuntimeError):
self.encoder.default(coreapi.Document())
with pytest.raises(RuntimeError):
self.encoder.default(coreapi.Error())
def test_encode_object_with_tolist(self):
"""
Tests encoding a object with tolist method
"""
foo = MockList()
assert self.encoder.default(foo) == [1, 2, 3]

View File

@ -1,9 +1,12 @@
from __future__ import unicode_literals
from django.test import TestCase
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ErrorDetail, _get_error_details
from rest_framework.exceptions import (
ErrorDetail, Throttled, _get_error_details
)
class ExceptionTestCase(TestCase):
@ -13,29 +16,38 @@ class ExceptionTestCase(TestCase):
example = "string"
lazy_example = _(example)
self.assertEqual(
_get_error_details(lazy_example),
example
)
assert _get_error_details(lazy_example) == example
assert isinstance(
_get_error_details(lazy_example),
ErrorDetail
)
self.assertEqual(
_get_error_details({'nested': lazy_example})['nested'],
example
)
assert _get_error_details({'nested': lazy_example})['nested'] == example
assert isinstance(
_get_error_details({'nested': lazy_example})['nested'],
ErrorDetail
)
self.assertEqual(
_get_error_details([[lazy_example]])[0][0],
example
)
assert _get_error_details([[lazy_example]])[0][0] == example
assert isinstance(
_get_error_details([[lazy_example]])[0][0],
ErrorDetail
)
def test_get_full_details_with_throttling(self):
exception = Throttled()
assert exception.get_full_details() == {
'message': 'Request was throttled.', 'code': 'throttled'}
exception = Throttled(wait=2)
assert exception.get_full_details() == {
'message': 'Request was throttled. Expected available in {} seconds.'.format(2 if six.PY3 else 2.),
'code': 'throttled'}
exception = Throttled(wait=2, detail='Slow down!')
assert exception.get_full_details() == {
'message': 'Slow down! Expected available in {} seconds.'.format(2 if six.PY3 else 2.),
'code': 'throttled'}

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