0

Как выполнить поиск в стиле getattr() в шаблоне Django

32

Метод getattr() в Python полезен, когда вы не знаете имя определенного атрибута заранее.

Эта функциональность также была бы полезна в шаблонах, но я так и не смог найти способ, как это сделать. Существует ли встроенный тег или не встроенный тег, который может выполнять динамический поиск атрибутов?

5 ответ(ов)

0

Чтобы создать пользовательский тег шаблона в 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 }}

Этот код позволяет более гибко обращаться к атрибутам объектов в шаблонах, что может быть полезно в различных сценариях, когда имя атрибута задается динамически.

0

Я в конечном итоге добавил метод в нужную модель, и этот метод теперь доступен как атрибут в шаблоне.

Тем не менее, я думаю, было бы здорово, если бы существовал встроенный тег, который позволял бы динамически получать атрибуты, так как это проблема, с которой многие из нас постоянно сталкиваются в своих шаблонах.

0

Конечно! Ваш код содержит два пользовательских фильтра для Django, один из которых называется get, а другой — getattr. Они предоставляют возможность безопасно получать элементы из объектов. Вот их краткое объяснение:

  1. Фильтр 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
    
  2. Фильтр 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
    

Таким образом, оба фильтра помогают избежать ошибок при попытке доступа к данным в шаблонах, и обеспечивают стандартное поведение при возникновении исключений.

0

Этот фрагмент кода значительно упростил мою жизнь, но мне нужно было сделать его работающим с вложенными атрибутами, поэтому я изменил его так, чтобы аргумент разбивался по точкам и рекурсивно получалось значение.

Это можно сделать и в одну строку:

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)

Если у вас есть вопросы или предложения, не стесняйтесь делиться!

0

Вот более универсальное решение, основанное на решении @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

Основные моменты, которые следует обсудить:

  1. Функция get_attr принимает объект и имя атрибута/функции, проверяет, существует ли атрибут. Если это метод, он вызывается, иначе возвращается значение.
  2. В функции get_by_name мы разбиваем имя на части по точкам и последовательно извлекаем атрибуты/функции из объектов. Обратите внимание, что мы пропускаем первый элемент в цикле, так как он уже сохранен в переменной object.
  3. Это решение универсально и позволяет работать не только с атрибутами, но и с методами, а также поддерживает индексацию по числовым ключам.
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь