Skip to content

Version 3.9 Release #6190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
108 changes: 108 additions & 0 deletions docs/community/3.9-announcement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>

# Django REST framework 3.9

The 3.9 release gives access to _extra actions_ in the Browsable API, introduces composable permissions and built-in [OpenAPI][openapi] schema support.

---

## Funding

If you use REST framework commercially and would like to see this work continue, we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.


TODO: UPDATE SPONSORS.

*We'd like to say thanks in particular our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*

---

## ViewSet _Extra Actions_ available in the Browsable API

Following the introduction of the `action` decorator in v3.8, _extra actions_ defined on a ViewSet are now available
from the Browsable API.

![Extra Actions displayed in the Browsable API](https://user-images.githubusercontent.com/2370209/32976956-1ca9ab7e-cbf1-11e7-981a-a20cb1e83d63.png)

When defined, a dropdown of "Extra Actions", appropriately filtered to detail/non-detail actions, is displayed.

## In-built OpenAPI schema support

TODO

---

## Deprecations

### `DjangoObjectPermissionsFilter` moved to third-party package.

The `DjangoObjectPermissionsFilter` class is pending deprecation, will be deprecated in 3.10 and removed entirely in 3.11.

It has been moved to the third-party [`djangorestframework-guardian`](https://github.com/rpkilby/django-rest-framework-guardian)
package. Please use this instead.

### Router argument/method renamed to use `basename` for consistency.

* The `Router.register` `base_name` argument has been renamed in favor of `basename`.
* The `Router.get_default_base_name` method has been renamed in favor of `Router.get_default_basename`. [#5990][gh5990]

See [#5990][gh5990].

[gh5990]: https://github.com/encode/django-rest-framework/pull/5990

`base_name` and `get_default_base_name()` are pending deprecation. They will be deprecated in 3.10 and removed entirely in 3.11.

### `action` decorator replaces `list_route` and `detail_route`

Both `list_route` and `detail_route` are now deprecated in favour of the single `action` decorator.
They will be removed entirely in 3.10.

The `action` decorator takes a boolean `detail` argument.

* Replace `detail_route` uses with `@action(detail=True)`.
* Replace `list_route` uses with `@action(detail=False)`.

### `exclude_from_schema`

Both `APIView.exclude_from_schema` and the `exclude_from_schema` argument to the `@api_view` have now been removed.

For `APIView` you should instead set a `schema = None` attribute on the view class.

For function based views the `@schema` decorator can be used to exclude the view from the schema, by using `@schema(None)`.

---

## Minor fixes and improvements

There are a large number of minor fixes and improvements in this release. See the [release notes](release-notes.md) page
for a complete listing.


## What's next


TODO...


[funding]: funding.md
[gh5886]: https://github.com/encode/django-rest-framework/issues/5886
[gh5705]: https://github.com/encode/django-rest-framework/issues/5705
[openapi]: https://www.openapis.org/
78 changes: 77 additions & 1 deletion docs/community/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,52 @@ You can determine your currently installed version using `pip show`:

### 3.9.0

**Date**: Unreleased
**Date**: [1st October 2018][3.9.0-milestone]

* Improvements to ViewSet extra actions [#5605][gh5605]
* Fix `action` support for ViewSet suffixes [#6081][gh6081]
* Allow `action` docs sections [#6060][gh6060]
* Deprecate the `Router.register` `base_name` argument in favor of `basename`. [#5990][gh5990]
* Deprecate the `Router.get_default_base_name` method in favor of `Router.get_default_basename`. [#5990][gh5990]
* Change `CharField` to disallow null bytes. [#6073][gh6073]
To revert to the old behavior, subclass `CharField` and remove `ProhibitNullCharactersValidator` from the validators.
```python
class NullableCharField(serializers.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.validators = [v for v in self.validators if not isinstance(v, ProhibitNullCharactersValidator)]
```
* Add `OpenAPIRenderer` and `generate_schema` management command. [#6229][gh6229]
* Add OpenAPIRenderer by default, and add schema docs. [#6233][gh6233]
* Allow permissions to be composed [#5753][gh5753]
* Allow nullable BooleanField in Django 2.1 [#6183][gh6183]
* Add testing of Python 3.7 support [#6141][gh6141]
* Test using Django 2.1 final release. [#6109][gh6109]
* Added djangorestframework-datatables to third-party packages [#5931][gh5931]
* Change ISO 8601 date format to exclude year/month [#5936][gh5936]
* Update all pypi.python.org URLs to pypi.org [#5942][gh5942]
* Ensure that html forms (multipart form data) respect optional fields [#5927][gh5927]
* Allow hashing of ErrorDetail. [#5932][gh5932]
* Correct schema parsing for JSONField [#5878][gh5878]
* Render descriptions (from help_text) using safe [#5869][gh5869]
* Removed input value from deault_error_message [#5881][gh5881]
* Added min_value/max_value support in DurationField [#5643][gh5643]
* Fixed instance being overwritten in pk-only optimization try/except block [#5747][gh5747]
* Fixed AttributeError from items filter when value is None [#5981][gh5981]
* Fixed Javascript `e.indexOf` is not a function error [#5982][gh5982]
* Fix schemas for extra actions [#5992][gh5992]
* Improved get_error_detail to use error_dict/error_list [#5785][gh5785]
* Imprvied URLs in Admin renderer [#5988][gh5988]
* Add "Community" section to docs, minor cleanup [#5993][gh5993]
* Moved guardian imports out of compat [#6054][gh6054]
* Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075]
* Drop Django 1.10 support [#5657][gh5657]
* Only catch TypeError/ValueError for object lookups [#6028][gh6028]
* Handle models without .objects manager in ModelSerializer. [#6111][gh6111]
* Improve ModelSerializer.create() error message. [#6112][gh6112]
* Fix CSRF cookie check failure when using session auth with django 1.11.6+ [#6113][gh6113]
* Updated JWT docs. [#6138][gh6138]
* Fix autoescape not getting passed to urlize_quoted_links filter [#6191][gh6191]


## 3.8.x series
Expand Down Expand Up @@ -1093,6 +1134,7 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.8.0-milestone]: https://github.com/encode/django-rest-framework/milestone/61?closed=1
[3.8.1-milestone]: https://github.com/encode/django-rest-framework/milestone/67?closed=1
[3.8.2-milestone]: https://github.com/encode/django-rest-framework/milestone/68?closed=1
[3.9.0-milestone]: https://github.com/encode/django-rest-framework/milestone/66?closed=1

<!-- 3.0.1 -->
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
Expand Down Expand Up @@ -1974,5 +2016,39 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh5920]: https://github.com/encode/django-rest-framework/issues/5920

<!-- 3.9.0 -->
[gh6109]: https://github.com/encode/django-rest-framework/issues/6109
[gh6141]: https://github.com/encode/django-rest-framework/issues/6141
[gh6113]: https://github.com/encode/django-rest-framework/issues/6113
[gh6112]: https://github.com/encode/django-rest-framework/issues/6112
[gh6111]: https://github.com/encode/django-rest-framework/issues/6111
[gh6028]: https://github.com/encode/django-rest-framework/issues/6028
[gh5657]: https://github.com/encode/django-rest-framework/issues/5657
[gh6054]: https://github.com/encode/django-rest-framework/issues/6054
[gh5993]: https://github.com/encode/django-rest-framework/issues/5993
[gh5990]: https://github.com/encode/django-rest-framework/issues/5990
[gh5988]: https://github.com/encode/django-rest-framework/issues/5988
[gh5785]: https://github.com/encode/django-rest-framework/issues/5785
[gh5992]: https://github.com/encode/django-rest-framework/issues/5992
[gh5605]: https://github.com/encode/django-rest-framework/issues/5605
[gh5982]: https://github.com/encode/django-rest-framework/issues/5982
[gh5981]: https://github.com/encode/django-rest-framework/issues/5981
[gh5747]: https://github.com/encode/django-rest-framework/issues/5747
[gh5643]: https://github.com/encode/django-rest-framework/issues/5643
[gh5881]: https://github.com/encode/django-rest-framework/issues/5881
[gh5869]: https://github.com/encode/django-rest-framework/issues/5869
[gh5878]: https://github.com/encode/django-rest-framework/issues/5878
[gh5932]: https://github.com/encode/django-rest-framework/issues/5932
[gh5927]: https://github.com/encode/django-rest-framework/issues/5927
[gh5942]: https://github.com/encode/django-rest-framework/issues/5942
[gh5936]: https://github.com/encode/django-rest-framework/issues/5936
[gh5931]: https://github.com/encode/django-rest-framework/issues/5931
[gh6183]: https://github.com/encode/django-rest-framework/issues/6183
[gh6075]: https://github.com/encode/django-rest-framework/issues/6075
[gh6138]: https://github.com/encode/django-rest-framework/issues/6138
[gh6081]: https://github.com/encode/django-rest-framework/issues/6081
[gh6073]: https://github.com/encode/django-rest-framework/issues/6073
[gh6191]: https://github.com/encode/django-rest-framework/issues/6191
[gh6060]: https://github.com/encode/django-rest-framework/issues/6060
[gh6233]: https://github.com/encode/django-rest-framework/issues/6233
[gh5753]: https://github.com/encode/django-rest-framework/issues/5753
[gh6229]: https://github.com/encode/django-rest-framework/issues/6229
2 changes: 1 addition & 1 deletion rest_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

__title__ = 'Django REST framework'
__version__ = '3.8.2'
__version__ = '3.9.0'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2018 Tom Christie'
Expand Down
19 changes: 6 additions & 13 deletions rest_framework/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from rest_framework.views import APIView


def api_view(http_method_names=None, exclude_from_schema=False):
def api_view(http_method_names=None):
"""
Decorator that converts a function-based view into an APIView subclass.
Takes a list of allowed methods for the view as an argument.
Expand Down Expand Up @@ -77,15 +77,8 @@ def handler(self, *args, **kwargs):
WrappedAPIView.schema = getattr(func, 'schema',
APIView.schema)

if exclude_from_schema:
warnings.warn(
"The `exclude_from_schema` argument to `api_view` is deprecated. "
"Use the `schema` decorator instead, passing `None`.",
DeprecationWarning
)
WrappedAPIView.exclude_from_schema = exclude_from_schema

return WrappedAPIView.as_view()

return decorator


Expand Down Expand Up @@ -230,9 +223,9 @@ def detail_route(methods=None, **kwargs):
Used to mark a method on a ViewSet that should be routed for detail requests.
"""
warnings.warn(
"`detail_route` is pending deprecation and will be removed in 3.10 in favor of "
"`detail_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
PendingDeprecationWarning, stacklevel=2
DeprecationWarning, stacklevel=2
)

def decorator(func):
Expand All @@ -248,9 +241,9 @@ def list_route(methods=None, **kwargs):
Used to mark a method on a ViewSet that should be routed for list requests.
"""
warnings.warn(
"`list_route` is pending deprecation and will be removed in 3.10 in favor of "
"`list_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
PendingDeprecationWarning, stacklevel=2
DeprecationWarning, stacklevel=2
)

def decorator(func):
Expand Down
14 changes: 7 additions & 7 deletions rest_framework/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@
class DynamicDetailRoute(object):
def __new__(cls, url, name, initkwargs):
warnings.warn(
"`DynamicDetailRoute` is pending deprecation and will be removed in 3.10 "
"`DynamicDetailRoute` is deprecated and will be removed in 3.10 "
"in favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, True, initkwargs)` instead.",
PendingDeprecationWarning, stacklevel=2
DeprecationWarning, stacklevel=2
)
return DynamicRoute(url, name, True, initkwargs)


class DynamicListRoute(object):
def __new__(cls, url, name, initkwargs):
warnings.warn(
"`DynamicListRoute` is pending deprecation and will be removed in 3.10 in "
"`DynamicListRoute` is deprecated and will be removed in 3.10 in "
"favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, False, initkwargs)` instead.",
PendingDeprecationWarning, stacklevel=2
DeprecationWarning, stacklevel=2
)
return DynamicRoute(url, name, False, initkwargs)

Expand All @@ -77,7 +77,7 @@ def flatten(list_of_lists):

class RenameRouterMethods(RenameMethodsBase):
renamed_methods = (
('get_default_base_name', 'get_default_basename', DeprecationWarning),
('get_default_base_name', 'get_default_basename', PendingDeprecationWarning),
)


Expand All @@ -87,8 +87,8 @@ def __init__(self):

def register(self, prefix, viewset, basename=None, base_name=None):
if base_name is not None:
msg = "The `base_name` argument has been deprecated in favor of `basename`."
warnings.warn(msg, DeprecationWarning, 2)
msg = "The `base_name` argument is pending deprecation in favor of `basename`."
warnings.warn(msg, PendingDeprecationWarning, 2)

assert not (basename and base_name), (
"Do not provide both the `basename` and `base_name` arguments.")
Expand Down
9 changes: 0 additions & 9 deletions rest_framework/schemas/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
See schemas.__init__.py for package overview.
"""
import re
import warnings
from collections import Counter, OrderedDict
from importlib import import_module

Expand Down Expand Up @@ -207,14 +206,6 @@ def should_include_endpoint(self, path, callback):
if not is_api_view(callback):
return False # Ignore anything except REST framework views.

if hasattr(callback.cls, 'exclude_from_schema'):
fmt = ("The `{}.exclude_from_schema` attribute is deprecated. "
"Set `schema = None` instead.")
msg = fmt.format(callback.cls.__name__)
warnings.warn(msg, DeprecationWarning)
if getattr(callback.cls, 'exclude_from_schema', False):
return False

if callback.cls.schema is None:
return False

Expand Down
10 changes: 5 additions & 5 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,34 +290,34 @@ def test_action():
raise NotImplementedError

def test_detail_route_deprecation(self):
with pytest.warns(PendingDeprecationWarning) as record:
with pytest.warns(DeprecationWarning) as record:
@detail_route()
def view(request):
raise NotImplementedError

assert len(record) == 1
assert str(record[0].message) == (
"`detail_route` is pending deprecation and will be removed in "
"`detail_route` is deprecated and will be removed in "
"3.10 in favor of `action`, which accepts a `detail` bool. Use "
"`@action(detail=True)` instead."
)

def test_list_route_deprecation(self):
with pytest.warns(PendingDeprecationWarning) as record:
with pytest.warns(DeprecationWarning) as record:
@list_route()
def view(request):
raise NotImplementedError

assert len(record) == 1
assert str(record[0].message) == (
"`list_route` is pending deprecation and will be removed in "
"`list_route` is deprecated and will be removed in "
"3.10 in favor of `action`, which accepts a `detail` bool. Use "
"`@action(detail=False)` instead."
)

def test_route_url_name_from_path(self):
# pre-3.8 behavior was to base the `url_name` off of the `url_path`
with pytest.warns(PendingDeprecationWarning):
with pytest.warns(DeprecationWarning):
@list_route(url_path='foo_bar')
def view(request):
raise NotImplementedError
Expand Down
Loading