Cómo repetir un "bloque" en una plantilla django

126

Quiero usar el mismo {% block%} dos veces en la misma plantilla de django. Quiero que este bloque aparezca más de una vez en mi plantilla base:

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

Y luego extiéndelo:

# 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 %}

Obtendré una excepción, ya que Django quiere que el bloque aparezca solo una vez:

TemplateSyntaxError en /

La etiqueta 'block' con el nombre 'title' aparece más de una vez

Una solución rápida y sucia sería duplicar el bloque de título en título1 y title2 :

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

Pero esto es una violación del principio DRY . Sería muy difícil ya que tengo muchas plantillas heredadas, y también porque no quiero ir al infierno ;-)

¿Hay algún truco o solución para este problema? ¿Cómo puedo repetir el mismo bloque en mi plantilla, sin duplicar todo el código?

David Arcos
fuente
1
También vea la solución a esta pregunta stackoverflow.com/q/1178743/168034
phunehehe
2
Vea esta respuesta particularmente a la pregunta a la que se vincula phunehehe.
Tobu

Respuestas:

69

Creo que el uso del procesador de contexto es en este caso una exageración. Puedes hacer esto fácilmente:

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

y entonces:

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

y así sucesivamente ... Parece compatible con DRY.

dqd
fuente
1
Podría intentar esto mañana: me he estado preguntando cómo guardar un poco de repetición en las plantillas y esto parece un buen enfoque. Gracias.
thebiglife
1
Este enfoque es excelente. He dividido mi base.html en base.html y superbase.html, por lo que esto también funciona si desea poner un marcado de título estándar (como un h1) en sus plantillas compartidas. Las páginas aún pueden anular el contenido del bloque de título y se actualizará en ambas ubicaciones.
SystemParadox
2
Esto no permite usar el texto más de dos veces, ¿verdad?
Dennis Golomazov
1
Denis Golomazov: No. En ese caso, es mejor usar el complemento macro (ver más abajo).
dqd
1
También se puede aplicar al revés: definiendo el h1contenido dentro del bloque que define el title. O un bloque que define una parte de title.
Mikael Lindlöf
83

Utilice el complemento de macros de plantilla de Django:

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

o

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>

y

# 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>

y

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}
John R Perry
fuente
2
¡Esto es fantástico! Esto realmente puede limpiar los problemas que obtengo al compartir plantillas con bucles django y bucles de datos ajax.
Glicerina
1
Buena solución. Sin embargo, es "use_macro". "usemacro" está mal.
Ramtin
Definitivamente debería estar integrado en Django por defecto.
zepp.lee
19

Probablemente no quiera usar un bloque, sino simplemente usar una variable:

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

Luego establece el título a través del contexto.

Aaron Maenpaa
fuente
17
Probablemente seco. Pero no querrás establecer el título dentro de la vista; pero en las plantillas.
Lakshman Prasad
66
Los títulos deben establecerse dentro de las plantillas, no deben ser proporcionados por el contexto, debe tener una forma de definir esta variable de "título", de lo contrario, esta no es una buena solución.
Guillaume Esquevin
Eso es lo que hacen las plantillas de administración de django (para {{title}}), pero definir el título en una eliminación es inconveniente.
Tobu
13

Aquí hay una forma que descubrí al intentar hacer lo mismo yo mismo:

# 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 %}

Desafortunadamente, requiere un archivo adicional, pero no requiere que pase el título de la vista.

Roman Starkov
fuente
Al final, me decidí por la solución {% macro%}, que no requiere un nuevo archivo, y en general me permite expresar exactamente lo que quiero expresar.
Roman Starkov
Simpel y eficiente. a diferencia de la respuesta de @dqd, los bloques no necesitan estar anidados, muy útil, por ejemplo, para las etiquetas de Facebook og, que pueden tener el mismo contenido que otros atributos principales. ¡Votar!
benzkji
2
Gran respuesta. Esto parece incluso MÁS SECO que la respuesta de @ dqd ya que no tiene que repetir la etiqueta <h1> en cada plantilla que hereda la base. Esta podría ser la respuesta aceptada.
Anupam
¡Excelente! He usado esto en escenarios más complejos donde quería repetir la fila de pie de página de una tabla en la parte superior. Y la <tr>fila era bastante compleja.
caram
12

Puedes usar {% include subtemplate.html %}más de una vez. No es lo mismo que los bloques, pero funciona.

Javier
fuente
Esto tiene el mismo problema. La plantilla base no sabrá qué sub-plantilla incluir.
Van Gale
Tenga en cuenta que includees más lento que block. docs.djangoproject.com/en/1.10/topics/performance/…
Wtower
5

Aquí hay una discusión: http://code.djangoproject.com/ticket/4529 Obviamente, el equipo central de django rechaza este ticket porque piensan que este no es un escenario común, sin embargo, no estoy de acuerdo.

repetir bloque es una implementación simple y limpia para esto: https://github.com/SmileyChris/django-repeatblock

la plantilla de macros es otra, sin embargo, el autor mencionó que no se ha probado cuidadosamente: http://www.djangosnippets.org/snippets/363/

Usé repetir bloque.

Robert Mao
fuente
44
El repositorio original django-repeatblock parece haber sido eliminado. El mejor tenedor de eso parece ser github.com/phretor/django-repeatblock ; También encontré github.com/ydm/django-sameas , que no requiere un parche de Django 'wontfix'.
natevw
4

Como actualización para cualquiera que se encuentre con esto, tomé el fragmento mencionado anteriormente y lo convertí en una biblioteca de etiquetas de plantilla, django-macros, hace que las macros sean más potentes y también implementa un patrón de bloque repetido explícitamente: django-macros .

Mella
fuente
4

Aquí hay una solución ligera similar a la anterior do_sety do_getla respuesta de la etiqueta de plantilla. Django le permite pasar todo el contexto de la plantilla a una etiqueta que le permite definir una variable global.

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 %}

etiqueta personalizada (tengo la idea aquí: https://stackoverflow.com/a/33564990/2747924 ):

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

Además, no olvide {% load %}sus etiquetas personalizadas ni las agregue a la lista integrada de opciones de plantilla para que no tenga que cargarlas en cada plantilla. La única limitación de este enfoque es {% define %}que debe llamarse desde una etiqueta de bloque porque las plantillas secundarias solo representan etiquetas de bloque que coinciden con las etiquetas principales. No estoy seguro si hay una forma de evitar eso. También asegúrese de que la definellamada llegue antes de intentar usarla obviamente.

manncito
fuente
3

A partir de la sugerencia de Van Gale, puede crear obtener y establecer etiquetas agregando lo siguiente a su archivo 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)

Luego establezca valores en una plantilla vía {% set foo %}put data here{% endset %}y consígalos {% get foo %}en otra.

kieran hervold
fuente
Creo que esa es la solución más elegante de todas. Gracias Kieran y Van Gale!
Robert Lacroix
Eso es bastante hábil, pero parece que podría ser aún mejor renderizar todos los nodos en la etiqueta Set, de lo contrario, Get los representa una y otra vez. Puedo pensar en razones que podrían ser una buena idea (representar el mismo bloque almacenado dentro de diferentes bloques en una página), pero solo pensé en señalarlo.
acjay
3

Yo también he encontrado la misma necesidad de repetir {% block%} en mis archivos de plantilla. El problema es que quiero que se use un Django {% block%} en cualquier caso de un condicional de Django, y quiero que el {% block%} se pueda sobrescribir por archivos posteriores que pueden extender el archivo actual. (Entonces, en este caso, lo que quiero es definitivamente más un bloque que una variable porque técnicamente no lo estoy reutilizando, solo aparece en cualquier extremo de un condicional.

El problema:

El siguiente código de plantilla de Django dará como resultado un error de sintaxis de plantilla, pero creo que es un "deseo" válido tener un {% block%} definido reutilizado en condicional (IE, ¿por qué el analizador Django valida la sintaxis en AMBOS extremos? de un condicional, ¿no debería solo validar la condición VERDADERA?)

# 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 %}

La solución:

Puede usar un {% include%} para insertar condicionalmente un {% block%} más de una vez. Esto funcionó para mí porque el verificador de sintaxis de Django incluye solo el VERDADERO {% include%}. Vea el resultado a continuación:

# 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
fuente
2

Utilizo esta respuesta para mantener las cosas secas.

{% extends "base.html" %}

{% with "Entry Title" as title %}
    {% block title %}{{ title }}{% endblock %}
    {% block h1 %}{{ title }}{% endblock %}
{% endwith %}
Cristiano largo
fuente
1

Hay dos soluciones fáciles para esto.

Lo más fácil es poner su título en una variable de contexto. Establecería la variable de contexto en su vista.

Si está utilizando algo como vistas genéricas y no tiene views.py para imágenes, gatos, etc., entonces puede seguir el camino de una etiqueta de plantilla personalizada que establece una variable en el contexto .

Seguir esta ruta te permitiría hacer algo como:

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

Luego en su base.html:

...
{% block title %}{{ page_title }}{% endblock %}
...
<h1>{{ page_title }}</h1>
Van Gale
fuente
Sin embargoAny 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.
Jonathan
0

La respuesta seleccionada alude a una solución fácil para envolver una etiqueta dentro de otra en la plantilla secundaria para darles a ambos el mismo valor. Lo uso para imágenes sociales así.

Plantilla hijo:

{% 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 %}
...

Luego en el padre 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 %}">
...
Ross rico
fuente
-3

En twig puedes hacer esto como:

# 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 %}
Marte
fuente
3
Esta es una pregunta sobre Django.
François Constant