Code

Ticket #14396: 14396_list_select_related.diff

File 14396_list_select_related.diff, 7.6 KB (added by danielr, 4 years ago)
Line 
1Index: django/contrib/admin/validation.py
2===================================================================
3--- django/contrib/admin/validation.py  (revision 14496)
4+++ django/contrib/admin/validation.py  (working copy)
5@@ -4,6 +4,7 @@
6     _get_foreign_key)
7 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
8 from django.contrib.admin.options import HORIZONTAL, VERTICAL
9+from django.contrib.admin.util import lookup_field
10 
11 
12 __all__ = ['validate']
13@@ -132,14 +133,17 @@
14                             raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
15                                 % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
16 
17-    # list_select_related = False
18     # save_as = False
19     # save_on_top = False
20-    for attr in ('list_select_related', 'save_as', 'save_on_top'):
21+    for attr in ('save_as', 'save_on_top'):
22         if not isinstance(getattr(cls, attr), bool):
23             raise ImproperlyConfigured("'%s.%s' should be a boolean."
24                     % (cls.__name__, attr))
25 
26+    # list_select_related = False
27+    if not isinstance(cls.list_select_related, (bool, list, tuple)):
28+        raise ImproperlyConfigured("'%s.list_select_related' should be a boolean, a list or a tuple."
29+                                   % cls.__name__)
30 
31     # inlines = []
32     if hasattr(cls, 'inlines'):
33@@ -169,7 +173,7 @@
34     fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
35 
36     # extra = 3
37-    if not isinstance(cls.extra, int):
38+    if not isinstance(getattr(cls, 'extra'), int):
39         raise ImproperlyConfigured("'%s.extra' should be a integer."
40                 % cls.__name__)
41 
42Index: django/contrib/admin/views/main.py
43===================================================================
44--- django/contrib/admin/views/main.py  (revision 14496)
45+++ django/contrib/admin/views/main.py  (working copy)
46@@ -3,6 +3,7 @@
47 from django.contrib.admin.util import quote
48 from django.core.paginator import Paginator, InvalidPage
49 from django.db import models
50+from django.db.models.query import QuerySet
51 from django.utils.encoding import force_unicode, smart_str
52 from django.utils.translation import ugettext
53 from django.utils.http import urlencode
54@@ -201,7 +202,9 @@
55         # with a relationship and the provided queryset doesn't already have
56         # select_related defined.
57         if not qs.query.select_related:
58-            if self.list_select_related:
59+            if isinstance(self.list_select_related, (list, tuple)):
60+                qs = qs.select_related(*self.list_select_related)
61+            elif self.list_select_related:
62                 qs = qs.select_related()
63             else:
64                 for field_name in self.list_display:
65@@ -210,9 +213,8 @@
66                     except models.FieldDoesNotExist:
67                         pass
68                     else:
69-                        if isinstance(f.rel, models.ManyToOneRel):
70-                            qs = qs.select_related()
71-                            break
72+                        if isinstance(f.rel, (models.ManyToOneRel, models.OneToOneRel)):
73+                            qs = qs.select_related(field_name)
74 
75         # Set ordering.
76         if self.order_field:
77Index: tests/regressiontests/admin_changelist/tests.py
78===================================================================
79--- tests/regressiontests/admin_changelist/tests.py     (revision 14496)
80+++ tests/regressiontests/admin_changelist/tests.py     (working copy)
81@@ -2,7 +2,7 @@
82 from django.contrib.admin.views.main import ChangeList
83 from django.template import Context, Template
84 from django.test import TransactionTestCase
85-from regressiontests.admin_changelist.models import Child, Parent
86+from regressiontests.admin_changelist.models import Child, Parent, NullableChild
87 
88 class ChangeListTests(TransactionTestCase):
89     def test_select_related_preserved(self):
90@@ -16,6 +16,18 @@
91                 m.list_select_related, m.list_per_page, m.list_editable, m)
92         self.assertEqual(cl.query_set.query.select_related, {'parent': {'name': {}}})
93 
94+    def test_select_related_nullable(self):
95+        """
96+        Regression test for #14396: allow list_select_related to explicitly
97+        specify relations to follow.
98+        """
99+        m = NullableChildAdmin(NullableChild, admin.site)
100+        cl = ChangeList(MockRequest(), NullableChild, m.list_display,
101+                        m.list_display_links, m.list_filter, m.date_hierarchy,
102+                        m.search_fields, m.list_select_related, m.list_per_page,
103+                        m.list_editable, m)
104+        self.assertEqual(cl.query_set.query.select_related, {'parent': {}})
105+
106     def test_result_list_html(self):
107         """
108         Verifies that inclusion tag result_list generates a table when with
109@@ -76,5 +88,8 @@
110     def queryset(self, request):
111         return super(ChildAdmin, self).queryset(request).select_related("parent__name")
112 
113+class NullableChildAdmin(admin.ModelAdmin):
114+    list_select_related = ('parent',)
115+
116 class MockRequest(object):
117     GET = {}
118Index: tests/regressiontests/admin_changelist/models.py
119===================================================================
120--- tests/regressiontests/admin_changelist/models.py    (revision 14496)
121+++ tests/regressiontests/admin_changelist/models.py    (working copy)
122@@ -6,4 +6,8 @@
123 
124 class Child(models.Model):
125     parent = models.ForeignKey(Parent, editable=False)
126-    name = models.CharField(max_length=30, blank=True)
127\ No newline at end of file
128+    name = models.CharField(max_length=30, blank=True)
129+
130+class NullableChild(models.Model):
131+    parent = models.ForeignKey(Parent, null=True, blank=True)
132+    name = models.CharField(max_length=30, blank=True)
133Index: tests/regressiontests/modeladmin/tests.py
134===================================================================
135--- tests/regressiontests/modeladmin/tests.py   (revision 14496)
136+++ tests/regressiontests/modeladmin/tests.py   (working copy)
137@@ -963,7 +963,7 @@
138 
139         self.assertRaisesRegexp(
140             ImproperlyConfigured,
141-            "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
142+            "'ValidationTestModelAdmin.list_select_related' should be a boolean, a list or a tuple.",
143             validate,
144             ValidationTestModelAdmin,
145             ValidationTestModel,
146@@ -974,6 +974,11 @@
147 
148         validate(ValidationTestModelAdmin, ValidationTestModel)
149 
150+        class ValidationTestModelAdmin(ModelAdmin):
151+            list_select_related = ('field1', 'field2')
152+
153+        validate(ValidationTestModelAdmin, ValidationTestModel)
154+
155     def test_save_as_validation(self):
156 
157         class ValidationTestModelAdmin(ModelAdmin):
158Index: docs/ref/contrib/admin/index.txt
159===================================================================
160--- docs/ref/contrib/admin/index.txt    (revision 14496)
161+++ docs/ref/contrib/admin/index.txt    (working copy)
162@@ -476,11 +476,20 @@
163 objects on the admin change list page. This can save you a bunch of database
164 queries.
165 
166-The value should be either ``True`` or ``False``. Default is ``False``.
167+The value should be one of:
168 
169+* ``True`` or ``False``. If ``True``, the standard ``select_related()`` call
170+  will be used.
171+
172+* A list or tuple of field names. In this case, the value is used as the
173+  argument to :meth:`~django.db.models.QuerySet.select_related` which determines
174+  which relations should be followed.
175+
176+The default is ``False``.
177+
178 Note that Django will use :meth:`~django.db.models.QuerySet.select_related`,
179 regardless of this setting, if one of the ``list_display`` fields is a
180-``ForeignKey``.
181+``ForeignKey`` or ``OneToOneField``.
182 
183 .. attribute:: ModelAdmin.inlines
184