summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRodrigo Vieira <rodrigo.vieira@gmail.com>2026-04-22 18:53:13 -0300
committerJacob Walls <jacobtylerwalls@gmail.com>2026-04-22 22:22:55 -0400
commita586f03f36f511064f171c0e30f4ca2ebfd60085 (patch)
tree9c7aced48d60452fdcbe5bf6bf99e8de05d60ffa /django
parent61a62be313e395ce1265132bfc99f51476fb3c95 (diff)
Refs #10919 -- Refactored walk_items as module-level _walk_items and added truncated_unordered_list filter.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/templatetags/admin_filters.py69
-rw-r--r--django/template/defaultfilters.py51
2 files changed, 94 insertions, 26 deletions
diff --git a/django/contrib/admin/templatetags/admin_filters.py b/django/contrib/admin/templatetags/admin_filters.py
index d0b17970a6..8c98a32c29 100644
--- a/django/contrib/admin/templatetags/admin_filters.py
+++ b/django/contrib/admin/templatetags/admin_filters.py
@@ -1,7 +1,10 @@
from django import template
from django.contrib.admin.options import EMPTY_VALUE_STRING
from django.contrib.admin.utils import display_for_value
-from django.template.defaultfilters import stringfilter
+from django.template.defaultfilters import _walk_items, stringfilter
+from django.utils.html import conditional_escape
+from django.utils.safestring import mark_safe
+from django.utils.translation import ngettext
register = template.Library()
@@ -10,3 +13,67 @@ register = template.Library()
@stringfilter
def to_object_display_value(value):
return display_for_value(str(value), EMPTY_VALUE_STRING)
+
+
+@register.filter(is_safe=True, needs_autoescape=True)
+def truncated_unordered_list(value, max_items, autoescape=True):
+ """
+ Render an unordered list, showing at most ``max_items`` items and a
+ "...and N more objects." item at the end.
+
+ Usage::
+
+ {{ deleted_objects|truncated_unordered_list:100 }}
+ """
+
+ has_unlimited_items = max_items is None
+ if not has_unlimited_items:
+ max_items = int(max_items)
+ if max_items <= 0:
+ return mark_safe("")
+
+ if autoescape:
+ escaper = conditional_escape
+ else:
+
+ def escaper(x):
+ return x
+
+ item_count = 0
+
+ def list_formatter(item_list, tabs=1):
+ nonlocal item_count
+ indent = "\t" * tabs
+ output = []
+ for item, children in _walk_items(item_list):
+ sublist = ""
+ item_count += 1
+ should_display_item = has_unlimited_items or 0 < item_count <= max_items
+ if children:
+ sublist = "\n%s<ul>\n%s\n%s</ul>\n%s" % (
+ indent,
+ list_formatter(children, tabs + 1),
+ indent,
+ indent,
+ )
+
+ if should_display_item:
+ output.append("%s<li>%s%s</li>" % (indent, escaper(item), sublist))
+
+ return "\n".join(output)
+
+ rendered_object_list = list_formatter(value)
+ remaining_objects_message = ""
+
+ if not has_unlimited_items and item_count > max_items:
+ remaining_object_count = item_count - max_items
+ remaining_objects_message = "\n\t<li>%s</li>" % (
+ ngettext(
+ "…and %(count)d more object.",
+ "…and %(count)d more objects.",
+ remaining_object_count,
+ )
+ % {"count": remaining_object_count}
+ )
+
+ return mark_safe(rendered_object_list + remaining_objects_message)
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 3ed9856c08..68641062a6 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -666,6 +666,31 @@ def slice_filter(value, arg):
return value # Fail silently.
+def _walk_items(item_list):
+ item_iterator = iter(item_list)
+ try:
+ item = next(item_iterator)
+ while True:
+ try:
+ next_item = next(item_iterator)
+ except StopIteration:
+ yield item, None
+ break
+ if isinstance(next_item, (list, tuple, types.GeneratorType)):
+ try:
+ iter(next_item)
+ except TypeError:
+ pass
+ else:
+ yield item, next_item
+ item = next(item_iterator)
+ continue
+ yield item, None
+ item = next_item
+ except StopIteration:
+ pass
+
+
@register.filter(is_safe=True, needs_autoescape=True)
def unordered_list(value, autoescape=True):
"""
@@ -695,34 +720,10 @@ def unordered_list(value, autoescape=True):
def escaper(x):
return x
- def walk_items(item_list):
- item_iterator = iter(item_list)
- try:
- item = next(item_iterator)
- while True:
- try:
- next_item = next(item_iterator)
- except StopIteration:
- yield item, None
- break
- if isinstance(next_item, (list, tuple, types.GeneratorType)):
- try:
- iter(next_item)
- except TypeError:
- pass
- else:
- yield item, next_item
- item = next(item_iterator)
- continue
- yield item, None
- item = next_item
- except StopIteration:
- pass
-
def list_formatter(item_list, tabs=1):
indent = "\t" * tabs
output = []
- for item, children in walk_items(item_list):
+ for item, children in _walk_items(item_list):
sublist = ""
if children:
sublist = "\n%s<ul>\n%s\n%s</ul>\n%s" % (