Как повторить «блок» в шаблоне django

126

Я хочу использовать один и тот же {% block%} дважды в одном шаблоне django. Я хочу, чтобы этот блок появлялся в моем базовом шаблоне более одного раза:

# base.html
<html>
    <head>
        <title>{% block title %}My Cool Website{% endblock %}</title>
    </head>
    <body>
        <h1>{% block title %}My Cool Website{% endblock %}</h1>
    </body>
</html>

А затем расширите его:

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}

# pictures.html
{% extends 'base.html' %}
{% block title %}My Pictures{% endblock %}

# cats.html
{% extends 'base.html' %}
{% block title %}My Cats{% endblock %}

Я получу исключение, так как Django хочет, чтобы блок появлялся только один раз:

TemplateSyntaxError в /

Тег 'block' с именем 'title' встречается более одного раза

Быстрое и грязное решение - дублировать заголовок блока в title1 и title2 :

# blog.html
{% extends 'base.html' %}
{% block title1 %}My Blog{% endblock %}
{% block title2 %}My Blog{% endblock %}

Но это нарушение принципа DRY . Это было бы очень сложно, так как у меня много наследующих шаблонов, а также потому, что я не хочу идти к черту ;-)

Есть ли какой-нибудь трюк или способ решения этой проблемы? Как я могу повторить один и тот же блок в моем шаблоне, не дублируя весь код?

Дэвид Аркос
источник
1
также см. решение по этому вопросу stackoverflow.com/q/1178743/168034
phunehehe
2
См. Этот ответ, в частности, на вопрос phunehe, на который он ссылается.
Tobu

Ответы:

69

Я считаю, что использование контекстного процессора в этом случае является излишним. Вы легко можете это сделать:

#base.html
<html>
    <head>
        <title>{% block title %}My Cool Website{% endblock %}</title>
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
</html>

а потом:

# blog.html
{% extends 'base.html' %}
{% block content %}
    <h1>{% block title %}My Blog{% endblock %}</h1>
    Lorem ipsum here...
{% endblock %}

и так далее ... Похоже, DRY-совместимый.

DQD
источник
1
Я мог бы попробовать это завтра - мне было интересно, как сэкономить немного повторений в шаблонах, и это кажется хорошим подходом. Спасибо.
thebiglife
1
Это отличный подход. Я разделил свой base.html на base.html и superbase.html, так что это также работает, если вы хотите добавить стандартную разметку заголовка (например, h1) в свои общие шаблоны. Страницы по-прежнему могут переопределять содержимое основной надписи, и оно будет обновляться в обоих местах.
SystemParadox
2
Это не позволяет использовать текст более двух раз?
Денис Голомазов
1
Денис Голомазов: Нет. В таком случае лучше использовать макроплагин (см. Ниже).
dqd
1
Может также применяться и наоборот: определение h1содержимого внутри блока, который определяет title. Или блок , который определяет часть из title.
Mikael Lindlöf
83

Используйте плагин макросов шаблонов Django:

https://gist.github.com/1715202 (django> = 1.4)

или

http://www.djangosnippets.org/snippets/363/ (django <1.4)

django> = 1.4

# base.html
{% kwacro title %}
    {% block title %}My Cool Website{% endblock %}
{% endkwacro %}

<html>
    <head>
        <title>{% usekwacro title %}</title>
    </head>
    <body>
        <h1>{% usekwacro title %}</h1>
    </body>
</html>

и

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}

django <1.4

# base.html
{% macro title %}
    {% block title %}My Cool Website{% endblock %}
{% endmacro %}

<html>
    <head>
        <title>{% usemacro title %}</title>
    </head>
    <body>
        <h1>{% usemacro title %}</h1>
    </body>
</html>

и

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}
Джон Р. Перри
источник
2
Это фантастика! Это действительно может решить проблемы, которые я получаю при совместном использовании шаблонов с циклами django и циклами данных ajax.
Глицерин
1
Хорошее решение. Однако это «use_macro». "усемакро" неверно.
Ramtin
Определенно должен быть встроен в Django по умолчанию.
zepp.lee
19

Вероятно, вы на самом деле не хотите использовать блок, а просто использовать переменную:

# base.html
<html>
    <head>
        <title>{{ title|default:"My Cool Website" }}</title>
    </head>
    <body>
        <h1>{{ title|default:"My Cool Website" }}</h1>
    </body>
</html>

Затем вы устанавливаете заголовок через контекст.

Аарон Маенпаа
источник
17
Наверное, сухо. Но вы не захотите устанавливать заголовок в представлении; но в шаблонах.
Лакшман Прасад,
6
Заголовки должны устанавливаться из шаблонов, а не предоставляться контекстом, вам нужно иметь способ определить эту переменную «заголовок», иначе это не очень хорошее решение.
Гийом Эскевен,
Это то, что делают шаблоны администратора django (для {{title}}), но определять заголовок при удалении неудобно.
Тобу,
13

Вот способ, который я обнаружил, пытаясь сделать то же самое сам:

# base_helper.html
<html>
    <head>
        <title>{% block _title1 %}{% endblock %}</title>
    </head>
    <body>
        <h1>{% block _title2 %}{% endblock %}</h1>
    </body>
</html>


# base.html
{% extends "base_helper.html" %}

# Copy title into _title1 & _title2, using "My Cool Website" as a default.
{% block _title1 %}{% block _title2 %}{% block title %}My Cool Website{% endblock %}{% endblock %}{% endblock %}

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

Роман Старков
источник
В конце концов я остановился на решении {% macro%}, которое не требует нового файла и в целом позволяет мне выразить именно то, что я хочу выразить.
Роман Старков
простой и эффективный. В отличие от ответа @dqd, блоки не должны быть вложенными, что очень полезно, например, для тегов facebook og, которые могут иметь то же содержимое, что и другие атрибуты заголовка. upvote!
benzkji
2
Отличный ответ. Это кажется даже СУЩЕСТВУЮЩИМ, чем ответ @ dqd, поскольку вам не нужно повторять тег <h1> в каждом шаблоне, наследующем base. Это может быть принятый ответ.
Anupam
Превосходно! Я использовал это в более сложных сценариях, где я хотел повторить строку нижнего колонтитула таблицы вверху. И <tr>ряд был довольно сложным.
caram 06
12

вы можете использовать {% include subtemplate.html %}более одного раза. это не то же самое, что блоки, но помогает.

Хавьер
источник
Это та же проблема. Базовый шаблон не знает, какой подшаблон включить.
Van Gale,
Обратите внимание, что includeэто медленнее, чем block. docs.djangoproject.com/en/1.10/topics/performance/…
Wtower
5

Здесь есть обсуждение: http://code.djangoproject.com/ticket/4529 Очевидно, основная команда django отклоняет этот билет, потому что они думают, что это не часто используемый сценарий, однако я не согласен.

Блок повторения - простая и понятная реализация для этого: https://github.com/SmileyChris/django-repeatblock

макросы шаблона - еще один, однако автор упомянул, что он не был тщательно протестирован: http://www.djangosnippets.org/snippets/363/

Я использовал Repeatblock.

Роберт Мао
источник
4
Исходный репозиторий django-repeatblock, похоже, был удален. Лучшим форком этого кажется github.com/phretor/django-repeatblock ; Я также нашел github.com/ydm/django-sameas , для которого не требуется патч Django wontfix.
natevw 02
4

В качестве обновления для всех, кто сталкивается с этим, я взял упомянутый выше фрагмент и превратил его в библиотеку тегов шаблонов, django-macros, которая делает макросы более мощными, а также явно реализует повторяющийся шаблон блока: django-macros .

Ник
источник
4

Вот легкое решение, подобное приведенному выше, do_setи do_getответ на шаблонный тег. Django позволяет передавать весь контекст шаблона в тег, который позволяет определять глобальную переменную.

base.html:

<!DOCTYPE html>
<html lang="en">
<head>
  {% block head %}
    <title>{{ title }}</title>
  {% endblock %}
</head>
<body>
  <h1>{{ title }}</h1>
</body>
</html>

page.html:

{% extends "base.html" %}

{% block head %}
  {% define 'title' 'Homepage | title' %}
  {{ block.super }}
{% endblock %}

пользовательский тег (идея здесь: https://stackoverflow.com/a/33564990/2747924 ):

@register.simple_tag(takes_context=True)
def define(context, key, value):
    context.dicts[0][key] = value
    return ''

Также не забывайте {% load %}настраиваемые теги или добавляйте их в список встроенных опций шаблона, чтобы вам не приходилось загружать их в каждый шаблон. Единственным ограничением этого подхода является {% define %}необходимость вызова из тега блока, потому что дочерние шаблоны отображают только теги блока, соответствующие родительским тегам. Не уверен, есть ли способ обойти это. Также убедитесь, что defineзвонок поступает, прежде чем вы попытаетесь его явно использовать.

manncito
источник
3

Основываясь на предложении Ван Гейла, вы можете создавать теги get и set, добавив следующее в свой файл templatetags.py:

register = template.Library()

Stateful = {}
def do_set(parser, token):
    _, key = token.split_contents()
    nodelist = parser.parse(('endset',))
    parser.delete_first_token()  # from the example -- why?
    return SetStatefulNode(key,nodelist)

class SetStatefulNode(template.Node):
    def __init__(self, key, nodes):
        Stateful[key] = nodes
    def render(self, context):
        return ''
register.tag('set', do_set)

def do_get(parser, token):
    tag_name, key = token.split_contents()
    return GetStatefulNode(key)

class GetStatefulNode(template.Node):
    def __init__(self, key):
       self.key = key
    def render(self, context):
        return ''.join( [x.render(context) for x in Stateful[self.key]] )

register.tag('get', do_get)

Затем установите значения в одном шаблоне через {% set foo %}put data here{% endset %}и получите их через {% get foo %}другой.

Киран Хервольд
источник
Я считаю, что это самое элегантное решение из всех. Спасибо Кирану и Ван Гейлу!
Роберт Лакруа
Это довольно удобно, но кажется, что было бы даже лучше визуализировать все узлы в теге Set, иначе они будут визуализироваться снова и снова с помощью Get. Я могу думать о причинах, которые могут быть хорошей идеей (отображение одного и того же сохраненного блока внутри разных блоков на странице), но я просто подумал, что укажу на это.
acjay
3

Я тоже столкнулся с такой же потребностью в повторяющемся {% block%} в моих файлах шаблонов. Проблема в том, что я хочу, чтобы Django {% block%} использовался в любом случае условного выражения Django, и я хочу, чтобы {% block%} был перезаписан последующими файлами, которые могут расширить текущий файл. (Итак, в этом случае я определенно хочу больше блока, чем переменной, потому что я технически не использую его повторно, он просто появляется на любом конце условного выражения.

Эта проблема:

Следующий код шаблона Django приведет к ошибке синтаксиса шаблона, но я думаю, что это действительное «желание», чтобы определенный {% block%} повторно использовался в условном (IE, почему синтаксис синтаксиса Django проверяет синтаксис на ОБЕИХ концах условного оператора, разве он не должен проверять только условие ИСТИНА?)

# This example shows a {{ DEBUG }} conditional that loads 
#   Uncompressed JavaScript files if TRUE 
#   and loads Asynchronous minified JavaScript files if FALSE.  

# BASE.html
{% if DEBUG %}
    <script src="{{MEDIA_URL}}js/flatfile.1.js"></script>
    <script src="{{MEDIA_URL}}js/flatfile.2.js"></script>
    <script src="{{MEDIA_URL}}js/flatfile.3.js"></script>
    <script type="text/javascript">
        {% block page_js %}
            var page = new $site.Page();
        {% endblock page_js %}
    </script>
{% else %}
    <script type="text/javascript">
        // load in the PRODUCTION VERSION of the site
        // minified and asynchronosly loaded
        yepnope([
            {
                load : '{MEDIA_URL}}js/flatfiles.min.js',
                wait : true,
                complete : function() {
                    {% block page_js %} // NOTE THE PAGE_JS BLOCK
                        var page = new $site.Page();
                    {% endblock page_js %}
                }
            }
        )];
    </script>
{% endif %}

# ABOUT.html
{% extends 'pages/base.html' %}
{% block page_js %}
var page = new $site.Page.About();
{% endblock page_js %}

Решение:

Вы можете использовать {% include%}, чтобы условно вставить {% block%} более одного раза. Это сработало для меня, потому что средство проверки синтаксиса Django включает только TRUTHY {% include%}. Смотрите результат ниже:

# partials/page.js
{% block page_js %}
    var page = new $site.Page();    
{% endblock %}

# base.html
{% if DEBUG %}
    <script src="{{MEDIA_URL}}js/flatfile.1.js"></script>
    <script src="{{MEDIA_URL}}js/flatfile.2.js"></script>
    <script src="{{MEDIA_URL}}js/flatfile.3.js"></script>
    <script type="text/javascript">
        {% include 'partials/page_js.html' %}
    </script>
{% else %}
    <script type="text/javascript">
        yepnope([
            {
                load : '{MEDIA_URL}}js/flatfiles.min.js',
                wait : true,
                complete : function() {
                    {% include 'partials/page_js.html' %}
                }
            }
        )];
    </script>
{% endif %}
potench
источник
2

Я использую этот ответ, чтобы вещи оставались сухими.

{% extends "base.html" %}

{% with "Entry Title" as title %}
    {% block title %}{{ title }}{% endblock %}
    {% block h1 %}{{ title }}{% endblock %}
{% endwith %}
Кристиан Лонг
источник
1

Для этого есть два простых решения.

Самый простой - поместить заголовок в контекстную переменную. Вы должны установить переменную контекста в своем представлении.

Если вы используете что-то вроде общих представлений и у вас нет views.py для изображений, кошек и т. Д., Вы можете использовать собственный тег шаблона, который устанавливает переменную в контексте .

Переход по этому маршруту позволит вам сделать что-то вроде:

{% extends "base.html" %}
{% load set_page_title %}
{% page_title "My Pictures" %}
...

Затем в вашем base.html:

...
{% block title %}{{ page_title }}{% endblock %}
...
<h1>{{ page_title }}</h1>
Ван Гейл
источник
ОднакоAny variable set in the context will only be available in the same block of the template in which it was assigned. This behavior is intentional; it provides a scope for variables so that they don’t conflict with context in other blocks.
Джонатан
0

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

Дочерний шаблон:

{% extends 'base.html' %}
...
{% block meta_image %}
{% block meta_image_secure %}
{% if object.cover_pic %}
{{ object.cover_pic.url }}
{% else %}
https://live-static.welovemicro.com/static/img/device-dark.png
{% endif %}
{% endblock %}
{% endblock %}
...

Затем в родительском base.html:

...
<meta property="og:image" itemprop="image" content="{% block meta_image %}https://live-static.welovemicro.com/static/img/device-dark.png{% endblock %}">
<meta property="og:image:secure_url" itemprop="image" content="{% block meta_image_secure %}https://live-static.welovemicro.com/static/img/device-dark.png{% endblock %}">
...
Рич Росс
источник
-3

В веточке вы можете сделать это так:

# base.html
<html>
    <head>
        <title>{{ block('title') }}</title>
    </head>
    <body>
        <h1>{{ block('title') }}</h1>
    </body>
</html>

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}

# pictures.html
{% extends 'base.html' %}
{% block title %}My Pictures{% endblock %}

# cats.html
{% extends 'base.html' %}
{% block title %}My Cats{% endblock %}
Марс
источник
3
Это вопрос о Django.
François Constant