diff --git a/django/db/migrations/recorder.py b/django/db/migrations/recorder.py index c3ed4148e7..20f5a7d85e 100644 --- a/django/db/migrations/recorder.py +++ b/django/db/migrations/recorder.py @@ -1,7 +1,7 @@ from django.apps.registry import Apps from django.db import models from django.db.utils import DatabaseError -from django.utils.decorators import classproperty +from django.utils.functional import classproperty from django.utils.timezone import now from .exceptions import MigrationSchemaMissing diff --git a/django/test/selenium.py b/django/test/selenium.py index 8910faec98..a114f77d14 100644 --- a/django/test/selenium.py +++ b/django/test/selenium.py @@ -3,7 +3,7 @@ import unittest from contextlib import contextmanager from django.test import LiveServerTestCase, tag -from django.utils.decorators import classproperty +from django.utils.functional import classproperty from django.utils.module_loading import import_string from django.utils.text import capfirst diff --git a/django/test/testcases.py b/django/test/testcases.py index ca5712ee8d..23459f22cd 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -37,7 +37,7 @@ from django.test.utils import ( CaptureQueriesContext, ContextList, compare_xml, modify_settings, override_settings, ) -from django.utils.decorators import classproperty +from django.utils.functional import classproperty from django.views.static import serve __all__ = ('TestCase', 'TransactionTestCase', diff --git a/django/utils/decorators.py b/django/utils/decorators.py index 8e274b61af..4dc6c18f45 100644 --- a/django/utils/decorators.py +++ b/django/utils/decorators.py @@ -150,15 +150,3 @@ def make_middleware_decorator(middleware_class): return _wrapped_view return _decorator return _make_decorator - - -class classproperty: - def __init__(self, method=None): - self.fget = method - - def __get__(self, instance, cls=None): - return self.fget(cls) - - def getter(self, method): - self.fget = method - return self diff --git a/django/utils/functional.py b/django/utils/functional.py index 1b81d414fa..bc3b65b709 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -49,6 +49,18 @@ class cached_property: return res +class classproperty: + def __init__(self, method=None): + self.fget = method + + def __get__(self, instance, cls=None): + return self.fget(cls) + + def getter(self, method): + self.fget = method + return self + + class Promise: """ Base class for the proxy class created in the closure of the lazy function. diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 656649f914..300c7a2c26 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -263,6 +263,9 @@ Miscellaneous ``ETag`` header to responses with an empty :attr:`~django.http.HttpResponse.content`. +* ``django.utils.decorators.classproperty()`` decorator is moved to + ``django.utils.functional.classproperty()``. + .. _deprecated-features-3.1: Features deprecated in 3.1 diff --git a/tests/utils_tests/test_decorators.py b/tests/utils_tests/test_decorators.py index fe5db876ef..367e78a4ad 100644 --- a/tests/utils_tests/test_decorators.py +++ b/tests/utils_tests/test_decorators.py @@ -2,7 +2,7 @@ from django.http import HttpResponse from django.template import engines from django.template.response import TemplateResponse from django.test import RequestFactory, SimpleTestCase -from django.utils.decorators import classproperty, decorator_from_middleware +from django.utils.decorators import decorator_from_middleware class ProcessViewMiddleware: @@ -108,41 +108,3 @@ class DecoratorFromMiddlewareTests(SimpleTestCase): self.assertTrue(getattr(request, 'process_response_reached', False)) # process_response saw the rendered content self.assertEqual(request.process_response_content, b"Hello world") - - -class ClassPropertyTest(SimpleTestCase): - def test_getter(self): - class Foo: - foo_attr = 123 - - def __init__(self): - self.foo_attr = 456 - - @classproperty - def foo(cls): - return cls.foo_attr - - class Bar: - bar = classproperty() - - @bar.getter - def bar(cls): - return 123 - - self.assertEqual(Foo.foo, 123) - self.assertEqual(Foo().foo, 123) - self.assertEqual(Bar.bar, 123) - self.assertEqual(Bar().bar, 123) - - def test_override_getter(self): - class Foo: - @classproperty - def foo(cls): - return 123 - - @foo.getter - def foo(cls): - return 456 - - self.assertEqual(Foo.foo, 456) - self.assertEqual(Foo().foo, 456) diff --git a/tests/utils_tests/test_functional.py b/tests/utils_tests/test_functional.py index af4bd197a6..6e454cfef3 100644 --- a/tests/utils_tests/test_functional.py +++ b/tests/utils_tests/test_functional.py @@ -1,7 +1,7 @@ from unittest import mock from django.test import SimpleTestCase -from django.utils.functional import cached_property, lazy +from django.utils.functional import cached_property, classproperty, lazy class FunctionalTests(SimpleTestCase): @@ -218,3 +218,39 @@ class FunctionalTests(SimpleTestCase): with mock.patch.object(__proxy__, '__prepare_class__') as mocked: lazified() mocked.assert_not_called() + + def test_classproperty_getter(self): + class Foo: + foo_attr = 123 + + def __init__(self): + self.foo_attr = 456 + + @classproperty + def foo(cls): + return cls.foo_attr + + class Bar: + bar = classproperty() + + @bar.getter + def bar(cls): + return 123 + + self.assertEqual(Foo.foo, 123) + self.assertEqual(Foo().foo, 123) + self.assertEqual(Bar.bar, 123) + self.assertEqual(Bar().bar, 123) + + def test_classproperty_override_getter(self): + class Foo: + @classproperty + def foo(cls): + return 123 + + @foo.getter + def foo(cls): + return 456 + + self.assertEqual(Foo.foo, 456) + self.assertEqual(Foo().foo, 456)