Как выполнить поиск в стиле getattr() в шаблоне Django
Метод getattr()
в Python полезен, когда вы не знаете имя определенного атрибута заранее.
Эта функциональность также была бы полезна в шаблонах, но я так и не смог найти способ, как это сделать. Существует ли встроенный тег или не встроенный тег, который может выполнять динамический поиск атрибутов?
5 ответ(ов)
Чтобы создать пользовательский тег шаблона в Django, который обрабатывает различные сценарии поиска атрибутов, я написал следующий код. Он сначала выполняет стандартный поиск атрибутов, затем ищет в словаре, затем выполняет поиск по индексу (чтобы работали списки), и, наконец, следует стандартному поведению шаблона Django, если объект не найден.
(Обновлено 26 августа 2009 года, чтобы теперь обрабатывать также поиски по индексам списков)
# app/templatetags/getattribute.py
import re
from django import template
from django.conf import settings
numeric_test = re.compile("^\d+$")
register = template.Library()
def getattribute(value, arg):
"""Динамически получает атрибут объекта по строковому имени"""
if hasattr(value, str(arg)):
return getattr(value, arg)
elif hasattr(value, 'has_key') and value.has_key(arg):
return value[arg]
elif numeric_test.match(str(arg)) and len(value) > int(arg):
return value[int(arg)]
else:
return settings.TEMPLATE_STRING_IF_INVALID
register.filter('getattribute', getattribute)
Использование в шаблоне:
{% load getattribute %}
{{ object|getattribute:dynamic_string_var }}
Этот код позволяет более гибко обращаться к атрибутам объектов в шаблонах, что может быть полезно в различных сценариях, когда имя атрибута задается динамически.
Я в конечном итоге добавил метод в нужную модель, и этот метод теперь доступен как атрибут в шаблоне.
Тем не менее, я думаю, было бы здорово, если бы существовал встроенный тег, который позволял бы динамически получать атрибуты, так как это проблема, с которой многие из нас постоянно сталкиваются в своих шаблонах.
Конечно! Ваш код содержит два пользовательских фильтра для Django, один из которых называется get
, а другой — getattr
. Они предоставляют возможность безопасно получать элементы из объектов. Вот их краткое объяснение:
Фильтр
get
: Этот фильтр принимает объект и индекс (или ключ) и пытается вернуть соответствующий элемент. Если элемент не может быть получен (например, если индекс выходит за пределы или объект не поддерживает индексацию), фильтр возвращает значение, определенное вsettings.TEMPLATE_STRING_IF_INVALID
.@register.filter(name='get') def get(o, index): try: return o[index] except: return settings.TEMPLATE_STRING_IF_INVALID
Фильтр
getattr
: Этот фильтр работает аналогично, но вместо индекса принимает имя атрибута. Он использует встроенную функциюgetattr
, чтобы получить значение атрибута объекта. Если атрибут не существует, возвращается то же значение по умолчанию изsettings.TEMPLATE_STRING_IF_INVALID
.@register.filter(name='getattr') def getattrfilter(o, attr): try: return getattr(o, attr) except: return settings.TEMPLATE_STRING_IF_INVALID
Таким образом, оба фильтра помогают избежать ошибок при попытке доступа к данным в шаблонах, и обеспечивают стандартное поведение при возникновении исключений.
Этот фрагмент кода значительно упростил мою жизнь, но мне нужно было сделать его работающим с вложенными атрибутами, поэтому я изменил его так, чтобы аргумент разбивался по точкам и рекурсивно получалось значение.
Это можно сделать и в одну строку:
return getattribute(getattribute(value, str(arg).split(".")[0]), ".".join(str(arg).split(".")[1:]))
Но я оставил это в четырех строках для удобочитаемости. Надеюсь, это поможет кому-то другому.
Вот сам код:
import re
from django import template
from django.conf import settings
numeric_test = re.compile("^\d+$")
register = template.Library()
def getattribute(value, arg):
"""Получает атрибут объекта динамически и рекурсивно по имени в виде строки."""
if "." in str(arg):
firstarg = str(arg).split(".")[0]
value = getattribute(value, firstarg)
arg = ".".join(str(arg).split(".")[1:])
return getattribute(value, arg)
if hasattr(value, str(arg)):
return getattr(value, arg)
elif hasattr(value, 'has_key') and value.has_key(arg):
return value[arg]
elif numeric_test.match(str(arg)) and len(value) > int(arg):
return value[int(arg)]
else:
# return settings.TEMPLATE_STRING_IF_INVALID
return 'no attr.' + str(arg) + ' for: ' + str(value)
register.filter('getattribute', getattribute)
Если у вас есть вопросы или предложения, не стесняйтесь делиться!
Вот более универсальное решение, основанное на решении @fotinakis. Оно позволяет находить значение строкового выражения, будь то атрибут или функция, и также поддерживает цепочки объектов.
import re
import types
numeric_test = re.compile("^\d+$")
register = template.Library()
def get_attr(object, arg):
if hasattr(object, str(arg)):
attr = getattr(object, arg)
if type(attr) == types.MethodType:
return attr()
return attr
elif hasattr(object, 'has_key') and object.has_key(arg):
return object[arg]
elif numeric_test.match(str(arg)) and len(object) > int(arg):
return object[int(arg)]
else:
return object
@register.simple_tag(takes_context=True)
def get_by_name(context, name):
""""Получить переменную по строковому имени {% get_by_name data_name.data_func... %}"""
print(context['instance'].get_edit_url())
arr = name.split('.')
obj = arr[0]
object = context[obj]
if len(arr) > 1:
for ar in arr[1:]: # начинаем с 1, так как 0-й элемент уже присвоен
object = get_attr(object, ar)
return object
Основные моменты, которые следует обсудить:
- Функция
get_attr
принимает объект и имя атрибута/функции, проверяет, существует ли атрибут. Если это метод, он вызывается, иначе возвращается значение. - В функции
get_by_name
мы разбиваем имя на части по точкам и последовательно извлекаем атрибуты/функции из объектов. Обратите внимание, что мы пропускаем первый элемент в цикле, так как он уже сохранен в переменнойobject
. - Это решение универсально и позволяет работать не только с атрибутами, но и с методами, а также поддерживает индексацию по числовым ключам.
Превысил ли Django 100 тыс. посещений в день? [закрыто]
Как отменить последнюю миграцию?
Как получить значения GET-запроса в Django?
Разделение бизнес-логики и доступа к данным в Django
Почему выполнение запланированных задач с использованием Celery предпочтительнее, чем crontab?