From 3d7ac6420c0ff23305bc50c69c419c4ed5d2a838 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Mon, 10 Jan 2022 08:31:43 +0100 Subject: [PATCH] Simplified @stringfilter decorator and Library with unwrap(). Nowadays we can use inspect.unwrap() to retrieve the innermost function object when needed, and most of the uses of _decorated_function were to access the original __name__ which is not needed because @functools.wraps sets that attribute correctly. --- django/template/defaultfilters.py | 22 +++++++++------------- django/template/library.py | 12 +++++------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 0813cc9dad..f78a96e3eb 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -4,6 +4,7 @@ import re import types from decimal import ROUND_HALF_UP, Context, Decimal, InvalidOperation from functools import wraps +from inspect import unwrap from operator import itemgetter from pprint import pformat from urllib.parse import quote @@ -37,20 +38,15 @@ def stringfilter(func): Decorator for filters which should only receive strings. The object passed as the first positional argument will be converted to a string. """ - def _dec(*args, **kwargs): - args = list(args) - args[0] = str(args[0]) - if (isinstance(args[0], SafeData) and - getattr(_dec._decorated_function, 'is_safe', False)): - return mark_safe(func(*args, **kwargs)) - return func(*args, **kwargs) + @wraps(func) + def _dec(first, *args, **kwargs): + first = str(first) + result = func(first, *args, **kwargs) + if isinstance(first, SafeData) and getattr(unwrap(func), 'is_safe', False): + result = mark_safe(result) + return result - # Include a reference to the real function (used to check original - # arguments by the template parser, and to bear the 'is_safe' attribute - # when multiple decorators are applied). - _dec._decorated_function = getattr(func, '_decorated_function', func) - - return wraps(func)(_dec) + return _dec ################### diff --git a/django/template/library.py b/django/template/library.py index be45331309..06ea5f1ad8 100644 --- a/django/template/library.py +++ b/django/template/library.py @@ -48,7 +48,7 @@ class Library: ) def tag_function(self, func): - self.tags[getattr(func, "_decorated_function", func).__name__] = func + self.tags[func.__name__] = func return func def filter(self, name=None, filter_func=None, **flags): @@ -83,8 +83,7 @@ class Library: setattr(filter_func, attr, value) # set the flag on the innermost decorated function # for decorators that need it, e.g. stringfilter - if hasattr(filter_func, "_decorated_function"): - setattr(filter_func._decorated_function, attr, value) + setattr(unwrap(filter_func), attr, value) filter_func._filter_name = name return filter_func else: @@ -94,8 +93,7 @@ class Library: ) def filter_function(self, func, **flags): - name = getattr(func, "_decorated_function", func).__name__ - return self.filter(name, func, **flags) + return self.filter(func.__name__, func, **flags) def simple_tag(self, func=None, takes_context=None, name=None): """ @@ -107,7 +105,7 @@ class Library: """ def dec(func): params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func)) - function_name = (name or getattr(func, '_decorated_function', func).__name__) + function_name = (name or func.__name__) @functools.wraps(func) def compile_func(parser, token): @@ -144,7 +142,7 @@ class Library: """ def dec(func): params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(unwrap(func)) - function_name = (name or getattr(func, '_decorated_function', func).__name__) + function_name = name or func.__name__ @functools.wraps(func) def compile_func(parser, token):