aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Douglas2013-07-04 05:29:42 -0700
committerPhilip Douglas2013-07-04 05:29:42 -0700
commitbf8e71c455a47a53898f8239ac7dad47e5f1d53a (patch)
tree967055f41f43a35b8d9362f47c81aad5af4036d3
parentfa9f5fb8dcf6d51b2db70d4e2a991779b056d1d4 (diff)
parent5fa100245cbf71a47c7a1ea7a869d03049380130 (diff)
downloaddjango-rest-framework-bf8e71c455a47a53898f8239ac7dad47e5f1d53a.tar.bz2
Merge pull request #1 from tomchristie/master
Update from main
-rw-r--r--.gitignore1
-rw-r--r--README.md23
-rwxr-xr-xdocs/api-guide/authentication.md7
-rwxr-xr-xdocs/api-guide/generic-views.md3
-rw-r--r--docs/api-guide/responses.md2
-rw-r--r--docs/api-guide/routers.md34
-rw-r--r--docs/css/default.css4
-rw-r--r--docs/img/apiary.pngbin0 -> 55554 bytes
-rw-r--r--docs/img/cerulean.pngbin0 -> 62558 bytes
-rw-r--r--docs/img/django-rest-swagger.pngbin0 -> 76945 bytes
-rw-r--r--docs/img/rest-framework-docs.pngbin0 -> 76612 bytes
-rw-r--r--docs/img/self-describing.pngbin0 -> 53953 bytes
-rw-r--r--docs/img/slate.pngbin0 -> 65706 bytes
-rw-r--r--docs/index.md18
-rw-r--r--docs/template.html10
-rw-r--r--docs/topics/browsable-api.md62
-rw-r--r--docs/topics/credits.md7
-rw-r--r--docs/topics/documenting-your-api.md112
-rw-r--r--docs/topics/release-notes.md7
-rw-r--r--docs/topics/rest-hypermedia-hateoas.md3
-rwxr-xr-xmkdocs.py1
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/authentication.py9
-rw-r--r--rest_framework/authtoken/models.py4
-rw-r--r--rest_framework/compat.py27
-rw-r--r--rest_framework/parsers.py25
-rw-r--r--rest_framework/routers.py16
-rw-r--r--rest_framework/runtests/settings.py2
-rw-r--r--rest_framework/serializers.py5
-rw-r--r--rest_framework/tests/description.py26
-rw-r--r--rest_framework/tests/test_description.py13
-rw-r--r--rest_framework/tests/test_routers.py57
-rw-r--r--rest_framework/utils/formatting.py4
-rw-r--r--rest_framework/views.py11
34 files changed, 405 insertions, 90 deletions
diff --git a/.gitignore b/.gitignore
index 2255cd9a..ae73f837 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
.*
html/
+htmlcov/
coverage/
build/
dist/
diff --git a/README.md b/README.md
index 12ed09f9..62883e32 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b
Some reasons you might want to use REST framework:
-* The Web browseable API is a huge useability win for your developers.
-* Authentication policies including OAuth1a and OAuth2 out of the box.
-* Serialization that supports both ORM and non-ORM data sources.
-* Customizable all the way down - just use regular function-based views if you don't need the more powerful features.
-* Extensive documentation, and great community support.
+* The [Web browseable API][sandbox] is a huge useability win for your developers.
+* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box.
+* [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].
There is a live example API for testing purposes, [available here][sandbox].
@@ -139,6 +139,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[sandbox]: http://restframework.herokuapp.com/
+
+[index]: http://django-rest-framework.org/
+[oauth1-section]: http://django-rest-framework.org/api-guide/authentication.html#oauthauthentication
+[oauth2-section]: http://django-rest-framework.org/api-guide/authentication.html#oauth2authentication
+[serializer-section]: http://django-rest-framework.org/api-guide/serializers.html#serializers
+[modelserializer-section]: http://django-rest-framework.org/api-guide/serializers.html#modelserializer
+[functionview-section]: http://django-rest-framework.org/api-guide/views.html#function-based-views
+[generic-views]: http://django-rest-framework.org/api-guide/generic-views.html
+[viewsets]: http://django-rest-framework.org/api-guide/viewsets.html
+[routers]: http://django-rest-framework.org/api-guide/routers.html
+[serializers]: http://django-rest-framework.org/api-guide/serializers.html
+[authentication]: http://django-rest-framework.org/api-guide/authentication.html
+
[rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html
[2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion
[image]: http://django-rest-framework.org/img/quickstart.png
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index 8cf995b3..5d6e0d91 100755
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -355,6 +355,10 @@ HTTP digest authentication is a widely implemented scheme that was intended to r
The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excelllent [OAuthLib][oauthlib]. The package is well documented, and comes as a recommended alternative for OAuth 2.0 support.
+## Django OAuth2 Consumer
+
+The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth 2.0 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API.
+
[cite]: http://jacobian.org/writing/rest-worst-practices/
[http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
[http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
@@ -376,3 +380,6 @@ The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 supp
[django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit
[evonove]: https://github.com/evonove/
[oauthlib]: https://github.com/idan/oauthlib
+[doac]: https://github.com/Rediker-Software/doac
+[rediker]: https://github.com/Rediker-Software
+[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/markdown/integrations.md#
diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md
index cd1bc7a1..67853ed0 100755
--- a/docs/api-guide/generic-views.md
+++ b/docs/api-guide/generic-views.md
@@ -92,7 +92,8 @@ May be overridden to provide dynamic behavior such as returning a queryset that
For example:
def get_queryset(self):
- return self.user.accounts.all()
+ user = self.request.user
+ return user.accounts.all()
#### `get_object(self)`
diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md
index f83b8194..399b7c23 100644
--- a/docs/api-guide/responses.md
+++ b/docs/api-guide/responses.md
@@ -10,7 +10,7 @@ REST framework supports HTTP content negotiation by providing a `Response` class
The `Response` class subclasses Django's `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native Python primitives. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content.
-There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it provides a nicer interface for returning Web API responses.
+There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` or `StreamingHttpResponse` objects from your views if required. Using the `Response` class simply provides a nicer interface for returning content-negotiated Web API responses, that can be rendered to multiple formats.
Unless you want to heavily customize REST framework for some reason, you should always use an `APIView` class or `@api_view` function for views that return `Response` objects. Doing so ensures that the view can perform content negotiation and select the appropriate renderer for the response, before it is returned from the view.
diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md
index f16fa946..86582905 100644
--- a/docs/api-guide/routers.md
+++ b/docs/api-guide/routers.md
@@ -26,7 +26,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.
+* `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.
The example above would generate the following URL patterns:
@@ -98,7 +98,23 @@ As with `SimpleRouter` the trailing slashs on the URL routes can be removed by s
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 strutured. 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 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.
+
+The arguments to the `Route` named tuple are:
+
+**url**: A string representing the URL to be routed. May include the following format strings:
+
+* `{prefix}` - The URL prefix to use for this set of routes.
+* `{lookup}` - The lookup field used to match against a single instance.
+* `{trailing_slash}` - Either a '/' or an empty string, depending on the `trailing_slash` argument.
+
+**mapping**: A mapping of HTTP method names to the view methods
+
+**name**: The name of the URL as used in `reverse` calls. May include the following format string:
+
+* `{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.
## Example
@@ -106,13 +122,21 @@ The following example will only route to the `list` and `retrieve` actions, and
class ReadOnlyRouter(SimpleRouter):
"""
- A router for read-only APIs, which doesn't use trailing suffixes.
+ A router for read-only APIs, which doesn't use trailing slashes.
"""
routes = [
- (r'^{prefix}$', {'get': 'list'}, '{basename}-list'),
- (r'^{prefix}/{lookup}$', {'get': 'retrieve'}, '{basename}-detail')
+ Route(url=r'^{prefix}$',
+ mapping={'get': 'list'},
+ name='{basename}-list',
+ initkwargs={'suffix': 'List'}),
+ Route(url=r'^{prefix}/{lookup}$',
+ mapping={'get': 'retrieve'},
+ name='{basename}-detail',
+ initkwargs={'suffix': 'Detail'})
]
+The `SimpleRouter` class provides another example of setting the `.routes` attribute.
+
## Advanced custom routers
If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls(self)` method. The method should insect 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.
diff --git a/docs/css/default.css b/docs/css/default.css
index a4f05daa..af6a9cc0 100644
--- a/docs/css/default.css
+++ b/docs/css/default.css
@@ -303,3 +303,7 @@ table {
border-color: white;
margin-bottom: 0.6em;
}
+
+.side-nav {
+ overflow-y: scroll;
+}
diff --git a/docs/img/apiary.png b/docs/img/apiary.png
new file mode 100644
index 00000000..923d384e
--- /dev/null
+++ b/docs/img/apiary.png
Binary files differ
diff --git a/docs/img/cerulean.png b/docs/img/cerulean.png
new file mode 100644
index 00000000..e647d5e8
--- /dev/null
+++ b/docs/img/cerulean.png
Binary files differ
diff --git a/docs/img/django-rest-swagger.png b/docs/img/django-rest-swagger.png
new file mode 100644
index 00000000..96a6b238
--- /dev/null
+++ b/docs/img/django-rest-swagger.png
Binary files differ
diff --git a/docs/img/rest-framework-docs.png b/docs/img/rest-framework-docs.png
new file mode 100644
index 00000000..736a0095
--- /dev/null
+++ b/docs/img/rest-framework-docs.png
Binary files differ
diff --git a/docs/img/self-describing.png b/docs/img/self-describing.png
new file mode 100644
index 00000000..ecbe4fe4
--- /dev/null
+++ b/docs/img/self-describing.png
Binary files differ
diff --git a/docs/img/slate.png b/docs/img/slate.png
new file mode 100644
index 00000000..31644eaf
--- /dev/null
+++ b/docs/img/slate.png
Binary files differ
diff --git a/docs/index.md b/docs/index.md
index b04e2346..99cd6b88 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,11 +15,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b
Some reasons you might want to use REST framework:
-* The Web browseable API is a huge usability win for your developers.
-* Authentication policies including OAuth1a and OAuth2 out of the box.
-* Serialization that supports both ORM and non-ORM data sources.
-* Customizable all the way down - just use regular function-based views if you don't need the more powerful features.
-* Extensive documentation, and great community support.
+* The [Web browseable 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.
+* [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].
There is a live example API for testing purposes, [available here][sandbox].
@@ -170,6 +170,7 @@ The API guide is your complete reference manual to all the functionality provide
General guides to using REST framework.
+* [Documenting your API][documenting-your-api]
* [AJAX, CSRF & CORS][ajax-csrf-cors]
* [Browser enhancements][browser-enhancements]
* [The Browsable API][browsableapi]
@@ -250,6 +251,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider
[0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X
[image]: img/quickstart.png
+[index]: .
+[oauth1-section]: api-guide/authentication.html#oauthauthentication
+[oauth2-section]: api-guide/authentication.html#oauth2authentication
+[serializer-section]: api-guide/serializers.html#serializers
+[modelserializer-section]: api-guide/serializers.html#modelserializer
+[functionview-section]: api-guide/views.html#function-based-views
[sandbox]: http://restframework.herokuapp.com/
[quickstart]: tutorial/quickstart.md
@@ -283,6 +290,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[status]: api-guide/status-codes.md
[settings]: api-guide/settings.md
+[documenting-your-api]: topics/documenting-your-api.md
[ajax-csrf-cors]: topics/ajax-csrf-cors.md
[browser-enhancements]: topics/browser-enhancements.md
[browsableapi]: topics/browsable-api.md
diff --git a/docs/template.html b/docs/template.html
index 53656e7d..27bc1062 100644
--- a/docs/template.html
+++ b/docs/template.html
@@ -95,6 +95,7 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
<ul class="dropdown-menu">
+ <li><a href="{{ base_url }}/topics/documenting-your-api{{ suffix }}">Documenting your API</a></li>
<li><a href="{{ base_url }}/topics/ajax-csrf-cors{{ suffix }}">AJAX, CSRF & CORS</a></li>
<li><a href="{{ base_url }}/topics/browser-enhancements{{ suffix }}">Browser enhancements</a></li>
<li><a href="{{ base_url }}/topics/browsable-api{{ suffix }}">The Browsable API</a></li>
@@ -198,5 +199,14 @@
$('.dropdown-menu').on('click touchstart', function(event) {
event.stopPropagation();
});
+
+ // Dynamically force sidenav to no higher than browser window
+ $('.side-nav').css('max-height', window.innerHeight - 130);
+
+ $(function(){
+ $(window).resize(function(){
+ $('.side-nav').css('max-height', window.innerHeight - 130);
+ });
+ });
</script>
</body></html>
diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md
index dcba9b0a..85f1faff 100644
--- a/docs/topics/browsable-api.md
+++ b/docs/topics/browsable-api.md
@@ -11,69 +11,85 @@ API may stand for Application *Programming* Interface, but humans have to be abl
If you include fully-qualified URLs in your resource output, they will be 'urlized' and made clickable for easy browsing by humans. The `rest_framework` package includes a [`reverse`][drfreverse] helper for this purpose.
-
## Formats
By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview].
-
## Customizing
-To customize the look-and-feel, create a template called `api.html` and add it to your project, eg: `templates/rest_framework/api.html`, that extends the `rest_framework/base.html` template.
+The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 2.1.1), making it easy to customize the look-and-feel.
+
+To customize the default style, create a template called `rest_framework/api.html` that extends from `rest_framework/base.html`. For example:
-The included browsable API template is built with [Bootstrap (2.1.1)][bootstrap], making it easy to customize the look-and-feel.
+**templates/rest_framework/api.html**
+
+ {% extends "rest_framework/base.html" %}
+
+ ... # Override blocks with required customizations
-### Theme
+### Overriding the default theme
-To replace the theme wholesale, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
+To replace the default theme, add a `bootstrap_theme` block to your `api.html` and insert a `link` to the desired Bootstrap theme css file. This will completely replace the included theme.
{% block bootstrap_theme %}
<link rel="stylesheet" href="/path/to/my/bootstrap.css" type="text/css">
{% endblock %}
-A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. Also, there are pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
+A suitable replacement theme can be generated using Bootstrap's [Customize Tool][bcustomize]. There are also pre-made themes available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above.
You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style.
-Full Example
+Full example:
{% extends "rest_framework/base.html" %}
{% block bootstrap_theme %}
- <link rel="stylesheet" href="/path/to/yourtheme/bootstrap.min.css' type="text/css">
+ <link rel="stylesheet" href="http://bootswatch.com/flatly/bootstrap.min.css" type="text/css">
{% endblock %}
{% block bootstrap_navbar_variant %}{% endblock %}
+For more specific CSS tweaks than simply overriding the default bootstrap theme you can override the `style` block.
+
+---
+
+![Cerulean theme][cerulean]
+
+*Screenshot of the bootswatch 'Cerulean' theme*
+
+---
+
+![Slate theme][slate]
-For more specific CSS tweaks, use the `style` block instead.
+*Screenshot of the bootswatch 'Slate' theme*
+---
### Blocks
All of the blocks available in the browsable API base template that can be used in your `api.html`.
-* `bodyclass` - (empty) class attribute for the `<body>`
-* `bootstrap_theme` - CSS for the Bootstrap theme
-* `bootstrap_navbar_variant` - CSS class for the navbar
-* `branding` - section of the navbar, see [Bootstrap components][bcomponentsnav]
+* `bodyclass` - Class attribute for the `<body>` tag, empty by default.
+* `bootstrap_theme` - CSS for the Bootstrap theme.
+* `bootstrap_navbar_variant` - CSS class for the navbar.
+* `branding` - Branding section of the navbar, see [Bootstrap components][bcomponentsnav].
* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
-* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned)
-* `style` - CSS stylesheets for the page
-* `title` - title of the page
-* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use {{ block.super }} to preserve the authentication links.
+* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned).
+* `style` - CSS stylesheets for the page.
+* `title` - Title of the page.
+* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use `{{ block.super }}` to preserve the authentication links.
#### Components
-All of the [Bootstrap components][bcomponents] are available.
+All of the standard [Bootstrap components][bcomponents] are available.
-##### Tooltips
+#### Tooltips
-The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content displayed in a tooltip on hover after a 1000ms delay.
+The browsable API makes use of the Bootstrap tooltips component. Any element with the `js-tooltip` class and a `title` attribute has that title content will display a tooltip on hover events.
### Login Template
-To add branding and customize the look-and-feel of the auth login template, create a template called `login.html` and add it to your project, eg: `templates/rest_framework/login.html`, that extends the `rest_framework/base_login.html` template.
+To add branding and customize the look-and-feel of the login template, create a template called `login.html` and add it to your project, eg: `templates/rest_framework/login.html`. The template should extend from `rest_framework/base_login.html`.
You can add your site name or branding by including the branding block:
@@ -114,6 +130,8 @@ For more advanced customization, such as not having a Bootstrap basis or tighter
[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
[chromejsonview]: https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc
[bootstrap]: http://getbootstrap.com
+[cerulean]: ../img/cerulean.png
+[slate]: ../img/slate.png
[bcustomize]: http://twitter.github.com/bootstrap/customize.html#variables
[bswatch]: http://bootswatch.com/
[bcomponents]: http://twitter.github.com/bootstrap/components.html
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 3f0ee429..e6fb9134 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -142,6 +142,9 @@ The following people have helped make REST framework great.
* Areski Belaid - [areski]
* Ethan Freman - [mindlace]
* David Sanders - [davesque]
+* Philip Douglas - [freakydug]
+* Igor Kalat - [trwired]
+* Rudolf Olah - [omouse]
Many thanks to everyone who's contributed to the project.
@@ -320,4 +323,6 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[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
diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md
new file mode 100644
index 00000000..7ee538f5
--- /dev/null
+++ b/docs/topics/documenting-your-api.md
@@ -0,0 +1,112 @@
+# Documenting your API
+
+> A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state.
+>
+> &mdash; 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.
+
+## Endpoint documentation
+
+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.
+
+---
+
+#### 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.
+
+The pacakge is fully documented, well supported, and comes highly recommended.
+
+Django REST Swagger supports REST framework versions 2.3 and above.
+
+![Screenshot - Django REST Swagger][image-django-rest-swagger]
+
+---
+
+#### REST Framework Docs
+
+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.
+
+![Screenshot - REST Framework Docs][image-rest-framework-docs]
+
+---
+
+#### Apiary
+
+There are various other online tools and services for providing API documentation. One notable service is [Apiary][apiary]. With Apiary, you describe your API using a simple markdown-like syntax. The generated documentation includes API interaction, a mock server for testing & prototyping, and various other tools.
+
+![Screenshot - Apiary][image-apiary]
+
+---
+
+## Self describing APIs
+
+The browsable API that REST framwork provides makes it possible for your API to be entirely self describing. The documentation for each API endpoint can be provided simply by visiting the URL in your browser.
+
+![Screenshot - Self describing API][image-self-describing-api]
+
+---
+
+#### Setting the title
+
+The title that is used in the browsable API is generated from the view class name or function name. Any trailing `View` or `ViewSet` suffix is stripped, and the string is whitespace separated on uppercase/lowercase boundaries or underscores.
+
+For example, the view `UserListView`, will be named `User List` when presented in the browsable API.
+
+When working with viewsets, an appropriate suffix is appended to each generated view. For example, the view set `UserViewSet` will generate views named `User List` and `User Instance`.
+
+#### Setting the description
+
+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:
+
+ class AccountListView(views.APIView):
+ """
+ Returns a list of all **active** accounts in the system.
+
+ For more details on how accounts are activated please [see here][ref].
+
+ [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.
+
+#### The `OPTIONS` method
+
+REST framework APIs also support programmatically accessible descriptions, using the `OPTIONS` HTTP method. A view will respond to an `OPTIONS` request with metadata including the name, description, and the various media types it accepts and responds with.
+
+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:
+
+ def metadata(self, request):
+ """
+ Don't include the view description in OPTIONS responses.
+ """
+ data = super(ExampleView, self).metadata(request)
+ data.pop('description')
+ return data
+
+---
+
+## The hypermedia approach
+
+To be fully RESTful an API should present it's available actions as hypermedia controls in the responses that it sends.
+
+In this approach, rather than documenting the available API endpoints up front, the description instead concentrates on the *media types* that are used. The available actions take may be taken on any given URL are not strictly fixed, but are instead made available by the presence of link and form controls in the returned document.
+
+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 documention 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/
+[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
+[image-self-describing-api]: ../img/self-describing.png
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index f49dd5c8..d379ab74 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -40,10 +40,15 @@ You can determine your currently installed version using `pip freeze`:
## 2.3.x series
-### Master
+### 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`.
diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
index b4899a36..3d700488 100644
--- a/docs/topics/rest-hypermedia-hateoas.md
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -32,7 +32,7 @@ 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] 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 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.
[cite]: http://vimeo.com/channels/restfest/page:2
[dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
@@ -46,6 +46,7 @@ What REST framework doesn't do is give you is machine readable hypermedia format
[hal]: http://stateless.co/hal_specification.html
[collection]: http://www.amundsen.com/media-types/collection/
+[json-api]: http://jsonapi.org/
[microformats]: http://microformats.org/wiki/Main_Page
[serialization]: ../api-guide/serializers.md
[parser]: ../api-guide/parsers.md
diff --git a/mkdocs.py b/mkdocs.py
index c4da7aea..1e3f1db3 100755
--- a/mkdocs.py
+++ b/mkdocs.py
@@ -70,6 +70,7 @@ path_list = [
'api-guide/exceptions.md',
'api-guide/status-codes.md',
'api-guide/settings.md',
+ 'topics/documenting-your-api.md',
'topics/ajax-csrf-cors.md',
'topics/browser-enhancements.md',
'topics/browsable-api.md',
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 0a210186..776618ac 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '2.3.5'
+__version__ = '2.3.6'
VERSION = __version__ # synonym
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
index f659a172..10298027 100644
--- a/rest_framework/authentication.py
+++ b/rest_framework/authentication.py
@@ -3,14 +3,13 @@ Provides various authentication policies.
"""
from __future__ import unicode_literals
import base64
-from datetime import datetime
from django.contrib.auth import authenticate
from django.core.exceptions import ImproperlyConfigured
from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework.compat import CsrfViewMiddleware
from rest_framework.compat import oauth, oauth_provider, oauth_provider_store
-from rest_framework.compat import oauth2_provider
+from rest_framework.compat import oauth2_provider, provider_now
from rest_framework.authtoken.models import Token
@@ -320,9 +319,9 @@ class OAuth2Authentication(BaseAuthentication):
try:
token = oauth2_provider.models.AccessToken.objects.select_related('user')
- # TODO: Change to timezone aware datetime when oauth2_provider add
- # support to it.
- token = token.get(token=access_token, expires__gt=datetime.now())
+ # provider_now switches to timezone aware datetime when
+ # the oauth2_provider version supports to it.
+ token = token.get(token=access_token, expires__gt=provider_now())
except oauth2_provider.models.AccessToken.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token')
diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py
index 52c45ad1..7601f5b7 100644
--- a/rest_framework/authtoken/models.py
+++ b/rest_framework/authtoken/models.py
@@ -1,7 +1,7 @@
import uuid
import hmac
from hashlib import sha1
-from rest_framework.compat import User
+from rest_framework.compat import AUTH_USER_MODEL
from django.conf import settings
from django.db import models
@@ -11,7 +11,7 @@ class Token(models.Model):
The default authorization token model.
"""
key = models.CharField(max_length=40, primary_key=True)
- user = models.OneToOneField(User, related_name='auth_token')
+ user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token')
created = models.DateTimeField(auto_now_add=True)
class Meta:
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 76dc0052..cb122846 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -2,6 +2,7 @@
The `compat` module provides support for backwards compatibility with older
versions of django/python, and compatibility wrappers around optional packages.
"""
+
# flake8: noqa
from __future__ import unicode_literals
@@ -33,6 +34,12 @@ except ImportError:
from django.utils.encoding import force_unicode as force_text
+# HttpResponseBase only exists from 1.5 onwards
+try:
+ from django.http.response import HttpResponseBase
+except ImportError:
+ from django.http import HttpResponse as HttpResponseBase
+
# django-filter is optional
try:
import django_filters
@@ -77,15 +84,9 @@ def get_concrete_model(model_cls):
# Django 1.5 add support for custom auth user model
if django.VERSION >= (1, 5):
from django.conf import settings
- if hasattr(settings, 'AUTH_USER_MODEL'):
- User = settings.AUTH_USER_MODEL
- else:
- from django.contrib.auth.models import User
+ AUTH_USER_MODEL = settings.AUTH_USER_MODEL
else:
- try:
- from django.contrib.auth.models import User
- except ImportError:
- raise ImportError("User model is not to be found.")
+ AUTH_USER_MODEL = 'auth.User'
if django.VERSION >= (1, 5):
@@ -489,12 +490,22 @@ try:
from provider.oauth2 import forms as oauth2_provider_forms
from provider import scope as oauth2_provider_scope
from provider import constants as oauth2_constants
+ from provider import __version__ as provider_version
+ if provider_version in ('0.2.3', '0.2.4'):
+ # 0.2.3 and 0.2.4 are supported version that do not support
+ # timezone aware datetimes
+ import datetime
+ provider_now = datetime.datetime.now
+ else:
+ # Any other supported version does use timezone aware datetimes
+ from django.utils.timezone import now as provider_now
except ImportError:
oauth2_provider = None
oauth2_provider_models = None
oauth2_provider_forms = None
oauth2_provider_scope = None
oauth2_constants = None
+ provider_now = None
# Handle lazy strings
from django.utils.functional import Promise
diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py
index 25be2e6a..96bfac84 100644
--- a/rest_framework/parsers.py
+++ b/rest_framework/parsers.py
@@ -50,10 +50,7 @@ class JSONParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
"""
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be an object which is the parsed content of the response.
- `files` will always be `None`.
+ Parses the incoming bytestream as JSON and returns the resulting data.
"""
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
@@ -74,10 +71,7 @@ class YAMLParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
"""
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be an object which is the parsed content of the response.
- `files` will always be `None`.
+ Parses the incoming bytestream as YAML and returns the resulting data.
"""
assert yaml, 'YAMLParser requires pyyaml to be installed'
@@ -100,10 +94,8 @@ class FormParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
"""
- Returns a 2-tuple of `(data, files)`.
-
- `data` will be a :class:`QueryDict` containing all the form parameters.
- `files` will always be :const:`None`.
+ Parses the incoming bytestream as a URL encoded form,
+ and returns the resulting QueryDict.
"""
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
@@ -120,7 +112,8 @@ class MultiPartParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
"""
- Returns a DataAndFiles object.
+ Parses the incoming bytestream as a multipart encoded form,
+ and returns a DataAndFiles object.
`.data` will be a `QueryDict` containing all the form parameters.
`.files` will be a `QueryDict` containing all the form files.
@@ -147,6 +140,9 @@ class XMLParser(BaseParser):
media_type = 'application/xml'
def parse(self, stream, media_type=None, parser_context=None):
+ """
+ Parses the incoming bytestream as XML and returns the resulting data.
+ """
assert etree, 'XMLParser requires defusedxml to be installed'
parser_context = parser_context or {}
@@ -216,7 +212,8 @@ class FileUploadParser(BaseParser):
def parse(self, stream, media_type=None, parser_context=None):
"""
- Returns a DataAndFiles object.
+ Treats the incoming bytestream as a raw file upload and returns
+ a `DateAndFiles` object.
`.data` will be None (we expect request body to be a file content).
`.files` will be a `QueryDict` containing one 'file' element.
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index ae64cc3b..930011d3 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -15,7 +15,9 @@ For example, you might have a `urls.py` that looks something like this:
"""
from __future__ import unicode_literals
+import itertools
from collections import namedtuple
+from django.core.exceptions import ImproperlyConfigured
from rest_framework import views
from rest_framework.compat import patterns, url
from rest_framework.response import Response
@@ -38,6 +40,13 @@ def replace_methodname(format_string, methodname):
return ret
+def flatten(list_of_lists):
+ """
+ Takes an iterable of iterables, returns a single iterable containing all items
+ """
+ return itertools.chain(*list_of_lists)
+
+
class BaseRouter(object):
def __init__(self):
self.registry = []
@@ -117,7 +126,7 @@ class SimpleRouter(BaseRouter):
if model_cls is None and queryset is not None:
model_cls = queryset.model
- assert model_cls, '`name` not argument not specified, and could ' \
+ assert model_cls, '`base_name` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
'it does not have a `.model` or `.queryset` attribute.'
@@ -130,12 +139,17 @@ class SimpleRouter(BaseRouter):
Returns a list of the Route namedtuple.
"""
+ known_actions = flatten([route.mapping.values() for route in self.routes])
+
# Determine any `@action` or `@link` decorated methods on the viewset
dynamic_routes = []
for methodname in dir(viewset):
attr = getattr(viewset, methodname)
httpmethods = getattr(attr, 'bind_to_methods', None)
if httpmethods:
+ if methodname in known_actions:
+ raise ImproperlyConfigured('Cannot use @action or @link decorator on '
+ 'method "%s" as it is an existing route' % methodname)
httpmethods = [method.lower() for method in httpmethods]
dynamic_routes.append((httpmethods, methodname))
diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py
index 9dd7b545..b3702d0b 100644
--- a/rest_framework/runtests/settings.py
+++ b/rest_framework/runtests/settings.py
@@ -134,6 +134,8 @@ PASSWORD_HASHERS = (
'django.contrib.auth.hashers.CryptPasswordHasher',
)
+AUTH_USER_MODEL = 'auth.User'
+
import django
if django.VERSION < (1, 3):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5a8fd89f..023f7ccf 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -915,7 +915,10 @@ class HyperlinkedModelSerializer(ModelSerializer):
view_name=self.opts.view_name,
lookup_field=self.opts.lookup_field
)
- fields.insert(0, 'url', url_field)
+ ret = self._dict_class()
+ ret['url'] = url_field
+ ret.update(fields)
+ fields = ret
return fields
diff --git a/rest_framework/tests/description.py b/rest_framework/tests/description.py
new file mode 100644
index 00000000..b46d7f54
--- /dev/null
+++ b/rest_framework/tests/description.py
@@ -0,0 +1,26 @@
+# -- coding: utf-8 --
+
+# Apparently there is a python 2.6 issue where docstrings of imported view classes
+# do not retain their encoding information even if a module has a proper
+# encoding declaration at the top of its source file. Therefore for tests
+# to catch unicode related errors, a mock view has to be declared in a separate
+# module.
+
+from rest_framework.views import APIView
+
+
+# test strings snatched from http://www.columbia.edu/~fdc/utf8/,
+# http://winrus.com/utf8-jap.htm and memory
+UTF8_TEST_DOCSTRING = (
+ 'zażółć gęślą jaźń'
+ 'Sîne klâwen durh die wolken sint geslagen'
+ 'Τη γλώσσα μου έδωσαν ελληνική'
+ 'யாமறிந்த மொழிகளிலே தமிழ்மொழி'
+ 'На берегу пустынных волн'
+ 'てすと'
+ 'アイウエオカキクケコサシスセソタチツテ'
+)
+
+
+class ViewWithNonASCIICharactersInDocstring(APIView):
+ __doc__ = UTF8_TEST_DOCSTRING
diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py
index 52c1a34c..8019f5ec 100644
--- a/rest_framework/tests/test_description.py
+++ b/rest_framework/tests/test_description.py
@@ -2,8 +2,10 @@
from __future__ import unicode_literals
from django.test import TestCase
+from rest_framework.compat import apply_markdown, smart_text
from rest_framework.views import APIView
-from rest_framework.compat import apply_markdown
+from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring
+from rest_framework.tests.description import UTF8_TEST_DOCSTRING
from rest_framework.utils.formatting import get_view_name, get_view_description
# We check that docstrings get nicely un-indented.
@@ -83,11 +85,10 @@ class TestViewNamesAndDescriptions(TestCase):
Unicode in docstrings should be respected.
"""
- class MockView(APIView):
- """Проверка"""
- pass
-
- self.assertEqual(get_view_description(MockView), "Проверка")
+ self.assertEqual(
+ get_view_description(ViewWithNonASCIICharactersInDocstring),
+ smart_text(UTF8_TEST_DOCSTRING)
+ )
def test_view_description_can_be_empty(self):
"""
diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py
index 291142cf..d375f4a8 100644
--- a/rest_framework/tests/test_routers.py
+++ b/rest_framework/tests/test_routers.py
@@ -2,7 +2,8 @@ from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from django.test.client import RequestFactory
-from rest_framework import serializers, viewsets
+from django.core.exceptions import ImproperlyConfigured
+from rest_framework import serializers, viewsets, permissions
from rest_framework.compat import include, patterns, url
from rest_framework.decorators import link, action
from rest_framework.response import Response
@@ -120,7 +121,7 @@ class TestCustomLookupFields(TestCase):
)
-class TestTrailingSlash(TestCase):
+class TestTrailingSlashIncluded(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
model = RouterTestModel
@@ -135,7 +136,7 @@ class TestTrailingSlash(TestCase):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
-class TestTrailingSlash(TestCase):
+class TestTrailingSlashRemoved(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
model = RouterTestModel
@@ -149,6 +150,7 @@ class TestTrailingSlash(TestCase):
for idx in range(len(expected)):
self.assertEqual(expected[idx], self.urls[idx].regex.pattern)
+
class TestNameableRoot(TestCase):
def setUp(self):
class NoteViewSet(viewsets.ModelViewSet):
@@ -162,3 +164,52 @@ class TestNameableRoot(TestCase):
expected = 'nameable-root'
self.assertEqual(expected, self.urls[0].name)
+
+class TestActionKeywordArgs(TestCase):
+ """
+ Ensure keyword arguments passed in the `@action` decorator
+ are properly handled. Refs #940.
+ """
+
+ def setUp(self):
+ class TestViewSet(viewsets.ModelViewSet):
+ permission_classes = []
+
+ @action(permission_classes=[permissions.AllowAny])
+ def custom(self, request, *args, **kwargs):
+ return Response({
+ 'permission_classes': self.permission_classes
+ })
+
+ self.router = SimpleRouter()
+ self.router.register(r'test', TestViewSet, base_name='test')
+ self.view = self.router.urls[-1].callback
+
+ def test_action_kwargs(self):
+ request = factory.post('/test/0/custom/')
+ response = self.view(request)
+ self.assertEqual(
+ response.data,
+ {'permission_classes': [permissions.AllowAny]}
+ )
+
+class TestActionAppliedToExistingRoute(TestCase):
+ """
+ Ensure `@action` decorator raises an except when applied
+ to an existing route
+ """
+
+ def test_exception_raised_when_action_applied_to_existing_route(self):
+ class TestViewSet(viewsets.ModelViewSet):
+
+ @action()
+ def retrieve(self, request, *args, **kwargs):
+ return Response({
+ 'hello': 'world'
+ })
+
+ self.router = SimpleRouter()
+ self.router.register(r'test', TestViewSet, base_name='test')
+
+ with self.assertRaises(ImproperlyConfigured):
+ self.router.urls
diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py
index ebadb3a6..4bec8387 100644
--- a/rest_framework/utils/formatting.py
+++ b/rest_framework/utils/formatting.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from rest_framework.compat import apply_markdown
+from rest_framework.compat import apply_markdown, smart_text
import re
@@ -63,7 +63,7 @@ def get_view_description(cls, html=False):
Return a description for an `APIView` class or `@api_view` function.
"""
description = cls.__doc__ or ''
- description = _remove_leading_indent(description)
+ description = _remove_leading_indent(smart_text(description))
if html:
return markup_description(description)
return description
diff --git a/rest_framework/views.py b/rest_framework/views.py
index c28d2835..37bba7f0 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -4,11 +4,11 @@ Provides an APIView class that is the base of all views in REST framework.
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
-from django.http import Http404, HttpResponse
+from django.http import Http404
from django.utils.datastructures import SortedDict
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions
-from rest_framework.compat import View
+from rest_framework.compat import View, HttpResponseBase
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.settings import api_settings
@@ -244,9 +244,10 @@ class APIView(View):
Returns the final response object.
"""
# Make the error obvious if a proper response is not returned
- assert isinstance(response, HttpResponse), (
- 'Expected a `Response` to be returned from the view, '
- 'but received a `%s`' % type(response)
+ assert isinstance(response, HttpResponseBase), (
+ 'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
+ 'to be returned from the view, but received a `%s`'
+ % type(response)
)
if isinstance(response, Response):