django icon indicating copy to clipboard operation
django copied to clipboard

Fixed #35333 -- Ensured that date and time filters honor the `unlocalize` tag

Open AhmedNassar7 opened this issue 1 year ago • 1 comments

Trac ticket number

ticket-35333

Branch description

The date and time template filters now respect the localization context when used with the unlocalize tag or within a {% localize off %} block. This is achieved by passing the current localization context (use_l10n flag) from the template rendering context to the filter functions, allowing them to properly format dates and times according to the unlocalized settings when localization is disabled. Previously, these filters would ignore the localization context setting.

Thanks to @claudep for the initial work on PR #18021 and to @nessita for guidance on the ticket.

Checklist

  • [x] This PR targets the main branch.
  • [x] The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • [x] I have checked the "Has patch" ticket flag in the Trac system.
  • [x] I have added or updated relevant tests.
  • [ ] I have added or updated relevant docs, including release notes if applicable.
  • [ ] I have attached screenshots in both light and dark modes for any UI changes.

AhmedNassar7 avatar Apr 20 '25 09:04 AhmedNassar7

Thank you for the PR @AhmedNassar7 I am not convinced with the approach here. I am wondering if we should introduce a keyword argument to filters similar to needs_autoescape in order to pass the context

For example (note more docs changes and a release note would be required):

--- a/django/template/base.py
+++ b/django/template/base.py
@@ -748,15 +748,14 @@ class FilterExpression:
                     arg_vals.append(mark_safe(arg))
                 else:
                     arg_vals.append(arg.resolve(context))
-            kwargs = {}
             if getattr(func, "expects_localtime", False):
                 obj = template_localtime(obj, context.use_tz)
-                if func.__name__ in ("date", "time"):
-                    kwargs["use_l10n"] = context.use_l10n
+            kwargs = {}
+            if getattr(func, "needs_use_l10n", False):
+                kwargs["use_l10n"] = context.use_l10n
             if getattr(func, "needs_autoescape", False):
-                new_obj = func(obj, autoescape=context.autoescape, *arg_vals, **kwargs)
-            else:
-                new_obj = func(obj, *arg_vals, **kwargs)
+                kwargs["autoescape"] = context.autoescape
+            new_obj = func(obj, *arg_vals, **kwargs)
             if getattr(func, "is_safe", False) and isinstance(obj, SafeData):
                 obj = mark_safe(new_obj)
             else:
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 7676f543f2..1917d34bd3 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -775,7 +775,7 @@ def get_digit(value, arg):
 ###################
 
 
[email protected](expects_localtime=True, is_safe=False)
[email protected](expects_localtime=True, is_safe=False, needs_use_l10n=True)
 def date(value, arg=None, use_l10n=None):
     """Format a date according to the given format."""
     if value in (None, ""):
@@ -789,7 +789,7 @@ def date(value, arg=None, use_l10n=None):
             return ""
 
 
[email protected](expects_localtime=True, is_safe=False)
[email protected](expects_localtime=True, is_safe=False, needs_use_l10n=True)
 def time(value, arg=None, use_l10n=None):
     """Format a time according to the given format."""
     if value in (None, ""):
diff --git a/django/template/library.py b/django/template/library.py
index 3ec39ff572..4107f10c27 100644
--- a/django/template/library.py
+++ b/django/template/library.py
@@ -80,7 +80,7 @@ class Library:
         elif name is not None and filter_func is not None:
             # register.filter('somename', somefunc)
             self.filters[name] = filter_func
-            for attr in ("expects_localtime", "is_safe", "needs_autoescape"):
+            for attr in ("expects_localtime", "is_safe", "needs_autoescape", "needs_use_l10n"):
                 if attr in flags:
                     value = flags[attr]
                     # set the flag on the filter for FilterExpression.resolve
diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
index e56ef54f02..1bd3ba2a01 100644
--- a/docs/howto/custom-template-tags.txt
+++ b/docs/howto/custom-template-tags.txt
@@ -157,11 +157,16 @@ You can use ``register.filter()`` as a decorator instead::
 If you leave off the ``name`` argument, as in the second example above, Django
 will use the function's name as the filter name.
 
-Finally, ``register.filter()`` also accepts three keyword arguments,
-``is_safe``, ``needs_autoescape``, and ``expects_localtime``. These arguments
-are described in :ref:`filters and auto-escaping <filters-auto-escaping>` and
+Finally, ``register.filter()`` also accepts four keyword arguments,
+``is_safe``, ``needs_autoescape``, ``needs_use_l10n``, and
+``expects_localtime``. These arguments are described in
+:ref:`filters and auto-escaping <filters-auto-escaping>` and
 :ref:`filters and time zones <filters-timezones>` below.
 
+.. versionchanged:: 6.0
+
+    The ``needs_use_l10n`` keyword argument was added.
+
 Template filters that expect strings
 ------------------------------------

sarahboyce avatar May 19 '25 12:05 sarahboyce