diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index fdf6e63f5f..7a49587172 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -214,6 +214,7 @@ def items_for_result(cl, result, form): for field_index, field_name in enumerate(cl.list_display): empty_value_display = cl.model_admin.get_empty_value_display() row_classes = ["field-%s" % _coerce_field_name(field_name, field_index)] + link_to_changelist = link_in_col(first, field_name, cl) try: f, attr, value = lookup_field(field_name, result, cl.model_admin) except ObjectDoesNotExist: @@ -240,14 +241,19 @@ def items_for_result(cl, result, form): else: result_repr = field_val else: - result_repr = display_for_field(value, f, empty_value_display) + result_repr = display_for_field( + value, + f, + empty_value_display, + avoid_link=link_to_changelist, + ) if isinstance( f, (models.DateField, models.TimeField, models.ForeignKey) ): row_classes.append("nowrap") row_class = mark_safe(' class="%s"' % " ".join(row_classes)) # If list_display_links not defined, add the link tag to the first field - if link_in_col(first, field_name, cl): + if link_to_changelist: table_tag = "th" if first else "td" first = False diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index c8e722bcc8..0fe0e4e6e6 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -426,7 +426,7 @@ def help_text_for_field(name, model): return help_text -def display_for_field(value, field, empty_value_display): +def display_for_field(value, field, empty_value_display, avoid_link=False): from django.contrib.admin.templatetags.admin_list import _boolean_icon if getattr(field, "flatchoices", None): @@ -452,7 +452,7 @@ def display_for_field(value, field, empty_value_display): return formats.number_format(value, field.decimal_places) elif isinstance(field, (models.IntegerField, models.FloatField)): return formats.number_format(value) - elif isinstance(field, models.FileField) and value: + elif isinstance(field, models.FileField) and value and not avoid_link: return format_html('{}', value.url, value) elif isinstance(field, models.JSONField) and value: try: diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py index 937beea48f..701d60cd05 100644 --- a/tests/admin_changelist/admin.py +++ b/tests/admin_changelist/admin.py @@ -3,7 +3,7 @@ from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.core.paginator import Paginator -from .models import Band, Child, Event, GrandChild, Parent, ProxyUser, Swallow +from .models import Band, Child, Event, Genre, GrandChild, Parent, ProxyUser, Swallow site = admin.AdminSite(name="admin") @@ -157,6 +157,14 @@ class NoListDisplayLinksParentAdmin(admin.ModelAdmin): site.register(Parent, NoListDisplayLinksParentAdmin) +class ListDisplayLinksGenreAdmin(admin.ModelAdmin): + list_display = ["name", "file"] + list_display_links = ["file"] + + +site.register(Genre, ListDisplayLinksGenreAdmin) + + class SwallowAdmin(admin.ModelAdmin): actions = None # prevent ['action_checkbox'] + list(list_display) list_display = ("origin", "load", "speed", "swallowonetoone") diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index 78e65ab878..6b2fba4ced 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -32,6 +32,7 @@ class GrandChild(models.Model): class Genre(models.Model): name = models.CharField(max_length=20) + file = models.FileField(upload_to="documents/", blank=True, null=True) class Band(models.Model): diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 0be6a54ed4..f682ac60bb 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1057,6 +1057,16 @@ class ChangeListTests(TestCase): link = reverse("admin:admin_changelist_parent_change", args=(p.pk,)) self.assertNotContains(response, '' % link) + def test_link_field_display_links(self): + self.client.force_login(self.superuser) + g = Genre.objects.create(name="Blues", file="documents/blues_history.txt") + response = self.client.get(reverse("admin:admin_changelist_genre_changelist")) + self.assertContains( + response, + '' + "documents/blues_history.txt" % g.pk, + ) + def test_clear_all_filters_link(self): self.client.force_login(self.superuser) url = reverse("admin:auth_user_changelist")