From a8ded528b3c2f66d56f1f5499cf2021f3829c8e4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 9 Sep 2014 08:29:49 -0400 Subject: [PATCH] [1.7.x] Fixed #11775 -- Made ABSOLUTE_URL_OVERRIDES work with models that don't define get_absolute_url(). Thanks jukvalim for the report and initial patch, and Preston Timmons for review. Backport of c32bc1a7a7 from master --- django/db/models/base.py | 17 +++----- docs/ref/settings.txt | 7 ++- docs/releases/1.7.1.txt | 5 +++ tests/absolute_url_overrides/__init__.py | 0 tests/absolute_url_overrides/tests.py | 54 ++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 tests/absolute_url_overrides/__init__.py create mode 100644 tests/absolute_url_overrides/tests.py diff --git a/django/db/models/base.py b/django/db/models/base.py index c593fec4da..c04e53eac9 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals import copy import sys -from functools import update_wrapper import warnings from django.apps import apps @@ -336,9 +335,11 @@ class ModelBase(type): if cls.__doc__ is None: cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join(f.attname for f in opts.fields)) - if hasattr(cls, 'get_absolute_url'): - cls.get_absolute_url = update_wrapper(curry(get_absolute_url, opts, cls.get_absolute_url), - cls.get_absolute_url) + get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get( + '%s.%s' % (opts.app_label, opts.model_name) + ) + if get_absolute_url_override: + setattr(cls, 'get_absolute_url', get_absolute_url_override) signals.class_prepared.send(sender=cls) @@ -1443,14 +1444,6 @@ def method_get_order(ordered_obj, self): ordered_obj.objects.filter(**{order_name: rel_val}).values(pk_name)] -############################################## -# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) # -############################################## - -def get_absolute_url(opts, func, self, *args, **kwargs): - return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.model_name), func)(self, *args, **kwargs) - - ######## # MISC # ######## diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 208f27159b..b8b4f5ffa9 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -28,7 +28,7 @@ ABSOLUTE_URL_OVERRIDES Default: ``{}`` (Empty dictionary) A dictionary mapping ``"app_label.model_name"`` strings to functions that take -a model object and return its URL. This is a way of overriding +a model object and return its URL. This is a way of inserting or overriding ``get_absolute_url()`` methods on a per-installation basis. Example:: ABSOLUTE_URL_OVERRIDES = { @@ -39,6 +39,11 @@ a model object and return its URL. This is a way of overriding Note that the model name used in this setting should be all lower-case, regardless of the case of the actual model class name. +.. versionchanged:: 1.7.1 + + ``ABSOLUTE_URL_OVERRIDES`` now works on models that don't declare + ``get_absolute_url()``. + .. setting:: ADMINS ADMINS diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt index 368749f595..46a19f1f6d 100644 --- a/docs/releases/1.7.1.txt +++ b/docs/releases/1.7.1.txt @@ -26,3 +26,8 @@ Bugfixes * Fixed a typo in an ``inlineformset_factory()`` error message that caused a crash (:ticket:`23451`). + +* Restored the ability to use :setting:`ABSOLUTE_URL_OVERRIDES` with the + ``'auth.User'`` model (:ticket:`11775`). As a side effect, the setting now + adds a ``get_absolute_url()`` method to any model that appears in + ``ABSOLUTE_URL_OVERRIDES`` but doesn't define ``get_absolute_url()``. diff --git a/tests/absolute_url_overrides/__init__.py b/tests/absolute_url_overrides/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/absolute_url_overrides/tests.py b/tests/absolute_url_overrides/tests.py new file mode 100644 index 0000000000..b57374cb6d --- /dev/null +++ b/tests/absolute_url_overrides/tests.py @@ -0,0 +1,54 @@ +from django.db import models +from django.test import TestCase + + +class AbsoluteUrlOverrideTests(TestCase): + + def test_get_absolute_url(self): + """ + get_absolute_url() functions as a normal method. + """ + get_absolute_url = lambda o: '/test-a/%s/' % o.pk + TestA = self._create_model_class('TestA', get_absolute_url) + + self.assertTrue(hasattr(TestA, 'get_absolute_url')) + obj = TestA(pk=1, name='Foo') + self.assertEqual('/test-a/%s/' % obj.pk, obj.get_absolute_url()) + + def test_override_get_absolute_url(self): + """ + ABSOLUTE_URL_OVERRIDES should override get_absolute_url(). + """ + get_absolute_url = lambda o: '/test-b/%s/' % o.pk + with self.settings( + ABSOLUTE_URL_OVERRIDES={ + 'absolute_url_overrides.testb': lambda o: '/overridden-test-b/%s/' % o.pk, + }, + ): + TestB = self._create_model_class('TestB', get_absolute_url) + obj = TestB(pk=1, name='Foo') + self.assertEqual('/overridden-test-b/%s/' % obj.pk, obj.get_absolute_url()) + + def test_insert_get_absolute_url(self): + """ + ABSOLUTE_URL_OVERRIDES should work even if the model doesn't have a + get_absolute_url() method. + """ + with self.settings( + ABSOLUTE_URL_OVERRIDES={ + 'absolute_url_overrides.testc': lambda o: '/test-c/%s/' % o.pk, + }, + ): + TestC = self._create_model_class('TestC') + obj = TestC(pk=1, name='Foo') + self.assertEqual('/test-c/%s/' % obj.pk, obj.get_absolute_url()) + + def _create_model_class(self, class_name, get_absolute_url_method=None): + attrs = { + 'name': models.CharField(max_length=50), + '__module__': 'absolute_url_overrides', + } + if get_absolute_url_method: + attrs['get_absolute_url'] = get_absolute_url_method + + return type(class_name, (models.Model,), attrs)