diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index ea150e10fa..4d4898e1aa 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -153,7 +153,7 @@ See the pagination documentation for further guidance on [setting the pagination --- -**This setting is pending deprecation.** +**This setting has been removed.** See the pagination documentation for further guidance on [setting the pagination style](pagination.md#modifying-the-pagination-style). diff --git a/docs/topics/3.8-announcement.md b/docs/topics/3.8-announcement.md new file mode 100644 index 0000000000..3d0de2d7d6 --- /dev/null +++ b/docs/topics/3.8-announcement.md @@ -0,0 +1,97 @@ + + +# Django REST framework 3.8 + +The 3.8 release is a maintenance focused release resolving a large number of previously outstanding issues and laying +the foundations for future changes. + +--- + +## 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 plan][funding]**. + + +*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).* + +--- + +## Breaking Changes + +### Altered the behaviour of `read_only` plus `default` on Field. + +[#5886][gh5886] `read_only` fields will now **always** be excluded from writable fields. + +Previously `read_only` fields when combined with a `default` value would use the `default` for create and update +operations. This was counter-intuitive in some circumstances and led to difficulties supporting dotted `source` +attributes on nullable relations. + +In order to maintain the old behaviour you may need to pass the value of `read_only` fields when calling `save()` in +the view: + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + +Alternatively you may override `save()` or `create()` or `update()` on the serialiser as appropriate. + +--- + +## Deprecations + +### `action` decorator replaces `list_route` and `detail_route` + +[#5705][gh5705] `list_route` and `detail_route` have been merge into a single `action` decorator. This improves viewset action introspection, and will allow extra actions to be displayed in the Browsable API in future versions. + +Both `list_route` and `detail_route` are now pending deprecation. They will be deprecated in 3.9 and removed entirely +in 3.10. + +The new `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` decorator are now deprecated. They will be removed entirely in 3.9. + +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 + +We're currently working towards moving to using [OpenAPI][openapi] as our default schema output. We'll also be revisiting our API documentation generation and client libraries. + +We're doing some consolidation in order to make this happen. It's planned that 3.9 will drop the `coreapi` and `coreschema` libraries, and instead use `apistar` for the API documentation generation, schema generation, and API client libraries. + +[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/ diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 820fa731b3..4103ef3bd1 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -42,7 +42,41 @@ You can determine your currently installed version using `pip show`: ### 3.8.0 -**Date**: [unreleased][3.8.0-milestone] +**Date**: [3rd April 2018][3.8.0-milestone] + + +* **Breaking Change**: Alter `read_only` plus `default` behaviour. [#5886][gh5886] + + `read_only` fields will now **always** be excluded from writable fields. + + Previously `read_only` fields with a `default` value would use the `default` for create and update operations. + + In order to maintain the old behaviour you may need to pass the value of `read_only` fields when calling `save()` in + the view: + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + Alternatively you may override `save()` or `create()` or `update()` on the serialiser as appropriate. +* Correct allow_null behaviour when required=False [#5888][gh5888] + + Without an explicit `default`, `allow_null` implies a default of `null` for outgoing serialisation. Previously such + fields were being skipped when read-only or otherwise not required. + + **Possible backwards compatibility break** if you were relying on such fields being excluded from the outgoing + representation. In order to restore the old behaviour you can override `data` to exclude the field when `None`. + + For example: + + @property + def data(self): + """ + Drop `maybe_none` field if None. + """ + data = super().data() + if 'maybe_none' in data and data['maybe_none'] is None: + del data['maybe_none'] + return data * Refactor dynamic route generation and improve viewset action introspectibility. [#5705][gh5705] @@ -62,6 +96,61 @@ You can determine your currently installed version using `pip show`: * Deprecated `list_route` & `detail_route` in favor of `action` decorator with `detail` boolean. * Deprecated dynamic list/detail route variants in favor of `DynamicRoute` with `detail` boolean. * Refactored the router's dynamic route generation. +* Fix formatting of the 3.7.4 release note [#5704][gh5704] +* Docs: Update DRF Writable Nested Serializers references [#5711][gh5711] +* Docs: Fixed typo in auth URLs example. [#5713][gh5713] +* Improve composite field child errors [#5655][gh5655] +* Disable HTML inputs for dict/list fields [#5702][gh5702] +* Fix typo in HostNameVersioning doc [#5709][gh5709] +* Use rsplit to get module and classname for imports [#5712][gh5712] +* Formalize URLPatternsTestCase [#5703][gh5703] +* Add exception translation test [#5700][gh5700] +* Test staticfiles [#5701][gh5701] +* Add drf-yasg to documentation and schema 3rd party packages [#5720][gh5720] +* Remove unused `compat._resolve_model()` [#5733][gh5733] +* Drop compat workaround for unsupported Python 3.2 [#5734][gh5734] +* Prefer `iter(dict)` over `iter(dict.keys())` [#5736][gh5736] +* Pass `python_requires` argument to setuptools [#5739][gh5739] +* Remove unused links from docs [#5735][gh5735] +* Prefer https protocol for links in docs when available [#5729][gh5729] +* Add HStoreField, postgres fields tests [#5654][gh5654] +* Always fully qualify ValidationError in docs [#5751][gh5751] +* Remove unreachable code from ManualSchema [#5766][gh5766] +* Allowed customising API documentation code samples [#5752][gh5752] +* Updated docs to use `pip show` [#5757][gh5757] +* Load 'static' instead of 'staticfiles' in templates [#5773][gh5773] +* Fixed a typo in `fields` docs [#5783][gh5783] +* Refer to "NamespaceVersioning" instead of "NamespacedVersioning" in the documentation [#5754][gh5754] +* ErrorDetail: add `__eq__`/`__ne__` and `__repr__` [#5787][gh5787] +* Replace `background-attachment: fixed` in docs [#5777][gh5777] +* Make 404 & 403 responses consistent with `exceptions.APIException` output [#5763][gh5763] +* Small fix to API documentation: schemas [#5796][gh5796] +* Fix schema generation for PrimaryKeyRelatedField [#5764][gh5764] +* Represent serializer DictField as an Object in schema [#5765][gh5765] +* Added docs example reimplementing ObtainAuthToken [#5802][gh5802] +* Add schema to the ObtainAuthToken view [#5676][gh5676] +* Fix request formdata handling [#5800][gh5800] +* Fix authtoken views imports [#5818][gh5818] +* Update pytest, isort [#5815][gh5815] [#5817][gh5817] [#5894][gh5894] +* Fixed active timezone handling for non ISO8601 datetimes. [#5833][gh5833] +* Made TemplateHTMLRenderer render IntegerField inputs when value is `0`. [#5834][gh5834] +* Corrected endpoint in tutorial instructions [#5835][gh5835] +* Add Django Rest Framework Role Filters to Third party packages [#5809][gh5809] +* Use single copy of static assets. Update jQuery [#5823][gh5823] +* Changes ternary conditionals to be PEP308 compliant [#5827][gh5827] +* Added links to 'A Todo List API with React' and 'Blog API' tutorials [#5837][gh5837] +* Fix comment typo in ModelSerializer [#5844][gh5844] +* Add admin to installed apps to avoid test failures. [#5870][gh5870] +* Fixed schema for UUIDField in SimpleMetadata. [#5872][gh5872] +* Corrected docs on router include with namespaces. [#5843][gh5843] +* Test using model objects for dotted source default [#5880][gh5880] +* Allow traversing nullable related fields [#5849][gh5849] +* Added: Tutorial: Django REST with React (Django 2.0) [#5891][gh5891] +* Add `LimitOffsetPagination.get_count` to allow method override [#5846][gh5846] +* Don't show hidden fields in metadata [#5854][gh5854] +* Enable OrderingFilter to handle an empty tuple (or list) for the 'ordering' field. [#5899][gh5899] +* Added generic 500 and 400 JSON error handlers. [#5904][gh5904] + ## 3.7.x series @@ -1778,4 +1867,62 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh5697]: https://github.com/encode/django-rest-framework/issues/5697 +[gh5886]: https://github.com/encode/django-rest-framework/issues/5886 +[gh5888]: https://github.com/encode/django-rest-framework/issues/5888 [gh5705]: https://github.com/encode/django-rest-framework/issues/5705 +[gh5796]: https://github.com/encode/django-rest-framework/issues/5796 +[gh5763]: https://github.com/encode/django-rest-framework/issues/5763 +[gh5777]: https://github.com/encode/django-rest-framework/issues/5777 +[gh5787]: https://github.com/encode/django-rest-framework/issues/5787 +[gh5754]: https://github.com/encode/django-rest-framework/issues/5754 +[gh5783]: https://github.com/encode/django-rest-framework/issues/5783 +[gh5773]: https://github.com/encode/django-rest-framework/issues/5773 +[gh5757]: https://github.com/encode/django-rest-framework/issues/5757 +[gh5752]: https://github.com/encode/django-rest-framework/issues/5752 +[gh5766]: https://github.com/encode/django-rest-framework/issues/5766 +[gh5751]: https://github.com/encode/django-rest-framework/issues/5751 +[gh5654]: https://github.com/encode/django-rest-framework/issues/5654 +[gh5729]: https://github.com/encode/django-rest-framework/issues/5729 +[gh5735]: https://github.com/encode/django-rest-framework/issues/5735 +[gh5739]: https://github.com/encode/django-rest-framework/issues/5739 +[gh5736]: https://github.com/encode/django-rest-framework/issues/5736 +[gh5734]: https://github.com/encode/django-rest-framework/issues/5734 +[gh5733]: https://github.com/encode/django-rest-framework/issues/5733 +[gh5720]: https://github.com/encode/django-rest-framework/issues/5720 +[gh5701]: https://github.com/encode/django-rest-framework/issues/5701 +[gh5700]: https://github.com/encode/django-rest-framework/issues/5700 +[gh5703]: https://github.com/encode/django-rest-framework/issues/5703 +[gh5712]: https://github.com/encode/django-rest-framework/issues/5712 +[gh5709]: https://github.com/encode/django-rest-framework/issues/5709 +[gh5702]: https://github.com/encode/django-rest-framework/issues/5702 +[gh5655]: https://github.com/encode/django-rest-framework/issues/5655 +[gh5713]: https://github.com/encode/django-rest-framework/issues/5713 +[gh5711]: https://github.com/encode/django-rest-framework/issues/5711 +[gh5704]: https://github.com/encode/django-rest-framework/issues/5704 +[gh5854]: https://github.com/encode/django-rest-framework/issues/5854 +[gh5846]: https://github.com/encode/django-rest-framework/issues/5846 +[gh5891]: https://github.com/encode/django-rest-framework/issues/5891 +[gh5849]: https://github.com/encode/django-rest-framework/issues/5849 +[gh5880]: https://github.com/encode/django-rest-framework/issues/5880 +[gh5843]: https://github.com/encode/django-rest-framework/issues/5843 +[gh5872]: https://github.com/encode/django-rest-framework/issues/5872 +[gh5870]: https://github.com/encode/django-rest-framework/issues/5870 +[gh5844]: https://github.com/encode/django-rest-framework/issues/5844 +[gh5837]: https://github.com/encode/django-rest-framework/issues/5837 +[gh5827]: https://github.com/encode/django-rest-framework/issues/5827 +[gh5823]: https://github.com/encode/django-rest-framework/issues/5823 +[gh5809]: https://github.com/encode/django-rest-framework/issues/5809 +[gh5835]: https://github.com/encode/django-rest-framework/issues/5835 +[gh5834]: https://github.com/encode/django-rest-framework/issues/5834 +[gh5833]: https://github.com/encode/django-rest-framework/issues/5833 +[gh5894]: https://github.com/encode/django-rest-framework/issues/5894 +[gh5817]: https://github.com/encode/django-rest-framework/issues/5817 +[gh5815]: https://github.com/encode/django-rest-framework/issues/5815 +[gh5818]: https://github.com/encode/django-rest-framework/issues/5818 +[gh5800]: https://github.com/encode/django-rest-framework/issues/5800 +[gh5676]: https://github.com/encode/django-rest-framework/issues/5676 +[gh5802]: https://github.com/encode/django-rest-framework/issues/5802 +[gh5765]: https://github.com/encode/django-rest-framework/issues/5765 +[gh5764]: https://github.com/encode/django-rest-framework/issues/5764 +[gh5904]: https://github.com/encode/django-rest-framework/issues/5904 +[gh5899]: https://github.com/encode/django-rest-framework/issues/5899 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 5d16490e85..a9657af0e2 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,10 +8,10 @@ """ __title__ = 'Django REST framework' -__version__ = '3.7.7' +__version__ = '3.8.0' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' -__copyright__ = 'Copyright 2011-2017 Tom Christie' +__copyright__ = 'Copyright 2011-2018 Tom Christie' # Version synonym VERSION = __version__ diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 62afa05979..e3428ef5b0 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -78,9 +78,9 @@ def handler(self, *args, **kwargs): if exclude_from_schema: warnings.warn( - "The `exclude_from_schema` argument to `api_view` is pending deprecation. " + "The `exclude_from_schema` argument to `api_view` is deprecated. " "Use the `schema` decorator instead, passing `None`.", - PendingDeprecationWarning + DeprecationWarning ) WrappedAPIView.exclude_from_schema = exclude_from_schema diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index 8807779185..629f92b0da 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -208,10 +208,10 @@ def should_include_endpoint(self, path, callback): return False # Ignore anything except REST framework views. if hasattr(callback.cls, 'exclude_from_schema'): - fmt = ("The `{}.exclude_from_schema` attribute is pending deprecation. " + fmt = ("The `{}.exclude_from_schema` attribute is deprecated. " "Set `schema = None` instead.") msg = fmt.format(callback.cls.__name__) - warnings.warn(msg, PendingDeprecationWarning) + warnings.warn(msg, DeprecationWarning) if getattr(callback.cls, 'exclude_from_schema', False): return False diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 267c445850..081cb809db 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -871,15 +871,15 @@ def test_should_include_endpoint_excludes_correctly(self): assert should_include == expected def test_deprecations(self): - with pytest.warns(PendingDeprecationWarning) as record: + with pytest.warns(DeprecationWarning) as record: @api_view(["GET"], exclude_from_schema=True) def view(request): pass assert len(record) == 1 assert str(record[0].message) == ( - "The `exclude_from_schema` argument to `api_view` is pending " - "deprecation. Use the `schema` decorator instead, passing `None`." + "The `exclude_from_schema` argument to `api_view` is deprecated. " + "Use the `schema` decorator instead, passing `None`." ) class OldFashionedExcludedView(APIView): @@ -893,13 +893,13 @@ def get(self, request, *args, **kwargs): ] inspector = EndpointEnumerator(patterns) - with pytest.warns(PendingDeprecationWarning) as record: + with pytest.warns(DeprecationWarning) as record: inspector.get_api_endpoints() assert len(record) == 1 assert str(record[0].message) == ( "The `OldFashionedExcludedView.exclude_from_schema` attribute is " - "pending deprecation. Set `schema = None` instead." + "deprecated. Set `schema = None` instead." )