Вместо предисловия
Привет! Давно здесь не отписывался. Почему? Наверное главная причина, что после выхода 1.1 версии уже не так активно слежу за развитием Django. Может быть в ближайшее время меня пробъет на творчество и я выдам пару-тройку новых постов, но не обещаю ;)
Но это я отвлекся от темы поста. А она заключается вот в чем. Надо было сегодня в шаблонах Django для некоторых урлов убрать конечные слеши, т.е. просто вызвать url.rstrip('/')
. Просмотрев в который раз список всторенных шаблонных фильтров и не обнаружив там нужного, я задумался: как быть? Создавать простой фильтр, типа:
from django.template import Library
from django.template.defaultfilters import stringfilter
register = Library()
@register.filter
@stringfilter
def rstrip(text, chars=None):
return text.rstrip(chars)
совершенно не хотелось. Ибо вдруг мне в будущем захочется добавить поддержку lstrip
или strip
метода. Что надо будет морочиться с Ctrl+C, Ctrl+V и минимальными исправлениями отяжеляя эту темплейт библиотеку?
Потому не долго думая и вроде бы не отыскав необходимого мне решения в гугле, я решил повелосипедить и добавить возможность вызова всех строковых методов Python в Django шаблоне.
Первая моя идея была очень проста, перебрать все публичные методы пустой строки и зарегестрировать их как шаблонный фильтр, используя lambda. Переходя на код, это приобрело примерно следующий вид:
from django.template import Library
from django.template.defaultfilters import stringfilter
register = Library()
for name in dir(u''):
if name.startswith('_'): continue
filter = lambda value, *args: getattr(unicode(value), name)(*args)
filter = stringfilter(filter)
register.filter(name, filter)
Написав простой тест-кейс я уже было приготовился доставать шампанское и переходить к следующей задаче, но не тут-то было :( TemplateSyntaxError
для {{ url|strip:"/" }}
заставила притормозить коней. Почему же так произошло? Я пошел к месту ошибки и понял, что проблема в *args
, точнее в том, что django.template.FilterExpression.args_check
ожидает определенный набор аргументов, а не, наоборот, не определенный заранее.
Что ж, пришлось усложнять код. Для начала я добавил поддержку только трех аргументов, ибо это максимальноя кол-во используемых аргументов для любого строкового метода, кроме format
. Затем переписал предыдущую безымянную функцию в:
@stringfilter
def make_filter(name):
def filter(value, first=None, second=None, third=None):
args = [first, second, third]
method = getattr(force_unicode(value), name)
while True:
try:
return method(*args)
except TypeError:
args.pop(len(args) - 1)
return filter
и заодно переписал регистрацию этого фильтра. Запустил тест-кейс, получил Ran OK! и на свою голову решил усложнить тест-кейс, проверив работу фильтра с двумя аргументами, например, {{ "abcdef"|replace:"abc":"def" }}
.
Каково же было мое удивление, когда Django сказала, нет много аргументов для фильтров - это не хорошо. И сгенерировала очередную TemplateSyntaxError
. Что ж, пришлось реализовывать это в виде отдельного простого тега {% stringmethod %}
. Принцип его работы простейший, как видно из кода:
@register.simple_tag
def stringmethod(name, value, first=None, second=None, third=None):
return make_filter(name)(value, first, second, third)
В свою очередь это повлекло за собой обновление теста, и {{ "abcdef"|replace:"abc":"def" }}
превратилось в {% stringmethod "replace" "abcdef" "abc" "def" %}
. Монструозно, соглашусь, но что поделаешь. В итоге, получив Ran OK! я немного подправил документацию и выложил это все дело, как отдельный гист на гитхаб.
Пользуйтесь, возможно вам это пригодится!
зы. На последок упомяну еще о пару особенностях stringmethods
. Во-первых, она не переписывает встроенный join
фильтр, во-вторых, она плохо справляется с format
.
зыы. Если вы знаете reusable apps решающие похожие проблемы - не стесняйтесь писать в комменты :)