diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index e405535ba2..84c6d8d63e 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -285,6 +285,12 @@ The `ordering` attribute may be either a string or a list/tuple of strings. The `DjangoObjectPermissionsFilter` is intended to be used together with the [`django-guardian`][guardian] package, with custom `'view'` permissions added. The filter will ensure that querysets only returns objects for which the user has the appropriate view permission. +--- + +**Note:** This filter has been deprecated as of version 3.9 and moved to the 3rd-party [`djangorestframework-guardian` package][django-rest-framework-guardian]. + +--- + If you're using `DjangoObjectPermissionsFilter`, you'll probably also want to add an appropriate object permissions class, to ensure that users can only operate on instances if they have the appropriate object permissions. The easiest way to do this is to subclass `DjangoObjectPermissions` and add `'view'` permissions to the `perms_map` attribute. A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectPermissions` might look something like this. @@ -388,6 +394,7 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter] [view-permissions-blogpost]: https://blog.nyaruka.com/adding-a-view-permission-to-django-models [search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields [django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters +[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian [django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter [django-url-filter]: https://github.com/miki725/django-url-filter [drf-url-filter]: https://github.com/manjitkumar/drf-url-filters diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index f5fc214cd6..0fef12a594 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -168,9 +168,7 @@ As with `DjangoModelPermissions` you can use custom model permissions by overrid --- -**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests, you'll want to consider also adding the `DjangoObjectPermissionsFilter` class to ensure that list endpoints only return results including objects for which the user has appropriate view permissions. - ---- +**Note**: If you need object level `view` permissions for `GET`, `HEAD` and `OPTIONS` requests and are using django-guardian for your object-level permissions backend, you'll want to consider using the `DjangoObjectPermissionsFilter` class provided by the [`djangorestframework-guardian` package][django-rest-framework-guardian]. It ensures that list endpoints only return results including objects for which the user has appropriate view permissions. --- @@ -287,3 +285,4 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac [django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles [django-rest-framework-api-key]: https://github.com/manosim/django-rest-framework-api-key [django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters +[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index e9f8d5abcc..700d1d97f6 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -46,6 +46,7 @@ You can determine your currently installed version using `pip show`: * 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] +* Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075] ## 3.8.x series @@ -1974,3 +1975,4 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh5990]: https://github.com/encode/django-rest-framework/issues/5990 +[gh6075]: https://github.com/encode/django-rest-framework/issues/6075 diff --git a/rest_framework/filters.py b/rest_framework/filters.py index ab2ce68b40..0627bd8c4a 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import operator +import warnings from functools import reduce from django.core.exceptions import ImproperlyConfigured @@ -284,6 +285,11 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend): has read object level permissions. """ def __init__(self): + warnings.warn( + "`DjangoObjectPermissionsFilter` has been deprecated and moved to " + "the 3rd-party django-rest-framework-guardian package.", + DeprecationWarning, stacklevel=2 + ) assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed' perm_format = '%(app_label)s.view_%(model_name)s' diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 37540eb8ea..31ee75b34c 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -2,6 +2,7 @@ import base64 import unittest +import warnings import django from django.contrib.auth.models import Group, Permission, User @@ -417,17 +418,34 @@ def test_can_read_get_queryset_permissions(self): self.assertEqual(response.status_code, status.HTTP_200_OK) # Read list + def test_django_object_permissions_filter_deprecated(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + DjangoObjectPermissionsFilter() + + message = ("`DjangoObjectPermissionsFilter` has been deprecated and moved " + "to the 3rd-party django-rest-framework-guardian package.") + self.assertEqual(len(w), 1) + self.assertIs(w[-1].category, DeprecationWarning) + self.assertEqual(str(w[-1].message), message) + def test_can_read_list_permissions(self): request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['readonly']) object_permissions_list_view.cls.filter_backends = (DjangoObjectPermissionsFilter,) - response = object_permissions_list_view(request) + # TODO: remove in version 3.10 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + response = object_permissions_list_view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0].get('id'), 1) def test_cannot_read_list_permissions(self): request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['writeonly']) object_permissions_list_view.cls.filter_backends = (DjangoObjectPermissionsFilter,) - response = object_permissions_list_view(request) + # TODO: remove in version 3.10 + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + response = object_permissions_list_view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertListEqual(response.data, [])