Fixed #15185 -- Allowed ModelAdmin.list_display_links=None to disable change list links.
Thanks rm_ for the suggestion.
This commit is contained in:
parent
bf757a2f4d
commit
1d0fc61b1c
|
@ -794,7 +794,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
on the changelist. The list_display parameter is the list of fields
|
on the changelist. The list_display parameter is the list of fields
|
||||||
returned by get_list_display().
|
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
|
return self.list_display_links
|
||||||
else:
|
else:
|
||||||
# Use only the first item in list_display as link
|
# Use only the first item in list_display as link
|
||||||
|
|
|
@ -178,6 +178,14 @@ def items_for_result(cl, result, form):
|
||||||
"""
|
"""
|
||||||
Generates the actual list of data.
|
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
|
first = True
|
||||||
pk = cl.lookup_opts.pk.attname
|
pk = cl.lookup_opts.pk.attname
|
||||||
for field_name in cl.list_display:
|
for field_name in cl.list_display:
|
||||||
|
@ -216,7 +224,7 @@ def items_for_result(cl, result, form):
|
||||||
result_repr = mark_safe(' ')
|
result_repr = mark_safe(' ')
|
||||||
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
|
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
|
||||||
# If list_display_links not defined, add the link tag to the first field
|
# 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'
|
table_tag = 'th' if first else 'td'
|
||||||
first = False
|
first = False
|
||||||
|
|
||||||
|
|
|
@ -257,8 +257,10 @@ class ModelAdminValidator(BaseValidator):
|
||||||
% (cls.__name__, idx, field))
|
% (cls.__name__, idx, field))
|
||||||
|
|
||||||
def validate_list_display_links(self, cls, model):
|
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 hasattr(cls, 'list_display_links'):
|
||||||
|
if cls.list_display_links is None:
|
||||||
|
return
|
||||||
check_isseq(cls, 'list_display_links', cls.list_display_links)
|
check_isseq(cls, 'list_display_links', cls.list_display_links)
|
||||||
for idx, field in enumerate(cls.list_display_links):
|
for idx, field in enumerate(cls.list_display_links):
|
||||||
if field not in cls.list_display:
|
if field not in cls.list_display:
|
||||||
|
@ -344,15 +346,16 @@ class ModelAdminValidator(BaseValidator):
|
||||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
|
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
|
||||||
"'%s' which is not defined in 'list_display'."
|
"'%s' which is not defined in 'list_display'."
|
||||||
% (cls.__name__, idx, field_name))
|
% (cls.__name__, idx, field_name))
|
||||||
if field_name in cls.list_display_links:
|
if cls.list_display_links is not None:
|
||||||
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
|
if field_name in cls.list_display_links:
|
||||||
" and '%s.list_display_links'"
|
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
|
||||||
% (field_name, cls.__name__, cls.__name__))
|
" and '%s.list_display_links'"
|
||||||
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
|
% (field_name, cls.__name__, cls.__name__))
|
||||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
|
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
|
||||||
" the first field in list_display, '%s', which can't be"
|
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
|
||||||
" used unless list_display_links is set."
|
" the first field in list_display, '%s', which can't be"
|
||||||
% (cls.__name__, idx, cls.list_display[0]))
|
" used unless list_display_links is set."
|
||||||
|
% (cls.__name__, idx, cls.list_display[0]))
|
||||||
if not field.editable:
|
if not field.editable:
|
||||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
|
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
|
||||||
"field, '%s', which isn't editable through the admin."
|
"field, '%s', which isn't editable through the admin."
|
||||||
|
|
|
@ -648,19 +648,21 @@ subclass::
|
||||||
|
|
||||||
.. attribute:: ModelAdmin.list_display_links
|
.. attribute:: ModelAdmin.list_display_links
|
||||||
|
|
||||||
Set ``list_display_links`` to control which fields in ``list_display``
|
Use ``list_display_links`` to control if and which fields in
|
||||||
should be linked to the "change" page for an object.
|
: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
|
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.
|
field specified in ``list_display`` -- to the change page for each item.
|
||||||
But ``list_display_links`` lets you change which columns are linked. Set
|
But ``list_display_links`` lets you change this:
|
||||||
``list_display_links`` to a list or tuple of fields (in the same
|
|
||||||
format as ``list_display``) to link.
|
|
||||||
|
|
||||||
``list_display_links`` can specify one or many fields. As long as the
|
* Set it to ``None`` to get no links at all.
|
||||||
fields appear in ``list_display``, Django doesn't care how many (or
|
* Set it to a list or tuple of fields (in the same format as
|
||||||
how few) fields are linked. The only requirement is: If you want to use
|
``list_display``) whose columns you want converted to links.
|
||||||
``list_display_links``, you must define ``list_display``.
|
|
||||||
|
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
|
In this example, the ``first_name`` and ``last_name`` fields will be
|
||||||
linked on the change list page::
|
linked on the change list page::
|
||||||
|
@ -669,7 +671,17 @@ subclass::
|
||||||
list_display = ('first_name', 'last_name', 'birthday')
|
list_display = ('first_name', 'last_name', 'birthday')
|
||||||
list_display_links = ('first_name', 'last_name')
|
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
|
.. 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 ``get_list_display_links`` method is given the ``HttpRequest`` and
|
||||||
the ``list`` or ``tuple`` returned by :meth:`ModelAdmin.get_list_display`.
|
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
|
It is expected to return either ``None`` or a ``list`` or ``tuple`` of field
|
||||||
changelist that will be linked to the change view, as described in the
|
names on the changelist that will be linked to the change view, as described
|
||||||
:attr:`ModelAdmin.list_display_links` section.
|
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)
|
.. method:: ModelAdmin.get_fields(self, request, obj=None)
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,10 @@ Minor features
|
||||||
new :func:`~django.contrib.admin.register` decorator to register a
|
new :func:`~django.contrib.admin.register` decorator to register a
|
||||||
:class:`~django.contrib.admin.ModelAdmin`.
|
: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`
|
:mod:`django.contrib.auth`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
|
||||||
|
|
||||||
site = admin.AdminSite(name="admin")
|
site = admin.AdminSite(name="admin")
|
||||||
|
|
||||||
|
|
||||||
class CustomPaginator(Paginator):
|
class CustomPaginator(Paginator):
|
||||||
def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
|
def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True):
|
||||||
super(CustomPaginator, self).__init__(queryset, 5, orphans=2,
|
super(CustomPaginator, self).__init__(queryset, 5, orphans=2,
|
||||||
|
@ -80,6 +81,7 @@ class DynamicListDisplayChildAdmin(admin.ModelAdmin):
|
||||||
my_list_display.remove('parent')
|
my_list_display.remove('parent')
|
||||||
return my_list_display
|
return my_list_display
|
||||||
|
|
||||||
|
|
||||||
class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
|
class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
|
||||||
list_display = ('parent', 'name', 'age')
|
list_display = ('parent', 'name', 'age')
|
||||||
list_display_links = ['parent', 'name']
|
list_display_links = ['parent', 'name']
|
||||||
|
@ -89,12 +91,20 @@ class DynamicListDisplayLinksChildAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
site.register(Child, DynamicListDisplayChildAdmin)
|
site.register(Child, DynamicListDisplayChildAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
class NoListDisplayLinksParentAdmin(admin.ModelAdmin):
|
||||||
|
list_display_links = None
|
||||||
|
|
||||||
|
site.register(Parent, NoListDisplayLinksParentAdmin)
|
||||||
|
|
||||||
|
|
||||||
class SwallowAdmin(admin.ModelAdmin):
|
class SwallowAdmin(admin.ModelAdmin):
|
||||||
actions = None # prevent ['action_checkbox'] + list(list_display)
|
actions = None # prevent ['action_checkbox'] + list(list_display)
|
||||||
list_display = ('origin', 'load', 'speed')
|
list_display = ('origin', 'load', 'speed')
|
||||||
|
|
||||||
site.register(Swallow, SwallowAdmin)
|
site.register(Swallow, SwallowAdmin)
|
||||||
|
|
||||||
|
|
||||||
class DynamicListFilterChildAdmin(admin.ModelAdmin):
|
class DynamicListFilterChildAdmin(admin.ModelAdmin):
|
||||||
list_filter = ('parent', 'name', 'age')
|
list_filter = ('parent', 'name', 'age')
|
||||||
|
|
||||||
|
@ -105,6 +115,7 @@ class DynamicListFilterChildAdmin(admin.ModelAdmin):
|
||||||
my_list_filter.remove('parent')
|
my_list_filter.remove('parent')
|
||||||
return my_list_filter
|
return my_list_filter
|
||||||
|
|
||||||
|
|
||||||
class DynamicSearchFieldsChildAdmin(admin.ModelAdmin):
|
class DynamicSearchFieldsChildAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
|
||||||
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
|
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
|
||||||
FilteredChildAdmin, CustomPaginator, site as custom_site,
|
FilteredChildAdmin, CustomPaginator, site as custom_site,
|
||||||
SwallowAdmin, DynamicListFilterChildAdmin, InvitationAdmin,
|
SwallowAdmin, DynamicListFilterChildAdmin, InvitationAdmin,
|
||||||
DynamicSearchFieldsChildAdmin)
|
DynamicSearchFieldsChildAdmin, NoListDisplayLinksParentAdmin)
|
||||||
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
|
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
|
||||||
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
|
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
|
||||||
UnorderedObject, OrderedObject, CustomIdUser)
|
UnorderedObject, OrderedObject, CustomIdUser)
|
||||||
|
@ -460,6 +460,16 @@ class ChangeListTests(TestCase):
|
||||||
self.assertEqual(list_display, ('parent', 'name', 'age'))
|
self.assertEqual(list_display, ('parent', 'name', 'age'))
|
||||||
self.assertEqual(list_display_links, ['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):
|
def test_tuple_list_display(self):
|
||||||
"""
|
"""
|
||||||
Regression test for #17128
|
Regression test for #17128
|
||||||
|
|
|
@ -967,6 +967,11 @@ class ValidationTests(unittest.TestCase):
|
||||||
|
|
||||||
ValidationTestModelAdmin.validate(ValidationTestModel)
|
ValidationTestModelAdmin.validate(ValidationTestModel)
|
||||||
|
|
||||||
|
class ValidationTestModelAdmin(ModelAdmin):
|
||||||
|
list_display_links = None
|
||||||
|
|
||||||
|
ValidationTestModelAdmin.validate(ValidationTestModel)
|
||||||
|
|
||||||
def test_list_filter_validation(self):
|
def test_list_filter_validation(self):
|
||||||
|
|
||||||
class ValidationTestModelAdmin(ModelAdmin):
|
class ValidationTestModelAdmin(ModelAdmin):
|
||||||
|
|
Loading…
Reference in New Issue