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")