summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRamiro Morales <cramm0@gmail.com>2013-09-06 15:25:13 -0300
committerTim Graham <timograham@gmail.com>2013-10-01 14:25:54 -0400
commit1d0fc61b1cadee584a27dbbe3ef3e21fd3202c85 (patch)
tree86a983c01d2bc888dd0511d3855b56a6a94c7ea6
parentbf757a2f4dad519fac1b4a458376de3a040f5ca8 (diff)
Fixed #15185 -- Allowed ModelAdmin.list_display_links=None to disable change list links.
Thanks rm_ for the suggestion.
-rw-r--r--django/contrib/admin/options.py2
-rw-r--r--django/contrib/admin/templatetags/admin_list.py10
-rw-r--r--django/contrib/admin/validation.py23
-rw-r--r--docs/ref/contrib/admin/index.txt42
-rw-r--r--docs/releases/1.7.txt4
-rw-r--r--tests/admin_changelist/admin.py11
-rw-r--r--tests/admin_changelist/tests.py12
-rw-r--r--tests/modeladmin/tests.py5
8 files changed, 83 insertions, 26 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index c5096076a9..521b2371a6 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -794,7 +794,7 @@ class ModelAdmin(BaseModelAdmin):
on the changelist. The list_display parameter is the list of fields
returned by get_list_display().
"""
- if self.list_display_links or not list_display:
+ if self.list_display_links or self.list_display_links is None or not list_display:
return self.list_display_links
else:
# Use only the first item in list_display as link
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 2b1fd8ad34..f1c11831ab 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -178,6 +178,14 @@ def items_for_result(cl, result, form):
"""
Generates the actual list of data.
"""
+
+ def link_in_col(is_first, field_name, cl):
+ if cl.list_display_links is None:
+ return False
+ if is_first and not cl.list_display_links:
+ return True
+ return field_name in cl.list_display_links
+
first = True
pk = cl.lookup_opts.pk.attname
for field_name in cl.list_display:
@@ -216,7 +224,7 @@ def items_for_result(cl, result, form):
result_repr = mark_safe('&nbsp;')
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
# If list_display_links not defined, add the link tag to the first field
- if (first and not cl.list_display_links) or field_name in cl.list_display_links:
+ if link_in_col(first, field_name, cl):
table_tag = 'th' if first else 'td'
first = False
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 495f72f727..e4a12211b5 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -257,8 +257,10 @@ class ModelAdminValidator(BaseValidator):
% (cls.__name__, idx, field))
def validate_list_display_links(self, cls, model):
- " Validate that list_display_links is a unique subset of list_display. "
+ " Validate that list_display_links either is None or a unique subset of list_display."
if hasattr(cls, 'list_display_links'):
+ if cls.list_display_links is None:
+ return
check_isseq(cls, 'list_display_links', cls.list_display_links)
for idx, field in enumerate(cls.list_display_links):
if field not in cls.list_display:
@@ -344,15 +346,16 @@ class ModelAdminValidator(BaseValidator):
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
"'%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field_name))
- if field_name in cls.list_display_links:
- raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
- " and '%s.list_display_links'"
- % (field_name, cls.__name__, cls.__name__))
- if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
- raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
- " the first field in list_display, '%s', which can't be"
- " used unless list_display_links is set."
- % (cls.__name__, idx, cls.list_display[0]))
+ if cls.list_display_links is not None:
+ if field_name in cls.list_display_links:
+ raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
+ " and '%s.list_display_links'"
+ % (field_name, cls.__name__, cls.__name__))
+ if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
+ raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
+ " the first field in list_display, '%s', which can't be"
+ " used unless list_display_links is set."
+ % (cls.__name__, idx, cls.list_display[0]))
if not field.editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', which isn't editable through the admin."
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 2e02785e81..8873375174 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -648,19 +648,21 @@ subclass::
.. attribute:: ModelAdmin.list_display_links
- Set ``list_display_links`` to control which fields in ``list_display``
- should be linked to the "change" page for an object.
+ Use ``list_display_links`` to control if and which fields in
+ :attr:`list_display` should be linked to the "change" page for an object.
By default, the change list page will link the first column -- the first
field specified in ``list_display`` -- to the change page for each item.
- But ``list_display_links`` lets you change which columns are linked. Set
- ``list_display_links`` to a list or tuple of fields (in the same
- format as ``list_display``) to link.
+ But ``list_display_links`` lets you change this:
- ``list_display_links`` can specify one or many fields. As long as the
- fields appear in ``list_display``, Django doesn't care how many (or
- how few) fields are linked. The only requirement is: If you want to use
- ``list_display_links``, you must define ``list_display``.
+ * Set it to ``None`` to get no links at all.
+ * Set it to a list or tuple of fields (in the same format as
+ ``list_display``) whose columns you want converted to links.
+
+ You can specify one or many fields. As long as the fields appear in
+ ``list_display``, Django doesn't care how many (or how few) fields are
+ linked. The only requirement is that if you want to use
+ ``list_display_links`` in this fashion, you must define ``list_display``.
In this example, the ``first_name`` and ``last_name`` fields will be
linked on the change list page::
@@ -669,7 +671,17 @@ subclass::
list_display = ('first_name', 'last_name', 'birthday')
list_display_links = ('first_name', 'last_name')
- .. _admin-list-editable:
+ In this example, the change list page grid will have no links::
+
+ class AuditEntryAdmin(admin.ModelAdmin):
+ list_display = ('timestamp', 'message')
+ list_display_links = None
+
+ .. versionchanged:: 1.7
+
+ ``None`` was added as a valid ``list_display_links`` value.
+
+.. _admin-list-editable:
.. attribute:: ModelAdmin.list_editable
@@ -1242,9 +1254,13 @@ templates used by the :class:`ModelAdmin` views:
The ``get_list_display_links`` method is given the ``HttpRequest`` and
the ``list`` or ``tuple`` returned by :meth:`ModelAdmin.get_list_display`.
- It is expected to return a ``list`` or ``tuple`` of field names on the
- changelist that will be linked to the change view, as described in the
- :attr:`ModelAdmin.list_display_links` section.
+ It is expected to return either ``None`` or a ``list`` or ``tuple`` of field
+ names on the changelist that will be linked to the change view, as described
+ in the :attr:`ModelAdmin.list_display_links` section.
+
+ .. versionchanged:: 1.7
+
+ ``None`` was added as a valid ``get_list_display_links()`` return value.
.. method:: ModelAdmin.get_fields(self, request, obj=None)
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index a978bb8ac1..d613220508 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -164,6 +164,10 @@ Minor features
new :func:`~django.contrib.admin.register` decorator to register a
:class:`~django.contrib.admin.ModelAdmin`.
+* You may specify :meth:`ModelAdmin.list_display_links
+ <django.contrib.admin.ModelAdmin.list_display_links>` ``= None`` to disable
+ links on the change list page grid.
+
:mod:`django.contrib.auth`
^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index a63f2b60ce..b42f025b79 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -7,6 +7,7 @@ from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
site = admin.AdminSite(name="admin")
+
class CustomPaginator(Paginator):
def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
super(CustomPaginator, self).__init__(queryset, 5, orphans=2,
@@ -80,6 +81,7 @@ class DynamicListDisplayChildAdmin(admin.ModelAdmin):
my_list_display.remove('parent')
return my_list_display
+
class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
list_display = ('parent', 'name', 'age')
list_display_links = ['parent', 'name']
@@ -89,12 +91,20 @@ class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
site.register(Child, DynamicListDisplayChildAdmin)
+
+class NoListDisplayLinksParentAdmin(admin.ModelAdmin):
+ list_display_links = None
+
+site.register(Parent, NoListDisplayLinksParentAdmin)
+
+
class SwallowAdmin(admin.ModelAdmin):
actions = None # prevent ['action_checkbox'] + list(list_display)
list_display = ('origin', 'load', 'speed')
site.register(Swallow, SwallowAdmin)
+
class DynamicListFilterChildAdmin(admin.ModelAdmin):
list_filter = ('parent', 'name', 'age')
@@ -105,6 +115,7 @@ class DynamicListFilterChildAdmin(admin.ModelAdmin):
my_list_filter.remove('parent')
return my_list_filter
+
class DynamicSearchFieldsChildAdmin(admin.ModelAdmin):
search_fields = ('name',)
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 2477edb55a..f9ef079904 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -19,7 +19,7 @@ from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
FilteredChildAdmin, CustomPaginator, site as custom_site,
SwallowAdmin, DynamicListFilterChildAdmin, InvitationAdmin,
- DynamicSearchFieldsChildAdmin)
+ DynamicSearchFieldsChildAdmin, NoListDisplayLinksParentAdmin)
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
UnorderedObject, OrderedObject, CustomIdUser)
@@ -460,6 +460,16 @@ class ChangeListTests(TestCase):
self.assertEqual(list_display, ('parent', 'name', 'age'))
self.assertEqual(list_display_links, ['age'])
+ def test_no_list_display_links(self):
+ """#15185 -- Allow no links from the 'change list' view grid."""
+ p = Parent.objects.create(name='parent')
+ m = NoListDisplayLinksParentAdmin(Parent, admin.site)
+ superuser = self._create_superuser('superuser')
+ request = self._mocked_authenticated_request('/parent/', superuser)
+ response = m.changelist_view(request)
+ link = reverse('admin:admin_changelist_parent_change', args=(p.pk,))
+ self.assertNotContains(response, '<a href="%s">' % link)
+
def test_tuple_list_display(self):
"""
Regression test for #17128
diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py
index 53c977a890..930563f5ef 100644
--- a/tests/modeladmin/tests.py
+++ b/tests/modeladmin/tests.py
@@ -967,6 +967,11 @@ class ValidationTests(unittest.TestCase):
ValidationTestModelAdmin.validate(ValidationTestModel)
+ class ValidationTestModelAdmin(ModelAdmin):
+ list_display_links = None
+
+ ValidationTestModelAdmin.validate(ValidationTestModel)
+
def test_list_filter_validation(self):
class ValidationTestModelAdmin(ModelAdmin):