diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 260dc476c..2a8b0839b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -24,6 +24,12 @@ The timeline for deprecation of a feature present in version 1.0 would work as f * Version 1.3 would remove the deprecated bits of API entirely. +Deprecations are marked using `rest_framework.compat.deprecated`, which accepts a version tuple for the version when code is first deprecated and message to pass to the `warnings` module: + + from rest_framework.compat import deprecated + ... + deprecated((3,1,0), "Using X for Y is deprecated. Prefer Z") + Note that in line with Django's policy, any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change. ## Upgrading diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 460795f65..b603b190a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -6,12 +6,32 @@ versions of Django/Python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals +import warnings + import django from django.conf import settings from django.db import connection, transaction from django.utils import six from django.views.generic import View +from rest_framework import VERSION + + +def deprecated(since, message): + current_version = [int(i) for i in VERSION.split('.')] + + assert current_version[0] == since[0], "Deprecated code must be removed before major version change. Current: {0} vs Deprecated Since: {1}".format(current_version[0], since[0]) + + minor_version_difference = current_version[1] - since[1] + assert minor_version_difference in [1,2], "Deprecated code must be removed within two minor versions" + + warning_type = PendingDeprecationWarning if minor_version_difference == 1 else DeprecationWarning + + warnings.warn(message, warning_type) + + + + try: import importlib # Available in Python 3.1+ except ImportError: diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index bf72ef4fc..4730623f7 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -5,7 +5,6 @@ be used for paginated responses. """ from __future__ import unicode_literals -import warnings from base64 import b64decode, b64encode from collections import namedtuple @@ -16,7 +15,7 @@ from django.utils import six from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import OrderedDict +from rest_framework.compat import OrderedDict, deprecated from rest_framework.exceptions import NotFound from rest_framework.response import Response from rest_framework.settings import api_settings @@ -216,13 +215,11 @@ class PageNumberPagination(BasePagination): value = getattr(api_settings, settings_key, None) if value is not None: setattr(self, attr_name, value) - warnings.warn( - "The `%s` settings key is deprecated. " - "Use the `%s` attribute on the pagination class instead." % ( - settings_key, attr_name - ), - DeprecationWarning, - ) + deprecated((3, 0, 0), + "The `%s` settings key is deprecated. " + "Use the `%s` attribute on the pagination class instead." % ( + settings_key, attr_name) + ) for (view_attr, attr_name) in ( ('paginate_by', 'page_size'), @@ -233,13 +230,11 @@ class PageNumberPagination(BasePagination): value = getattr(view, view_attr, None) if value is not None: setattr(self, attr_name, value) - warnings.warn( - "The `%s` view attribute is deprecated. " - "Use the `%s` attribute on the pagination class instead." % ( - view_attr, attr_name - ), - DeprecationWarning, - ) + deprecated((3, 0, 0), + "The `%s` view attribute is deprecated. " + "Use the `%s` attribute on the pagination class instead." % ( + view_attr, attr_name) + ) def paginate_queryset(self, queryset, request, view=None): """ diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 9c30c2f8b..6a6e3d26a 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -6,13 +6,13 @@ from django.http import Http404 from django.test import TestCase, TransactionTestCase from django.utils.decorators import method_decorator from django.utils.unittest import skipUnless +from tests.models import BasicModel from rest_framework import status from rest_framework.exceptions import APIException from rest_framework.response import Response from rest_framework.test import APIRequestFactory from rest_framework.views import APIView -from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_generics.py b/tests/test_generics.py index 000adffa7..4398febc3 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -6,13 +6,13 @@ from django.db import models from django.shortcuts import get_object_or_404 from django.test import TestCase from django.utils import six +from tests.models import ( + BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel +) from rest_framework import generics, renderers, serializers, status from rest_framework.response import Response from rest_framework.test import APIRequestFactory -from tests.models import ( - BasicModel, ForeignKeySource, ForeignKeyTarget, RESTFrameworkModel -) factory = APIRequestFactory() diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index 340d4966a..5867f1966 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -2,9 +2,9 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase +from tests.models import RESTFrameworkModel from rest_framework import serializers -from tests.models import RESTFrameworkModel # Models diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 398020002..ac0ecab90 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -7,6 +7,7 @@ from django.core.urlresolvers import ResolverMatch from django.db import models from django.test import TestCase from django.utils import unittest +from tests.models import BasicModel from rest_framework import ( HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, @@ -16,7 +17,6 @@ from rest_framework.compat import get_model_name, guardian from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory -from tests.models import BasicModel factory = APIRequestFactory() diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index c0642eda2..b203884d8 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -2,14 +2,14 @@ from __future__ import unicode_literals from django.conf.urls import url from django.test import TestCase - -from rest_framework import serializers -from rest_framework.test import APIRequestFactory from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +from rest_framework import serializers +from rest_framework.test import APIRequestFactory + factory = APIRequestFactory() request = factory.get('/') # Just to ensure we have a request in the serializer context diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 169f7d9c5..fb797e993 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -2,13 +2,13 @@ from __future__ import unicode_literals from django.test import TestCase from django.utils import six - -from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +from rest_framework import serializers + # ManyToMany class ManyToManyTargetSerializer(serializers.ModelSerializer): diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index 680aee417..cee18031d 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -1,10 +1,10 @@ from django.test import TestCase - -from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, NullableForeignKeySource ) +from rest_framework import serializers + class ForeignKeyTargetSerializer(serializers.ModelSerializer): sources = serializers.SlugRelatedField( diff --git a/tests/test_response.py b/tests/test_response.py index 1dd5d5ea0..84b0935d7 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.conf.urls import include, url from django.test import TestCase from django.utils import six +from tests.models import BasicModel from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.renderers import ( @@ -11,7 +12,6 @@ from rest_framework.renderers import ( from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.views import APIView -from tests.models import BasicModel # Serializer used to test BasicModel diff --git a/tests/test_utils.py b/tests/test_utils.py index 062f78e11..acda7adf8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,12 +4,12 @@ from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.utils import six +from tests.models import BasicModel import rest_framework.utils.model_meta from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.model_meta import _resolve_model from rest_framework.views import APIView -from tests.models import BasicModel class Root(APIView):