'
return Response(data)
-You can use `TemplateHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
+You can use `StaticHTMLRenderer` either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
**.media_type**: `text/html`
-**.format**: `'.html'`
+**.format**: `'html'`
**.charset**: `utf-8`
See also: `TemplateHTMLRenderer`
-## HTMLFormRenderer
+## BrowsableAPIRenderer
-Renders data returned by a serializer into an HTML form. The output of this renderer does not include the enclosing `
+
+For more information see the [HTML & Forms][html-and-forms] documentation.
+
+**.media_type**: `text/html`
+
+**.format**: `'form'`
+
+**.charset**: `utf-8`
+
+**.template**: `'rest_framework/horizontal/form.html'`
## MultiPartRenderer
@@ -259,7 +249,7 @@ This renderer is used for rendering HTML multipart form data. **It is not suita
**.media_type**: `multipart/form-data; boundary=BoUnDaRyStRiNg`
-**.format**: `'.multipart'`
+**.format**: `'multipart'`
**.charset**: `utf-8`
@@ -267,7 +257,7 @@ This renderer is used for rendering HTML multipart form data. **It is not suita
# Custom renderers
-To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type=None, renderer_context=None)` method.
+To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, accepted_media_type=None, renderer_context=None)` method.
The method should return a bytestring, which will be used as the body of the HTTP response.
@@ -277,7 +267,7 @@ The arguments passed to the `.render()` method are:
The request data, as set by the `Response()` instantiation.
-### `media_type=None`
+### `accepted_media_type=None`
Optional. If provided, this is the accepted media type, as determined by the content negotiation stage.
@@ -293,7 +283,7 @@ By default this will include the following keys: `view`, `request`, `response`,
The following is an example plaintext renderer that will return a response with the `data` parameter as the content of the response.
- from django.utils.encoding import smart_unicode
+ from django.utils.encoding import smart_str
from rest_framework import renderers
@@ -301,8 +291,8 @@ The following is an example plaintext renderer that will return a response with
media_type = 'text/plain'
format = 'txt'
- def render(self, data, media_type=None, renderer_context=None):
- return data.encode(self.charset)
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ return smart_str(data, encoding=self.charset)
## Setting the character set
@@ -313,7 +303,7 @@ By default renderer classes are assumed to be using the `UTF-8` encoding. To us
format = 'txt'
charset = 'iso-8859-1'
- def render(self, data, media_type=None, renderer_context=None):
+ def render(self, data, accepted_media_type=None, renderer_context=None):
return data.encode(self.charset)
Note that if a renderer class returns a unicode string, then the response content will be coerced into a bytestring by the `Response` class, with the `charset` attribute set on the renderer used to determine the encoding.
@@ -328,7 +318,7 @@ In some cases you may also want to set the `render_style` attribute to `'binary'
charset = None
render_style = 'binary'
- def render(self, data, media_type=None, renderer_context=None):
+ def render(self, data, accepted_media_type=None, renderer_context=None):
return data
---
@@ -342,14 +332,14 @@ You can do some pretty flexible things using REST framework's renderers. Some e
* Specify multiple types of HTML representation for API clients to use.
* Underspecify a renderer's media type, such as using `media_type = 'image/*'`, and use the `Accept` header to vary the encoding of the response.
-## Varying behaviour by media type
+## Varying behavior by media type
In some cases you might want your view to use different serialization styles depending on the accepted media type. If you need to do this you can access `request.accepted_renderer` to determine the negotiated renderer that will be used for the response.
For example:
- @api_view(('GET',))
- @renderer_classes((TemplateHTMLRenderer, JSONRenderer))
+ @api_view(['GET'])
+ @renderer_classes([TemplateHTMLRenderer, JSONRenderer])
def list_users(request):
"""
A view that can return JSON or HTML representations
@@ -408,53 +398,173 @@ Templates will render with a `RequestContext` which includes the `status_code` a
The following third party packages are also available.
+## YAML
+
+[REST framework YAML][rest-framework-yaml] provides [YAML][yaml] parsing and rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-yaml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': [
+ 'rest_framework_yaml.parsers.YAMLParser',
+ ],
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework_yaml.renderers.YAMLRenderer',
+ ],
+ }
+
+## XML
+
+[REST Framework XML][rest-framework-xml] provides a simple informal XML format. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-xml
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PARSER_CLASSES': [
+ 'rest_framework_xml.parsers.XMLParser',
+ ],
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework_xml.renderers.XMLRenderer',
+ ],
+ }
+
+## JSONP
+
+[REST framework JSONP][rest-framework-jsonp] provides JSONP rendering support. It was previously included directly in the REST framework package, and is now instead supported as a third-party package.
+
+---
+
+**Warning**: If you require cross-domain AJAX requests, you should generally be using the more modern approach of [CORS][cors] as an alternative to `JSONP`. See the [CORS documentation][cors-docs] for more details.
+
+The `jsonp` approach is essentially a browser hack, and is [only appropriate for globally readable API endpoints][jsonp-security], where `GET` requests are unauthenticated and do not require any user permissions.
+
+---
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install djangorestframework-jsonp
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework_jsonp.renderers.JSONPRenderer',
+ ],
+ }
+
## MessagePack
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.
+## Microsoft Excel: XLSX (Binary Spreadsheet Endpoints)
+
+XLSX is the world's most popular binary spreadsheet format. [Tim Allen][flipperpa] of [The Wharton School][wharton] maintains [drf-excel][drf-excel], which renders an endpoint as an XLSX spreadsheet using OpenPyXL, and allows the client to download it. Spreadsheets can be styled on a per-view basis.
+
+#### Installation & configuration
+
+Install using pip.
+
+ $ pip install drf-excel
+
+Modify your REST framework settings.
+
+ REST_FRAMEWORK = {
+ ...
+
+ 'DEFAULT_RENDERER_CLASSES': [
+ 'rest_framework.renderers.JSONRenderer',
+ 'rest_framework.renderers.BrowsableAPIRenderer',
+ 'drf_excel.renderers.XLSXRenderer',
+ ],
+ }
+
+To avoid having a file streamed without a filename (which the browser will often default to the filename "download", with no extension), we need to use a mixin to override the `Content-Disposition` header. If no filename is provided, it will default to `export.xlsx`. For example:
+
+ from rest_framework.viewsets import ReadOnlyModelViewSet
+ from drf_excel.mixins import XLSXFileMixin
+ from drf_excel.renderers import XLSXRenderer
+
+ from .models import MyExampleModel
+ from .serializers import MyExampleSerializer
+
+ class MyExampleViewSet(XLSXFileMixin, ReadOnlyModelViewSet):
+ queryset = MyExampleModel.objects.all()
+ serializer_class = MyExampleSerializer
+ renderer_classes = [XLSXRenderer]
+ filename = 'my_export.xlsx'
+
## CSV
-Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework.
+Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework.
## UltraJSON
-[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Jacob Haslehurst][hzy] maintains the [drf-ujson-renderer][drf-ujson-renderer] package which implements JSON rendering using the UJSON package.
+[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Adam Mertz][Amertz08] maintains [drf_ujson2][drf_ujson2], a fork of the now unmaintained [drf-ujson-renderer][drf-ujson-renderer], which implements JSON rendering using the UJSON package.
## CamelCase JSON
[djangorestframework-camel-case] provides camel case JSON renderers and parsers for REST framework. This allows serializers to use Python-style underscored field names, but be exposed in the API as Javascript-style camel case field names. It is maintained by [Vitaly Babiy][vbabiy].
-
## Pandas (CSV, Excel, PNG)
[Django REST Pandas] provides a serializer and renderers that support additional data processing and output via the [Pandas] DataFrame API. Django REST Pandas includes renderers for Pandas-style CSV files, Excel workbooks (both `.xls` and `.xlsx`), and a number of [other formats]. It is maintained by [S. Andrew Sheppard][sheppard] as part of the [wq Project][wq].
+## LaTeX
+
+[Rest Framework Latex] provides a renderer that outputs PDFs using Lualatex. It is maintained by [Pebble (S/F Software)][mypebble].
+
-[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
+[cite]: https://docs.djangoproject.com/en/stable/ref/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
-[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
-[cors]: http://www.w3.org/TR/cors/
-[cors-docs]: ../topics/ajax-csrf-cors.md
-[jsonp-security]: http://stackoverflow.com/questions/613962/is-jsonp-safe-to-use
[testing]: testing.md
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
-[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
-[application/vnd.github+json]: http://developer.github.com/v3/media/
+[quote]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
+[application/vnd.github+json]: https://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
-[messagepack]: http://msgpack.org/
+[django-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
+[rest-framework-jsonp]: https://jpadilla.github.io/django-rest-framework-jsonp/
+[cors]: https://www.w3.org/TR/cors/
+[cors-docs]: https://www.django-rest-framework.org/topics/ajax-csrf-cors/
+[jsonp-security]: https://stackoverflow.com/questions/613962/is-jsonp-safe-to-use
+[rest-framework-yaml]: https://jpadilla.github.io/django-rest-framework-yaml/
+[rest-framework-xml]: https://jpadilla.github.io/django-rest-framework-xml/
+[messagepack]: https://msgpack.org/
[juanriaza]: https://github.com/juanriaza
[mjumbewu]: https://github.com/mjumbewu
+[flipperpa]: https://github.com/flipperpa
+[wharton]: https://github.com/wharton
+[drf-excel]: https://github.com/wharton/drf-excel
[vbabiy]: https://github.com/vbabiy
+[rest-framework-yaml]: https://jpadilla.github.io/django-rest-framework-yaml/
+[rest-framework-xml]: https://jpadilla.github.io/django-rest-framework-xml/
+[yaml]: http://www.yaml.org/
[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
[ultrajson]: https://github.com/esnme/ultrajson
-[hzy]: https://github.com/hzy
+[Amertz08]: https://github.com/Amertz08
[drf-ujson-renderer]: https://github.com/gizmag/drf-ujson-renderer
+[drf_ujson2]: https://github.com/Amertz08/drf_ujson2
[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case
[Django REST Pandas]: https://github.com/wq/django-rest-pandas
-[Pandas]: http://pandas.pydata.org/
+[Pandas]: https://pandas.pydata.org/
[other formats]: https://github.com/wq/django-rest-pandas#supported-formats
[sheppard]: https://github.com/sheppard
[wq]: https://github.com/wq
+[mypebble]: https://github.com/mypebble
+[Rest Framework Latex]: https://github.com/mypebble/rest-framework-latex
diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md
index 77000ffa23..d072ac4367 100644
--- a/docs/api-guide/requests.md
+++ b/docs/api-guide/requests.md
@@ -1,9 +1,6 @@
-source: request.py
-
---
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
+source:
+ - request.py
---
# Requests
@@ -26,7 +23,7 @@ REST framework's Request objects provide flexible request parsing that allows yo
* It includes all parsed content, including *file and non-file* inputs.
* It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests.
-* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming JSON data in the same way that you handle incoming form data.
+* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming [JSON data] similarly to how you handle incoming [form data].
For more details see the [parsers documentation].
@@ -36,14 +33,6 @@ For more details see the [parsers documentation].
For clarity inside your code, we recommend using `request.query_params` instead of the Django's standard `request.GET`. Doing so will help keep your codebase more correct and obvious - any HTTP method type may include query parameters, not just `GET` requests.
-## .DATA and .FILES
-
-The old-style version 2.x `request.data` and `request.FILES` attributes are still available, but are now pending deprecation in favor of the unified `request.data` attribute.
-
-## .QUERY_PARAMS
-
-The old-style version 2.x `request.QUERY_PARAMS` attribute is still available, but is now pending deprecation in favor of the more pythonic `request.query_params`.
-
## .parsers
The `APIView` class or `@api_view` decorator will ensure that this property is automatically set to a list of `Parser` instances, based on the `parser_classes` set on the view or based on the `DEFAULT_PARSER_CLASSES` setting.
@@ -60,11 +49,11 @@ If a client sends a request with a content-type that cannot be parsed then a `Un
# Content negotiation
-The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialisation schemes for different media types.
+The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behavior such as selecting a different serialization schemes for different media types.
## .accepted_renderer
-The renderer instance what was selected by the content negotiation stage.
+The renderer instance that was selected by the content negotiation stage.
## .accepted_media_type
@@ -104,6 +93,10 @@ You won't typically need to access this property.
---
+**Note:** You may see a `WrappedAttributeError` raised when calling the `.user` or `.auth` properties. These errors originate from an authenticator as a standard `AttributeError`, however it's necessary that they be re-raised as a different exception type in order to prevent them from being suppressed by the outer property access. Python will not recognize that the `AttributeError` originates from the authenticator and will instead assume that the request object does not have a `.user` or `.auth` property. The authenticator will need to be fixed.
+
+---
+
# Browser enhancements
REST framework supports a few browser enhancements such as browser-based `PUT`, `PATCH` and `DELETE` forms.
@@ -132,10 +125,6 @@ For more information see the [browser enhancements documentation].
You won't typically need to directly access the request's content, as you'll normally rely on REST framework's default request parsing behavior.
-If you do need to access the raw content directly, you should use the `.stream` property in preference to using `request.content`, as it provides transparent support for browser-based non-form content.
-
-For more information see the [browser enhancements documentation].
-
---
# Standard HttpRequest attributes
@@ -147,5 +136,7 @@ Note that due to implementation reasons the `Request` class does not inherit fro
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
[parsers documentation]: parsers.md
+[JSON data]: parsers.md#jsonparser
+[form data]: parsers.md#formparser
[authentication documentation]: authentication.md
[browser enhancements documentation]: ../topics/browser-enhancements.md
diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md
index 97f3127106..dbdc8ff2cc 100644
--- a/docs/api-guide/responses.md
+++ b/docs/api-guide/responses.md
@@ -1,4 +1,7 @@
-source: response.py
+---
+source:
+ - response.py
+---
# Responses
@@ -42,7 +45,7 @@ Arguments:
## .data
-The unrendered content of a `Request` object.
+The unrendered, serialized data of the response.
## .status_code
@@ -91,5 +94,5 @@ As with any other `TemplateResponse`, this method is called to render the serial
You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
-[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
+[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/
[statuscodes]: status-codes.md
diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md
index 71fb83f9e9..c3fa52f21f 100644
--- a/docs/api-guide/reverse.md
+++ b/docs/api-guide/reverse.md
@@ -1,4 +1,7 @@
-source: reverse.py
+---
+source:
+ - reverse.py
+---
# Returning URLs
@@ -23,33 +26,33 @@ There's no requirement for you to use them, but if you do then the self-describi
**Signature:** `reverse(viewname, *args, **kwargs)`
-Has the same behavior as [`django.core.urlresolvers.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
+Has the same behavior as [`django.urls.reverse`][reverse], except that it returns a fully qualified URL, using the request to determine the host and port.
You should **include the request as a keyword argument** to the function, for example:
from rest_framework.reverse import reverse
from rest_framework.views import APIView
- from django.utils.timezone import now
-
- class APIRootView(APIView):
- def get(self, request):
- year = now().year
- data = {
- ...
- 'year-summary-url': reverse('year-summary', args=[year], request=request)
+ from django.utils.timezone import now
+
+ class APIRootView(APIView):
+ def get(self, request):
+ year = now().year
+ data = {
+ ...
+ 'year-summary-url': reverse('year-summary', args=[year], request=request)
}
- return Response(data)
+ return Response(data)
## reverse_lazy
**Signature:** `reverse_lazy(viewname, *args, **kwargs)`
-Has the same behavior as [`django.core.urlresolvers.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
+Has the same behavior as [`django.urls.reverse_lazy`][reverse-lazy], except that it returns a fully qualified URL, using the request to determine the host and port.
As with the `reverse` function, you should **include the request as a keyword argument** to the function, for example:
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
+[cite]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
+[reverse]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse
+[reverse-lazy]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse-lazy
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index 3a8a8f6cdb..d2be3991fb 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -1,4 +1,7 @@
-source: routers.py
+---
+source:
+ - routers.py
+---
# Routers
@@ -28,7 +31,7 @@ There are two mandatory arguments to the `register()` method:
Optionally, you may also specify an additional argument:
-* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset.
+* `basename` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `queryset` attribute of the viewset, if it has one. Note that if the viewset does not include a `queryset` attribute then you must set `basename` when registering the viewset.
The example above would generate the following URL patterns:
@@ -39,13 +42,13 @@ The example above would generate the following URL patterns:
---
-**Note**: The `base_name` argument is used to specify the initial part of the view name pattern. In the example above, that's the `user` or `account` part.
+**Note**: The `basename` argument is used to specify the initial part of the view name pattern. In the example above, that's the `user` or `account` part.
-Typically you won't *need* to specify the `base_name` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have a `.queryset` attribute set. If you try to register that viewset you'll see an error like this:
+Typically you won't *need* to specify the `basename` argument, but if you have a viewset where you've defined a custom `get_queryset` method, then the viewset may not have a `.queryset` attribute set. If you try to register that viewset you'll see an error like this:
- 'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
+ 'basename' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.
-This means you'll need to explicitly set the `base_name` argument when registering the viewset, as it could not be automatically determined from the model name.
+This means you'll need to explicitly set the `basename` argument when registering the viewset, as it could not be automatically determined from the model name.
---
@@ -53,104 +56,135 @@ This means you'll need to explicitly set the `base_name` argument when registeri
The `.urls` attribute on a router instance is simply a standard list of URL patterns. There are a number of different styles for how you can include these URLs.
-For example, you can append `router.urls` to a list of existing views…
+For example, you can append `router.urls` to a list of existing views...
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
-
+
urlpatterns = [
- url(https://github.com/r'%5Eforgot-password/$,%20ForgotPasswordFormView.as_view(),
+ path('forgot-password/', ForgotPasswordFormView.as_view()),
]
-
+
urlpatterns += router.urls
-Alternatively you can use Django's `include` function, like so…
+Alternatively you can use Django's `include` function, like so...
+
+ urlpatterns = [
+ path('forgot-password', ForgotPasswordFormView.as_view()),
+ path('', include(router.urls)),
+ ]
+
+You may use `include` with an application namespace:
urlpatterns = [
- url(https://github.com/r'%5Eforgot-password/$,%20ForgotPasswordFormView.as_view(),
- url(https://github.com/r'%5E',%20include(router.urls))
+ path('forgot-password/', ForgotPasswordFormView.as_view()),
+ path('api/', include((router.urls, 'app_name'))),
]
-Router URL patterns can also be namespaces.
+Or both an application and instance namespace:
urlpatterns = [
- url(https://github.com/r'%5Eforgot-password/$,%20ForgotPasswordFormView.as_view(),
- url(https://github.com/r'%5Eapi/',%20include(router.urls,%20namespace='api'))
+ path('forgot-password/', ForgotPasswordFormView.as_view()),
+ path('api/', include((router.urls, 'app_name'), namespace='instance_name')),
]
-If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters on the serializers correctly reflect the namespace. In the example above you'd need to include a parameter such as `view_name='api:user-detail'` for serializer fields hyperlinked to the user detail view.
+See Django's [URL namespaces docs][url-namespace-docs] and the [`include` API reference][include-api-reference] for more details.
-### Extra link and actions
+---
+
+**Note**: If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters
+on the serializers correctly reflect the namespace. In the examples above you'd need to include a parameter such as
+`view_name='app_name:user-detail'` for serializer fields hyperlinked to the user detail view.
+
+The automatic `view_name` generation uses a pattern like `%(model_name)-detail`. Unless your models names actually clash
+you may be better off **not** namespacing your Django REST Framework views when using hyperlinked serializers.
+
+---
+
+### Routing for extra actions
-Any methods on the viewset decorated with `@detail_route` or `@list_route` will also be routed.
-For example, given a method like this on the `UserViewSet` class:
+A viewset may [mark extra actions for routing][route-decorators] by decorating a method with the `@action` decorator. These extra actions will be included in the generated routes. For example, given the `set_password` method on the `UserViewSet` class:
from myapp.permissions import IsAdminOrIsSelf
- from rest_framework.decorators import detail_route
+ from rest_framework.decorators import action
class UserViewSet(ModelViewSet):
...
- @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf])
+ @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
-The following URL pattern would additionally be generated:
+The following route would be generated:
-* URL pattern: `^users/{pk}/set_password/$` Name: `'user-set-password'`
+* URL pattern: `^users/{pk}/set_password/$`
+* URL name: `'user-set-password'`
-If you do not want to use the default URL generated for your custom action, you can instead use the url_path parameter to customize it.
+By default, the URL pattern is based on the method name, and the URL name is the combination of the `ViewSet.basename` and the hyphenated method name.
+If you don't want to use the defaults for either of these values, you can instead provide the `url_path` and `url_name` arguments to the `@action` decorator.
For example, if you want to change the URL for our custom action to `^users/{pk}/change-password/$`, you could write:
from myapp.permissions import IsAdminOrIsSelf
- from rest_framework.decorators import detail_route
-
+ from rest_framework.decorators import action
+
class UserViewSet(ModelViewSet):
...
-
- @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password')
+
+ @action(methods=['post'], detail=True, permission_classes=[IsAdminOrIsSelf],
+ url_path='change-password', 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}/change-password/$` Name: `'user-change-password'`
+* URL path: `^users/{pk}/change-password/$`
+* URL name: `'user-change_password'`
-For more information see the viewset documentation on [marking extra actions for routing][route-decorators].
+### Using Django `path()` with routers
+
+By default, the URLs created by routers use regular expressions. This behavior can be modified by setting the `use_regex_path` argument to `False` when instantiating the router, in this case [path converters][path-converters-topic-reference] are used. For example:
+
+ router = SimpleRouter(use_regex_path=False)
+
+The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_value_regex` attribute on the viewset or `lookup_value_converter` if using path converters. For example, you can limit the lookup to valid UUIDs:
+
+ class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
+ lookup_field = 'my_model_id'
+ lookup_value_regex = '[0-9a-f]{32}'
+
+ class MyPathModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
+ lookup_field = 'my_model_uuid'
+ lookup_value_converter = 'uuid'
+
+Note that path converters will be used on all URLs registered in the router, including viewset actions.
# API Guide
## SimpleRouter
-This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions. The viewset can also mark additional methods to be routed, using the `@detail_route` or `@list_route` decorators.
+This router includes routes for the standard set of `list`, `create`, `retrieve`, `update`, `partial_update` and `destroy` actions. The viewset can also mark additional methods to be routed, using the `@action` decorator.
URL Style
HTTP Method
Action
URL Name
{prefix}/
GET
list
{basename}-list
POST
create
-
{prefix}/{methodname}/
GET, or as specified by `methods` argument
`@list_route` decorated method
{basename}-{methodname}
+
{prefix}/{url_path}/
GET, or as specified by `methods` argument
`@action(detail=False)` decorated method
{basename}-{url_name}
{prefix}/{lookup}/
GET
retrieve
{basename}-detail
PUT
update
PATCH
partial_update
DELETE
destroy
-
{prefix}/{lookup}/{methodname}/
GET, or as specified by `methods` argument
`@detail_route` decorated method
{basename}-{methodname}
+
{prefix}/{lookup}/{url_path}/
GET, or as specified by `methods` argument
`@action(detail=True)` decorated method
{basename}-{url_name}
-By default the URLs created by `SimpleRouter` are appended with a trailing slash.
+By default, the URLs created by `SimpleRouter` are appended with a trailing slash.
This behavior can be modified by setting the `trailing_slash` argument to `False` when instantiating the router. For example:
router = SimpleRouter(trailing_slash=False)
Trailing slashes are conventional in Django, but are not used by default in some other frameworks such as Rails. Which style you choose to use is largely a matter of preference, although some javascript frameworks may expect a particular routing style.
-The router will match lookup values containing any characters except slashes and period characters. For a more restrictive (or lenient) lookup pattern, set the `lookup_value_regex` attribute on the viewset. For example, you can limit the lookup to valid UUIDs:
-
- class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
- lookup_field = 'my_model_id'
- lookup_value_regex = '[0-9a-f]{32}'
-
## DefaultRouter
This router is similar to `SimpleRouter` as above, but additionally includes a default API root view, that returns a response containing hyperlinks to all the list views. It also generates routes for optional `.json` style format suffixes.
@@ -160,12 +194,12 @@ This router is similar to `SimpleRouter` as above, but additionally includes a d
[.format]
GET
automatically generated root view
api-root
{prefix}/[.format]
GET
list
{basename}-list
POST
create
-
{prefix}/{methodname}/[.format]
GET, or as specified by `methods` argument
`@list_route` decorated method
{basename}-{methodname}
+
{prefix}/{url_path}/[.format]
GET, or as specified by `methods` argument
`@action(detail=False)` decorated method
{basename}-{url_name}
{prefix}/{lookup}/[.format]
GET
retrieve
{basename}-detail
PUT
update
PATCH
partial_update
DELETE
destroy
-
{prefix}/{lookup}/{methodname}/[.format]
GET, or as specified by `methods` argument
`@detail_route` decorated method
{basename}-{methodname}
+
{prefix}/{lookup}/{url_path}/[.format]
GET, or as specified by `methods` argument
`@action(detail=True)` decorated method
{basename}-{url_name}
As with `SimpleRouter` the trailing slashes on the URL routes can be removed by setting the `trailing_slash` argument to `False` when instantiating the router.
@@ -174,7 +208,7 @@ As with `SimpleRouter` the trailing slashes on the URL routes can be removed by
# Custom Routers
-Implementing a custom router isn't something you'd need to do very often, but it can be useful if you have specific requirements about how the your URLs for your API are structured. Doing so allows you to encapsulate the URL structure in a reusable way that ensures you don't have to write your URL patterns explicitly for each new view.
+Implementing a custom router isn't something you'd need to do very often, but it can be useful if you have specific requirements about how the URLs for your API are structured. Doing so allows you to encapsulate the URL structure in a reusable way that ensures you don't have to write your URL patterns explicitly for each new view.
The simplest way to implement a custom router is to subclass one of the existing router classes. The `.routes` attribute is used to template the URL patterns that will be mapped to each viewset. The `.routes` attribute is a list of `Route` named tuples.
@@ -192,18 +226,18 @@ The arguments to the `Route` named tuple are:
* `{basename}` - The base to use for the URL names that are created.
-**initkwargs**: A dictionary of any additional arguments that should be passed when instantiating the view. Note that the `suffix` argument is reserved for identifying the viewset type, used when generating the view name and breadcrumb links.
+**initkwargs**: A dictionary of any additional arguments that should be passed when instantiating the view. Note that the `detail`, `basename`, and `suffix` arguments are reserved for viewset introspection and are also used by the browsable API to generate the view name and breadcrumb links.
## Customizing dynamic routes
-You can also customize how the `@list_route` and `@detail_route` decorators are routed.
-To route either or both of these decorators, include a `DynamicListRoute` and/or `DynamicDetailRoute` named tuple in the `.routes` list.
+You can also customize how the `@action` decorator is routed. Include the `DynamicRoute` named tuple in the `.routes` list, setting the `detail` argument as appropriate for the list-based and detail-based routes. In addition to `detail`, the arguments to `DynamicRoute` are:
-The arguments to `DynamicListRoute` and `DynamicDetailRoute` are:
+**url**: A string representing the URL to be routed. May include the same format strings as `Route`, and additionally accepts the `{url_path}` format string.
-**url**: A string representing the URL to be routed. May include the same format strings as `Route`, and additionally accepts the `{methodname}` and `{methodnamehyphen}` format strings.
+**name**: The name of the URL as used in `reverse` calls. May include the following format strings:
-**name**: The name of the URL as used in `reverse` calls. May include the following format strings: `{basename}`, `{methodname}` and `{methodnamehyphen}`.
+* `{basename}` - The base to use for the URL names that are created.
+* `{url_name}` - The `url_name` provided to the `@action`.
**initkwargs**: A dictionary of any additional arguments that should be passed when instantiating the view.
@@ -211,7 +245,7 @@ The arguments to `DynamicListRoute` and `DynamicDetailRoute` are:
The following example will only route to the `list` and `retrieve` actions, and does not use the trailing slash convention.
- from rest_framework.routers import Route, DynamicDetailRoute, SimpleRouter
+ from rest_framework.routers import Route, DynamicRoute, SimpleRouter
class CustomReadOnlyRouter(SimpleRouter):
"""
@@ -219,22 +253,25 @@ The following example will only route to the `list` and `retrieve` actions, and
"""
routes = [
Route(
- url=r'^{prefix}$',
- mapping={'get': 'list'},
- name='{basename}-list',
- initkwargs={'suffix': 'List'}
+ url=r'^{prefix}$',
+ mapping={'get': 'list'},
+ name='{basename}-list',
+ detail=False,
+ initkwargs={'suffix': 'List'}
),
Route(
- url=r'^{prefix}/{lookup}$',
- mapping={'get': 'retrieve'},
- name='{basename}-detail',
- initkwargs={'suffix': 'Detail'}
+ url=r'^{prefix}/{lookup}$',
+ mapping={'get': 'retrieve'},
+ name='{basename}-detail',
+ detail=True,
+ initkwargs={'suffix': 'Detail'}
),
- DynamicDetailRoute(
- url=r'^{prefix}/{lookup}/{methodnamehyphen}$',
- name='{basename}-{methodnamehyphen}',
- initkwargs={}
- )
+ DynamicRoute(
+ url=r'^{prefix}/{lookup}/{url_path}$',
+ name='{basename}-{url_name}',
+ detail=True,
+ initkwargs={}
+ )
]
Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a simple viewset.
@@ -249,8 +286,8 @@ Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a
serializer_class = UserSerializer
lookup_field = 'username'
- @detail_route()
- def group_names(self, request):
+ @action(detail=True)
+ def group_names(self, request, pk=None):
"""
Returns a list of all the group names that the given
user belongs to.
@@ -263,7 +300,7 @@ Let's take a look at the routes our `CustomReadOnlyRouter` would generate for a
router = CustomReadOnlyRouter()
router.register('users', UserViewSet)
- urlpatterns = router.urls
+ urlpatterns = router.urls
The following mappings would be generated...
@@ -271,7 +308,7 @@ The following mappings would be generated...
URL
HTTP Method
Action
URL Name
/users
GET
list
user-list
/users/{username}
GET
retrieve
user-detail
-
/users/{username}/group-names
GET
group_names
user-group-names
+
/users/{username}/group_names
GET
group_names
user-group-names
For another example of setting the `.routes` attribute, see the source code for the `SimpleRouter` class.
@@ -280,7 +317,7 @@ For another example of setting the `.routes` attribute, see the source code for
If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls(self)` method. The method should inspect the registered viewsets and return a list of URL patterns. The registered prefix, viewset and basename tuples may be inspected by accessing the `self.registry` attribute.
-You may also want to override the `get_default_base_name(self, viewset)` method, or else always explicitly set the `base_name` argument when registering your viewsets with the router.
+You may also want to override the `get_default_basename(self, viewset)` method, or else always explicitly set the `basename` argument when registering your viewsets with the router.
# Third Party Packages
@@ -290,26 +327,29 @@ The following third party packages are also available.
The [drf-nested-routers package][drf-nested-routers] provides routers and relationship fields for working with nested resources.
-## wq.db
+## ModelRouter (wq.db.rest)
-The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (and singleton instance) that extends `DefaultRouter` with a `register_model()` API. Much like Django's `admin.site.register`, the only required argument to `app.router.register_model` is a model class. Reasonable defaults for a url prefix and viewset will be inferred from the model and global configuration.
+The [wq.db package][wq.db] provides an advanced [ModelRouter][wq.db-router] class (and singleton instance) that extends `DefaultRouter` with a `register_model()` API. Much like Django's `admin.site.register`, the only required argument to `rest.router.register_model` is a model class. Reasonable defaults for a url prefix, serializer, and viewset will be inferred from the model and global configuration.
- from wq.db.rest import app
+ from wq.db import rest
from myapp.models import MyModel
- app.router.register_model(MyModel)
+ rest.router.register_model(MyModel)
## DRF-extensions
The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names].
-[cite]: http://guides.rubyonrails.org/routing.html
-[route-decorators]: viewsets.html#marking-extra-actions-for-routing
+[cite]: https://guides.rubyonrails.org/routing.html
+[route-decorators]: viewsets.md#marking-extra-actions-for-routing
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
-[wq.db]: http://wq.io/wq.db
-[wq.db-router]: http://wq.io/docs/app.py
-[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/
-[drf-extensions-routers]: http://chibisov.github.io/drf-extensions/docs/#routers
-[drf-extensions-nested-viewsets]: http://chibisov.github.io/drf-extensions/docs/#nested-routes
-[drf-extensions-collection-level-controllers]: http://chibisov.github.io/drf-extensions/docs/#collection-level-controllers
-[drf-extensions-customizable-endpoint-names]: http://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name
+[wq.db]: https://wq.io/wq.db
+[wq.db-router]: https://wq.io/docs/router
+[drf-extensions]: https://chibisov.github.io/drf-extensions/docs/
+[drf-extensions-routers]: https://chibisov.github.io/drf-extensions/docs/#routers
+[drf-extensions-nested-viewsets]: https://chibisov.github.io/drf-extensions/docs/#nested-routes
+[drf-extensions-collection-level-controllers]: https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers
+[drf-extensions-customizable-endpoint-names]: https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name
+[url-namespace-docs]: https://docs.djangoproject.com/en/stable/topics/http/urls/#url-namespaces
+[include-api-reference]: https://docs.djangoproject.com/en/stable/ref/urls/#include
+[path-converters-topic-reference]: https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters
diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md
new file mode 100644
index 0000000000..0eee3c99f2
--- /dev/null
+++ b/docs/api-guide/schemas.md
@@ -0,0 +1,465 @@
+---
+source:
+ - schemas
+---
+
+# Schema
+
+> A machine-readable [schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support.
+>
+> — Heroku, [JSON Schema for the Heroku Platform API][cite]
+
+---
+
+**Deprecation notice:**
+
+REST framework's built-in support for generating OpenAPI schemas is
+**deprecated** in favor of 3rd party packages that can provide this
+functionality instead. The built-in support will be moved into a separate
+package and then subsequently retired over the next releases.
+
+As a full-fledged replacement, we recommend the [drf-spectacular] package.
+It has extensive support for generating OpenAPI 3 schemas from
+REST framework APIs, with both automatic and customisable options available.
+For further information please refer to
+[Documenting your API](../topics/documenting-your-api.md#drf-spectacular).
+
+---
+
+API schemas are a useful tool that allow for a range of use cases, including
+generating reference documentation, or driving dynamic client libraries that
+can interact with your API.
+
+Django REST Framework provides support for automatic generation of
+[OpenAPI][openapi] schemas.
+
+## Overview
+
+Schema generation has several moving parts. It's worth having an overview:
+
+* `SchemaGenerator` is a top-level class that is responsible for walking your
+ configured URL patterns, finding `APIView` subclasses, enquiring for their
+ schema representation, and compiling the final schema object.
+* `AutoSchema` encapsulates all the details necessary for per-view schema
+ introspection. Is attached to each view via the `schema` attribute. You
+ subclass `AutoSchema` in order to customize your schema.
+* The `generateschema` management command allows you to generate a static schema
+ offline.
+* Alternatively, you can route `SchemaView` to dynamically generate and serve
+ your schema.
+* `settings.DEFAULT_SCHEMA_CLASS` allows you to specify an `AutoSchema`
+ subclass to serve as your project's default.
+
+The following sections explain more.
+
+## Generating an OpenAPI Schema
+
+### Install dependencies
+
+ pip install pyyaml uritemplate inflection
+
+* `pyyaml` is used to generate schema into YAML-based OpenAPI format.
+* `uritemplate` is used internally to get parameters in path.
+* `inflection` is used to pluralize operations more appropriately in the list endpoints.
+
+### Generating a static schema with the `generateschema` management command
+
+If your schema is static, you can use the `generateschema` management command:
+
+```bash
+./manage.py generateschema --file openapi-schema.yml
+```
+
+Once you've generated a schema in this way you can annotate it with any
+additional information that cannot be automatically inferred by the schema
+generator.
+
+You might want to check your API schema into version control and update it
+with each new release, or serve the API schema from your site's static media.
+
+### Generating a dynamic schema with `SchemaView`
+
+If you require a dynamic schema, because foreign key choices depend on database
+values, for example, you can route a `SchemaView` that will generate and serve
+your schema on demand.
+
+To route a `SchemaView`, use the `get_schema_view()` helper.
+
+In `urls.py`:
+
+```python
+from rest_framework.schemas import get_schema_view
+
+urlpatterns = [
+ # ...
+ # Use the `get_schema_view()` helper to add a `SchemaView` to project URLs.
+ # * `title` and `description` parameters are passed to `SchemaGenerator`.
+ # * Provide view name for use with `reverse()`.
+ path(
+ "openapi",
+ get_schema_view(
+ title="Your Project", description="API for all things …", version="1.0.0"
+ ),
+ name="openapi-schema",
+ ),
+ # ...
+]
+```
+
+#### `get_schema_view()`
+
+The `get_schema_view()` helper takes the following keyword arguments:
+
+* `title`: May be used to provide a descriptive title for the schema definition.
+* `description`: Longer descriptive text.
+* `version`: The version of the API.
+* `url`: May be used to pass a canonical base URL for the schema.
+
+ schema_view = get_schema_view(
+ title='Server Monitoring API',
+ 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'
+ )
+
+* `patterns`: List of url patterns to limit the schema introspection to. If you
+ only want the `myproject.api` urls to be exposed in the schema:
+
+ schema_url_patterns = [
+ path('api/', include('myproject.api.urls')),
+ ]
+
+ schema_view = get_schema_view(
+ title='Server Monitoring API',
+ url='https://www.example.org/api/',
+ patterns=schema_url_patterns,
+ )
+* `public`: May be used to specify if schema should bypass views permissions. Default to False
+
+* `generator_class`: May be used to specify a `SchemaGenerator` subclass to be
+ passed to the `SchemaView`.
+* `authentication_classes`: May be used to specify the list of authentication
+ classes that will apply to the schema endpoint. Defaults to
+ `settings.DEFAULT_AUTHENTICATION_CLASSES`
+* `permission_classes`: May be used to specify the list of permission classes
+ that will apply to the schema endpoint. Defaults to
+ `settings.DEFAULT_PERMISSION_CLASSES`.
+* `renderer_classes`: May be used to pass the set of renderer classes that can
+ be used to render the API root endpoint.
+
+
+## SchemaGenerator
+
+**Schema-level customization**
+
+```python
+from rest_framework.schemas.openapi import SchemaGenerator
+```
+
+`SchemaGenerator` is a class that walks a list of routed URL patterns, requests
+the schema for each view and collates the resulting OpenAPI schema.
+
+Typically you won't need to instantiate `SchemaGenerator` yourself, but you can
+do so like so:
+
+ generator = SchemaGenerator(title='Stock Prices API')
+
+Arguments:
+
+* `title` **required**: The name of the API.
+* `description`: Longer descriptive text.
+* `version`: The version of the API. Defaults to `0.1.0`.
+* `url`: The root URL of the API schema. This option is not required unless the schema is included under path prefix.
+* `patterns`: A list of URLs to inspect when generating the schema. Defaults to the project's URL conf.
+* `urlconf`: A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`.
+
+In order to customize the top-level schema, subclass
+`rest_framework.schemas.openapi.SchemaGenerator` and provide your subclass
+as an argument to the `generateschema` command or `get_schema_view()` helper
+function.
+
+### get_schema(self, request=None, public=False)
+
+Returns a dictionary that represents the OpenAPI schema:
+
+ generator = SchemaGenerator(title='Stock Prices API')
+ schema = generator.get_schema()
+
+The `request` argument is optional, and may be used if you want to apply
+per-user permissions to the resulting schema generation.
+
+This is a good point to override if you want to customize the generated
+dictionary For example you might wish to add terms of service to the [top-level
+`info` object][info-object]:
+
+```
+class TOSSchemaGenerator(SchemaGenerator):
+ def get_schema(self, *args, **kwargs):
+ schema = super().get_schema(*args, **kwargs)
+ schema["info"]["termsOfService"] = "https://example.com/tos.html"
+ return schema
+```
+
+## AutoSchema
+
+**Per-View Customization**
+
+```python
+from rest_framework.schemas.openapi import AutoSchema
+```
+
+By default, view introspection is performed by an `AutoSchema` instance
+accessible via the `schema` attribute on `APIView`.
+
+ auto_schema = some_view.schema
+
+`AutoSchema` provides the OpenAPI elements needed for each view, request method
+and path:
+
+* A list of [OpenAPI components][openapi-components]. In DRF terms these are
+ mappings of serializers that describe request and response bodies.
+* The appropriate [OpenAPI operation object][openapi-operation] that describes
+ the endpoint, including path and query parameters for pagination, filtering,
+ and so on.
+
+```python
+components = auto_schema.get_components(...)
+operation = auto_schema.get_operation(...)
+```
+
+In compiling the schema, `SchemaGenerator` calls `get_components()` and
+`get_operation()` for each view, allowed method, and path.
+
+----
+
+**Note**: The automatic introspection of components, and many operation
+parameters relies on the relevant attributes and methods of
+`GenericAPIView`: `get_serializer()`, `pagination_class`, `filter_backends`,
+etc. For basic `APIView` subclasses, default introspection is essentially limited to
+the URL kwarg path parameters for this reason.
+
+----
+
+`AutoSchema` encapsulates the view introspection needed for schema generation.
+Because of this all the schema generation logic is kept in a single place,
+rather than being spread around the already extensive view, serializer and
+field APIs.
+
+Keeping with this pattern, try not to let schema logic leak into your own
+views, serializers, or fields when customizing the schema generation. You might
+be tempted to do something like this:
+
+```python
+class CustomSchema(AutoSchema):
+ """
+ AutoSchema subclass using schema_extra_info on the view.
+ """
+
+ ...
+
+
+class CustomView(APIView):
+ schema = CustomSchema()
+ schema_extra_info = ... # some extra info
+```
+
+Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the
+view. This is _OK_ (it doesn't actually hurt) but it means you'll end up with
+your schema logic spread out in a number of different places.
+
+Instead try to subclass `AutoSchema` such that the `extra_info` doesn't leak
+out into the view:
+
+```python
+class BaseSchema(AutoSchema):
+ """
+ AutoSchema subclass that knows how to use extra_info.
+ """
+
+ ...
+
+
+class CustomSchema(BaseSchema):
+ extra_info = ... # some extra info
+
+
+class CustomView(APIView):
+ schema = CustomSchema()
+```
+
+This style is slightly more verbose but maintains the encapsulation of the
+schema related code. It's more _cohesive_ in the _parlance_. It'll keep the
+rest of your API code more tidy.
+
+If an option applies to many view classes, rather than creating a specific
+subclass per-view, you may find it more convenient to allow specifying the
+option as an `__init__()` kwarg to your base `AutoSchema` subclass:
+
+```python
+class CustomSchema(BaseSchema):
+ def __init__(self, **kwargs):
+ # store extra_info for later
+ self.extra_info = kwargs.pop("extra_info")
+ super().__init__(**kwargs)
+
+
+class CustomView(APIView):
+ schema = CustomSchema(extra_info=...) # some extra info
+```
+
+This saves you having to create a custom subclass per-view for a commonly used option.
+
+Not all `AutoSchema` methods expose related `__init__()` kwargs, but those for
+the more commonly needed options do.
+
+### `AutoSchema` methods
+
+#### `get_components()`
+
+Generates the OpenAPI components that describe request and response bodies,
+deriving their properties from the serializer.
+
+Returns a dictionary mapping the component name to the generated
+representation. By default this has just a single pair but you may override
+`get_components()` to return multiple pairs if your view uses multiple
+serializers.
+
+#### `get_component_name()`
+
+Computes the component's name from the serializer.
+
+You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names.
+
+#### `get_reference()`
+
+Returns a reference to the serializer component. This may be useful if you override `get_schema()`.
+
+
+#### `map_serializer()`
+
+Maps serializers to their OpenAPI representations.
+
+Most serializers should conform to the standard OpenAPI `object` type, but you may
+wish to override `map_serializer()` in order to customize this or other
+serializer-level fields.
+
+#### `map_field()`
+
+Maps individual serializer fields to their schema representation. The base implementation
+will handle the default fields that Django REST Framework provides.
+
+For `SerializerMethodField` instances, for which the schema is unknown, or custom field subclasses you should override `map_field()` to generate the correct schema:
+
+```python
+class CustomSchema(AutoSchema):
+ """Extension of ``AutoSchema`` to add support for custom field schemas."""
+
+ def map_field(self, field):
+ # Handle SerializerMethodFields or custom fields here...
+ # ...
+ return super().map_field(field)
+```
+
+Authors of third-party packages should aim to provide an `AutoSchema` subclass,
+and a mixin, overriding `map_field()` so that users can easily generate schemas
+for their custom fields.
+
+#### `get_tags()`
+
+OpenAPI groups operations by tags. By default tags taken from the first path
+segment of the routed URL. For example, a URL like `/users/{id}/` will generate
+the tag `users`.
+
+You can pass an `__init__()` kwarg to manually specify tags (see below), or
+override `get_tags()` to provide custom logic.
+
+#### `get_operation()`
+
+Returns the [OpenAPI operation object][openapi-operation] that describes the
+endpoint, including path and query parameters for pagination, filtering, and so
+on.
+
+Together with `get_components()`, this is the main entry point to the view
+introspection.
+
+#### `get_operation_id()`
+
+There must be a unique [operationid](openapi-operationid) for each operation.
+By default the `operationId` is deduced from the model name, serializer name or
+view name. The operationId looks like "listItems", "retrieveItem",
+"updateItem", etc. The `operationId` is camelCase by convention.
+
+#### `get_operation_id_base()`
+
+If you have several views with the same model name, you may see duplicate
+operationIds.
+
+In order to work around this, you can override `get_operation_id_base()` to
+provide a different base for name part of the ID.
+
+#### `get_serializer()`
+
+If the view has implemented `get_serializer()`, returns the result.
+
+#### `get_request_serializer()`
+
+By default returns `get_serializer()` but can be overridden to
+differentiate between request and response objects.
+
+#### `get_response_serializer()`
+
+By default returns `get_serializer()` but can be overridden to
+differentiate between request and response objects.
+
+### `AutoSchema.__init__()` kwargs
+
+`AutoSchema` provides a number of `__init__()` kwargs that can be used for
+common customizations, if the default generated values are not appropriate.
+
+The available kwargs are:
+
+* `tags`: Specify a list of tags.
+* `component_name`: Specify the component name.
+* `operation_id_base`: Specify the resource-name part of operation IDs.
+
+You pass the kwargs when declaring the `AutoSchema` instance on your view:
+
+```
+class PetDetailView(generics.RetrieveUpdateDestroyAPIView):
+ schema = AutoSchema(
+ tags=['Pets'],
+ component_name='Pet',
+ operation_id_base='Pet',
+ )
+ ...
+```
+
+Assuming a `Pet` model and `PetSerializer` serializer, the kwargs in this
+example are probably not needed. Often, though, you'll need to pass the kwargs
+if you have multiple view targeting the same model, or have multiple views with
+identically named serializers.
+
+If your views have related customizations that are needed frequently, you can
+create a base `AutoSchema` subclass for your project that takes additional
+`__init__()` kwargs to save subclassing `AutoSchema` for each view.
+
+[cite]: https://www.heroku.com/blog/json_schema_for_heroku_platform_api/
+[openapi]: https://github.com/OAI/OpenAPI-Specification
+[openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions
+[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
+[openapi-tags]: https://swagger.io/specification/#tagObject
+[openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-17
+[openapi-components]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
+[openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject
+[openapi-generator]: https://github.com/OpenAPITools/openapi-generator
+[swagger-codegen]: https://github.com/swagger-api/swagger-codegen
+[info-object]: https://swagger.io/specification/#infoObject
+[drf-spectacular]: https://drf-spectacular.readthedocs.io/en/latest/readme.html
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index f88ec51f2c..8d56d36f5a 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -1,9 +1,6 @@
-source: serializers.py
-
---
-
-**Note**: This is the documentation for the **version 3.0** of REST framework. Documentation for [version 2.4](http://tomchristie.github.io/rest-framework-2-docs/) is also available.
-
+source:
+ - serializers.py
---
# Serializers
@@ -23,8 +20,8 @@ The serializers in REST framework work very similarly to Django's `Form` and `Mo
Let's start by creating a simple object we can use for example purposes:
from datetime import datetime
-
- class Comment(object):
+
+ class Comment:
def __init__(self, email, content, created=None):
self.email = email
self.content = content
@@ -49,7 +46,7 @@ We can now use `CommentSerializer` to serialize a comment, or list of comments.
serializer = CommentSerializer(comment)
serializer.data
- # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
+ # {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into `json`.
@@ -57,16 +54,16 @@ At this point we've translated the model instance into Python native datatypes.
json = JSONRenderer().render(serializer.data)
json
- # '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'
+ # b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
## Deserializing objects
Deserialization is similar. First we parse a stream into Python native datatypes...
- from django.utils.six import BytesIO
+ import io
from rest_framework.parsers import JSONParser
- stream = BytesIO(json)
+ stream = io.BytesIO(json)
data = JSONParser().parse(stream)
...then we restore those native datatypes into a dictionary of validated data.
@@ -79,7 +76,7 @@ Deserialization is similar. First we parse a stream into Python native datatypes
## Saving instances
-If we want to be able to return complete object instances based on the validated data we need to implement one or both of the `.create()` and `update()` methods. For example:
+If we want to be able to return complete object instances based on the validated data we need to implement one or both of the `.create()` and `.update()` methods. For example:
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
@@ -119,7 +116,7 @@ Calling `.save()` will either create a new instance, or update an existing insta
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
-Both the `.create()` and `.update()` methods are optional. You can implement either neither, one, or both of them, depending on the use-case for your serializer class.
+Both the `.create()` and `.update()` methods are optional. You can implement either none, one, or both of them, depending on the use-case for your serializer class.
#### Passing additional attributes to `.save()`
@@ -158,13 +155,13 @@ When deserializing data, you always need to call `is_valid()` before attempting
serializer.is_valid()
# False
serializer.errors
- # {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
+ # {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field. The `non_field_errors` key may also be present, and will list any general validation errors. The name of the `non_field_errors` key may be customized using the `NON_FIELD_ERRORS_KEY` REST framework setting.
When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items.
-#### Raising an exception on invalid data
+#### Raising an exception on invalid data
The `.is_valid()` method takes an optional `raise_exception` flag that will cause it to raise a `serializers.ValidationError` exception if there are validation errors.
@@ -195,9 +192,15 @@ Your `validate_` methods should return the validated value or raise
raise serializers.ValidationError("Blog post is not about Django")
return value
+---
+
+**Note:** If your `` is declared on your serializer with the parameter `required=False` then this validation step will not take place if the field is not included.
+
+---
+
#### Object-level validation
-To do any other validation that requires access to multiple fields, add a method called `.validate()` to your `Serializer` subclass. This method takes a single argument, which is a dictionary of field values. It should raise a `ValidationError` if necessary, or just return the validated values. For example:
+To do any other validation that requires access to multiple fields, add a method called `.validate()` to your `Serializer` subclass. This method takes a single argument, which is a dictionary of field values. It should raise a `serializers.ValidationError` if necessary, or just return the validated values. For example:
from rest_framework import serializers
@@ -208,7 +211,7 @@ To do any other validation that requires access to multiple fields, add a method
def validate(self, data):
"""
- Check that the start is before the stop.
+ Check that start is before finish.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
@@ -223,22 +226,24 @@ Individual fields on a serializer can include validators, by declaring them on t
raise serializers.ValidationError('Not a multiple of ten')
class GameRecord(serializers.Serializer):
- score = IntegerField(validators=[multiple_of_ten])
+ score = serializers.IntegerField(validators=[multiple_of_ten])
...
Serializer classes can also include reusable validators that are applied to the complete set of field data. These validators are included by declaring them on an inner `Meta` class, like so:
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
- room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
+ room_number = serializers.ChoiceField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
# Each room only has one event per day.
- validators = UniqueTogetherValidator(
- queryset=Event.objects.all(),
- fields=['room_number', 'date']
- )
+ validators = [
+ UniqueTogetherValidator(
+ queryset=Event.objects.all(),
+ fields=['room_number', 'date']
+ )
+ ]
For more information see the [validators documentation](validators.md).
@@ -246,14 +251,14 @@ For more information see the [validators documentation](validators.md).
When passing an initial object or queryset to a serializer instance, the object will be made available as `.instance`. If no initial object is passed then the `.instance` attribute will be `None`.
-When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the data keyword argument is not passed then the `.initial_data` attribute will not exist.
+When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the `data` keyword argument is not passed then the `.initial_data` attribute will not exist.
## Partial updates
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates.
# Update `comment` with partial data
- serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
+ serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
## Dealing with nested objects
@@ -277,7 +282,7 @@ If a nested representation may optionally accept the `None` value you should pas
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
-Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serialized.
+Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serializer.
class CommentSerializer(serializers.Serializer):
user = UserSerializer(required=False)
@@ -287,13 +292,13 @@ Similarly if a nested representation should be a list of items, you should pass
## Writable nested representations
-When dealing with nested representations that support deserializing the data, an errors with nested objects will be nested under the field name of the nested object.
+When dealing with nested representations that support deserializing the data, any errors with nested objects will be nested under the field name of the nested object.
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
- # {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
+ # {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}
Similarly, the `.validated_data` property will include nested data structures.
@@ -308,7 +313,7 @@ The following example demonstrates how you might handle creating a user with a n
class Meta:
model = User
- fields = ('username', 'email', 'profile')
+ fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
@@ -325,12 +330,12 @@ For updates you'll want to think carefully about how to handle updates to relati
* Ignore the data and leave the instance as it is.
* Raise a validation error.
-Here's an example for an `update()` method on our previous `UserSerializer` class.
+Here's an example for an `.update()` method on our previous `UserSerializer` class.
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile')
# Unless the application properly enforces that this field is
- # always set, the follow could raise a `DoesNotExist`, which
+ # always set, the following could raise a `DoesNotExist`, which
# would need to be handled.
profile = instance.profile
@@ -350,13 +355,13 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
return instance
-Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
+Because the behavior of nested creates and updates can be ambiguous, and may require complex dependencies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
-It is possible that a third party package, providing automatic support some kinds of automatic writable nested representations may be released alongside the 3.1 release.
+There are however, third-party packages available such as [DRF Writable Nested][thirdparty-writable-nested] that support automatic writable nested representations.
#### Handling saving related instances in model manager classes
-An alternative to saving multiple related instances in the serializer is to write custom model manager classes handle creating the correct instances.
+An alternative to saving multiple related instances in the serializer is to write custom model manager classes that handle creating the correct instances.
For example, suppose we wanted to ensure that `User` instances and `Profile` instances are always created together as a pair. We might write a custom manager class that looks something like this:
@@ -379,12 +384,12 @@ This manager class now more nicely encapsulates that user instances and profile
def create(self, validated_data):
return User.objects.create(
username=validated_data['username'],
- email=validated_data['email']
- is_premium_member=validated_data['profile']['is_premium_member']
+ email=validated_data['email'],
+ is_premium_member=validated_data['profile']['is_premium_member'],
has_support_contract=validated_data['profile']['has_support_contract']
)
-For more details on this approach see the Django documentation on [model managers](model-managers), and [this blogpost on using model and manager classes](encapsulation-blogpost).
+For more details on this approach see the Django documentation on [model managers][model-managers], and [this blogpost on using model and manager classes][encapsulation-blogpost].
## Dealing with multiple objects
@@ -405,7 +410,7 @@ To serialize a queryset or list of objects instead of a single object instance,
#### Deserializing multiple objects
-The default behavior for deserializing multiple objects is to support multiple object creation, but not support multiple object updates. For more information on how to support or customize either of these cases, see the [ListSerializer](#ListSerializer) documentation below.
+The default behavior for deserializing multiple objects is to support multiple object creation, but not support multiple object updates. For more information on how to support or customize either of these cases, see the [ListSerializer](#listserializer) documentation below.
## Including extra context
@@ -415,7 +420,7 @@ You can provide arbitrary additional context by passing a `context` argument whe
serializer = AccountSerializer(account, context={'request': request})
serializer.data
- # {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
+ # {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
The context dictionary can be used within any serializer field logic, such as a custom `.to_representation()` method, by accessing the `self.context` attribute.
@@ -438,10 +443,11 @@ Declaring a `ModelSerializer` looks like this:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
+ fields = ['id', 'account_name', 'users', 'created']
By default, all the model fields on the class will be mapped to a corresponding serializer fields.
-Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as described below.
+Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Reverse relationships are not included by default unless explicitly included as specified in the [serializer relations][relations] documentation.
#### Inspecting a `ModelSerializer`
@@ -451,27 +457,49 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
- >>> print repr(serializer) # Or `print(repr(serializer))` in Python 3.x.
+ >>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
-## Specifying which fields should be included
+## Specifying which fields to include
+
+If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`. It is strongly recommended that you explicitly set all fields that should be serialized using the `fields` attribute. This will make it less likely to result in unintentionally exposing data when your models change.
+
+For example:
+
+ class AccountSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Account
+ fields = ['id', 'account_name', 'users', 'created']
+
+You can also set the `fields` attribute to the special value `'__all__'` to indicate that all fields in the model should be used.
+
+For example:
+
+ class AccountSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Account
+ fields = '__all__'
-If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
+You can set the `exclude` attribute to a list of fields to be excluded from the serializer.
For example:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
- fields = ('id', 'account_name', 'users', 'created')
+ exclude = ['users']
-The names in the `fields` option will normally map to model fields on the model class.
+In the example above, if the `Account` model had 3 fields `account_name`, `users`, and `created`, this will result in the fields `account_name` and `created` to be serialized.
+
+The names in the `fields` and `exclude` attributes will normally map to model fields on the model class.
Alternatively names in the `fields` options can map to properties or methods which take no arguments that exist on the model class.
+Since version 3.3.0, it is **mandatory** to provide one of the attributes `fields` or `exclude`.
+
## Specifying nested serialization
The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `depth` option:
@@ -479,12 +507,12 @@ The default `ModelSerializer` uses primary keys for relationships, but you can a
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
- fields = ('id', 'account_name', 'users', 'created')
+ fields = ['id', 'account_name', 'users', 'created']
depth = 1
The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
-If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself.
+If you want to customize the way the serialization is done you'll need to define the field yourself.
## Specifying fields explicitly
@@ -496,10 +524,11 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
class Meta:
model = Account
+ fields = ['url', 'groups']
Extra fields can correspond to any property or callable on the model.
-## Specifying which fields should be read-only
+## Specifying read only fields
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the shortcut Meta option, `read_only_fields`.
@@ -508,8 +537,8 @@ This option should be a list or tuple of field names, and is declared as follows
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
- fields = ('id', 'account_name', 'users', 'created')
- read_only_fields = ('account_name',)
+ fields = ['id', 'account_name', 'users', 'created']
+ read_only_fields = ['account_name']
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
@@ -528,16 +557,16 @@ Please review the [Validators Documentation](/api-guide/validators/) for details
---
-## Specifying additional keyword arguments for fields.
+## Additional keyword arguments
-There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
+There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. As in the case of `read_only_fields`, this means you do not need to explicitly declare the field on the serializer.
This option is a dictionary, mapping field names to a dictionary of keyword arguments. For example:
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
- fields = ('email', 'username', 'password')
+ fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
@@ -549,6 +578,8 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
user.save()
return user
+Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored.
+
## Relational fields
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances.
@@ -557,15 +588,78 @@ Alternative representations include serializing using hyperlinks, serializing co
For full details see the [serializer relations][relations] documentation.
-## Inheritance of the 'Meta' class
+## Customizing field mappings
-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:
+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.
- class AccountSerializer(MyBaseSerializer):
- class Meta(MyBaseSerializer.Meta):
- model = Account
+Normally if a `ModelSerializer` does not generate the fields you need by default then you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model.
-Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
+### `serializer_field_mapping`
+
+A mapping of Django model fields to REST framework serializer fields. You can override this mapping to alter the default serializer fields that should be used for each model field.
+
+### `serializer_related_field`
+
+This property should be the serializer field class, that is used for relational fields by default.
+
+For `ModelSerializer` this defaults to `serializers.PrimaryKeyRelatedField`.
+
+For `HyperlinkedModelSerializer` this defaults to `serializers.HyperlinkedRelatedField`.
+
+### `serializer_url_field`
+
+The serializer field class that should be used for any `url` field on the serializer.
+
+Defaults to `serializers.HyperlinkedIdentityField`
+
+### `serializer_choice_field`
+
+The serializer field class that should be used for any choice fields on the serializer.
+
+Defaults to `serializers.ChoiceField`
+
+### The field_class and field_kwargs API
+
+The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`.
+
+### `build_standard_field(self, field_name, model_field)`
+
+Called to generate a serializer field that maps to a standard model field.
+
+The default implementation returns a serializer class based on the `serializer_field_mapping` attribute.
+
+### `build_relational_field(self, field_name, relation_info)`
+
+Called to generate a serializer field that maps to a relational model field.
+
+The default implementation returns a serializer class based on the `serializer_related_field` attribute.
+
+The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
+
+### `build_nested_field(self, field_name, relation_info, nested_depth)`
+
+Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set.
+
+The default implementation dynamically creates a nested serializer class based on either `ModelSerializer` or `HyperlinkedModelSerializer`.
+
+The `nested_depth` will be the value of the `depth` option, minus one.
+
+The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties.
+
+### `build_property_field(self, field_name, model_class)`
+
+Called to generate a serializer field that maps to a property or zero-argument method on the model class.
+
+The default implementation returns a `ReadOnlyField` class.
+
+### `build_url_field(self, field_name, model_class)`
+
+Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class.
+
+### `build_unknown_field(self, field_name, model_class)`
+
+Called when the field name did not map to any model field or model property.
+The default implementation raises an error, although subclasses may customize this behavior.
---
@@ -582,7 +676,26 @@ You can explicitly include the primary key by adding it to the `fields` option,
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
- fields = ('url', 'id', 'account_name', 'users', 'created')
+ fields = ['url', 'id', 'account_name', 'users', 'created']
+
+## Absolute and relative URLs
+
+When instantiating a `HyperlinkedModelSerializer` you must include the current
+`request` in the serializer context, for example:
+
+ serializer = AccountSerializer(queryset, context={'request': request})
+
+Doing so will ensure that the hyperlinks can include an appropriate hostname,
+so that the resulting representation uses fully qualified URLs, such as:
+
+ http://api.example.com/accounts/1/
+
+Rather than relative URLs, such as:
+
+ /accounts/1/
+
+If you *do* want to use relative URLs, you should explicitly pass `{'request': None}`
+in the serializer context.
## How hyperlinked views are determined
@@ -595,9 +708,9 @@ You can override a URL field view name and lookup field by using either, or both
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
- fields = ('account_url', 'account_name', 'users', 'created')
+ 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'}
}
@@ -617,7 +730,7 @@ Alternatively you can set the fields on the serializer explicitly. For example:
class Meta:
model = Account
- fields = ('url', 'account_name', 'users', 'created')
+ fields = ['url', 'account_name', 'users', 'created']
---
@@ -637,9 +750,25 @@ The `ListSerializer` class provides the behavior for serializing and validating
When a serializer is instantiated and `many=True` is passed, a `ListSerializer` instance will be created. The serializer class then becomes a child of the parent `ListSerializer`
+The following argument can also be passed to a `ListSerializer` field or a serializer that is passed `many=True`:
+
+### `allow_empty`
+
+This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input.
+
+### `max_length`
+
+This is `None` by default, but can be set to a positive integer if you want to validate that the list contains no more than this number of elements.
+
+### `min_length`
+
+This is `None` by default, but can be set to a positive integer if you want to validate that the list contains no fewer than this number of elements.
+
+### Customizing `ListSerializer` behavior
+
There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example:
-* You want to provide particular validation of the lists, such as always ensuring that there is at least one element in a list.
+* You want to provide particular validation of the lists, such as checking that one element does not conflict with another element in a list.
* You want to customize the create or update behavior of multiple objects.
For these cases you can modify the class that is used when `many=True` is passed, by using the `list_serializer_class` option on the serializer `Meta` class.
@@ -681,6 +810,8 @@ To support multiple updates you'll need to do so explicitly. When writing your m
* How should removals be handled? Do they imply object deletion, or removing a relationship? Should they be silently ignored, or are they invalid?
* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
+You will need to add an explicit `id` field to the instance serializer. The default implicitly-generated `id` field is marked as `read_only`. This causes it to be removed on updates. Once you declare it explicitly, it will be available in the list serializer's `update` method.
+
Here's an example of how you might choose to implement multiple updates:
class BookListSerializer(serializers.ListSerializer):
@@ -692,7 +823,7 @@ Here's an example of how you might choose to implement multiple updates:
# Perform creations and updates.
ret = []
for book_id, data in data_mapping.items():
- book = book_mapping.get(book_id, None):
+ book = book_mapping.get(book_id, None)
if book is None:
ret.append(self.child.create(data))
else:
@@ -706,12 +837,14 @@ Here's an example of how you might choose to implement multiple updates:
return ret
class BookSerializer(serializers.Serializer):
+ # We need to identify elements in the list using their primary key,
+ # so use a writable field here, rather than the default which would be read-only.
+ id = serializers.IntegerField()
...
+
class Meta:
list_serializer_class = BookListSerializer
-It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the `allow_add_remove` behavior that was present in REST framework 2.
-
#### Customizing ListSerializer initialization
When a serializer with `many=True` is instantiated, we need to determine which arguments and keyword arguments should be passed to the `.__init__()` method for both the child `Serializer` class, and for the parent `ListSerializer` class.
@@ -738,20 +871,20 @@ This class implements the same basic API as the `Serializer` class:
* `.data` - Returns the outgoing primitive representation.
* `.is_valid()` - Deserializes and validates incoming data.
* `.validated_data` - Returns the validated incoming data.
-* `.errors` - Returns an errors during validation.
+* `.errors` - Returns any errors during validation.
* `.save()` - Persists the validated data into an object instance.
There are four methods that can be overridden, depending on what functionality you want the serializer class to support:
* `.to_representation()` - Override this to support serialization, for read operations.
* `.to_internal_value()` - Override this to support deserialization, for write operations.
-* `.create()` and `.update()` - Overide either or both of these to support saving instances.
+* `.create()` and `.update()` - Override either or both of these to support saving instances.
-Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
+Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class-based views exactly as you would for a regular `Serializer` or `ModelSerializer`.
The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input.
-##### Read-only `BaseSerializer` classes
+#### Read-only `BaseSerializer` classes
To implement a read-only serializer using the `BaseSerializer` class, we just need to override the `.to_representation()` method. Let's take a look at an example using a simple Django model:
@@ -763,10 +896,10 @@ To implement a read-only serializer using the `BaseSerializer` class, we just ne
It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types.
class HighScoreSerializer(serializers.BaseSerializer):
- def to_representation(self, obj):
+ def to_representation(self, instance):
return {
- 'score': obj.score,
- 'player_name': obj.player_name
+ 'score': instance.score,
+ 'player_name': instance.player_name
}
We can now use this class to serialize single `HighScore` instances:
@@ -775,7 +908,7 @@ We can now use this class to serialize single `HighScore` instances:
def high_score(request, pk):
instance = HighScore.objects.get(pk=pk)
serializer = HighScoreSerializer(instance)
- return Response(serializer.data)
+ return Response(serializer.data)
Or use it to serialize multiple instances:
@@ -783,11 +916,11 @@ Or use it to serialize multiple instances:
def all_high_scores(request):
queryset = HighScore.objects.order_by('-score')
serializer = HighScoreSerializer(queryset, many=True)
- return Response(serializer.data)
+ return Response(serializer.data)
-##### Read-write `BaseSerializer` classes
+#### Read-write `BaseSerializer` classes
-To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `ValidationError` if the supplied data is in an incorrect format.
+To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `serializers.ValidationError` if the supplied data is in an incorrect format.
Once you've implemented `.to_internal_value()`, the basic validation API will be available on the serializer, and you will be able to use `.is_valid()`, `.validated_data` and `.errors`.
@@ -802,29 +935,29 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
# Perform the data validation.
if not score:
- raise ValidationError({
+ raise serializers.ValidationError({
'score': 'This field is required.'
})
if not player_name:
- raise ValidationError({
+ raise serializers.ValidationError({
'player_name': 'This field is required.'
})
if len(player_name) > 10:
- raise ValidationError({
+ raise serializers.ValidationError({
'player_name': 'May not be more than 10 characters.'
})
- # Return the validated values. This will be available as
- # the `.validated_data` property.
+ # Return the validated values. This will be available as
+ # the `.validated_data` property.
return {
'score': int(score),
'player_name': player_name
}
- def to_representation(self, obj):
+ def to_representation(self, instance):
return {
- 'score': obj.score,
- 'player_name': obj.player_name
+ 'score': instance.score,
+ 'player_name': instance.player_name
}
def create(self, validated_data):
@@ -834,17 +967,18 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
The `BaseSerializer` class is also useful if you want to implement new generic serializer classes for dealing with particular serialization styles, or for integrating with alternative storage backends.
-The following class is an example of a generic serializer that can handle coercing arbitrary objects into primitive representations.
+The following class is an example of a generic serializer that can handle coercing arbitrary complex objects into primitive representations.
class ObjectSerializer(serializers.BaseSerializer):
"""
A read-only serializer that coerces arbitrary complex objects
into primitive representations.
"""
- def to_representation(self, obj):
- for attribute_name in dir(obj):
- attribute = getattr(obj, attribute_name)
- if attribute_name('_'):
+ def to_representation(self, instance):
+ output = {}
+ for attribute_name in dir(instance):
+ attribute = getattr(instance, attribute_name)
+ if attribute_name.startswith('_'):
# Ignore private attributes.
pass
elif hasattr(attribute, '__call__'):
@@ -867,6 +1001,7 @@ The following class is an example of a generic serializer that can handle coerci
else:
# Force anything else to its string representation.
output[attribute_name] = str(attribute)
+ return output
---
@@ -874,7 +1009,7 @@ The following class is an example of a generic serializer that can handle coerci
## Overriding serialization and deserialization behavior
-If you need to alter the serialization, deserialization or validation of a serializer class you can do so by overriding the `.to_representation()` or `.to_internal_value()` methods.
+If you need to alter the serialization or deserialization behavior of a serializer class, you can do so by overriding the `.to_representation()` or `.to_internal_value()` methods.
Some reasons this might be useful include...
@@ -884,18 +1019,60 @@ Some reasons this might be useful include...
The signatures for these methods are as follows:
-#### `.to_representation(self, obj)`
+#### `to_representation(self, instance)`
Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API.
-#### ``.to_internal_value(self, data)``
+May be overridden in order to modify the representation style. For example:
+
+ def to_representation(self, instance):
+ """Convert `username` to lowercase."""
+ ret = super().to_representation(instance)
+ ret['username'] = ret['username'].lower()
+ return ret
+
+#### ``to_internal_value(self, data)``
Takes the unvalidated incoming data as input and should return the validated data that will be made available as `serializer.validated_data`. The return value will also be passed to the `.create()` or `.update()` methods if `.save()` is called on the serializer class.
-If any of the validation fails, then the method should raise a `serializers.ValidationError(errors)`. Typically the `errors` argument here will be a dictionary mapping field names to error messages.
+If any of the validation fails, then the method should raise a `serializers.ValidationError(errors)`. The `errors` argument should be a dictionary mapping field names (or `settings.NON_FIELD_ERRORS_KEY`) to a list of error messages. If you don't need to alter deserialization behavior and instead want to provide object-level validation, it's recommended that you instead override the [`.validate()`](#object-level-validation) method.
The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.
+## 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, value):
+ ...
+
+ 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 child’s `Meta`, if it exists, otherwise the `Meta` of the first parent, etc.
+* It’s 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 won’t 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.
@@ -917,12 +1094,12 @@ For example, if you wanted to be able to set which fields should be used by a se
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
- super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
- existing = set(self.fields.keys())
+ existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
@@ -931,12 +1108,12 @@ This would then allow you to do the following:
>>> class UserSerializer(DynamicFieldsModelSerializer):
>>> class Meta:
>>> model = User
- >>> fields = ('id', 'username', 'email')
+ >>> fields = ['id', 'username', 'email']
>>>
- >>> print UserSerializer(user)
+ >>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
- >>> print UserSerializer(user, fields=('id', 'email'))
+ >>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}
## Customizing the default fields
@@ -947,14 +1124,20 @@ 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
The following third party packages are also available.
+## Django REST marshmallow
+
+The [django-rest-marshmallow][django-rest-marshmallow] package provides an alternative implementation for serializers, using the python [marshmallow][marshmallow] library. It exposes the same API as the REST framework serializers, and can be used as a drop-in replacement in some use-cases.
+
+## Serpy
+
+The [serpy][serpy] package is an alternative implementation for serializers that is built for speed. [Serpy][serpy] serializes complex datatypes to simple native types. The native types can be easily converted to JSON or any other format needed.
+
## MongoengineModelSerializer
The [django-rest-framework-mongoengine][mongoengine] package provides a `MongoEngineModelSerializer` serializer class that supports using MongoDB as the storage layer for Django REST framework.
@@ -967,11 +1150,65 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides a `G
The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreSerializer` to support [django-hstore][django-hstore] `DictionaryField` model field and its `schema-mode` feature.
+## Dynamic REST
+
+The [dynamic-rest][dynamic-rest] package extends the ModelSerializer and ModelViewSet interfaces, adding API query parameters for filtering, sorting, and including / excluding all fields and relationships defined by your serializers.
+
+## Dynamic Fields Mixin
+
+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 `
-
-Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.
+Django REST framework is a powerful and flexible toolkit for building Web APIs.
Some reasons you might want to use REST framework:
-* The [Web browsable API][sandbox] is a huge usability win for your developers.
-* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box.
+* The Web browsable API is a huge usability win for your developers.
+* [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section].
* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources.
* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers].
-* [Extensive documentation][index], and [great community support][group].
-* Used and trusted by large companies such as [Mozilla][mozilla] and [Eventbrite][eventbrite].
+* Extensive documentation, and [great community support][group].
+* Used and trusted by internationally recognised companies including [Mozilla][mozilla], [Red Hat][redhat], [Heroku][heroku], and [Eventbrite][eventbrite].
---
-![Screenshot][image]
+## Funding
-**Above**: *Screenshot from the browsable API*
+REST framework is a *collaboratively funded project*. If you use
+REST framework commercially we strongly encourage you to invest in its
+continued development by **[signing up for a paid plan][funding]**.
+
+*Every single sign-up helps us make REST framework long-term financially sustainable.*
+
+
{% endblock %}
@@ -147,21 +204,29 @@ An alternative, but more complex option would be to replace the input with an au
There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light], that you may want to refer to. Note that you will not be able to simply include these components as standard widgets, but will need to write the HTML template explicitly. This is because REST framework 3.0 no longer supports the `widget` keyword argument since it now uses templated HTML generation.
-Better support for autocomplete inputs is planned in future versions.
-
---
-[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead
+[cite]: https://en.wikiquote.org/wiki/Alfred_North_Whitehead
[drfreverse]: ../api-guide/reverse.md
[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
[chromejsonview]: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc
-[bootstrap]: http://getbootstrap.com
+[bootstrap]: https://getbootstrap.com/
[cerulean]: ../img/cerulean.png
[slate]: ../img/slate.png
-[bcustomize]: http://getbootstrap.com/2.3.2/customize.html
-[bswatch]: http://bootswatch.com/
-[bcomponents]: http://getbootstrap.com/2.3.2/components.html
-[bcomponentsnav]: http://getbootstrap.com/2.3.2/components.html#navbar
+[bswatch]: https://bootswatch.com/
+[bcomponents]: https://getbootstrap.com/2.3.2/components.html
+[bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
-[django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install
+[drf-restwind]: https://github.com/youzarsiph/drf-restwind
+[drf-rw-api-root]: ../img/drf-rw-api-root.png
+[drf-rw-list-view]: ../img/drf-rw-list-view.png
+[drf-rw-detail-view]: ../img/drf-rw-detail-view.png
+[drf-redesign]: https://github.com/youzarsiph/drf-redesign
+[drf-r-api-root]: ../img/drf-r-api-root.png
+[drf-r-list-view]: ../img/drf-r-list-view.png
+[drf-r-detail-view]: ../img/drf-r-detail-view.png
+[drf-material]: https://github.com/youzarsiph/drf-material
+[drf-m-api-root]: ../img/drf-m-api-root.png
+[drf-m-list-view]: ../img/drf-m-list-view.png
+[drf-m-detail-view]: ../img/drf-m-detail-view.png
diff --git a/docs/topics/browser-enhancements.md b/docs/topics/browser-enhancements.md
index 5a17262016..67c1c1898f 100644
--- a/docs/topics/browser-enhancements.md
+++ b/docs/topics/browser-enhancements.md
@@ -4,67 +4,76 @@
>
> — [RESTful Web Services][cite], Leonard Richardson & Sam Ruby.
-## Browser based PUT, DELETE, etc...
+In order to allow the browsable API to function, there are a couple of browser enhancements that REST framework needs to provide.
+
+As of version 3.3.0 onwards these are enabled with javascript, using the [ajax-form][ajax-form] library.
-REST framework supports browser-based `PUT`, `DELETE` and other methods, by
-overloading `POST` requests using a hidden form field.
+## Browser based PUT, DELETE, etc...
-Note that this is the same strategy as is used in [Ruby on Rails][rails].
+The [AJAX form library][ajax-form] supports browser-based `PUT`, `DELETE` and other methods on HTML forms.
-For example, given the following form:
+After including the library, use the `data-method` attribute on the form, like so:
-
-
+
+
+ ...
-`request.method` would return `"DELETE"`.
+Note that prior to 3.3.0, this support was server-side rather than javascript based. The method overloading style (as used in [Ruby on Rails][rails]) is no longer supported due to subtle issues that it introduces in request parsing.
-## HTTP header based method overriding
+## Browser based submission of non-form content
-REST framework also supports method overriding via the semi-standard `X-HTTP-Method-Override` header. This can be useful if you are working with non-form content such as JSON and are working with an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as `PATCH`. For example [Amazon Web Services ELB][aws_elb].
+Browser-based submission of content types such as JSON are supported by the [AJAX form library][ajax-form], using form fields with `data-override='content-type'` and `data-override='content'` attributes.
-To use it, make a `POST` request, setting the `X-HTTP-Method-Override` header.
+For example:
-For example, making a `PATCH` request via `POST` in jQuery:
+
+
+
+
+
- $.ajax({
- url: '/myresource/',
- method: 'POST',
- headers: {'X-HTTP-Method-Override': 'PATCH'},
- ...
- });
+Note that prior to 3.3.0, this support was server-side rather than javascript based.
-## Browser based submission of non-form content
+## URL based format suffixes
-Browser-based submission of content types other than form are supported by
-using form fields named `_content` and `_content_type`:
+REST framework can take `?format=json` style URL parameters, which can be a
+useful shortcut for determining which content type should be returned from
+the view.
-For example, given the following form:
+This behavior is controlled using the `URL_FORMAT_OVERRIDE` setting.
-
-
-
-
+## HTTP header based method overriding
-`request.content_type` would return `"application/json"`, and
-`request.stream` would return `"{'count': 1}"`
+Prior to version 3.3.0 the semi extension header `X-HTTP-Method-Override` was supported for overriding the request method. This behavior is no longer in core, but can be adding if needed using middleware.
-## URL based accept headers
+For example:
-REST framework can take `?accept=application/json` style URL parameters,
-which allow the `Accept` header to be overridden.
+ METHOD_OVERRIDE_HEADER = 'HTTP_X_HTTP_METHOD_OVERRIDE'
-This can be useful for testing the API from a web browser, where you don't
-have any control over what is sent in the `Accept` header.
+ class MethodOverrideMiddleware:
-## URL based format suffixes
+ def __init__(self, get_response):
+ self.get_response = get_response
-REST framework can take `?format=json` style URL parameters, which can be a
-useful shortcut for determining which content type should be returned from
-the view.
+ def __call__(self, request):
+ if request.method == 'POST' and METHOD_OVERRIDE_HEADER in request.META:
+ request.method = request.META[METHOD_OVERRIDE_HEADER]
+ return self.get_response(request)
+
+## URL based accept headers
+
+Until version 3.3.0 REST framework included built-in support for `?accept=application/json` style URL parameters, which would allow the `Accept` header to be overridden.
+
+Since the introduction of the content negotiation API this behavior is no longer included in core, but may be added using a custom content negotiation class, if needed.
+
+For example:
-This is a more concise than using the `accept` override, but it also gives
-you less control. (For example you can't specify any media type parameters)
+ class AcceptQueryParamOverride()
+ def get_accept_list(self, request):
+ header = request.META.get('HTTP_ACCEPT', '*/*')
+ header = request.query_params.get('_accept', header)
+ return [token.strip() for token in header.split(',')]
## Doesn't HTML5 support PUT and DELETE forms?
@@ -73,8 +82,8 @@ was later [dropped from the spec][html5]. There remains
[ongoing discussion][put_delete] about adding support for `PUT` and `DELETE`,
as well as how to support content types other than form-encoded data.
-[cite]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
-[rails]: http://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
-[html5]: http://www.w3.org/TR/html5-diff/#changes-2010-06-24
+[cite]: https://www.amazon.com/RESTful-Web-Services-Leonard-Richardson/dp/0596529260
+[ajax-form]: https://github.com/tomchristie/ajax-form
+[rails]: https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
+[html5]: https://www.w3.org/TR/html5-diff/#changes-2010-06-24
[put_delete]: http://amundsen.com/examples/put-delete-forms/
-[aws_elb]: https://forums.aws.amazon.com/thread.jspa?messageID=400724
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
deleted file mode 100644
index 5f0dc75220..0000000000
--- a/docs/topics/credits.md
+++ /dev/null
@@ -1,404 +0,0 @@
-# Credits
-
-The following people have helped make REST framework great.
-
-* Tom Christie - [tomchristie]
-* Marko Tibold - [markotibold]
-* Paul Miller - [paulmillr]
-* Sébastien Piquemal - [sebpiq]
-* Carmen Wick - [cwick]
-* Alex Ehlke - [aehlke]
-* Alen Mujezinovic - [flashingpumpkin]
-* Carles Barrobés - [txels]
-* Michael Fötsch - [mfoetsch]
-* David Larlet - [david]
-* Andrew Straw - [astraw]
-* Zeth - [zeth]
-* Fernando Zunino - [fzunino]
-* Jens Alm - [ulmus]
-* Craig Blaszczyk - [jakul]
-* Garcia Solero - [garciasolero]
-* Tom Drummond - [devioustree]
-* Danilo Bargen - [dbrgn]
-* Andrew McCloud - [amccloud]
-* Thomas Steinacher - [thomasst]
-* Meurig Freeman - [meurig]
-* Anthony Nemitz - [anemitz]
-* Ewoud Kohl van Wijngaarden - [ekohl]
-* Michael Ding - [yandy]
-* Mjumbe Poe - [mjumbewu]
-* Natim - [natim]
-* Sebastian Żurek - [sebzur]
-* Benoit C - [dzen]
-* Chris Pickett - [bunchesofdonald]
-* Ben Timby - [btimby]
-* Michele Lazzeri - [michelelazzeri-nextage]
-* Camille Harang - [mammique]
-* Paul Oswald - [poswald]
-* Sean C. Farley - [scfarley]
-* Daniel Izquierdo - [izquierdo]
-* Can Yavuz - [tschan]
-* Shawn Lewis - [shawnlewis]
-* Alec Perkins - [alecperkins]
-* Michael Barrett - [phobologic]
-* Mathieu Dhondt - [laundromat]
-* Johan Charpentier - [cyberj]
-* Jamie Matthews - [j4mie]
-* Mattbo - [mattbo]
-* Max Hurl - [maximilianhurl]
-* Tomi Pajunen - [eofs]
-* Rob Dobson - [rdobson]
-* Daniel Vaca Araujo - [diviei]
-* Madis Väin - [madisvain]
-* Stephan Groß - [minddust]
-* Pavel Savchenko - [asfaltboy]
-* Otto Yiu - [ottoyiu]
-* Jacob Magnusson - [jmagnusson]
-* Osiloke Harold Emoekpere - [osiloke]
-* Michael Shepanski - [mjs7231]
-* Toni Michel - [tonimichel]
-* Ben Konrath - [benkonrath]
-* Marc Aymerich - [glic3rinu]
-* Ludwig Kraatz - [ludwigkraatz]
-* Rob Romano - [robromano]
-* Eugene Mechanism - [mechanism]
-* Jonas Liljestrand - [jonlil]
-* Justin Davis - [irrelative]
-* Dustin Bachrach - [dbachrach]
-* Mark Shirley - [maspwr]
-* Olivier Aubert - [oaubert]
-* Yuri Prezument - [yprez]
-* Fabian Buechler - [fabianbuechler]
-* Mark Hughes - [mhsparks]
-* Michael van de Waeter - [mvdwaeter]
-* Reinout van Rees - [reinout]
-* Michael Richards - [justanotherbody]
-* Ben Roberts - [roberts81]
-* Venkata Subramanian Mahalingam - [annacoder]
-* George Kappel - [gkappel]
-* Colin Murtaugh - [cmurtaugh]
-* Simon Pantzare - [pilt]
-* Szymon Teżewski - [sunscrapers]
-* Joel Marcotte - [joual]
-* Trey Hunner - [treyhunner]
-* Roman Akinfold - [akinfold]
-* Toran Billups - [toranb]
-* Sébastien Béal - [sebastibe]
-* Andrew Hankinson - [ahankinson]
-* Juan Riaza - [juanriaza]
-* Michael Mior - [michaelmior]
-* Marc Tamlyn - [mjtamlyn]
-* Richard Wackerbarth - [wackerbarth]
-* Johannes Spielmann - [shezi]
-* James Cleveland - [radiosilence]
-* Steve Gregory - [steve-gregory]
-* Federico Capoano - [nemesisdesign]
-* Bruno Renié - [brutasse]
-* Kevin Stone - [kevinastone]
-* Guglielmo Celata - [guglielmo]
-* Mike Tums - [mktums]
-* Michael Elovskikh - [wronglink]
-* Michał Jaworski - [swistakm]
-* Andrea de Marco - [z4r]
-* Fernando Rocha - [fernandogrd]
-* Xavier Ordoquy - [xordoquy]
-* Adam Wentz - [floppya]
-* Andreas Pelme - [pelme]
-* Ryan Detzel - [ryanrdetzel]
-* Omer Katz - [thedrow]
-* Wiliam Souza - [waa]
-* Jonas Braun - [iekadou]
-* Ian Dash - [bitmonkey]
-* Bouke Haarsma - [bouke]
-* Pierre Dulac - [dulaccc]
-* Dave Kuhn - [kuhnza]
-* Sitong Peng - [stoneg]
-* Victor Shih - [vshih]
-* Atle Frenvik Sveen - [atlefren]
-* J Paul Reed - [preed]
-* Matt Majewski - [forgingdestiny]
-* Jerome Chen - [chenjyw]
-* Andrew Hughes - [eyepulp]
-* Daniel Hepper - [dhepper]
-* Hamish Campbell - [hamishcampbell]
-* Marlon Bailey - [avinash240]
-* James Summerfield - [jsummerfield]
-* Andy Freeland - [rouge8]
-* Craig de Stigter - [craigds]
-* Pablo Recio - [pyriku]
-* Brian Zambrano - [brianz]
-* Òscar Vilaplana - [grimborg]
-* Ryan Kaskel - [ryankask]
-* Andy McKay - [andymckay]
-* Matteo Suppo - [matteosuppo]
-* Karol Majta - [lolek09]
-* David Jones - [commonorgarden]
-* Andrew Tarzwell - [atarzwell]
-* Michal Dvořák - [mikee2185]
-* Markus Törnqvist - [mjtorn]
-* Pascal Borreli - [pborreli]
-* Alex Burgel - [aburgel]
-* David Medina - [copitux]
-* Areski Belaid - [areski]
-* Ethan Freman - [mindlace]
-* David Sanders - [davesque]
-* Philip Douglas - [freakydug]
-* Igor Kalat - [trwired]
-* Rudolf Olah - [omouse]
-* Gertjan Oude Lohuis - [gertjanol]
-* Matthias Jacob - [cyroxx]
-* Pavel Zinovkin - [pzinovkin]
-* Will Kahn-Greene - [willkg]
-* Kevin Brown - [kevin-brown]
-* Rodrigo Martell - [coderigo]
-* James Rutherford - [jimr]
-* Ricky Rosario - [rlr]
-* Veronica Lynn - [kolvia]
-* Dan Stephenson - [etos]
-* Martin Clement - [martync]
-* Jeremy Satterfield - [jsatt]
-* Christopher Paolini - [chrispaolini]
-* Filipe A Ximenes - [filipeximenes]
-* Ramiro Morales - [ramiro]
-* Krzysztof Jurewicz - [krzysiekj]
-* Eric Buehl - [ericbuehl]
-* Kristian Øllegaard - [kristianoellegaard]
-* Alexander Akhmetov - [alexander-akhmetov]
-* Andrey Antukh - [niwibe]
-* Mathieu Pillard - [diox]
-* Edmond Wong - [edmondwong]
-* Ben Reilly - [bwreilly]
-* Tai Lee - [mrmachine]
-* Markus Kaiserswerth - [mkai]
-* Henry Clifford - [hcliff]
-* Thomas Badaud - [badale]
-* Colin Huang - [tamakisquare]
-* Ross McFarland - [ross]
-* Jacek Bzdak - [jbzdak]
-* Alexander Lukanin - [alexanderlukanin13]
-* Yamila Moreno - [yamila-moreno]
-* Rob Hudson - [robhudson]
-* Alex Good - [alexjg]
-* Ian Foote - [ian-foote]
-* Chuck Harmston - [chuckharmston]
-* Philip Forget - [philipforget]
-* Artem Mezhenin - [amezhenin]
-
-Many thanks to everyone who's contributed to the project.
-
-## Additional thanks
-
-The documentation is built with [Bootstrap] and [Markdown].
-
-Project hosting is with [GitHub].
-
-Continuous integration testing is managed with [Travis CI][travis-ci].
-
-The [live sandbox][sandbox] is hosted on [Heroku].
-
-Various inspiration taken from the [Rails], [Piston], [Tastypie], [Dagny] and [django-viewsets] projects.
-
-Development of REST framework 2.0 was sponsored by [DabApps].
-
-## Contact
-
-For usage questions please see the [REST framework discussion group][group].
-
-You can also contact [@_tomchristie][twitter] directly on twitter.
-
-[twitter]: http://twitter.com/_tomchristie
-[bootstrap]: http://twitter.github.com/bootstrap/
-[markdown]: http://daringfireball.net/projects/markdown/
-[github]: https://github.com/tomchristie/django-rest-framework
-[travis-ci]: https://secure.travis-ci.org/tomchristie/django-rest-framework
-[rails]: http://rubyonrails.org/
-[piston]: https://bitbucket.org/jespern/django-piston
-[tastypie]: https://github.com/toastdriven/django-tastypie
-[dagny]: https://github.com/zacharyvoase/dagny
-[django-viewsets]: https://github.com/BertrandBordage/django-viewsets
-[dabapps]: http://lab.dabapps.com
-[sandbox]: http://restframework.herokuapp.com/
-[heroku]: http://www.heroku.com/
-[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-
-[tomchristie]: https://github.com/tomchristie
-[markotibold]: https://github.com/markotibold
-[paulmillr]: https://github.com/paulmillr
-[sebpiq]: https://github.com/sebpiq
-[cwick]: https://github.com/cwick
-[aehlke]: https://github.com/aehlke
-[flashingpumpkin]: https://github.com/flashingpumpkin
-[txels]: https://github.com/txels
-[mfoetsch]: https://github.com/mfoetsch
-[david]: https://github.com/david
-[astraw]: https://github.com/astraw
-[zeth]: https://github.com/zeth
-[fzunino]: https://github.com/fzunino
-[ulmus]: https://github.com/ulmus
-[jakul]: https://github.com/jakul
-[garciasolero]: https://github.com/garciasolero
-[devioustree]: https://github.com/devioustree
-[dbrgn]: https://github.com/dbrgn
-[amccloud]: https://github.com/amccloud
-[thomasst]: https://github.com/thomasst
-[meurig]: https://github.com/meurig
-[anemitz]: https://github.com/anemitz
-[ekohl]: https://github.com/ekohl
-[yandy]: https://github.com/yandy
-[mjumbewu]: https://github.com/mjumbewu
-[natim]: https://github.com/natim
-[sebzur]: https://github.com/sebzur
-[dzen]: https://github.com/dzen
-[bunchesofdonald]: https://github.com/bunchesofdonald
-[btimby]: https://github.com/btimby
-[michelelazzeri-nextage]: https://github.com/michelelazzeri-nextage
-[mammique]: https://github.com/mammique
-[poswald]: https://github.com/poswald
-[scfarley]: https://github.com/scfarley
-[izquierdo]: https://github.com/izquierdo
-[tschan]: https://github.com/tschan
-[shawnlewis]: https://github.com/shawnlewis
-[alecperkins]: https://github.com/alecperkins
-[phobologic]: https://github.com/phobologic
-[laundromat]: https://github.com/laundromat
-[cyberj]: https://github.com/cyberj
-[j4mie]: https://github.com/j4mie
-[mattbo]: https://github.com/mattbo
-[maximilianhurl]: https://github.com/maximilianhurl
-[eofs]: https://github.com/eofs
-[rdobson]: https://github.com/rdobson
-[diviei]: https://github.com/diviei
-[madisvain]: https://github.com/madisvain
-[minddust]: https://github.com/minddust
-[asfaltboy]: https://github.com/asfaltboy
-[ottoyiu]: https://github.com/OttoYiu
-[jmagnusson]: https://github.com/jmagnusson
-[osiloke]: https://github.com/osiloke
-[mjs7231]: https://github.com/mjs7231
-[tonimichel]: https://github.com/tonimichel
-[benkonrath]: https://github.com/benkonrath
-[glic3rinu]: https://github.com/glic3rinu
-[ludwigkraatz]: https://github.com/ludwigkraatz
-[robromano]: https://github.com/robromano
-[mechanism]: https://github.com/mechanism
-[jonlil]: https://github.com/jonlil
-[irrelative]: https://github.com/irrelative
-[dbachrach]: https://github.com/dbachrach
-[maspwr]: https://github.com/maspwr
-[oaubert]: https://github.com/oaubert
-[yprez]: https://github.com/yprez
-[fabianbuechler]: https://github.com/fabianbuechler
-[mhsparks]: https://github.com/mhsparks
-[mvdwaeter]: https://github.com/mvdwaeter
-[reinout]: https://github.com/reinout
-[justanotherbody]: https://github.com/justanotherbody
-[roberts81]: https://github.com/roberts81
-[annacoder]: https://github.com/annacoder
-[gkappel]: https://github.com/gkappel
-[cmurtaugh]: https://github.com/cmurtaugh
-[pilt]: https://github.com/pilt
-[sunscrapers]: https://github.com/sunscrapers
-[joual]: https://github.com/joual
-[treyhunner]: https://github.com/treyhunner
-[akinfold]: https://github.com/akinfold
-[toranb]: https://github.com/toranb
-[sebastibe]: https://github.com/sebastibe
-[ahankinson]: https://github.com/ahankinson
-[juanriaza]: https://github.com/juanriaza
-[michaelmior]: https://github.com/michaelmior
-[mjtamlyn]: https://github.com/mjtamlyn
-[wackerbarth]: https://github.com/wackerbarth
-[shezi]: https://github.com/shezi
-[radiosilence]: https://github.com/radiosilence
-[steve-gregory]: https://github.com/steve-gregory
-[nemesisdesign]: https://github.com/nemesisdesign
-[brutasse]: https://github.com/brutasse
-[kevinastone]: https://github.com/kevinastone
-[guglielmo]: https://github.com/guglielmo
-[mktums]: https://github.com/mktums
-[wronglink]: https://github.com/wronglink
-[swistakm]: https://github.com/swistakm
-[z4r]: https://github.com/z4r
-[fernandogrd]: https://github.com/fernandogrd
-[xordoquy]: https://github.com/xordoquy
-[floppya]: https://github.com/floppya
-[pelme]: https://github.com/pelme
-[ryanrdetzel]: https://github.com/ryanrdetzel
-[thedrow]: https://github.com/thedrow
-[waa]: https://github.com/wiliamsouza
-[iekadou]: https://github.com/iekadou
-[bitmonkey]: https://github.com/bitmonkey
-[bouke]: https://github.com/bouke
-[dulaccc]: https://github.com/dulaccc
-[kuhnza]: https://github.com/kuhnza
-[stoneg]: https://github.com/stoneg
-[vshih]: https://github.com/vshih
-[atlefren]: https://github.com/atlefren
-[preed]: https://github.com/preed
-[forgingdestiny]: https://github.com/forgingdestiny
-[chenjyw]: https://github.com/chenjyw
-[eyepulp]: https://github.com/eyepulp
-[dhepper]: https://github.com/dhepper
-[hamishcampbell]: https://github.com/hamishcampbell
-[avinash240]: https://github.com/avinash240
-[jsummerfield]: https://github.com/jsummerfield
-[rouge8]: https://github.com/rouge8
-[craigds]: https://github.com/craigds
-[pyriku]: https://github.com/pyriku
-[brianz]: https://github.com/brianz
-[grimborg]: https://github.com/grimborg
-[ryankask]: https://github.com/ryankask
-[andymckay]: https://github.com/andymckay
-[matteosuppo]: https://github.com/matteosuppo
-[lolek09]: https://github.com/lolek09
-[commonorgarden]: https://github.com/commonorgarden
-[atarzwell]: https://github.com/atarzwell
-[mikee2185]: https://github.com/mikee2185
-[mjtorn]: https://github.com/mjtorn
-[pborreli]: https://github.com/pborreli
-[aburgel]: https://github.com/aburgel
-[copitux]: https://github.com/copitux
-[areski]: https://github.com/areski
-[mindlace]: https://github.com/mindlace
-[davesque]: https://github.com/davesque
-[freakydug]: https://github.com/freakydug
-[trwired]: https://github.com/trwired
-[omouse]: https://github.com/omouse
-[gertjanol]: https://github.com/gertjanol
-[cyroxx]: https://github.com/cyroxx
-[pzinovkin]: https://github.com/pzinovkin
-[coderigo]: https://github.com/coderigo
-[willkg]: https://github.com/willkg
-[kevin-brown]: https://github.com/kevin-brown
-[jimr]: https://github.com/jimr
-[rlr]: https://github.com/rlr
-[kolvia]: https://github.com/kolvia
-[etos]: https://github.com/etos
-[martync]: https://github.com/martync
-[jsatt]: https://github.com/jsatt
-[chrispaolini]: https://github.com/chrispaolini
-[filipeximenes]: https://github.com/filipeximenes
-[ramiro]: https://github.com/ramiro
-[krzysiekj]: https://github.com/krzysiekj
-[ericbuehl]: https://github.com/ericbuehl
-[kristianoellegaard]: https://github.com/kristianoellegaard
-[alexander-akhmetov]: https://github.com/alexander-akhmetov
-[niwibe]: https://github.com/niwibe
-[diox]: https://github.com/diox
-[edmondwong]: https://github.com/edmondwong
-[bwreilly]: https://github.com/bwreilly
-[mrmachine]: https://github.com/mrmachine
-[mkai]: https://github.com/mkai
-[hcliff]: https://github.com/hcliff
-[badale]: https://github.com/badale
-[tamakisquare]: https://github.com/tamakisquare
-[ross]: https://github.com/ross
-[jbzdak]: https://github.com/jbzdak
-[alexanderlukanin13]: https://github.com/alexanderlukanin13
-[yamila-moreno]: https://github.com/yamila-moreno
-[robhudson]: https://github.com/robhudson
-[alexjg]: https://github.com/alexjg
-[ian-foote]: https://github.com/ian-foote
-[chuckharmston]: https://github.com/chuckharmston
-[philipforget]: https://github.com/philipforget
-[amezhenin]: https://github.com/amezhenin
diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md
index d65e251f1e..edb989290d 100644
--- a/docs/topics/documenting-your-api.md
+++ b/docs/topics/documenting-your-api.md
@@ -4,41 +4,163 @@
>
> — Roy Fielding, [REST APIs must be hypertext driven][cite]
-There are a variety of approaches to API documentation. This document introduces a few of the various tools and options you might choose from. The approaches should not be considered exclusive - you may want to provide more than one documentation style for you API, such as a self describing API that also includes static documentation of the various API endpoints.
+REST framework provides a range of different choices for documenting your API. The following
+is a non-exhaustive list of the most popular ones.
-## Endpoint documentation
+## Third party packages for OpenAPI support
-The most common way to document Web APIs today is to produce documentation that lists the API endpoints verbatim, and describes the allowable operations on each. There are various tools that allow you to do this in an automated or semi-automated way.
+### drf-spectacular
----
-
-#### Django REST Swagger
-
-Marc Gibbons' [Django REST Swagger][django-rest-swagger] integrates REST framework with the [Swagger][swagger] API documentation tool. The package produces well presented API documentation, and includes interactive tools for testing API endpoints.
+[drf-spectacular][drf-spectacular] is an [OpenAPI 3][open-api] schema generation library with explicit
+focus on extensibility, customizability and client generation. It is the recommended way for
+generating and presenting OpenAPI schemas.
-The package is fully documented, well supported, and comes highly recommended.
+The library aims to extract as much schema information as possible, while providing decorators and extensions for easy
+customization. There is explicit support for [swagger-codegen][swagger], [SwaggerUI][swagger-ui] and [Redoc][redoc],
+i18n, versioning, authentication, polymorphism (dynamic requests and responses), query/path/header parameters,
+documentation and more. Several popular plugins for DRF are supported out-of-the-box as well.
-Django REST Swagger supports REST framework versions 2.3 and above.
+### drf-yasg
-![Screenshot - Django REST Swagger][image-django-rest-swagger]
+[drf-yasg][drf-yasg] is a [Swagger / OpenAPI 2][swagger] generation tool implemented without using the schema generation provided
+by Django Rest Framework.
----
-
-#### REST Framework Docs
+It aims to implement as much of the [OpenAPI 2][open-api] specification as possible - nested schemas, named models,
+response bodies, enum/pattern/min/max validators, form parameters, etc. - and to generate documents usable with code
+generation tools like `swagger-codegen`.
-The [REST Framework Docs][rest-framework-docs] package is an earlier project, also by Marc Gibbons, that offers clean, simple autogenerated documentation for your API.
+This also translates into a very useful interactive documentation viewer in the form of `swagger-ui`:
-![Screenshot - REST Framework Docs][image-rest-framework-docs]
+![Screenshot - drf-yasg][image-drf-yasg]
---
-#### 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.
+## Built-in OpenAPI schema generation (deprecated)
+
+**Deprecation notice: REST framework's built-in support for generating OpenAPI schemas is
+deprecated in favor of 3rd party packages that can provide this functionality instead.
+As replacement, we recommend using the [drf-spectacular](#drf-spectacular) package.**
+
+There are a number of packages available that allow you to generate HTML
+documentation pages from OpenAPI schemas.
+
+Two popular options are [Swagger UI][swagger-ui] and [ReDoc][redoc].
+
+Both require little more than the location of your static schema file or
+dynamic `SchemaView` endpoint.
+
+### A minimal example with Swagger UI
+
+Assuming you've followed the example from the schemas documentation for routing
+a dynamic `SchemaView`, a minimal Django template for using Swagger UI might be
+this:
+
+```html
+
+
+
+ Swagger
+
+
+
+
+
+
+
+
+
+
+```
+
+Save this in your templates folder as `swagger-ui.html`. Then route a
+`TemplateView` in your project's URL conf:
+
+```python
+from django.views.generic import TemplateView
+
+urlpatterns = [
+ # ...
+ # Route TemplateView to serve Swagger UI template.
+ # * Provide `extra_context` with view name of `SchemaView`.
+ path(
+ "swagger-ui/",
+ TemplateView.as_view(
+ template_name="swagger-ui.html",
+ extra_context={"schema_url": "openapi-schema"},
+ ),
+ name="swagger-ui",
+ ),
+]
+```
+
+See the [Swagger UI documentation][swagger-ui] for advanced usage.
+
+### A minimal example with ReDoc.
+
+Assuming you've followed the example from the schemas documentation for routing
+a dynamic `SchemaView`, a minimal Django template for using ReDoc might be
+this:
+
+```html
+
+
+
+ ReDoc
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Save this in your templates folder as `redoc.html`. Then route a `TemplateView`
+in your project's URL conf:
+
+```python
+from django.views.generic import TemplateView
+
+urlpatterns = [
+ # ...
+ # Route TemplateView to serve the ReDoc template.
+ # * Provide `extra_context` with view name of `SchemaView`.
+ path(
+ "redoc/",
+ TemplateView.as_view(
+ template_name="redoc.html", extra_context={"schema_url": "openapi-schema"}
+ ),
+ name="redoc",
+ ),
+]
+```
+
+See the [ReDoc documentation][redoc] for advanced usage.
-![Screenshot - Apiary][image-apiary]
-
----
## Self describing APIs
@@ -60,7 +182,7 @@ When working with viewsets, an appropriate suffix is appended to each generated
The description in the browsable API is generated from the docstring of the view or viewset.
-If the python `markdown` library is installed, then [markdown syntax][markdown] may be used in the docstring, and will be converted to HTML in the browsable API. For example:
+If the python `Markdown` library is installed, then [markdown syntax][markdown] may be used in the docstring, and will be converted to HTML in the browsable API. For example:
class AccountListView(views.APIView):
"""
@@ -71,7 +193,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 list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
#### The `OPTIONS` method
@@ -79,15 +201,18 @@ REST framework APIs also support programmatically accessible descriptions, using
When using the generic views, any `OPTIONS` requests will additionally respond with metadata regarding any `POST` or `PUT` actions available, describing which fields are on the serializer.
-You can modify the response behavior to `OPTIONS` requests by overriding the `metadata` view method. For example:
+You can modify the response behavior to `OPTIONS` requests by overriding the `options` view method and/or by providing a custom Metadata class. For example:
- def metadata(self, request):
+ def options(self, request, *args, **kwargs):
"""
Don't include the view description in OPTIONS responses.
"""
- data = super(ExampleView, self).metadata(request)
+ meta = self.metadata_class()
+ data = meta.determine_metadata(request, self)
data.pop('description')
- return data
+ return Response(data=data, status=status.HTTP_200_OK)
+
+See [the Metadata docs][metadata-docs] for more details.
---
@@ -99,14 +224,19 @@ In this approach, rather than documenting the available API endpoints up front,
To implement a hypermedia API you'll need to decide on an appropriate media type for the API, and implement a custom renderer and parser for that media type. The [REST, Hypermedia & HATEOAS][hypermedia-docs] section of the documentation includes pointers to background reading, as well as links to various hypermedia formats.
-[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
-[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
-[apiary]: http://apiary.io/
-[markdown]: http://daringfireball.net/projects/markdown/
+[cite]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
+
[hypermedia-docs]: rest-hypermedia-hateoas.md
-[image-django-rest-swagger]: ../img/django-rest-swagger.png
-[image-rest-framework-docs]: ../img/rest-framework-docs.png
-[image-apiary]: ../img/apiary.png
+[metadata-docs]: ../api-guide/metadata.md
+[schemas-examples]: ../api-guide/schemas.md#examples
+
+[image-drf-yasg]: ../img/drf-yasg.png
[image-self-describing-api]: ../img/self-describing.png
+
+[drf-yasg]: https://github.com/axnsan12/drf-yasg/
+[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/
+[markdown]: https://daringfireball.net/projects/markdown/syntax
+[open-api]: https://openapis.org/
+[redoc]: https://github.com/Rebilly/ReDoc
+[swagger]: https://swagger.io/
+[swagger-ui]: https://swagger.io/tools/swagger-ui/
diff --git a/docs/topics/html-and-forms.md b/docs/topics/html-and-forms.md
new file mode 100644
index 0000000000..c7e51c1526
--- /dev/null
+++ b/docs/topics/html-and-forms.md
@@ -0,0 +1,220 @@
+# HTML & Forms
+
+REST framework is suitable for returning both API style responses, and regular HTML pages. Additionally, serializers can be used as HTML forms and rendered in templates.
+
+## Rendering HTML
+
+In order to return HTML responses you'll need to use either `TemplateHTMLRenderer`, or `StaticHTMLRenderer`.
+
+The `TemplateHTMLRenderer` class expects the response to contain a dictionary of context data, and renders an HTML page based on a template that must be specified either in the view or on the response.
+
+The `StaticHTMLRender` class expects the response to contain a string of the pre-rendered HTML content.
+
+Because static HTML pages typically have different behavior from API responses you'll probably need to write any HTML views explicitly, rather than relying on the built-in generic views.
+
+Here's an example of a view that returns a list of "Profile" instances, rendered in an HTML template:
+
+**views.py**:
+
+ from my_project.example.models import Profile
+ from rest_framework.renderers import TemplateHTMLRenderer
+ from rest_framework.response import Response
+ from rest_framework.views import APIView
+
+
+ class ProfileList(APIView):
+ renderer_classes = [TemplateHTMLRenderer]
+ template_name = 'profile_list.html'
+
+ def get(self, request):
+ queryset = Profile.objects.all()
+ return Response({'profiles': queryset})
+
+**profile_list.html**:
+
+
+
Profiles
+
+ {% for profile in profiles %}
+
{{ profile.name }}
+ {% endfor %}
+
+
+
+## Rendering Forms
+
+Serializers may be rendered as forms by using the `render_form` template tag, and including the serializer instance as context to the template.
+
+The following view demonstrates an example of using a serializer in a template for viewing and updating a model instance:
+
+**views.py**:
+
+ from django.shortcuts import get_object_or_404
+ from my_project.example.models import Profile
+ from rest_framework.renderers import TemplateHTMLRenderer
+ from rest_framework.views import APIView
+
+
+ class ProfileDetail(APIView):
+ renderer_classes = [TemplateHTMLRenderer]
+ template_name = 'profile_detail.html'
+
+ def get(self, request, pk):
+ profile = get_object_or_404(Profile, pk=pk)
+ serializer = ProfileSerializer(profile)
+ return Response({'serializer': serializer, 'profile': profile})
+
+ def post(self, request, pk):
+ profile = get_object_or_404(Profile, pk=pk)
+ serializer = ProfileSerializer(profile, data=request.data)
+ if not serializer.is_valid():
+ return Response({'serializer': serializer, 'profile': profile})
+ serializer.save()
+ return redirect('profile-list')
+
+**profile_detail.html**:
+
+ {% load rest_framework %}
+
+
+
+
+
+
+
+### Using template packs
+
+The `render_form` tag takes an optional `template_pack` argument, that specifies which template directory should be used for rendering the form and form fields.
+
+REST framework includes three built-in template packs, all based on Bootstrap 3. The built-in styles are `horizontal`, `vertical`, and `inline`. The default style is `horizontal`. To use any of these template packs you'll want to also include the Bootstrap 3 CSS.
+
+The following HTML will link to a CDN hosted version of the Bootstrap 3 CSS:
+
+
+ …
+
+
+
+Third party packages may include alternate template packs, by bundling a template directory containing the necessary form and field templates.
+
+Let's take a look at how to render each of the three available template packs. For these examples we'll use a single serializer class to present a "Login" form.
+
+ class LoginSerializer(serializers.Serializer):
+ email = serializers.EmailField(
+ max_length=100,
+ style={'placeholder': 'Email', 'autofocus': True}
+ )
+ password = serializers.CharField(
+ max_length=100,
+ style={'input_type': 'password', 'placeholder': 'Password'}
+ )
+ remember_me = serializers.BooleanField()
+
+---
+
+#### `rest_framework/vertical`
+
+Presents form labels above their corresponding control inputs, using the standard Bootstrap layout.
+
+*This is the default template pack.*
+
+ {% load rest_framework %}
+
+ ...
+
+
+
+
+
+---
+
+#### `rest_framework/horizontal`
+
+Presents labels and controls alongside each other, using a 2/10 column split.
+
+*This is the form style used in the browsable API and admin renderers.*
+
+ {% load rest_framework %}
+
+ ...
+
+
+
+
+
+## Field styles
+
+Serializer fields can have their rendering style customized by using the `style` keyword argument. This argument is a dictionary of options that control the template and layout used.
+
+The most common way to customize the field style is to use the `base_template` style keyword argument to select which template in the template pack should be use.
+
+For example, to render a `CharField` as an HTML textarea rather than the default HTML input, you would use something like this:
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'base_template': 'textarea.html'}
+ )
+
+If you instead want a field to be rendered using a custom template that is *not part of an included template pack*, you can instead use the `template` style option, to fully specify a template name:
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'template': 'my-field-templates/custom-input.html'}
+ )
+
+Field templates can also use additional style properties, depending on their type. For example, the `textarea.html` template also accepts a `rows` property that can be used to affect the sizing of the control.
+
+ details = serializers.CharField(
+ max_length=1000,
+ style={'base_template': 'textarea.html', 'rows': 10}
+ )
+
+The complete list of `base_template` options and their associated style options is listed below.
+
+base_template | Valid field types | Additional style options
+-----------------------|-------------------------------------------------------------|-----------------------------------------------
+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
+select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label
+checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label
+checkbox.html | `BooleanField` | hide_label
+fieldset.html | Nested serializer | hide_label
+list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label
diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md
new file mode 100644
index 0000000000..2f8f2abf09
--- /dev/null
+++ b/docs/topics/internationalization.md
@@ -0,0 +1,112 @@
+# Internationalization
+
+> Supporting internationalization is not optional. It must be a core feature.
+>
+> — [Jannis Leidel, speaking at Django Under the Hood, 2015][cite].
+
+REST framework ships with translatable error messages. You can make these appear in your language enabling [Django's standard translation mechanisms][django-translation].
+
+Doing so will allow you to:
+
+* Select a language other than English as the default, using the standard `LANGUAGE_CODE` Django setting.
+* Allow clients to choose a language themselves, using the `LocaleMiddleware` included with Django. A typical usage for API clients would be to include an `Accept-Language` request header.
+
+## Enabling internationalized APIs
+
+You can change the default language by using the standard Django `LANGUAGE_CODE` setting:
+
+ LANGUAGE_CODE = "es-es"
+
+You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE` setting:
+
+ MIDDLEWARE = [
+ ...
+ 'django.middleware.locale.LocaleMiddleware'
+ ]
+
+When per-request internationalization is enabled, client requests will respect the `Accept-Language` header where possible. For example, let's make a request for an unsupported media type:
+
+**Request**
+
+ GET /api/users HTTP/1.1
+ Accept: application/xml
+ Accept-Language: es-es
+ Host: example.org
+
+**Response**
+
+ HTTP/1.0 406 NOT ACCEPTABLE
+
+ {"detail": "No se ha podido satisfacer la solicitud de cabecera de Accept."}
+
+REST framework includes these built-in translations both for standard exception cases, and for serializer validation errors.
+
+Note that the translations only apply to the error strings themselves. The format of error messages, and the keys of field names will remain the same. An example `400 Bad Request` response body might look like this:
+
+ {"detail": {"username": ["Esse campo deve ser único."]}}
+
+If you want to use different string for parts of the response such as `detail` and `non_field_errors` then you can modify this behavior by using a [custom exception handler][custom-exception-handler].
+
+#### Specifying the set of supported languages.
+
+By default all available languages will be supported.
+
+If you only wish to support a subset of the available languages, use Django's standard `LANGUAGES` setting:
+
+ LANGUAGES = [
+ ('de', _('German')),
+ ('en', _('English')),
+ ]
+
+## Adding new translations
+
+REST framework translations are managed online using [Transifex][transifex-project]. You can use the Transifex service to add new translation languages. The maintenance team will then ensure that these translation strings are included in the REST framework package.
+
+Sometimes you may need to add translation strings to your project locally. You may need to do this if:
+
+* You want to use REST Framework in a language which has not been translated yet on Transifex.
+* Your project includes custom error messages, which are not part of REST framework's default translation strings.
+
+#### Translating a new language locally
+
+This guide assumes you are already familiar with how to translate a Django app. If you're not, start by reading [Django's translation docs][django-translation].
+
+If you're translating a new language you'll need to translate the existing REST framework error messages:
+
+1. Make a new folder where you want to store the internationalization resources. Add this path to your [`LOCALE_PATHS`][django-locale-paths] setting.
+
+2. Now create a subfolder for the language you want to translate. The folder should be named using [locale name][django-locale-name] notation. For example: `de`, `pt_BR`, `es_AR`.
+
+3. Now copy the [base translations file][django-po-source] from the REST framework source code into your translations folder.
+
+4. Edit the `django.po` file you've just copied, translating all the error messages.
+
+5. Run `manage.py compilemessages -l pt_BR` to make the translations
+available for Django to use. You should see a message like `processing file django.po in <...>/locale/pt_BR/LC_MESSAGES`.
+
+6. Restart your development server to see the changes take effect.
+
+If you're only translating custom error messages that exist inside your project codebase you don't need to copy the REST framework source `django.po` file into a `LOCALE_PATHS` folder, and can instead simply run Django's standard `makemessages` process.
+
+## How the language is determined
+
+If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE` setting.
+
+You can find more information on how the language preference is determined in the [Django documentation][django-language-preference]. For reference, the method is:
+
+1. First, it looks for the language prefix in the requested URL.
+2. Failing that, it looks for the `LANGUAGE_SESSION_KEY` key in the current user’s session.
+3. Failing that, it looks for a cookie.
+4. Failing that, it looks at the `Accept-Language` HTTP header.
+5. Failing that, it uses the global `LANGUAGE_CODE` setting.
+
+For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes.
+
+[cite]: https://youtu.be/Wa0VfS2q94Y
+[django-translation]: https://docs.djangoproject.com/en/stable/topics/i18n/translation
+[custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling
+[transifex-project]: https://explore.transifex.com/django-rest-framework-1/django-rest-framework/
+[django-po-source]: https://raw.githubusercontent.com/encode/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po
+[django-language-preference]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#how-django-discovers-language-preference
+[django-locale-paths]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOCALE_PATHS
+[django-locale-name]: https://docs.djangoproject.com/en/stable/topics/i18n/#term-locale-name
diff --git a/docs/topics/project-management.md b/docs/topics/project-management.md
deleted file mode 100644
index f581cabd3b..0000000000
--- a/docs/topics/project-management.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Project management
-
-> "No one can whistle a symphony; it takes a whole orchestra to play it"
->
-> — Halford E. Luccock
-
-This document outlines our project management processes for REST framework.
-
-The aim is to ensure that the project has a high
-["bus factor"][bus-factor], and can continue to remain well supported for the foreseeable future. Suggestions for improvements to our process are welcome.
-
----
-
-## Maintenance team
-
-We have a quarterly maintenance cycle where new members may join the maintenance team. We currently cap the size of the team at 5 members, and may encourage folks to step out of the team for a cycle to allow new members to participate.
-
-#### Current team
-
-The [maintenance team for Q1 2015](https://github.com/tomchristie/django-rest-framework/issues/2190):
-
-* [@tomchristie](https://github.com/tomchristie/)
-* [@xordoquy](https://github.com/xordoquy/) (Release manager.)
-* [@carltongibson](https://github.com/carltongibson/)
-* [@kevin-brown](https://github.com/kevin-brown/)
-* [@jpadilla](https://github.com/jpadilla/)
-
-#### Maintenance cycles
-
-Each maintenance cycle is initiated by an issue being opened with the `Process` label.
-
-* To be considered for a maintainer role simply comment against the issue.
-* Existing members must explicitly opt-in to the next cycle by check-marking their name.
-* The final decision on the incoming team will be made by `@tomchristie`.
-
-Members of the maintenance team will be added as collaborators to the repository.
-
-The following template should be used for the description of the issue, and serves as the formal process for selecting the team.
-
- This issue is for determining the maintenance team for the *** period.
-
- Please see the [Project management](http://www.django-rest-framework.org/topics/project-management/) section of our documentation for more details.
-
- ---
-
- #### Renewing existing members.
-
- The following people are the current maintenance team. Please checkmark your name if you wish to continue to have write permission on the repository for the *** period.
-
- - [ ] @***
- - [ ] @***
- - [ ] @***
- - [ ] @***
- - [ ] @***
-
- ---
-
- #### New members.
-
- If you wish to be considered for this or a future date, please comment against this or subsequent issues.
-
-#### Responsibilities of team members
-
-Team members have the following responsibilities.
-
-* Add triage labels and milestones to tickets.
-* Close invalid or resolved tickets.
-* Merge finalized pull requests.
-* Build and deploy the documentation, using `mkdocs gh-deploy`.
-
-Further notes for maintainers:
-
-* Code changes should come in the form of a pull request - do not push directly to master.
-* Maintainers should typically not merge their own pull requests.
-* Each issue/pull request should have exactly one label once triaged.
-* Search for un-triaged issues with [is:open no:label][un-triaged].
-
-It should be noted that participating actively in the REST framework project clearly **does not require being part of the maintenance team**. Almost every import part of issue triage and project improvement can be actively worked on regardless of your collaborator status on the repository.
-
----
-
-## Release process
-
-The release manager is selected on every quarterly maintenance cycle.
-
-* The manager should be selected by `@tomchristie`.
-* The manager will then have the maintainer role added to PyPI package.
-* The previous manager will then have the maintainer role removed from the PyPI package.
-
-Our PyPI releases will be handled by either the current release manager, or by `@tomchristie`. Every release should have an open issue tagged with the `Release` label and marked against the appropriate milestone.
-
-The following template should be used for the description of the issue, and serves as a release checklist.
-
- Release manager is @***.
- Pull request is #***.
-
- Checklist:
-
- - [ ] Create pull request for [release notes](https://github.com/tomchristie/django-rest-framework/blob/master/docs/topics/release-notes.md) based on the [*.*.* milestone](https://github.com/tomchristie/django-rest-framework/milestones/***).
- - [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/__init__.py).
- - [ ] Confirm with @tomchristie that release is finalized and ready to go.
- - [ ] Ensure that release date is included in pull request.
- - [ ] Merge the release pull request.
- - [ ] Push the package to PyPI with `./setup.py publish`.
- - [ ] Tag the release, with `git tag -a *.*.* -m 'version *.*.*'; git push --tags`.
- - [ ] Deploy the documentation with `mkdocs gh-deploy`.
- - [ ] Make a release announcement on the [discussion group](https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework).
- - [ ] Make a release announcement on twitter.
- - [ ] Close the milestone on GitHub.
-
-When pushing the release to PyPI ensure that your environment has been installed from our development `requirement.txt`, so that documentation and PyPI installs are consistently being built against a pinned set of packages.
-
----
-
-## Project ownership
-
-The PyPI package is owned by `@tomchristie`. As a backup `@j4mie` also has ownership of the package.
-
-If `@tomchristie` ceases to participate in the project then `@j4mie` has responsibility for handing over ownership duties.
-
-#### Outstanding management & ownership issues
-
-The following issues still need to be addressed:
-
-* [Consider moving the repo into a proper GitHub organization][github-org].
-* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
-* Document ownership of the [live example][sandbox] API.
-* Document ownership of the [mailing list][mailing-list] and IRC channel.
-
-[bus-factor]: http://en.wikipedia.org/wiki/Bus_factor
-[un-triaged]: https://github.com/tomchristie/django-rest-framework/issues?q=is%3Aopen+no%3Alabel
-[github-org]: https://github.com/tomchristie/django-rest-framework/issues/2162
-[sandbox]: http://restframework.herokuapp.com/
-[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
deleted file mode 100644
index b9216e36f9..0000000000
--- a/docs/topics/release-notes.md
+++ /dev/null
@@ -1,731 +0,0 @@
-# Release Notes
-
-> Release Early, Release Often
->
-> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
-
-## Versioning
-
-Minor version numbers (0.0.x) are used for changes that are API compatible. You should be able to upgrade between minor point releases without any other code changes.
-
-Medium version numbers (0.x.0) may include API changes, in line with the [deprecation policy][deprecation-policy]. You should read the release notes carefully before upgrading between medium point releases.
-
-Major version numbers (x.0.0) are reserved for substantial project milestones.
-
-## Deprecation policy
-
-REST framework releases follow a formal deprecation policy, which is in line with [Django's deprecation policy][django-deprecation-policy].
-
-The timeline for deprecation of a feature present in version 1.0 would work as follows:
-
-* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `PendingDeprecationWarning` warnings if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
-
-* Version 1.2 would escalate these warnings to `DeprecationWarning`, which is loud by default.
-
-* Version 1.3 would remove the deprecated bits of API entirely.
-
-Note that in line with Django's policy, any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.
-
-## Upgrading
-
-To upgrade Django REST framework to the latest version, use pip:
-
- pip install -U djangorestframework
-
-You can determine your currently installed version using `pip freeze`:
-
- pip freeze | grep djangorestframework
-
----
-
-## 3.0.x series
-
-### 3.0.2
-
-**Date**: [17th December 2014][3.0.2-milestone].
-
-* Ensure `request.user` is made available to response middleware. ([#2155][gh2155])
-* `Client.logout()` also cancels any existing `force_authenticate`. ([#2218][gh2218], [#2259][gh2259])
-* Extra assertions and better checks to preventing incorrect serializer API use. ([#2228][gh2228], [#2234][gh2234], [#2262][gh2262], [#2263][gh2263], [#2266][gh2266], [#2267][gh2267], [#2289][gh2289], [#2291][gh2291])
-* Fixed `min_length` message for `CharField`. ([#2255][gh2255])
-* Fix `UnicodeDecodeError`, which can occur on serializer `repr`. ([#2270][gh2270], [#2279][gh2279])
-* Fix empty HTML values when a default is provided. ([#2280][gh2280], [#2294][gh2294])
-* Fix `SlugRelatedField` raising `UnicodeEncodeError` when used as a multiple choice input. ([#2290][gh2290])
-
-### 3.0.1
-
-**Date**: [11th December 2014][3.0.1-milestone].
-
-* More helpful error message when the default Serializer `create()` fails. ([#2013][gh2013])
-* Raise error when attempting to save serializer if data is not valid. ([#2098][gh2098])
-* Fix `FileUploadParser` breaks with empty file names and multiple upload handlers. ([#2109][gh2109])
-* Improve `BindingDict` to support standard dict-functions. ([#2135][gh2135], [#2163][gh2163])
-* Add `validate()` to `ListSerializer`. ([#2168][gh2168], [#2225][gh2225], [#2232][gh2232])
-* Fix JSONP renderer failing to escape some characters. ([#2169][gh2169], [#2195][gh2195])
-* Add missing default style for `FileField`. ([#2172][gh2172])
-* Actions are required when calling `ViewSet.as_view()`. ([#2175][gh2175])
-* Add `allow_blank` to `ChoiceField`. ([#2184][gh2184], [#2239][gh2239])
-* Cosmetic fixes in the HTML renderer. ([#2187][gh2187])
-* Raise error if `fields` on serializer is not a list of strings. ([#2193][gh2193], [#2213][gh2213])
-* Improve checks for nested creates and updates. ([#2194][gh2194], [#2196][gh2196])
-* `validated_attrs` argument renamed to `validated_data` in `Serializer` `create()`/`update()`. ([#2197][gh2197])
-* Remove deprecated code to reflect the dropped Django versions. ([#2200][gh2200])
-* Better serializer errors for nested writes. ([#2202][gh2202], [#2215][gh2215])
-* Fix pagination and custom permissions incompatibility. ([#2205][gh2205])
-* Raise error if `fields` on serializer is not a list of strings. ([#2213][gh2213])
-* Add missing translation markers for relational fields. ([#2231][gh2231])
-* Improve field lookup behavior for dicts/mappings. ([#2244][gh2244], [#2243][gh2243])
-* Optimized hyperlinked PK. ([#2242][gh2242])
-
-### 3.0.0
-
-**Date**: 1st December 2014
-
-For full details see the [3.0 release announcement](3.0-announcement.md).
-
----
-
-## 2.4.x series
-
-### 2.4.4
-
-**Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+).
-
-* **Security fix**: Escape URLs when replacing `format=` query parameter, as used in dropdown on `GET` button in browsable API to allow explicit selection of JSON vs HTML output.
-* Maintain ordering of URLs in API root view for `DefaultRouter`.
-* Fix `follow=True` in `APIRequestFactory`
-* Resolve issue with invalid `read_only=True`, `required=True` fields being automatically generated by `ModelSerializer` in some cases.
-* Resolve issue with `OPTIONS` requests returning incorrect information for views using `get_serializer_class` to dynamically determine serializer based on request method.
-
-### 2.4.3
-
-**Date**: [19th September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+).
-
-* Support translatable view docstrings being displayed in the browsable API.
-* Support [encoded `filename*`][rfc-6266] in raw file uploads with `FileUploadParser`.
-* Allow routers to support viewsets that don't include any list routes or that don't include any detail routes.
-* Don't render an empty login control in browsable API if `login` view is not included.
-* CSRF exemption performed in `.as_view()` to prevent accidental omission if overriding `.dispatch()`.
-* Login on browsable API now displays validation errors.
-* Bugfix: Fix migration in `authtoken` application.
-* Bugfix: Allow selection of integer keys in nested choices.
-* Bugfix: Return `None` instead of `'None'` in `CharField` with `allow_none=True`.
-* Bugfix: Ensure custom model fields map to equivelent serializer fields more reliably.
-* Bugfix: `DjangoFilterBackend` no longer quietly changes queryset ordering.
-
-### 2.4.2
-
-**Date**: [3rd September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.2+Release%22+).
-
-* Bugfix: Fix broken pagination for 2.4.x series.
-
-### 2.4.1
-
-**Date**: [1st September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.1+Release%22+).
-
-* Bugfix: Fix broken login template for browsable API.
-
-### 2.4.0
-
-**Date**: [29th August 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.0+Release%22+).
-
-**Django version requirements**: The lowest supported version of Django is now 1.4.2.
-
-**South version requirements**: This note applies to any users using the optional `authtoken` application, which includes an associated database migration. You must now *either* upgrade your `south` package to version 1.0, *or* instead use the built-in migration support available with Django 1.7.
-
-* Added compatibility with Django 1.7's database migration support.
-* New test runner, using `py.test`.
-* Deprecated `.model` view attribute in favor of explicit `.queryset` and `.serializer_class` attributes. The `DEFAULT_MODEL_SERIALIZER_CLASS` setting is also deprecated.
-* `@detail_route` and `@list_route` decorators replace `@action` and `@link`.
-* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
-* Added `NUM_PROXIES` setting for smarter client IP identification.
-* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
-* Added `Retry-After` header to throttled responses, as per [RFC 6585](http://tools.ietf.org/html/rfc6585). This should now be used in preference to the custom `X-Trottle-Wait-Seconds` header which will be fully deprecated in 3.0.
-* Added `cache` attribute to throttles to allow overriding of default cache.
-* Added `lookup_value_regex` attribute to routers, to allow the URL argument matching to be constrainted by the user.
-* Added `allow_none` option to `CharField`.
-* Support Django's standard `status_code` class attribute on responses.
-* More intuitive behavior on the test client, as `client.logout()` now also removes any credentials that have been set.
-* Bugfix: `?page_size=0` query parameter now falls back to default page size for view, instead of always turning pagination off.
-* Bugfix: Always uppercase `X-Http-Method-Override` methods.
-* Bugfix: Copy `filter_backends` list before returning it, in order to prevent view code from mutating the class attribute itself.
-* Bugfix: Set the `.action` attribute on viewsets when introspected by `OPTIONS` for testing permissions on the view.
-* Bugfix: Ensure `ValueError` raised during deserialization results in a error list rather than a single error. This is now consistent with other validation errors.
-* Bugfix: Fix `cache_format` typo on throttle classes, was `"throtte_%(scope)s_%(ident)s"`. Note that this will invalidate existing throttle caches.
-
----
-
-## 2.3.x series
-
-### 2.3.14
-
-**Date**: 12th June 2014
-
-* **Security fix**: Escape request path when it is include as part of the login and logout links in the browsable API.
-* `help_text` and `verbose_name` automatically set for related fields on `ModelSerializer`.
-* Fix nested serializers linked through a backward foreign key relation.
-* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer`.
-* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode.
-* Fix `parse_header` argument convertion.
-* Fix mediatype detection under Python 3.
-* Web browsable API now offers blank option on dropdown when the field is not required.
-* `APIException` representation improved for logging purposes.
-* Allow source="*" within nested serializers.
-* Better support for custom oauth2 provider backends.
-* Fix field validation if it's optional and has no value.
-* Add `SEARCH_PARAM` and `ORDERING_PARAM`.
-* Fix `APIRequestFactory` to support arguments within the url string for GET.
-* Allow three transport modes for access tokens when accessing a protected resource.
-* Fix `QueryDict` encoding on request objects.
-* Ensure throttle keys do not contain spaces, as those are invalid if using `memcached`.
-* Support `blank_display_value` on `ChoiceField`.
-
-### 2.3.13
-
-**Date**: 6th March 2014
-
-* Django 1.7 Support.
-* Fix `default` argument when used with serializer relation fields.
-* Display the media type of the content that is being displayed in the browsable API, rather than 'text/html'.
-* Bugfix for `urlize` template failure when URL regex is matched, but value does not `urlparse`.
-* Use `urandom` for token generation.
-* Only use `Vary: Accept` when more than one renderer exists.
-
-### 2.3.12
-
-**Date**: 15th January 2014
-
-* **Security fix**: `OrderingField` now only allows ordering on readable serializer fields, or on fields explicitly specified using `ordering_fields`. This prevents users being able to order by fields that are not visible in the API, and exploiting the ordering of sensitive data such as password hashes.
-* Bugfix: `write_only = True` fields now display in the browsable API.
-
-### 2.3.11
-
-**Date**: 14th January 2014
-
-* Added `write_only` serializer field argument.
-* Added `write_only_fields` option to `ModelSerializer` classes.
-* JSON renderer now deals with objects that implement a dict-like interface.
-* Fix compatiblity with newer versions of `django-oauth-plus`.
-* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unnecessary queryset re-evaluations.
-* Bugfix: Allow defaults on BooleanFields to be properly honored when values are not supplied.
-* Bugfix: Prevent double-escaping of non-latin1 URL query params when appending `format=json` params.
-
-### 2.3.10
-
-**Date**: 6th December 2013
-
-* Add in choices information for ChoiceFields in response to `OPTIONS` requests.
-* Added `pre_delete()` and `post_delete()` method hooks.
-* Added status code category helper functions.
-* Bugfix: Partial updates which erronously set a related field to `None` now correctly fail validation instead of raising an exception.
-* Bugfix: Responses without any content no longer include an HTTP `'Content-Type'` header.
-* Bugfix: Correctly handle validation errors in PUT-as-create case, responding with 400.
-
-### 2.3.9
-
-**Date**: 15th November 2013
-
-* Fix Django 1.6 exception API compatibility issue caused by `ValidationError`.
-* Include errors in HTML forms in browsable API.
-* Added JSON renderer support for numpy scalars.
-* Added `transform_` hooks on serializers for easily modifying field output.
-* Added `get_context` hook in `BrowsableAPIRenderer`.
-* Allow serializers to be passed `files` but no `data`.
-* `HTMLFormRenderer` now renders serializers directly to HTML without needing to create an intermediate form object.
-* Added `get_filter_backends` hook.
-* Added queryset aggregates to allowed fields in `OrderingFilter`.
-* Bugfix: Fix decimal suppoprt with `YAMLRenderer`.
-* Bugfix: Fix submission of unicode in browsable API through raw data form.
-
-### 2.3.8
-
-**Date**: 11th September 2013
-
-* Added `DjangoObjectPermissions`, and `DjangoObjectPermissionsFilter`.
-* Support customizable exception handling, using the `EXCEPTION_HANDLER` setting.
-* Support customizable view name and description functions, using the `VIEW_NAME_FUNCTION` and `VIEW_DESCRIPTION_FUNCTION` settings.
-* Added `MAX_PAGINATE_BY` setting and `max_paginate_by` generic view attribute.
-* Added `cache` attribute to throttles to allow overriding of default cache.
-* 'Raw data' tab in browsable API now contains pre-populated data.
-* 'Raw data' and 'HTML form' tab preference in browsable API now saved between page views.
-* Bugfix: `required=True` argument fixed for boolean serializer fields.
-* Bugfix: `client.force_authenticate(None)` should also clear session info if it exists.
-* Bugfix: Client sending empty string instead of file now clears `FileField`.
-* Bugfix: Empty values on ChoiceFields with `required=False` now consistently return `None`.
-* Bugfix: Clients setting `page_size=0` now simply returns the default page size, instead of disabling pagination. [*]
-
----
-
-[*] Note that the change in `page_size=0` behaviour fixes what is considered to be a bug in how clients can effect the pagination size. However if you were relying on this behavior you will need to add the following mixin to your list views in order to preserve the existing behavior.
-
- class DisablePaginationMixin(object):
- def get_paginate_by(self, queryset=None):
- if self.request.QUERY_PARAMS[self.paginate_by_param] == '0':
- return None
- return super(DisablePaginationMixin, self).get_paginate_by(queryset)
-
----
-
-### 2.3.7
-
-**Date**: 16th August 2013
-
-* Added `APITestClient`, `APIRequestFactory` and `APITestCase` etc...
-* Refactor `SessionAuthentication` to allow esier override for CSRF exemption.
-* Remove 'Hold down "Control" message from help_text' widget messaging when not appropriate.
-* Added admin configuration for auth tokens.
-* Bugfix: `AnonRateThrottle` fixed to not throttle authenticated users.
-* Bugfix: Don't set `X-Throttle-Wait-Seconds` when throttle does not have `wait` value.
-* Bugfix: Fixed `PATCH` button title in browsable API.
-* Bugfix: Fix issue with OAuth2 provider naive datetimes.
-
-### 2.3.6
-
-**Date**: 27th June 2013
-
-* Added `trailing_slash` option to routers.
-* Include support for `HttpStreamingResponse`.
-* Support wider range of default serializer validation when used with custom model fields.
-* UTF-8 Support for browsable API descriptions.
-* OAuth2 provider uses timezone aware datetimes when supported.
-* Bugfix: Return error correctly when OAuth non-existent consumer occurs.
-* Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg.
-* Bugfix: Fix `ScopedRateThrottle`.
-
-### 2.3.5
-
-**Date**: 3rd June 2013
-
-* Added `get_url` hook to `HyperlinkedIdentityField`.
-* Serializer field `default` argument may be a callable.
-* `@action` decorator now accepts a `methods` argument.
-* Bugfix: `request.user` should be still be accessible in renderer context if authentication fails.
-* Bugfix: The `lookup_field` option on `HyperlinkedIdentityField` should apply by default to the url field on the serializer.
-* Bugfix: `HyperlinkedIdentityField` should continue to support `pk_url_kwarg`, `slug_url_kwarg`, `slug_field`, in a pending deprecation state.
-* Bugfix: Ensure we always return 404 instead of 500 if a lookup field cannot be converted to the correct lookup type. (Eg non-numeric `AutoInteger` pk lookup)
-
-### 2.3.4
-
-**Date**: 24th May 2013
-
-* Serializer fields now support `label` and `help_text`.
-* Added `UnicodeJSONRenderer`.
-* `OPTIONS` requests now return metadata about fields for `POST` and `PUT` requests.
-* Bugfix: `charset` now properly included in `Content-Type` of responses.
-* Bugfix: Blank choice now added in browsable API on nullable relationships.
-* Bugfix: Many to many relationships with `through` tables are now read-only.
-* Bugfix: Serializer fields now respect model field args such as `max_length`.
-* Bugfix: SlugField now performs slug validation.
-* Bugfix: Lazy-translatable strings now properly serialized.
-* Bugfix: Browsable API now supports bootswatch styles properly.
-* Bugfix: HyperlinkedIdentityField now uses `lookup_field` kwarg.
-
-**Note**: Responses now correctly include an appropriate charset on the `Content-Type` header. For example: `application/json; charset=utf-8`. If you have tests that check the content type of responses, you may need to update these accordingly.
-
-### 2.3.3
-
-**Date**: 16th May 2013
-
-* Added SearchFilter
-* Added OrderingFilter
-* Added GenericViewSet
-* Bugfix: Multiple `@action` and `@link` methods now allowed on viewsets.
-* Bugfix: Fix API Root view issue with DjangoModelPermissions
-
-### 2.3.2
-
-**Date**: 8th May 2013
-
-* Bugfix: Fix `TIME_FORMAT`, `DATETIME_FORMAT` and `DATE_FORMAT` settings.
-* Bugfix: Fix `DjangoFilterBackend` issue, failing when used on view with queryset attribute.
-
-### 2.3.1
-
-**Date**: 7th May 2013
-
-* Bugfix: Fix breadcrumb rendering issue.
-
-### 2.3.0
-
-**Date**: 7th May 2013
-
-* ViewSets and Routers.
-* ModelSerializers support reverse relations in 'fields' option.
-* HyperLinkedModelSerializers support 'id' field in 'fields' option.
-* Cleaner generic views.
-* Support for multiple filter classes.
-* FileUploadParser support for raw file uploads.
-* DecimalField support.
-* Made Login template easier to restyle.
-* Bugfix: Fix issue with depth>1 on ModelSerializer.
-
-**Note**: See the [2.3 announcement][2.3-announcement] for full details.
-
----
-
-## 2.2.x series
-
-### 2.2.7
-
-**Date**: 17th April 2013
-
-* Loud failure when view does not return a `Response` or `HttpResponse`.
-* Bugfix: Fix for Django 1.3 compatibility.
-* Bugfix: Allow overridden `get_object()` to work correctly.
-
-### 2.2.6
-
-**Date**: 4th April 2013
-
-* OAuth2 authentication no longer requires unnecessary URL parameters in addition to the token.
-* URL hyperlinking in browsable API now handles more cases correctly.
-* Long HTTP headers in browsable API are broken in multiple lines when possible.
-* Bugfix: Fix regression with DjangoFilterBackend not worthing correctly with single object views.
-* Bugfix: OAuth should fail hard when invalid token used.
-* Bugfix: Fix serializer potentially returning `None` object for models that define `__bool__` or `__len__`.
-
-### 2.2.5
-
-**Date**: 26th March 2013
-
-* Serializer support for bulk create and bulk update operations.
-* Regression fix: Date and time fields return date/time objects by default. Fixes regressions caused by 2.2.2. See [#743][743] for more details.
-* Bugfix: Fix 500 error is OAuth not attempted with OAuthAuthentication class installed.
-* `Serializer.save()` now supports arbitrary keyword args which are passed through to the object `.save()` method. Mixins use `force_insert` and `force_update` where appropriate, resulting in one less database query.
-
-### 2.2.4
-
-**Date**: 13th March 2013
-
-* OAuth 2 support.
-* OAuth 1.0a support.
-* Support X-HTTP-Method-Override header.
-* Filtering backends are now applied to the querysets for object lookups as well as lists. (Eg you can use a filtering backend to control which objects should 404)
-* Deal with error data nicely when deserializing lists of objects.
-* Extra override hook to configure `DjangoModelPermissions` for unauthenticated users.
-* Bugfix: Fix regression which caused extra database query on paginated list views.
-* Bugfix: Fix pk relationship bug for some types of 1-to-1 relations.
-* Bugfix: Workaround for Django bug causing case where `Authtoken` could be registered for cascade delete from `User` even if not installed.
-
-### 2.2.3
-
-**Date**: 7th March 2013
-
-* Bugfix: Fix None values for for `DateField`, `DateTimeField` and `TimeField`.
-
-### 2.2.2
-
-**Date**: 6th March 2013
-
-* Support for custom input and output formats for `DateField`, `DateTimeField` and `TimeField`.
-* Cleanup: Request authentication is no longer lazily evaluated, instead authentication is always run, which results in more consistent, obvious behavior. Eg. Supplying bad auth credentials will now always return an error response, even if no permissions are set on the view.
-* Bugfix for serializer data being uncacheable with pickle protocol 0.
-* Bugfixes for model field validation edge-cases.
-* Bugfix for authtoken migration while using a custom user model and south.
-
-### 2.2.1
-
-**Date**: 22nd Feb 2013
-
-* Security fix: Use `defusedxml` package to address XML parsing vulnerabilities.
-* Raw data tab added to browsable API. (Eg. Allow for JSON input.)
-* Added TimeField.
-* Serializer fields can be mapped to any method that takes no args, or only takes kwargs which have defaults.
-* Unicode support for view names/descriptions in browsable API.
-* Bugfix: request.DATA should return an empty `QueryDict` with no data, not `None`.
-* Bugfix: Remove unneeded field validation, which caused extra queries.
-
-**Security note**: Following the [disclosure of security vulnerabilities][defusedxml-announce] in Python's XML parsing libraries, use of the `XMLParser` class now requires the `defusedxml` package to be installed.
-
-The security vulnerabilities only affect APIs which use the `XMLParser` class, by enabling it in any views, or by having it set in the `DEFAULT_PARSER_CLASSES` setting. Note that the `XMLParser` class is not enabled by default, so this change should affect a minority of users.
-
-### 2.2.0
-
-**Date**: 13th Feb 2013
-
-* Python 3 support.
-* Added a `post_save()` hook to the generic views.
-* Allow serializers to handle dicts as well as objects.
-* Deprecate `ManyRelatedField()` syntax in favor of `RelatedField(many=True)`
-* Deprecate `null=True` on relations in favor of `required=False`.
-* Deprecate `blank=True` on CharFields, just use `required=False`.
-* Deprecate optional `obj` argument in permissions checks in favor of `has_object_permission`.
-* Deprecate implicit hyperlinked relations behavior.
-* Bugfix: Fix broken DjangoModelPermissions.
-* Bugfix: Allow serializer output to be cached.
-* Bugfix: Fix styling on browsable API login.
-* Bugfix: Fix issue with deserializing empty to-many relations.
-* Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom `.restore_object()` method.
-
-**Note**: See the [2.2 announcement][2.2-announcement] for full details.
-
----
-
-## 2.1.x series
-
-### 2.1.17
-
-**Date**: 26th Jan 2013
-
-* Support proper 401 Unauthorized responses where appropriate, instead of always using 403 Forbidden.
-* Support json encoding of timedelta objects.
-* `format_suffix_patterns()` now supports `include` style URL patterns.
-* Bugfix: Fix issues with custom pagination serializers.
-* Bugfix: Nested serializers now accept `source='*'` argument.
-* Bugfix: Return proper validation errors when incorrect types supplied for relational fields.
-* Bugfix: Support nullable FKs with `SlugRelatedField`.
-* Bugfix: Don't call custom validation methods if the field has an error.
-
-**Note**: If the primary authentication class is `TokenAuthentication` or `BasicAuthentication`, a view will now correctly return 401 responses to unauthenticated access, with an appropriate `WWW-Authenticate` header, instead of 403 responses.
-
-### 2.1.16
-
-**Date**: 14th Jan 2013
-
-* Deprecate `django.utils.simplejson` in favor of Python 2.6's built-in json module.
-* Bugfix: `auto_now`, `auto_now_add` and other `editable=False` fields now default to read-only.
-* Bugfix: PK fields now only default to read-only if they are an AutoField or if `editable=False`.
-* Bugfix: Validation errors instead of exceptions when serializers receive incorrect types.
-* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
-* Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one
-
-**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [ticket 582](ticket-582) for more details.
-
-### 2.1.15
-
-**Date**: 3rd Jan 2013
-
-* Added `PATCH` support.
-* Added `RetrieveUpdateAPIView`.
-* Remove unused internal `save_m2m` flag on `ModelSerializer.save()`.
-* Tweak behavior of hyperlinked fields with an explicit format suffix.
-* Relation changes are now persisted in `.save()` instead of in `.restore_object()`.
-* Bugfix: Fix issue with FileField raising exception instead of validation error when files=None.
-* Bugfix: Partial updates should not set default values if field is not included.
-
-### 2.1.14
-
-**Date**: 31st Dec 2012
-
-* Bugfix: ModelSerializers now include reverse FK fields on creation.
-* Bugfix: Model fields with `blank=True` are now `required=False` by default.
-* Bugfix: Nested serializers now support nullable relationships.
-
-**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to separate them from regular data type fields, such as `CharField` and `IntegerField`.
-
-This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and referring to fields using the style `serializers.PrimaryKeyRelatedField`.
-
-
-### 2.1.13
-
-**Date**: 28th Dec 2012
-
-* Support configurable `STATICFILES_STORAGE` storage.
-* Bugfix: Related fields now respect the required flag, and may be required=False.
-
-### 2.1.12
-
-**Date**: 21st Dec 2012
-
-* Bugfix: Fix bug that could occur using ChoiceField.
-* Bugfix: Fix exception in browsable API on DELETE.
-* Bugfix: Fix issue where pk was was being set to a string if set by URL kwarg.
-
-### 2.1.11
-
-**Date**: 17th Dec 2012
-
-* Bugfix: Fix issue with M2M fields in browsable API.
-
-### 2.1.10
-
-**Date**: 17th Dec 2012
-
-* Bugfix: Ensure read-only fields don't have model validation applied.
-* Bugfix: Fix hyperlinked fields in paginated results.
-
-### 2.1.9
-
-**Date**: 11th Dec 2012
-
-* Bugfix: Fix broken nested serialization.
-* Bugfix: Fix `Meta.fields` only working as tuple not as list.
-* Bugfix: Edge case if unnecessarily specifying `required=False` on read only field.
-
-### 2.1.8
-
-**Date**: 8th Dec 2012
-
-* Fix for creating nullable Foreign Keys with `''` as well as `None`.
-* Added `null=` related field option.
-
-### 2.1.7
-
-**Date**: 7th Dec 2012
-
-* Serializers now properly support nullable Foreign Keys.
-* Serializer validation now includes model field validation, such as uniqueness constraints.
-* Support 'true' and 'false' string values for BooleanField.
-* Added pickle support for serialized data.
-* Support `source='dotted.notation'` style for nested serializers.
-* Make `Request.user` settable.
-* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`.
-
-### 2.1.6
-
-**Date**: 23rd Nov 2012
-
-* Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
-
-### 2.1.5
-
-**Date**: 23rd Nov 2012
-
-* Bugfix: Fix DjangoModelPermissions.
-
-### 2.1.4
-
-**Date**: 22nd Nov 2012
-
-* Support for partial updates with serializers.
-* Added `RegexField`.
-* Added `SerializerMethodField`.
-* Serializer performance improvements.
-* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
-* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
-
-### 2.1.3
-
-**Date**: 16th Nov 2012
-
-* Added `FileField` and `ImageField`. For use with `MultiPartParser`.
-* Added `URLField` and `SlugField`.
-* Support for `read_only_fields` on `ModelSerializer` classes.
-* Support for clients overriding the pagination page sizes. Use the `PAGINATE_BY_PARAM` setting or set the `paginate_by_param` attribute on a generic view.
-* 201 Responses now return a 'Location' header.
-* Bugfix: Serializer fields now respect `max_length`.
-
-### 2.1.2
-
-**Date**: 9th Nov 2012
-
-* **Filtering support.**
-* Bugfix: Support creation of objects with reverse M2M relations.
-
-### 2.1.1
-
-**Date**: 7th Nov 2012
-
-* Support use of HTML exception templates. Eg. `403.html`
-* Hyperlinked fields take optional `slug_field`, `slug_url_kwarg` and `pk_url_kwarg` arguments.
-* Bugfix: Deal with optional trailing slashes properly when generating breadcrumbs.
-* Bugfix: Make textareas same width as other fields in browsable API.
-* Private API change: `.get_serializer` now uses same `instance` and `data` ordering as serializer initialization.
-
-### 2.1.0
-
-**Date**: 5th Nov 2012
-
-* **Serializer `instance` and `data` keyword args have their position swapped.**
-* `queryset` argument is now optional on writable model fields.
-* Hyperlinked related fields optionally take `slug_field` and `slug_url_kwarg` arguments.
-* Support Django's cache framework.
-* Minor field improvements. (Don't stringify dicts, more robust many-pk fields.)
-* Bugfix: Support choice field in Browsable API.
-* Bugfix: Related fields with `read_only=True` do not require a `queryset` argument.
-
-**API-incompatible changes**: Please read [this thread][2.1.0-notes] regarding the `instance` and `data` keyword args before updating to 2.1.0.
-
----
-
-## 2.0.x series
-
-### 2.0.2
-
-**Date**: 2nd Nov 2012
-
-* Fix issues with pk related fields in the browsable API.
-
-### 2.0.1
-
-**Date**: 1st Nov 2012
-
-* Add support for relational fields in the browsable API.
-* Added SlugRelatedField and ManySlugRelatedField.
-* If PUT creates an instance return '201 Created', instead of '200 OK'.
-
-### 2.0.0
-
-**Date**: 30th Oct 2012
-
-* **Fix all of the things.** (Well, almost.)
-* For more information please see the [2.0 announcement][announcement].
-
-For older release notes, [please see the GitHub repo](old-release-notes).
-
-[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
-[defusedxml-announce]: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
-[2.2-announcement]: 2.2-announcement.md
-[2.3-announcement]: 2.3-announcement.md
-[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
-[staticfiles13]: https://docs.djangoproject.com/en/1.3/howto/static-files/#with-a-template-tag
-[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
-[announcement]: rest-framework-2-announcement.md
-[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582
-[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3
-[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series
-
-[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
-[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
-
-
-[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
-[gh2098]: https://github.com/tomchristie/django-rest-framework/issues/2098
-[gh2109]: https://github.com/tomchristie/django-rest-framework/issues/2109
-[gh2135]: https://github.com/tomchristie/django-rest-framework/issues/2135
-[gh2163]: https://github.com/tomchristie/django-rest-framework/issues/2163
-[gh2168]: https://github.com/tomchristie/django-rest-framework/issues/2168
-[gh2169]: https://github.com/tomchristie/django-rest-framework/issues/2169
-[gh2172]: https://github.com/tomchristie/django-rest-framework/issues/2172
-[gh2175]: https://github.com/tomchristie/django-rest-framework/issues/2175
-[gh2184]: https://github.com/tomchristie/django-rest-framework/issues/2184
-[gh2187]: https://github.com/tomchristie/django-rest-framework/issues/2187
-[gh2193]: https://github.com/tomchristie/django-rest-framework/issues/2193
-[gh2194]: https://github.com/tomchristie/django-rest-framework/issues/2194
-[gh2195]: https://github.com/tomchristie/django-rest-framework/issues/2195
-[gh2196]: https://github.com/tomchristie/django-rest-framework/issues/2196
-[gh2197]: https://github.com/tomchristie/django-rest-framework/issues/2197
-[gh2200]: https://github.com/tomchristie/django-rest-framework/issues/2200
-[gh2202]: https://github.com/tomchristie/django-rest-framework/issues/2202
-[gh2205]: https://github.com/tomchristie/django-rest-framework/issues/2205
-[gh2213]: https://github.com/tomchristie/django-rest-framework/issues/2213
-[gh2213]: https://github.com/tomchristie/django-rest-framework/issues/2213
-[gh2215]: https://github.com/tomchristie/django-rest-framework/issues/2215
-[gh2225]: https://github.com/tomchristie/django-rest-framework/issues/2225
-[gh2231]: https://github.com/tomchristie/django-rest-framework/issues/2231
-[gh2232]: https://github.com/tomchristie/django-rest-framework/issues/2232
-[gh2239]: https://github.com/tomchristie/django-rest-framework/issues/2239
-[gh2242]: https://github.com/tomchristie/django-rest-framework/issues/2242
-[gh2243]: https://github.com/tomchristie/django-rest-framework/issues/2243
-[gh2244]: https://github.com/tomchristie/django-rest-framework/issues/2244
-
-[gh2155]: https://github.com/tomchristie/django-rest-framework/issues/2155
-[gh2218]: https://github.com/tomchristie/django-rest-framework/issues/2218
-[gh2228]: https://github.com/tomchristie/django-rest-framework/issues/2228
-[gh2234]: https://github.com/tomchristie/django-rest-framework/issues/2234
-[gh2255]: https://github.com/tomchristie/django-rest-framework/issues/2255
-[gh2259]: https://github.com/tomchristie/django-rest-framework/issues/2259
-[gh2262]: https://github.com/tomchristie/django-rest-framework/issues/2262
-[gh2263]: https://github.com/tomchristie/django-rest-framework/issues/2263
-[gh2266]: https://github.com/tomchristie/django-rest-framework/issues/2266
-[gh2267]: https://github.com/tomchristie/django-rest-framework/issues/2267
-[gh2270]: https://github.com/tomchristie/django-rest-framework/issues/2270
-[gh2279]: https://github.com/tomchristie/django-rest-framework/issues/2279
-[gh2280]: https://github.com/tomchristie/django-rest-framework/issues/2280
-[gh2289]: https://github.com/tomchristie/django-rest-framework/issues/2289
-[gh2290]: https://github.com/tomchristie/django-rest-framework/issues/2290
-[gh2291]: https://github.com/tomchristie/django-rest-framework/issues/2291
-[gh2294]: https://github.com/tomchristie/django-rest-framework/issues/2294
diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md
deleted file mode 100644
index ed41bb4864..0000000000
--- a/docs/topics/rest-framework-2-announcement.md
+++ /dev/null
@@ -1,98 +0,0 @@
-# Django REST framework 2.0
-
-> Most people just make the mistake that it should be simple to design simple things. In reality, the effort required to design something is inversely proportional to the simplicity of the result.
->
-> — [Roy Fielding][cite]
-
----
-
-**Announcement:** REST framework 2 released - Tue 30th Oct 2012
-
----
-
-REST framework 2 is an almost complete reworking of the original framework, which comprehensively addresses some of the original design issues.
-
-Because the latest version should be considered a re-release, rather than an incremental improvement, we've skipped a version, and called this release Django REST framework 2.0.
-
-This article is intended to give you a flavor of what REST framework 2 is, and why you might want to give it a try.
-
-## User feedback
-
-Before we get cracking, let's start with the hard sell, with a few bits of feedback from some early adopters…
-
-"Django REST framework 2 is beautiful. Some of the API design is worthy of @kennethreitz." - [Kit La Touche][quote1]
-
-"Since it's pretty much just Django, controlling things like URLs has been a breeze... I think [REST framework 2] has definitely got the right approach here; even simple things like being able to override a function called post to do custom work during rather than having to intimately know what happens during a post make a huge difference to your productivity." - [Ian Strachan][quote2]
-
-"I switched to the 2.0 branch and I don't regret it - fully refactored my code in another ½ day and it's *much* more to my tastes" - [Bruno Desthuilliers][quote3]
-
-Sounds good, right? Let's get into some details...
-
-## Serialization
-
-REST framework 2 includes a totally re-worked serialization engine, that was initially intended as a replacement for Django's existing inflexible fixture serialization, and which meets the following design goals:
-
-* A declarative serialization API, that mirrors Django's `Forms`/`ModelForms` API.
-* Structural concerns are decoupled from encoding concerns.
-* Able to support rendering and parsing to many formats, including both machine-readable representations and HTML forms.
-* Validation that can be mapped to obvious and comprehensive error responses.
-* Serializers that support both nested, flat, and partially-nested representations.
-* Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations.
-
-Mapping between the internal state of the system and external representations of that state is the core concern of building Web APIs. Designing serializers that allow the developer to do so in a flexible and obvious way is a deceptively difficult design task, and with the new serialization API we think we've pretty much nailed it.
-
-## Generic views
-
-When REST framework was initially released at the start of 2011, the current Django release was version 1.2. REST framework included a backport of Django 1.3's upcoming `View` class, but it didn't take full advantage of the generic view implementations.
-
-With the new release the generic views in REST framework now tie in with Django's generic views. The end result is that framework is clean, lightweight and easy to use.
-
-## Requests, Responses & Views
-
-REST framework 2 includes `Request` and `Response` classes, than are used in place of Django's existing `HttpRequest` and `HttpResponse` classes. Doing so allows logic such as parsing the incoming request or rendering the outgoing response to be supported transparently by the framework.
-
-The `Request`/`Response` approach leads to a much cleaner API, less logic in the view itself, and a simple, obvious request-response cycle.
-
-REST framework 2 also allows you to work with both function-based and class-based views. For simple API views all you need is a single `@api_view` decorator, and you're good to go.
-
-
-## API Design
-
-Pretty much every aspect of REST framework has been reworked, with the aim of ironing out some of the design flaws of the previous versions. Each of the components of REST framework are cleanly decoupled, and can be used independently of each-other, and there are no monolithic resource classes, overcomplicated mixin combinations, or opinionated serialization or URL routing decisions.
-
-## The Browsable API
-
-Django REST framework's most unique feature is the way it is able to serve up both machine-readable representations, and a fully browsable HTML representation to the same endpoints.
-
-Browsable Web APIs are easier to work with, visualize and debug, and generally makes it easier and more frictionless to inspect and work with.
-
-With REST framework 2, the browsable API gets a snazzy new bootstrap-based theme that looks great and is even nicer to work with.
-
-There are also some functionality improvements - actions such as as `POST` and `DELETE` will only display if the user has the appropriate permissions.
-
-![Browsable API][image]
-
-**Image above**: An example of the browsable API in REST framework 2
-
-## Documentation
-
-As you can see the documentation for REST framework has been radically improved. It gets a completely new style, using markdown for the documentation source, and a bootstrap-based theme for the styling.
-
-We're really pleased with how the docs style looks - it's simple and clean, is easy to navigate around, and we think it reads great.
-
-## Summary
-
-In short, we've engineered the hell outta this thing, and we're incredibly proud of the result.
-
-If you're interested please take a browse around the documentation. [The tutorial][tut] is a great place to get started.
-
-There's also a [live sandbox version of the tutorial API][sandbox] available for testing.
-
-[cite]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-724
-[quote1]: https://twitter.com/kobutsu/status/261689665952833536
-[quote2]: https://groups.google.com/d/msg/django-rest-framework/heRGHzG6BWQ/ooVURgpwVC0J
-[quote3]: https://groups.google.com/d/msg/django-rest-framework/flsXbvYqRoY/9lSyntOf5cUJ
-[image]: ../img/quickstart.png
-[readthedocs]: https://readthedocs.org/
-[tut]: ../tutorial/1-serialization.md
-[sandbox]: http://restframework.herokuapp.com/
diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
index 7e6d240813..3498bddd1c 100644
--- a/docs/topics/rest-hypermedia-hateoas.md
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -4,7 +4,7 @@
>
> — Mike Amundsen, [REST fest 2012 keynote][cite].
-First off, the disclaimer. The name "Django REST framework" was decided back in early 2011 and was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".
+First off, the disclaimer. The name "Django REST framework" was decided back in early 2011 and was chosen simply to ensure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".
If you are serious about designing a Hypermedia API, you should look to resources outside of this documentation to help inform your design choices.
@@ -32,17 +32,16 @@ REST framework also includes [serialization] and [parser]/[renderer] components
## What REST framework doesn't provide.
-What REST framework doesn't do is give you is machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
+What REST framework doesn't do is give you machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
-[cite]: http://vimeo.com/channels/restfest/page:2
-[dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
-[hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
+[cite]: https://vimeo.com/channels/restfest/49503453
+[dissertation]: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
+[hypertext-driven]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[restful-web-apis]: http://restfulwebapis.org/
-[building-hypermedia-apis]: http://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578
+[building-hypermedia-apis]: https://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578
[designing-hypermedia-apis]: http://designinghypermediaapis.com/
-[restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over
[readinglist]: http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list
-[maturitymodel]: http://martinfowler.com/articles/richardsonMaturityModel.html
+[maturitymodel]: https://martinfowler.com/articles/richardsonMaturityModel.html
[hal]: http://stateless.co/hal_specification.html
[collection]: http://www.amundsen.com/media-types/collection/
diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md
deleted file mode 100644
index 6f4df28860..0000000000
--- a/docs/topics/third-party-resources.md
+++ /dev/null
@@ -1,326 +0,0 @@
-# Third Party Resources
-
-> Software ecosystems […] establish a community that further accelerates the sharing of knowledge, content, issues, expertise and skills.
->
-> — [Jan Bosch][cite].
-
-## About Third Party Packages
-
-Third Party Packages allow developers to share code that extends the functionality of Django REST framework, in order to support additional use-cases.
-
-We **support**, **encourage** and **strongly favor** the creation of Third Party Packages to encapsulate new behavior rather than adding additional functionality directly to Django REST Framework.
-
-We aim to make creating third party packages as easy as possible, whilst keeping a **simple** and **well maintained** core API. By promoting third party packages we ensure that the responsibility for a package remains with its author. If a package proves suitably popular it can always be considered for inclusion into the core REST framework.
-
-If you have an idea for a new feature please consider how it may be packaged as a Third Party Package. We're always happy to discuss ideas on the [Mailing List][discussion-group].
-
-## How to create a Third Party Package
-
-### Creating your package
-
-You can use [this cookiecutter template][cookiecutter] for creating reusable Django REST Framework packages quickly. Cookiecutter creates projects from project templates. While optional, this cookiecutter template includes best practices from Django REST framework and other packages, as well as a Travis CI configuration, Tox configuration, and a sane setup.py for easy PyPI registration/distribution.
-
-Note: Let us know if you have an alternate cookiecuter package so we can also link to it.
-
-#### Running the initial cookiecutter command
-
-To run the initial cookiecutter command, you'll first need to install the Python `cookiecutter` package.
-
- $ pip install cookiecutter
-
-Once `cookiecutter` is installed just run the following to create a new project.
-
- $ cookiecutter gh:jpadilla/cookiecutter-django-rest-framework
-
-You'll be prompted for some questions, answer them, then it'll create your Python package in the current working directory based on those values.
-
- full_name (default is "Your full name here")? Johnny Appleseed
- email (default is "you@example.com")? jappleseed@example.com
- github_username (default is "yourname")? jappleseed
- pypi_project_name (default is "dj-package")? djangorestframework-custom-auth
- repo_name (default is "dj-package")? django-rest-framework-custom-auth
- app_name (default is "djpackage")? custom_auth
- project_short_description (default is "Your project description goes here")?
- year (default is "2014")?
- version (default is "0.1.0")?
-
-#### Getting it onto GitHub
-
-To put your project up on GitHub, you'll need a repository for it to live in. You can create a new repository [here][new-repo]. If you need help, check out the [Create A Repo][create-a-repo] article on GitHub.
-
-
-#### Adding to Travis CI
-
-We recommend using [Travis CI][travis-ci], a hosted continuous integration service which integrates well with GitHub and is free for public repositories.
-
-To get started with Travis CI, [sign in][travis-ci] with your GitHub account. Once you're signed in, go to your [profile page][travis-profile] and enable the service hook for the repository you want.
-
-If you use the cookiecutter template, your project will already contain a `.travis.yml` file which Travis CI will use to build your project and run tests. By default, builds are triggered everytime you push to your repository or create Pull Request.
-
-#### Uploading to PyPI
-
-Once you've got at least a prototype working and tests running, you should publish it on PyPI to allow others to install it via `pip`.
-
-You must [register][pypi-register] an account before publishing to PyPI.
-
-To register your package on PyPI run the following command.
-
- $ python setup.py register
-
-If this is the first time publishing to PyPI, you'll be prompted to login.
-
-Note: Before publishing you'll need to make sure you have the latest pip that supports `wheel` as well as install the `wheel` package.
-
- $ pip install --upgrade pip
- $ pip install wheel
-
-After this, every time you want to release a new version on PyPI just run the following command.
-
- $ python setup.py publish
- You probably want to also tag the version now:
- git tag -a {0} -m 'version 0.1.0'
- git push --tags
-
-After releasing a new version to PyPI, it's always a good idea to tag the version and make available as a GitHub Release.
-
-We recommend to follow [Semantic Versioning][semver] for your package's versions.
-
-### Development
-
-#### Version requirements
-
-The cookiecutter template assumes a set of supported versions will be provided for Python and Django. Make sure you correctly update your requirements, docs, `tox.ini`, `.travis.yml`, and `setup.py` to match the set of versions you wish to support.
-
-#### Tests
-
-The cookiecutter template includes a `runtests.py` which uses the `pytest` package as a test runner.
-
-Before running, you'll need to install a couple test requirements.
-
- $ pip install -r requirements.txt
-
-Once requirements installed, you can run `runtests.py`.
-
- $ ./runtests.py
-
-Run using a more concise output style.
-
- $ ./runtests.py -q
-
-Run the tests using a more concise output style, no coverage, no flake8.
-
- $ ./runtests.py --fast
-
-Don't run the flake8 code linting.
-
- $ ./runtests.py --nolint
-
-Only run the flake8 code linting, don't run the tests.
-
- $ ./runtests.py --lintonly
-
-Run the tests for a given test case.
-
- $ ./runtests.py MyTestCase
-
-Run the tests for a given test method.
-
- $ ./runtests.py MyTestCase.test_this_method
-
-Shorter form to run the tests for a given test method.
-
- $ ./runtests.py test_this_method
-
-To run your tests against multiple versions of Python as different versions of requirements such as Django we recommend using `tox`. [Tox][tox-docs] is a generic virtualenv management and test command line tool.
-
-First, install `tox` globally.
-
- $ pip install tox
-
-To run `tox`, just simply run:
-
- $ tox
-
-To run a particular `tox` environment:
-
- $ tox -e envlist
-
-`envlist` is a comma-separated value to that specifies the environments to run tests against. To view a list of all possible test environments, run:
-
- $ tox -l
-
-#### Version compatibility
-
-Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into a `compat.py` module, and should provide a single common interface that the rest of the codebase can use.
-
-Check out Django REST framework's [compat.py][drf-compat] for an example.
-
-### Once your package is available
-
-Once your package is decently documented and available on PyPI, you might want share it with others that might find it useful.
-
-#### Adding to the Django REST framework grid
-
-We suggest adding your package to the [REST Framework][rest-framework-grid] grid on Django Packages.
-
-#### 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.
-
-#### Announce on the discussion group.
-
-You can also let others know about your package through the [discussion group][discussion-group].
-
-## Existing Third Party Packages
-
-Django REST Framework has a growing community of developers, packages, and resources.
-
-Check out a grid detailing all the packages and ecosystem around Django REST Framework at [Django Packages][rest-framework-grid].
-
-To submit new content, [open an issue][drf-create-issue] or [create a pull request][drf-create-pr].
-
-### Authentication
-
-* [djangorestframework-digestauth][djangorestframework-digestauth] - Provides Digest Access Authentication support.
-* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
-* [doac][doac] - Provides OAuth 2.0 support.
-* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
-* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
-* [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.
-
-### Permissions
-
-* [drf-any-permissions][drf-any-permissions] - Provides alternative permission handling.
-* [djangorestframework-composed-permissions][djangorestframework-composed-permissions] - Provides a simple way to define complex permissions.
-* [rest_condition][rest-condition] - Another extension for building complex permissions in a simple and convenient way.
-
-### Serializers
-
-* [django-rest-framework-mongoengine][django-rest-framework-mongoengine] - Serializer class that supports using MongoDB as the storage layer for Django REST framework.
-* [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.
-
-### Serializer fields
-
-* [drf-compound-fields][drf-compound-fields] - Provides "compound" serializer fields, such as lists of simple values.
-* [django-extra-fields][django-extra-fields] - Provides extra serializer fields.
-
-### Views
-
-* [djangorestframework-bulk][djangorestframework-bulk] - Implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.
-
-### Routers
-
-* [drf-nested-routers][drf-nested-routers] - Provides routers and relationship fields for working with nested resources.
-* [wq.db.rest][wq.db.rest] - Provides an admin-style model registration API with reasonable default URLs and viewsets.
-
-### Parsers
-
-* [djangorestframework-msgpack][djangorestframework-msgpack] - Provides MessagePack renderer and parser support.
-* [djangorestframework-camel-case][djangorestframework-camel-case] - Provides camel case JSON renderers and parsers.
-
-### Renderers
-
-* [djangorestframework-csv][djangorestframework-csv] - Provides CSV renderer support.
-* [drf_ujson][drf_ujson] - Implements JSON rendering using the UJSON package.
-* [rest-pandas][rest-pandas] - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats.
-
-### Filtering
-
-* [djangorestframework-chain][djangorestframework-chain] - Allows arbitrary chaining of both relations and lookup filters.
-
-### Misc
-
-* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
-* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI.
-* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
-* [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok
-* [drf-extensions][drf-extensions] - A collection of custom extensions
-* [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter
-
-## 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]
-
-### 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]
-* [Pyowa July 2013 - Django Rest Framework (Video)][pyowa-july-2013-django-rest-framework-video]
-* [django-rest-framework and angularjs (Video)][django-rest-framework-and-angularjs-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]
-
-[cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html
-[cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework
-[new-repo]: https://github.com/new
-[create-a-repo]: https://help.github.com/articles/create-a-repo/
-[travis-ci]: https://travis-ci.org
-[travis-profile]: https://travis-ci.org/profile
-[pypi-register]: https://pypi.python.org/pypi?%3Aaction=register_form
-[semver]: http://semver.org/
-[tox-docs]: https://tox.readthedocs.org/en/latest/
-[drf-compat]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/compat.py
-[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/
-[drf-create-pr]: https://github.com/tomchristie/django-rest-framework/compare
-[drf-create-issue]: https://github.com/tomchristie/django-rest-framework/issues/new
-[authentication]: ../api-guide/authentication.md
-[permissions]: ../api-guide/permissions.md
-[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
-[doac]: https://github.com/Rediker-Software/doac
-[djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt
-[hawkrest]: https://github.com/kumar303/hawkrest
-[djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature
-[djoser]: https://github.com/sunscrapers/djoser
-[drf-any-permissions]: https://github.com/kevin-brown/drf-any-permissions
-[djangorestframework-composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions
-[rest-condition]: https://github.com/caxap/rest_condition
-[django-rest-framework-mongoengine]: https://github.com/umutbozkurt/django-rest-framework-mongoengine
-[djangorestframework-gis]: https://github.com/djangonauts/django-rest-framework-gis
-[djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
-[drf-compound-fields]: https://github.com/estebistec/drf-compound-fields
-[django-extra-fields]: https://github.com/Hipo/drf-extra-fields
-[djangorestframework-bulk]: https://github.com/miki725/django-rest-framework-bulk
-[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
-[wq.db.rest]: http://wq.io/docs/about-rest
-[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack
-[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case
-[djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv
-[drf_ujson]: https://github.com/gizmag/drf-ujson-renderer
-[rest-pandas]: https://github.com/wq/django-rest-pandas
-[djangorestframework-chain]: https://github.com/philipn/django-rest-framework-chain
-[djangorestrelationalhyperlink]: https://github.com/fredkingham/django_rest_model_hyperlink_serializers_project
-[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
-[django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy
-[gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
-[drf-extensions]: https://github.com/chibisov/drf-extensions
-[ember-data-django-rest-adapter]: https://github.com/toranb/ember-data-django-rest-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://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework
-[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
-[pyowa-july-2013-django-rest-framework-video]: http://www.youtube.com/watch?v=e1zrehvxpbo
-[django-rest-framework-and-angularjs-video]: http://www.youtube.com/watch?v=q8frbgtj020
-[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/
diff --git a/docs/topics/writable-nested-serializers.md b/docs/topics/writable-nested-serializers.md
index ed614bd246..3bac84ffa9 100644
--- a/docs/topics/writable-nested-serializers.md
+++ b/docs/topics/writable-nested-serializers.md
@@ -12,27 +12,27 @@ Nested data structures are easy enough to work with if they're read-only - simpl
*Example of a **read-only** nested serializer. Nothing complex to worry about here.*
- class ToDoItemSerializer(serializers.ModelSerializer):
- class Meta:
- model = ToDoItem
- fields = ('text', 'is_completed')
+ class ToDoItemSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ToDoItem
+ fields = ['text', 'is_completed']
- class ToDoListSerializer(serializers.ModelSerializer):
- items = ToDoItemSerializer(many=True, read_only=True)
+ class ToDoListSerializer(serializers.ModelSerializer):
+ items = ToDoItemSerializer(many=True, read_only=True)
- class Meta:
- model = ToDoList
- fields = ('title', 'items')
+ class Meta:
+ model = ToDoList
+ fields = ['title', 'items']
Some example output from our serializer.
{
- 'title': 'Leaving party preperations',
- 'items': {
+ 'title': 'Leaving party preparations',
+ 'items': [
{'text': 'Compile playlist', 'is_completed': True},
{'text': 'Send invites', 'is_completed': False},
{'text': 'Clean house', 'is_completed': False}
- }
+ ]
}
Let's take a look at updating our nested one-to-many data structure.
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index 60a3d9897d..b9bf67acbb 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -8,24 +8,24 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
---
-**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
+**Note**: The code for this tutorial is available in the [encode/rest-framework-tutorial][repo] repository on GitHub. Feel free to clone the repository and see the code in action.
---
## Setting up a new environment
-Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
+Before we do anything else we'll create a new virtual environment, using [venv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
- virtualenv env
+ python3 -m venv env
source env/bin/activate
-Now that we're inside a virtualenv environment, we can install our package requirements.
+Now that we're inside a virtual environment, we can install our package requirements.
pip install django
pip install djangorestframework
pip install pygments # We'll be using this for the code highlighting
-**Note:** To exit the virtualenv environment at any time, just type `deactivate`. For more information see the [virtualenv documentation][virtualenv].
+**Note:** To exit the virtual environment at any time, just type `deactivate`. For more information see the [venv documentation][venv].
## Getting started
@@ -33,7 +33,7 @@ Okay, we're ready to get coding.
To get started, let's create a new project to work with.
cd ~
- django-admin.py startproject tutorial
+ django-admin startproject tutorial
cd tutorial
Once that's done we can create an app that we'll use to create a simple Web API.
@@ -42,16 +42,10 @@ Once that's done we can create an app that we'll use to create a simple Web API.
We'll need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. Let's edit the `tutorial/settings.py` file:
- INSTALLED_APPS = (
+ INSTALLED_APPS = [
...
'rest_framework',
'snippets',
- )
-
-We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
-
- urlpatterns = [
- url(https://github.com/r'%5E',%20include('snippets.urls')),
]
Okay, we're ready to roll.
@@ -66,7 +60,7 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
- STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
+ STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
class Snippet(models.Model):
@@ -78,26 +72,25 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
- ordering = ('created',)
+ ordering = ['created']
We'll also need to create an initial migration for our snippet model, and sync the database for the first time.
python manage.py makemigrations snippets
- python manage.py migrate
+ python manage.py migrate snippets
## Creating a Serializer class
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as `json`. We can do this by declaring serializers that work very similar to Django's forms. Create a file in the `snippets` directory named `serializers.py` and add the following.
- from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
- pk = serializers.IntegerField(read_only=True)
+ id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
- code = serializers.CharField(style={'type': 'textarea'})
+ code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
@@ -124,7 +117,7 @@ The first part of the serializer class defines the fields that get serialized/de
A serializer class is very similar to a Django `Form` class, and includes similar validation flags on the various fields, such as `required`, `max_length` and `default`.
-The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `{'base_template': 'textarea.html'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
+The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `{'base_template': 'textarea.html'}` flag above is equivalent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial.
We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit.
@@ -144,35 +137,35 @@ Okay, once we've got a few imports out of the way, let's create a couple of code
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
- snippet = Snippet(code='print "hello, world"\n')
+ snippet = Snippet(code='print("hello, world")\n')
snippet.save()
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
serializer = SnippetSerializer(snippet)
serializer.data
- # {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
+ # {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
content = JSONRenderer().render(serializer.data)
content
- # '{"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
+ # b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'
Deserialization is similar. First we parse a stream into Python native datatypes...
- from django.utils.six import BytesIO
+ import io
- stream = BytesIO(content)
+ stream = io.BytesIO(content)
data = JSONParser().parse(stream)
-...then we restore those native datatypes into to a fully populated object instance.
+...then we restore those native datatypes into a fully populated object instance.
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
- # OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
+ # {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
serializer.save()
#
@@ -182,34 +175,34 @@ We can also serialize querysets instead of model instances. To do so we simply
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
- # [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}]
+ # [{'id': 1, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}]
## Using ModelSerializers
-Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep our code a bit more concise.
+Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep our code a bit more concise.
In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes.
Let's look at refactoring our serializer using the `ModelSerializer` class.
-Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` class.
+Open the file `snippets/serializers.py` again, and replace the `SnippetSerializer` class with the following.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
- fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
+ fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
-One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing it's representation. Open the Django shell with `python manage.py shell`, then try the following:
+One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing its representation. Open the Django shell with `python manage.py shell`, then try the following:
- >>> from snippets.serializers import SnippetSerializer
- >>> serializer = SnippetSerializer()
- >>> print(repr(serializer))
- SnippetSerializer():
- id = IntegerField(label='ID', read_only=True)
- title = CharField(allow_blank=True, max_length=100, required=False)
- code = CharField(style={'base_template': 'textarea.html'})
- linenos = BooleanField(required=False)
- language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
- style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
+ from snippets.serializers import SnippetSerializer
+ serializer = SnippetSerializer()
+ print(repr(serializer))
+ # SnippetSerializer():
+ # id = IntegerField(label='ID', read_only=True)
+ # title = CharField(allow_blank=True, max_length=100, required=False)
+ # code = CharField(style={'base_template': 'textarea.html'})
+ # linenos = BooleanField(required=False)
+ # language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
+ # style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
It's important to remember that `ModelSerializer` classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes:
@@ -221,26 +214,14 @@ It's important to remember that `ModelSerializer` classes don't do anything part
Let's see how we can write some API views using our new Serializer class.
For the moment we won't use any of REST framework's other features, we'll just write the views as regular Django views.
-We'll start off by creating a subclass of HttpResponse that we can use to render any data we return into `json`.
-
Edit the `snippets/views.py` file, and add the following.
- from django.http import HttpResponse
+ from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
- from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
- class JSONResponse(HttpResponse):
- """
- An HttpResponse that renders its content into JSON.
- """
- def __init__(self, data, **kwargs):
- content = JSONRenderer().render(data)
- kwargs['content_type'] = 'application/json'
- super(JSONResponse, self).__init__(content, **kwargs)
-
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
@csrf_exempt
@@ -251,15 +232,15 @@ The root of our API is going to be a view that supports listing all the existing
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
- return JSONResponse(serializer.data)
+ return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
- return JSONResponse(serializer.data, status=201)
- return JSONResponse(serializer.errors, status=400)
+ return JsonResponse(serializer.data, status=201)
+ return JsonResponse(serializer.errors, status=400)
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as `csrf_exempt`. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
@@ -277,15 +258,15 @@ We'll also need a view which corresponds to an individual snippet, and can be us
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
- return JSONResponse(serializer.data)
+ return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
- return JSONResponse(serializer.data)
- return JSONResponse(serializer.errors, status=400)
+ return JsonResponse(serializer.data)
+ return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
@@ -293,12 +274,20 @@ We'll also need a view which corresponds to an individual snippet, and can be us
Finally we need to wire these views up. Create the `snippets/urls.py` file:
- from django.conf.urls import url
+ from django.urls import path
from snippets import views
urlpatterns = [
- url(https://github.com/r'%5Esnippets/),%20views.snippet_list),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/$', views.snippet_detail),
+ path('snippets/', views.snippet_list),
+ path('snippets//', 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.
+
+ from django.urls import path, include
+
+ urlpatterns = [
+ path('', include('snippets.urls')),
]
It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now.
@@ -309,22 +298,22 @@ Now we can start up a sample server that serves our snippets.
Quit out of the shell...
- quit()
+ quit()
...and start up Django's development server.
- python manage.py runserver
+ python manage.py runserver
- Validating models...
+ Validating models...
- 0 errors found
- Django version 1.4.3, using settings 'tutorial.settings'
- Development server is running at http://127.0.0.1:8000/
- Quit the server with CONTROL-C.
+ 0 errors found
+ Django version 5.0, using settings 'tutorial.settings'
+ Starting Development server at http://127.0.0.1:8000/
+ Quit the server with CONTROL-C.
In another terminal window, we can test the server.
-We can test our API using using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
+We can test our API using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
You can install httpie using pip:
@@ -332,42 +321,50 @@ You can install httpie using pip:
Finally, we can get a list of all of the snippets:
- http http://127.0.0.1:8000/snippets/
+ http GET http://127.0.0.1:8000/snippets/ --unsorted
HTTP/1.1 200 OK
...
[
- {
- "id": 1,
- "title": "",
- "code": "foo = \"bar\"\n",
- "linenos": false,
- "language": "python",
- "style": "friendly"
- },
- {
- "id": 2,
- "title": "",
- "code": "print \"hello, world\"\n",
- "linenos": false,
- "language": "python",
- "style": "friendly"
- }
+ {
+ "id": 1,
+ "title": "",
+ "code": "foo = \"bar\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ },
+ {
+ "id": 2,
+ "title": "",
+ "code": "print(\"hello, world\")\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ },
+ {
+ "id": 3,
+ "title": "",
+ "code": "print(\"hello, world\")",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
]
Or we can get a particular snippet by referencing its id:
- http http://127.0.0.1:8000/snippets/2/
+ http GET http://127.0.0.1:8000/snippets/2/ --unsorted
HTTP/1.1 200 OK
...
{
- "id": 2,
- "title": "",
- "code": "print \"hello, world\"\n",
- "linenos": false,
- "language": "python",
- "style": "friendly"
+ "id": 2,
+ "title": "",
+ "code": "print(\"hello, world\")\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
}
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
@@ -381,9 +378,8 @@ Our API views don't do anything particularly special at the moment, beyond servi
We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[quickstart]: quickstart.md
-[repo]: https://github.com/tomchristie/rest-framework-tutorial
-[sandbox]: http://restframework.herokuapp.com/
-[virtualenv]: http://www.virtualenv.org/en/latest/index.html
+[repo]: https://github.com/encode/rest-framework-tutorial
+[venv]: https://docs.python.org/3/library/venv.html
[tut-2]: 2-requests-and-responses.md
-[httpie]: https://github.com/jakubroztocil/httpie#installation
-[curl]: http://curl.haxx.se
+[httpie]: https://github.com/httpie/httpie#installation
+[curl]: https://curl.haxx.se/
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index c04269695a..47c7facfc6 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -25,17 +25,15 @@ Using numeric HTTP status codes in your views doesn't always make for obvious re
REST framework provides two wrappers you can use to write API views.
1. The `@api_view` decorator for working with function based views.
-2. The `APIView` class for working with class based views.
+2. The `APIView` class for working with class-based views.
These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed.
-The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.data` with malformed input.
+The wrappers also provide behavior such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exceptions that occur when accessing `request.data` with malformed input.
## Pulling it all together
-Okay, let's go ahead and start using these new components to write a few views.
-
-We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly.
+Okay, let's go ahead and start using these new components to refactor our views slightly.
from rest_framework import status
from rest_framework.decorators import api_view
@@ -47,7 +45,7 @@ We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and de
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
- List all snippets, or create a new snippet.
+ List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
@@ -68,7 +66,7 @@ Here is the view for an individual snippet, in the `views.py` module.
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
- Retrieve, update or delete a snippet instance.
+ Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
@@ -92,7 +90,7 @@ Here is the view for an individual snippet, in the `views.py` module.
This should all feel very familiar - it is not a lot different from working with regular Django views.
-Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
+Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
## Adding optional format suffixes to our URLs
@@ -106,15 +104,15 @@ and
def snippet_detail(request, pk, format=None):
-Now update the `urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
+Now update the `snippets/urls.py` file slightly, to append a set of `format_suffix_patterns` in addition to the existing URLs.
- from django.conf.urls import patterns, url
+ from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
- url(https://github.com/r'%5Esnippets/),%20views.snippet_list),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)$', views.snippet_detail),
+ path('snippets/', views.snippet_list),
+ path('snippets//', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)
@@ -143,7 +141,7 @@ We can get a list of all of the snippets, as before.
{
"id": 2,
"title": "",
- "code": "print \"hello, world\"\n",
+ "code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@@ -157,35 +155,37 @@ We can control the format of the response that we get back, either by using the
Or by appending a format suffix:
- http http://127.0.0.1:8000/snippets/.json # JSON suffix
- http http://127.0.0.1:8000/snippets/.api # Browsable API suffix
+ http http://127.0.0.1:8000/snippets.json # JSON suffix
+ http http://127.0.0.1:8000/snippets.api # Browsable API suffix
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
# POST using form data
- http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
+ http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"id": 3,
"title": "",
- "code": "print 123",
+ "code": "print(123)",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
- http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
+ http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
{
"id": 4,
"title": "",
- "code": "print 456",
+ "code": "print(456)",
"linenos": false,
"language": "python",
"style": "friendly"
}
+If you add a `--debug` switch to the `http` requests above, you will be able to see the request type in request headers.
+
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
### Browsability
@@ -198,7 +198,7 @@ See the [browsable api][browsable-api] topic for more information about the brow
## What's next?
-In [tutorial part 3][tut-3], we'll start using class based views, and see how generic views reduce the amount of code we need to write.
+In [tutorial part 3][tut-3], we'll start using class-based views, and see how generic views reduce the amount of code we need to write.
[json-url]: http://example.com/api/items/4.json
[devserver]: http://127.0.0.1:8000/snippets/
diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index 0a9ea3f154..ccfcd095da 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -1,10 +1,10 @@
-# Tutorial 3: Class Based Views
+# Tutorial 3: Class-based Views
-We can also write our API views using class based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][dry].
+We can also write our API views using class-based views, rather than function based views. As we'll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code [DRY][dry].
-## Rewriting our API using class based views
+## Rewriting our API using class-based views
-We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring of `views.py`.
+We'll start by rewriting the root view as a class-based view. All this involves is a little bit of refactoring of `views.py`.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@@ -62,15 +62,15 @@ So far, so good. It looks pretty similar to the previous case, but we've got be
That's looking good. Again, it's still pretty similar to the function based view right now.
-We'll also need to refactor our `urls.py` slightly now we're using class based views.
+We'll also need to refactor our `snippets/urls.py` slightly now that we're using class-based views.
- from django.conf.urls import patterns, url
+ from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
- url(https://github.com/r'%5Esnippets/),%20views.SnippetList.as_view()),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/$', views.SnippetDetail.as_view()),
+ path('snippets/', views.SnippetList.as_view()),
+ path('snippets//', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
@@ -79,9 +79,9 @@ Okay, we're done. If you run the development server everything should be workin
## Using mixins
-One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour.
+One of the big wins of using class-based views is that it allows us to easily compose reusable bits of behavior.
-The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes.
+The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behavior are implemented in REST framework's mixin classes.
Let's take a look at how we can compose the views by using the mixin classes. Here's our `views.py` module again.
@@ -124,7 +124,7 @@ The base class provides the core functionality, and the mixin classes provide th
Pretty similar. Again we're using the `GenericAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
-## Using generic class based views
+## Using generic class-based views
Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our `views.py` module even more.
@@ -146,5 +146,5 @@ Wow, that's pretty concise. We've gotten a huge amount for free, and our code l
Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can deal with authentication and permissions for our API.
-[dry]: http://en.wikipedia.org/wiki/Don't_repeat_yourself
+[dry]: https://en.wikipedia.org/wiki/Don't_repeat_yourself
[tut-4]: 4-authentication-and-permissions.md
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index 592c77e814..cb0321ea21 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -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.
@@ -33,17 +33,17 @@ And now we can add a `.save()` method to our model class:
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
- linenos = self.linenos and 'table' or False
- options = self.title and {'title': self.title} or {}
+ linenos = 'table' if self.linenos else False
+ options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
- super(Snippet, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
When that's all done we'll need to update our database tables.
Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again.
- rm -f tmp.db db.sqlite3
+ rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
@@ -63,11 +63,11 @@ Now that we've got some users to work with, we'd better add representations of t
class Meta:
model = User
- fields = ('id', 'username', 'snippets')
+ fields = ['id', 'username', 'snippets']
Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it.
-We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views.
+We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class-based views.
from django.contrib.auth.models import User
@@ -83,12 +83,12 @@ We'll also add a couple of views to `views.py`. We'd like to just use read-only
Make sure to also import the `UserSerializer` class
- from snippets.serializers import UserSerializer
+ from snippets.serializers import UserSerializer
-Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `urls.py`.
+Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `snippets/urls.py`.
- url(https://github.com/r'%5Eusers/),%20views.UserList.as_view()),
- url(https://github.com/r'^users/(?P%3Cpk%3E[0-9]+)/$', views.UserDetail.as_view()),
+ path('users/', views.UserList.as_view()),
+ path('users//', views.UserDetail.as_view()),
## Associating Snippets with Users
@@ -127,7 +127,7 @@ First add the following import in the views module
Then, add the following property to **both** the `SnippetList` and `SnippetDetail` view classes.
- permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
+ permission_classes = [permissions.IsAuthenticatedOrReadOnly]
## Adding login to the Browsable API
@@ -137,20 +137,19 @@ We can add a login view for use with the browsable API, by editing the URLconf i
Add the following import at the top of the file:
- from django.conf.urls import include
+ from django.urls import path, include
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
urlpatterns += [
- url(r'^api-auth/', include('rest_framework.urls',
- namespace='rest_framework')),
+ path('api-auth/', include('rest_framework.urls')),
]
-The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace.
+The `'api-auth/'` part of pattern can actually be whatever URL you want to use.
Now if you open up the browser again and refresh the page you'll see a 'Login' link in the top right of the page. If you log in as one of the users you created earlier, you'll be able to create code snippets again.
-Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet pks that are associated with each user, in each user's 'snippets' field.
+Once you've created a few code snippets, navigate to the '/users/' endpoint, and notice that the representation includes a list of the snippet ids that are associated with each user, in each user's 'snippets' field.
## Object level permissions
@@ -177,10 +176,10 @@ In the snippets app, create a new file, `permissions.py`
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
-Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` class:
+Now we can add that custom permission to our snippet instance endpoint, by editing the `permission_classes` property on the `SnippetDetail` view class:
- permission_classes = (permissions.IsAuthenticatedOrReadOnly,
- IsOwnerOrReadOnly,)
+ permission_classes = [permissions.IsAuthenticatedOrReadOnly,
+ IsOwnerOrReadOnly]
Make sure to also import the `IsOwnerOrReadOnly` class.
@@ -198,7 +197,7 @@ If we're interacting with the API programmatically we need to explicitly provide
If we try to create a snippet without authenticating, we'll get an error:
- http POST http://127.0.0.1:8000/snippets/ code="print 123"
+ http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
@@ -206,13 +205,13 @@ If we try to create a snippet without authenticating, we'll get an error:
We can make a successful request by including the username and password of one of the users we created earlier.
- http -a tom:password POST http://127.0.0.1:8000/snippets/ code="print 789"
+ http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
- "id": 5,
- "owner": "tom",
+ "id": 1,
+ "owner": "admin",
"title": "foo",
- "code": "print 789",
+ "code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index c21efd7f63..f999fdf507 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -11,14 +11,14 @@ Right now we have endpoints for 'snippets' and 'users', but we don't have a sing
from rest_framework.reverse import reverse
- @api_view(('GET',))
+ @api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
-Notice that we're using REST framework's `reverse` function in order to return fully-qualified URLs.
+Two things should be noticed here. First, we're using REST framework's `reverse` function in order to return fully-qualified URLs; second, URL patterns are identified by convenience names that we will declare later on in our `snippets/urls.py`.
## Creating an endpoint for the highlighted snippets
@@ -31,11 +31,10 @@ The other thing we need to consider when creating the code highlight view is tha
Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method. In your `snippets/views.py` add:
from rest_framework import renderers
- from rest_framework.response import Response
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
- renderer_classes = (renderers.StaticHTMLRenderer,)
+ renderer_classes = [renderers.StaticHTMLRenderer]
def get(self, request, *args, **kwargs):
snippet = self.get_object()
@@ -44,11 +43,11 @@ Instead of using a concrete generic view, we'll use the base class for represent
As usual we need to add the new views that we've created in to our URLconf.
We'll add a url pattern for our new API root in `snippets/urls.py`:
- url(https://github.com/r'%5E),%20views.api_root),
+ path('', views.api_root),
And then add a url pattern for the snippet highlights:
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
+ path('snippets//highlight/', views.SnippetHighlight.as_view()),
## Hyperlinking our API
@@ -67,7 +66,7 @@ In this case we'd like to use a hyperlinked style between entities. In order to
The `HyperlinkedModelSerializer` has the following differences from `ModelSerializer`:
-* It does not include the `pk` field by default.
+* It does not include the `id` field by default.
* It includes a `url` field, using `HyperlinkedIdentityField`.
* Relationships use `HyperlinkedRelatedField`,
instead of `PrimaryKeyRelatedField`.
@@ -80,8 +79,8 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
class Meta:
model = Snippet
- fields = ('url', 'highlight', 'owner',
- 'title', 'code', 'linenos', 'language', 'style')
+ fields = ['url', 'id', 'highlight', 'owner',
+ 'title', 'code', 'linenos', 'language', 'style']
class UserSerializer(serializers.HyperlinkedModelSerializer):
@@ -89,7 +88,7 @@ We can easily re-write our existing serializers to use hyperlinking. In your `sn
class Meta:
model = User
- fields = ('url', 'username', 'snippets')
+ fields = ['url', 'id', 'username', 'snippets']
Notice that we've also added a new `'highlight'` field. This field is of the same type as the `url` field, except that it points to the `'snippet-highlight'` url pattern, instead of the `'snippet-detail'` url pattern.
@@ -104,47 +103,46 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
* Our user serializer includes a field that refers to `'snippet-detail'`.
* Our snippet and user serializers include `'url'` fields that by default will refer to `'{model_name}-detail'`, which in this case will be `'snippet-detail'` and `'user-detail'`.
-After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this:
+After adding all those names into our URLconf, our final `snippets/urls.py` file should look like this:
+
+ from django.urls import path
+ from rest_framework.urlpatterns import format_suffix_patterns
+ from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
- url(https://github.com/r'%5E),%20views.api_root),
- url(r'^snippets/$',
+ path('', views.api_root),
+ path('snippets/',
views.SnippetList.as_view(),
name='snippet-list'),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/$',
+ path('snippets//',
views.SnippetDetail.as_view(),
name='snippet-detail'),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/highlight/$',
+ path('snippets//highlight/',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
- url(r'^users/$',
+ path('users/',
views.UserList.as_view(),
name='user-list'),
- url(https://github.com/r'^users/(?P%3Cpk%3E[0-9]+)/$',
+ path('users//',
views.UserDetail.as_view(),
name='user-detail')
])
- # Login and logout views for the browsable API
- urlpatterns += [
- url(r'^api-auth/', include('rest_framework.urls',
- namespace='rest_framework')),
- ]
-
## Adding pagination
The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages.
-We can change the default list style to use pagination, by modifying our `settings.py` file slightly. Add the following setting:
+We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting:
REST_FRAMEWORK = {
- 'PAGINATE_BY': 10
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ 'PAGE_SIZE': 10
}
-Note that settings in REST framework are all namespaced into a single dictionary setting, named 'REST_FRAMEWORK', which helps keep them well separated from your other project settings.
+Note that settings in REST framework are all namespaced into a single dictionary setting, named `REST_FRAMEWORK`, which helps keep them well separated from your other project settings.
-We could also customize the pagination style if we needed too, but in this case we'll just stick with the default.
+We could also customize the pagination style if we needed to, but in this case we'll just stick with the default.
## Browsing the API
diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md
index 63dff73fcf..6fa2111e7b 100644
--- a/docs/tutorial/6-viewsets-and-routers.md
+++ b/docs/tutorial/6-viewsets-and-routers.md
@@ -2,7 +2,7 @@
REST framework includes an abstraction for dealing with `ViewSets`, that allows the developer to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically, based on common conventions.
-`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `read`, or `update`, and not method handlers such as `get` or `put`.
+`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `retrieve`, or `update`, and not method handlers such as `get` or `put`.
A `ViewSet` class is only bound to a set of method handlers at the last moment, when it is instantiated into a set of views, typically by using a `Router` class which handles the complexities of defining the URL conf for you.
@@ -10,13 +10,14 @@ A `ViewSet` class is only bound to a set of method handlers at the last moment,
Let's take our current set of views, and refactor them into view sets.
-First of all let's refactor our `UserList` and `UserDetail` views into a single `UserViewSet`. We can remove the two views, and replace them with a single class:
+First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class:
from rest_framework import viewsets
+
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
- This viewset automatically provides `list` and `detail` actions.
+ This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@@ -25,46 +26,51 @@ Here we've used the `ReadOnlyModelViewSet` class to automatically provide the de
Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class.
- from rest_framework.decorators import detail_route
+ from rest_framework import permissions
+ from rest_framework import renderers
+ from rest_framework.decorators import action
+ from rest_framework.response import Response
+
class SnippetViewSet(viewsets.ModelViewSet):
"""
- This viewset automatically provides `list`, `create`, `retrieve`,
+ This ViewSet automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
- permission_classes = (permissions.IsAuthenticatedOrReadOnly,
- IsOwnerOrReadOnly,)
+ permission_classes = [permissions.IsAuthenticatedOrReadOnly,
+ IsOwnerOrReadOnly]
- @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
+ @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
- serializer.save(owner=self.request.user)
+ serializer.save(owner=self.request.user)
This time we've used the `ModelViewSet` class in order to get the complete set of default read and write operations.
-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.
+Notice that we've also used the `@action` 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 `@action` 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.
+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.
## Binding ViewSets to URLs explicitly
The handler methods only get bound to the actions when we define the URLConf.
To see what's going on under the hood let's first explicitly create a set of views from our ViewSets.
-In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views.
+In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concrete views.
- from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
+ from snippets.views import api_root, SnippetViewSet, UserViewSet
+
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
@@ -85,72 +91,46 @@ In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views
'get': 'retrieve'
})
-Notice how we're creating multiple views from each `ViewSet` class, by binding the http methods to the required action for each view.
+Notice how we're creating multiple views from each `ViewSet` class, by binding the HTTP methods to the required action for each view.
Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual.
urlpatterns = format_suffix_patterns([
- url(https://github.com/r'%5E),%20api_root),
- url(https://github.com/r'%5Esnippets/'),%20snippet_list,%20name='snippet-list'),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/$', snippet_detail, name='snippet-detail'),
- url(https://github.com/r'^snippets/(?P%3Cpk%3E[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
- url(https://github.com/r'%5Eusers/'),%20user_list,%20name='user-list'),
- url(https://github.com/r'^users/(?P%3Cpk%3E[0-9]+)/$', user_detail, name='user-detail')
+ path('', api_root),
+ path('snippets/', snippet_list, name='snippet-list'),
+ path('snippets//', snippet_detail, name='snippet-detail'),
+ path('snippets//highlight/', snippet_highlight, name='snippet-highlight'),
+ path('users/', user_list, name='user-list'),
+ path('users//', user_detail, name='user-detail')
])
## Using Routers
Because we're using `ViewSet` classes rather than `View` classes, we actually don't need to design the URL conf ourselves. The conventions for wiring up resources into views and urls can be handled automatically, using a `Router` class. All we need to do is register the appropriate view sets with a router, and let it do the rest.
-Here's our re-wired `urls.py` file.
+Here's our re-wired `snippets/urls.py` file.
- from django.conf.urls import url, include
- from snippets import views
+ from django.urls import path, include
from rest_framework.routers import DefaultRouter
- # Create a router and register our viewsets with it.
+ from snippets import views
+
+ # Create a router and register our ViewSets with it.
router = DefaultRouter()
- router.register(r'snippets', views.SnippetViewSet)
- router.register(r'users', views.UserViewSet)
+ router.register(r'snippets', views.SnippetViewSet, basename='snippet')
+ router.register(r'users', views.UserViewSet, basename='user')
# The API URLs are now determined automatically by the router.
- # Additionally, we include the login URLs for the browsable API.
urlpatterns = [
- url(https://github.com/r'%5E',%20include(router.urls)),
- url(https://github.com/r'%5Eapi-auth/',%20include('rest_framework.urls',%20namespace='rest_framework'))
+ path('', include(router.urls)),
]
-Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself.
-
-The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` method from our `views` module.
-
-## Trade-offs between views vs viewsets
-
-Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf.
-
-That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually.
-
-## Reviewing our work
-
-With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
-
-We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views.
-
-You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox].
-
-## Onwards and upwards
-
-We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start:
+Registering the ViewSets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the view set itself.
-* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
-* Join the [REST framework discussion group][group], and help build the community.
-* Follow [the author][twitter] on Twitter and say hi.
+The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` function from our `views` module.
-**Now go build awesome things.**
+## Trade-offs between views vs ViewSets
+Using ViewSets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf.
-[repo]: https://github.com/tomchristie/rest-framework-tutorial
-[sandbox]: http://restframework.herokuapp.com/
-[github]: https://github.com/tomchristie/django-rest-framework
-[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-[twitter]: https://twitter.com/_tomchristie
+That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function-based views. Using ViewSets is less explicit than building your API views individually.
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index a4474c34ee..a140dbce0a 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -10,89 +10,115 @@ Create a new Django project named `tutorial`, then start a new app called `quick
mkdir tutorial
cd tutorial
- # Create a virtualenv to isolate our package dependencies locally
- virtualenv env
+ # Create a virtual environment to isolate our package dependencies locally
+ python3 -m venv env
source env/bin/activate # On Windows use `env\Scripts\activate`
- # Install Django and Django REST framework into the virtualenv
- pip install django
+ # Install Django and Django REST framework into the virtual environment
pip install djangorestframework
# Set up a new project with a single application
- django-admin.py startproject tutorial . # Note the trailing '.' character
+ django-admin startproject tutorial . # Note the trailing '.' character
cd tutorial
- django-admin.py startapp quickstart
+ django-admin startapp quickstart
cd ..
+The project layout should look like:
+
+ $ pwd
+ /tutorial
+ $ find .
+ .
+ ./tutorial
+ ./tutorial/asgi.py
+ ./tutorial/__init__.py
+ ./tutorial/quickstart
+ ./tutorial/quickstart/migrations
+ ./tutorial/quickstart/migrations/__init__.py
+ ./tutorial/quickstart/models.py
+ ./tutorial/quickstart/__init__.py
+ ./tutorial/quickstart/apps.py
+ ./tutorial/quickstart/admin.py
+ ./tutorial/quickstart/tests.py
+ ./tutorial/quickstart/views.py
+ ./tutorial/settings.py
+ ./tutorial/urls.py
+ ./tutorial/wsgi.py
+ ./env
+ ./env/...
+ ./manage.py
+
+It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart).
+
Now sync your database for the first time:
python manage.py migrate
-We'll also create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example.
+We'll also create an initial user named `admin` with a password. We'll authenticate as that user later in our example.
- python manage.py createsuperuser
+ python manage.py createsuperuser --username admin --email admin@example.com
-Once you've set up a database and initial user created and ready to go, open up the app's directory and we'll get coding...
+Once you've set up a database and the initial user is created and ready to go, open up the app's directory and we'll get coding...
## Serializers
First up we're going to define some serializers. Let's create a new module named `tutorial/quickstart/serializers.py` that we'll use for our data representations.
- from django.contrib.auth.models import User, Group
+ from django.contrib.auth.models import Group, User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
- fields = ('url', 'username', 'email', 'groups')
+ fields = ['url', 'username', 'email', 'groups']
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
- fields = ('url', 'name')
+ fields = ['url', 'name']
-Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
+Notice that we're using hyperlinked relations in this case with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
## Views
Right, we'd better write some views then. Open `tutorial/quickstart/views.py` and get typing.
- from django.contrib.auth.models import User, Group
- from rest_framework import viewsets
- from tutorial.quickstart.serializers import UserSerializer, GroupSerializer
+ from django.contrib.auth.models import Group, User
+ from rest_framework import permissions, viewsets
+
+ from tutorial.quickstart.serializers import GroupSerializer, UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
- queryset = User.objects.all()
+ queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
+ permission_classes = [permissions.IsAuthenticated]
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
- queryset = Group.objects.all()
+ queryset = Group.objects.all().order_by('name')
serializer_class = GroupSerializer
+ permission_classes = [permissions.IsAuthenticated]
Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`.
We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise.
-Notice that our viewset classes here are a little different from those in the [frontpage example][readme-example-api], as they include `queryset` and `serializer_class` attributes, instead of a `model` attribute.
-
-For trivial cases you can simply set a `model` attribute on the `ViewSet` class and the serializer and queryset will be automatically generated for you. Setting the `queryset` and/or `serializer_class` attributes gives you more explicit control of the API behaviour, and is the recommended style for most applications.
-
## URLs
Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
- from django.conf.urls import url, include
+ from django.urls import include, path
from rest_framework import routers
+
from tutorial.quickstart import views
router = routers.DefaultRouter()
@@ -102,29 +128,32 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
- url(https://github.com/r'%5E',%20include(router.urls)),
- url(https://github.com/r'%5Eapi-auth/',%20include('rest_framework.urls',%20namespace='rest_framework'))
+ path('', include(router.urls)),
+ path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
-Again, if we need more control over the API URLs we can simply drop down to using regular class based views, and writing the URL conf explicitly.
+Again, if we need more control over the API URLs we can simply drop down to using regular class-based views, and writing the URL conf explicitly.
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
+## Pagination
+Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
+
+ REST_FRAMEWORK = {
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ 'PAGE_SIZE': 10
+ }
+
## Settings
-We'd also like to set a few global settings. We'd like to turn on pagination, and we want our API to only be accessible to admin users. The settings module will be in `tutorial/settings.py`
+Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py`
- INSTALLED_APPS = (
+ INSTALLED_APPS = [
...
'rest_framework',
- )
-
- REST_FRAMEWORK = {
- 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
- 'PAGINATE_BY': 10
- }
+ ]
Okay, we're done.
@@ -134,59 +163,48 @@ Okay, we're done.
We're now ready to test the API we've built. Let's fire up the server from the command line.
- python ./manage.py runserver
+ python manage.py runserver
We can now access our API, both from the command-line, using tools like `curl`...
- bash: curl -H 'Accept: application/json; indent=4' -u admin:password http://127.0.0.1:8000/users/
+ bash: curl -u admin -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/users/
+ Enter host password for user 'admin':
{
- "count": 2,
+ "count": 1,
"next": null,
"previous": null,
"results": [
{
- "email": "admin@example.com",
- "groups": [],
"url": "http://127.0.0.1:8000/users/1/",
- "username": "admin"
- },
- {
- "email": "tom@example.com",
- "groups": [ ],
- "url": "http://127.0.0.1:8000/users/2/",
- "username": "tom"
+ "username": "admin",
+ "email": "admin@example.com",
+ "groups": []
}
]
}
Or using the [httpie][httpie], command line tool...
- bash: http -a username:password http://127.0.0.1:8000/users/
-
- HTTP/1.1 200 OK
+ bash: http -a admin http://127.0.0.1:8000/users/
+ http: password for admin@127.0.0.1:8000::
+ $HTTP/1.1 200 OK
...
{
- "count": 2,
+ "count": 1,
"next": null,
"previous": null,
"results": [
{
"email": "admin@example.com",
"groups": [],
- "url": "http://localhost:8000/users/1/",
- "username": "paul"
- },
- {
- "email": "tom@example.com",
- "groups": [ ],
- "url": "http://127.0.0.1:8000/users/2/",
- "username": "tom"
+ "url": "http://127.0.0.1:8000/users/1/",
+ "username": "admin"
}
]
}
-Or directly through the browser...
+Or directly through the browser, by going to the URL `http://127.0.0.1:8000/users/`...
![Quick start image][image]
@@ -196,8 +214,7 @@ Great, that was easy!
If you want to get a more in depth understanding of how REST framework fits together head on over to [the tutorial][tutorial], or start browsing the [API guide][guide].
-[readme-example-api]: ../#example
[image]: ../img/quickstart.png
[tutorial]: 1-serialization.md
-[guide]: ../#api-guide
-[httpie]: https://github.com/jakubroztocil/httpie#installation
+[guide]: ../api-guide/requests.md
+[httpie]: https://httpie.io/docs#installation
diff --git a/docs_theme/404.html b/docs_theme/404.html
index 44993e37d3..bbb6b70ffc 100644
--- a/docs_theme/404.html
+++ b/docs_theme/404.html
@@ -1,216 +1,9 @@
-
-
+{% extends "main.html" %}
-
-
-
- Django REST framework - 404 - Page not found
-
-
-
-
-
+{% block content %}
-
-
-
-
-
+