Ticket #11058: 1.2.3...ticket-11058-list_display_links.txt

File 1.2.3...ticket-11058-list_display_links.txt, 7.1 KB (added by Chris Adams, 13 years ago)

New patch against 1.2.3

Line 
1From 0a07e0d820f31f47c0052cf62a6b2b7110d5348a Mon Sep 17 00:00:00 2001
2From: Chris Adams <chris@improbable.org>
3Date: Mon, 6 Dec 2010 14:37:05 -0500
4Subject: [PATCH] ModelAdmin validation: allow non-model fields in list_display_links (#11058)
5
6In addition to model field names, list_display accepts callables, ModelAdmin
7attributes or Model attributes.
8
9This patch reuses the list_display resolution for these values for
10list_display_links validation as well and adds a test for each of the three
11extra types.
12
13See http://code.djangoproject.com/ticket/11058
14---
15 django/contrib/admin/validation.py | 18 +++++++++++--
16 docs/ref/contrib/admin/index.txt | 10 ++++----
17 tests/regressiontests/admin_validation/tests.py | 30 ++++++++++++++++++++++-
18 tests/regressiontests/modeladmin/models.py | 2 +-
19 4 files changed, 50 insertions(+), 10 deletions(-)
20
21diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
22index bee2891..9a653ea 100644
23--- a/django/contrib/admin/validation.py
24+++ b/django/contrib/admin/validation.py
25@@ -22,35 +22,47 @@ def validate(cls, model):
26 opts = model._meta
27 validate_base(cls, model)
28
29+ # To handle values other than pure strings, we'll maintain a list of
30+ # possible values allowing the possibility of using ModelAdmin fields
31+ # or arbitrary callables in list_display_links.
32+ list_display_fields = set()
33+
34 # list_display
35 if hasattr(cls, 'list_display'):
36 check_isseq(cls, 'list_display', cls.list_display)
37 for idx, field in enumerate(cls.list_display):
38+ list_display_fields.add(field)
39+
40 if not callable(field):
41 if not hasattr(cls, field):
42 if not hasattr(model, field):
43 try:
44- opts.get_field(field)
45+ f = opts.get_field(field)
46 except models.FieldDoesNotExist:
47 raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
48 % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
49 else:
50 # getattr(model, field) could be an X_RelatedObjectsDescriptor
51 f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field)
52+
53 if isinstance(f, models.ManyToManyField):
54 raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
55 % (cls.__name__, idx, field))
56
57+ list_display_fields.add(f)
58+
59 # list_display_links
60 if hasattr(cls, 'list_display_links'):
61 check_isseq(cls, 'list_display_links', cls.list_display_links)
62+
63 for idx, field in enumerate(cls.list_display_links):
64- fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field)
65- if field not in cls.list_display:
66+ if field not in list_display_fields:
67 raise ImproperlyConfigured("'%s.list_display_links[%d]'"
68 "refers to '%s' which is not defined in 'list_display'."
69 % (cls.__name__, idx, field))
70
71+ del list_display_fields
72+
73 # list_filter
74 if hasattr(cls, 'list_filter'):
75 check_isseq(cls, 'list_filter', cls.list_filter)
76diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
77index 0550576..0cc6c3c 100644
78--- a/docs/ref/contrib/admin/index.txt
79+++ b/docs/ref/contrib/admin/index.txt
80@@ -404,12 +404,12 @@ be linked to the "change" page for an object.
81 By default, the change list page will link the first column -- the first field
82 specified in ``list_display`` -- to the change page for each item. But
83 ``list_display_links`` lets you change which columns are linked. Set
84-``list_display_links`` to a list or tuple of field names (in the same format as
85-``list_display``) to link.
86+``list_display_links`` to a list or tuple of fields in the same format as
87+``list_display`` to link.
88
89-``list_display_links`` can specify one or many field names. As long as the
90-field names appear in ``list_display``, Django doesn't care how many (or how
91-few) fields are linked. The only requirement is: If you want to use
92+``list_display_links`` can specify one or many fields. As long as the fields
93+appear in ``list_display``, Django doesn't care how many (or how few) fields
94+are linked. The only requirement is: If you want to use
95 ``list_display_links``, you must define ``list_display``.
96
97 In this example, the ``first_name`` and ``last_name`` fields will be linked on
98diff --git a/tests/regressiontests/admin_validation/tests.py b/tests/regressiontests/admin_validation/tests.py
99index 9166360..aa44e92 100644
100--- a/tests/regressiontests/admin_validation/tests.py
101+++ b/tests/regressiontests/admin_validation/tests.py
102@@ -6,7 +6,9 @@ from models import Song
103
104
105 class ValidationTestCase(TestCase):
106+
107 def test_readonly_and_editable(self):
108+
109 class SongAdmin(admin.ModelAdmin):
110 readonly_fields = ["original_release"]
111 fieldsets = [
112@@ -14,5 +16,31 @@ class ValidationTestCase(TestCase):
113 "fields": ["title", "original_release"],
114 }),
115 ]
116-
117+
118+ validate(SongAdmin, Song)
119+
120+ def test_list_display_links(self):
121+ """
122+ Confirm that list_display_links supports model fields, model admin
123+ methods and arbitrary callables
124+ """
125+
126+ def fancy_formatter(obj):
127+ return "Custom display of %s" % obj
128+
129+ class SongAdmin(admin.ModelAdmin):
130+ foo_date = fancy_formatter
131+
132+ list_display = ("title", "album", "original_release",
133+ "readonly_method_on_model", "short_title",
134+ foo_date)
135+ list_display_links = ("title", "album", "original_release",
136+ "readonly_method_on_model", "short_title",
137+ foo_date)
138+
139+ def short_title(self, obj):
140+ # In real code this would use truncatewords/chars:
141+ return obj.title[:20]
142+ short_title.short_description = "Short Title"
143+
144 validate(SongAdmin, Song)
145diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
146index 36ea416..8ca2031 100644
147--- a/tests/regressiontests/modeladmin/models.py
148+++ b/tests/regressiontests/modeladmin/models.py
149@@ -653,7 +653,7 @@ ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links' must be a li
150 >>> validate(ValidationTestModelAdmin, ValidationTestModel)
151 Traceback (most recent call last):
152 ...
153-ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links[0]' refers to 'non_existent_field' that is neither a field, method or property of model 'ValidationTestModel'.
154+ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links[0]'refers to 'non_existent_field' which is not defined in 'list_display'.
155
156 >>> class ValidationTestModelAdmin(ModelAdmin):
157 ... list_display_links = ('name',)
158--
1591.7.0.2
160
Back to Top