Конфигурационные данные в шаблонах

Часто нужно в шаблоне вывести какой-то конфигурационный параметр. Нет, не тот который в settings.py, а тот который хранится в базе. Ну например префикс заголовка страниц, содержимое мета-тега в head или ещё какую-то информацию. Эти все данные(пары имя-значение) можно либо хранить в простой модели или взять что-то стороннее посерьезней, например dbsettings.

Для примера я возьму простую модель:

class Entry( models.Model ):
   name = models.CharField( max_length = 50, unique = True )
   value = models.CharField( max_length = 150 )

Здесь и далее буду писать упрощенный код

Всё, данные есть где хранить, но их ещё и нужно удобно вывести в шаблон. Первое что приходит в голову для решения - сделать шаблонный тег. Да, и вправду просто и сердито:

@register.simple_tag
def get_conf( name ):
    return Entry.objects.get( name = name )

И использовать легко, примерно так:

<meta name="keywords" content="{% get_conf "meta_keywords" %}"/>

Но у этого способа по сути одно маленькое положительное качество - в этот тег можно передать переменную, а не жестко закодированную строку("hardcoded" - правильно перевел?:)) с названием. Но для таких данных это не особый плюс, поскольку они очень специфичны и полиморфность имени не особа важна. Ещё конечно нужно отметить доступность значения тега в любом шаблоне, нужно только подключить соответствующую библиотеку тегов, где он лежит. Но это плюс можно и другими способами воссоздать.

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

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

Самый плохой такой:

def conf_processor( request ):
    return dict( [ ( e.name, e.value ) for e in Entry.objects.all() ] )

Почему плохой: засоряем контекст значениями, а их может быть много. Потом, достаем сразу все значения, что опять таки при их большом количестве накладно(хотя много мелких запросов может быть ещё более накладно, но тут уж от конкретной ситуации зависит).

Думаем дальше и находим вариант получше, решающий первую проблему предыдущего:

class Conf( object ):
    def __init__( self ):
        for e in Entry.objects.all():
            setattr( self, e.name, e.value )

def conf_processor( request ):
    return { "conf" : Conf() }

Получилось, теперь доступ {{conf.name}} уже лаконичен и прозрачен, но опять оверхед на доставание всех значений, которые могут и не понадобиться в большинстве своём.

Продолжаем мозговой штурм и вспоминаем что пишем на питоне с богатейшим рефлекшеном, который позволяет творить с любыми объектами настоящие чудеса. Но самые магические питоновские заклинания нам тут даже не пригодятся. Достаточно реализации простого геттера __getattr__. Итак:

class Conf( object ):
    def __getattr__( self, name ):
        return Entry.objects.get( name = name ).value

def conf_processor( request ):
    return { "conf" : Conf() }

Ура, похожу уже приемлемый вариант. Лаконичный доступ {{conf.name}}, не тянем все данные за раз(хотя, как я уже отмечал, в некоторых случаях это может быть и не оптимально) и можем передавать значения в другие теги.

Так, теперь мы почувствовали вкус крови и хотим немного расширить функциональность. Чего нам не хватает - логического разделения конфигурационных данных на секции. И вот уже руки сами тянутся это "хочу" сделать. Исходная модель становится чуть сложнее и добавляется еще одна:

class Section( models.Model ):
   """Тут будем хранить имена секций"""
   name = models.CharField( max_length = 50 )

class Entry( models.Model ):
   section = models.ForeignKey( Section )
   name = models.CharField( max_length = 50 )
   value = models.CharField( max_length = 150 )

   class Meta:
       unique_together = ( ( "section", "name", ), )

Теперь как быть с представлением этого хозяйства в шаблоне. Очень просто. Изменим наш прокси класс:

class SectionProxy( object ):
    def __init__( self, section_name ):
        self._section_name = section_name

    def __getattr__( self, name ):
        return Entry.objects.get( section__name = self._section_name, name = name ).value

class Conf( object ):
    def __getattr__( self, name ):
        return SectionProxy( name )

def conf_processor( request ):
    return { "conf" : Conf() }

Что изменилось в доступе - добавился ещё один уровень. Представим что у нас есть секция common и запись в ней title, тогда чтобы достучаться до неё в шаблоне, пишем {{conf.common.title}}. Вот и всё.

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

А вы как решаете подобную задачу?

Комментарии 6

  1. Glader написал:

    А можно поподробнее насчет "хитрого кэширования"?

    Оставлен 30 Январь 2008 в 10:37
  2. Александр Кошелев написал:

    А можно поподробнее насчет "хитрого кэширования"?

    Конечно можно. Скоро очень подробно напишу про кеширование. Есть чем поделиться:)

    Оставлен 30 Январь 2008 в 10:59
  3. dvska написал:

    Красиво получилось, спасибо

    Оставлен 30 Январь 2008 в 12:06
  4. playpauseandstop написал:

    очень красиво и очень вовремя )))

    я как раз делал контекст процессоры для Баннеров и Меню и мне этот подход вполне пригодился... теперь вместо нудного добавления каждого необходимого контейнера баннеров или меню, я использую в шаблонах: {{ menu.global.as html }} {{ banners.index.as html }}

    еще раз гран мерси )))

    Оставлен 31 Январь 2008 в 15:06
  5. Александр Кошелев написал:

    Для баннеров и меню я бы использовал наверно шаблонные теги...

    Оставлен 01 Февраль 2008 в 01:41
  6. playpauseandstop написал:

    Согласен насчет использования шаблонных тегов для Баннеров или Меню в общих случаях, но у меня вышло так что любая страница вызывает 4 или более меню и еще не менее трех блоков с баннерами...

    Причем в идеале на одной странице отображаются все добавленные в базу данных баннеры и пункты меню...

    Потому я рационализировал это все через контекстные процессоры... Мне показалось, что так будет быстрее и что не мало важно удобнее )))

    Оставлен 01 Февраль 2008 в 02:32

Пингбеки 1

  1. От Serge Matveenko &raquo; links for 2008-02-05 05 Февраль 2008 в 22:18

    [...]Ð&Yuml;Ð¾Ñ Ñ&sbquo; &#8220;Ð&scaron;онÑ&bdquo;игÑ&fnof;Ñ&euro;аÑ&dagger;ионнÑ&lsaquo;е даннÑ&lsaquo;е в Ñ&circ;аблонаÑ&hellip;&#8221; | Ð&tilde;нÑ&sbquo;еÑ&euro;неÑ&sbquo; нового века | webnewage.org[...]