Como repetir um "bloco" em um modelo de django

126

Eu quero usar o mesmo {% block%} duas vezes no mesmo modelo de django. Quero que esse bloco apareça mais de uma vez no meu modelo 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>

E depois estenda-o:

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

Eu receberei uma exceção, pois o Django deseja que o bloco apareça apenas uma vez:

TemplateSyntaxError em /

A tag 'block' com o nome 'title' aparece mais de uma vez

Uma solução rápida e suja seria duplicar o título do bloco em title1 e title2 :

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

Mas isso é uma violação do princípio DRY . Seria muito difícil, pois tenho muitos modelos herdados e também porque não quero ir para o inferno ;-)

Existe algum truque ou solução alternativa para esse problema? Como posso repetir o mesmo bloco no meu modelo, sem duplicar todo o código?

David Arcos
fonte
1
veja também a solução nesta questão stackoverflow.com/q/1178743/168034
phunehehe
2
Veja esta resposta particularmente à pergunta sobre a qual os links estão relacionados.
Tobu

Respostas:

69

Eu acho que o uso do processador de contexto é, neste caso, um exagero. Você pode fazer isso facilmente:

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

e depois:

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

e assim por diante ... Parece compatível com DRY.

dqd
fonte
1
Talvez eu tente fazer isso amanhã - estive pensando em como economizar um pouco de repetição nos modelos e isso parece uma boa abordagem. obrigado.
thebiglife 27/09/09
1
Essa abordagem é excelente. Dividi meu base.html em base.html e superbase.html, então isso também funciona se você quiser colocar uma marcação de título padrão (como um h1) em seus modelos compartilhados. As páginas ainda podem substituir o conteúdo do bloco de título e ele será atualizado nos dois locais.
SystemParadox
2
Isso não permite usar o texto mais do que duas vezes, não é?
Dennis Golomazov 23/01
1
Denis Golomazov: Não. Nesse caso, é melhor usar o plug-in de macro (veja abaixo).
dqd
1
Também pode ser aplicado o contrário: definir o h1conteúdo dentro do bloco que define o arquivo title. Ou um bloco que define uma parte do title.
Mikael Lindlöf 7/03/16
83

Use o plug-in de macros de modelo do Django:

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

ou

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>

e

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

e

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}
John R Perry
fonte
2
Isto é fantástico! Isso pode realmente resolver os problemas que ganho com o compartilhamento de modelos com loops de django e loops de dados ajax.
Glicerina
1
Boa solução. No entanto, é "use_macro". "usemacro" está errado.
Ramtin 28/11
Definitivamente deve ser incorporado no Django por padrão.
Zepp.lee # 15/18
19

Você provavelmente não quer usar um bloco, mas apenas usar uma variável:

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

Você define o título pelo contexto.

Aaron Maenpaa
fonte
17
Provavelmente seco. Mas você não gostaria de definir o título na visualização; mas nos modelos.
Lakshman Prasad
6
Os títulos devem ser definidos de dentro dos modelos, não devem ser fornecidos pelo contexto; você precisa ter uma maneira de definir essa variável "title", caso contrário, essa não é uma boa solução.
Guillaume Esquevin
É o que os modelos de administração do django fazem (em {{title}}), mas definir o título em uma remoção é inconveniente.
Tobu
13

Aqui está uma maneira que eu descobri ao tentar fazer a mesma coisa:

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

Infelizmente, requer um arquivo extra, mas não exige que você passe o título da visualização.

Roman Starkov
fonte
No final, decidi pela solução {% macro%}, que não requer um novo arquivo, e no geral me permite expressar exatamente o que quero expressar.
11119 Roman Starkov
simpel e eficiente. diferentemente da resposta do @dqd, os blocos não precisam ser aninhados, muito útil, por exemplo, para tags og do facebook, que podem ter o mesmo conteúdo que outros atributos do cabeçalho. upvote!
benzkji
2
Ótima resposta. Isso parece ainda mais DRY do que a resposta do @ dqd, pois você não precisa repetir a tag <h1> em cada modelo que herda a base. Esta poderia ser a resposta aceita.
Anupam
Excelente! Eu usei isso em cenários mais complexos, nos quais queria repetir a linha de rodapé de uma tabela no topo. E a <tr>briga era bastante complexa.
Caram
12

você pode usar {% include subtemplate.html %}mais de uma vez. não é o mesmo que blocos, mas faz o truque.

Javier
fonte
Isso tem o mesmo problema. O modelo base não saberá qual sub-modelo incluir.
Van Gale
Observe que includeé mais lento que block. docs.djangoproject.com/en/1.10/topics/performance/…
Wtower
5

Há alguma discussão aqui: http://code.djangoproject.com/ticket/4529 Obviamente, a equipe principal do django rejeita esse ticket porque acha que esse não é um cenário usado comum, mas eu discordo.

O bloco de repetição é uma implementação simples e limpa para isso: https://github.com/SmileyChris/django-repeatblock

macros de modelo é outra, no entanto, o autor mencionou que não foi testado com cuidado: http://www.djangosnippets.org/snippets/363/

Eu usei o repeatblock.

Robert Mao
fonte
4
O repositório django-repeatblock original parece ter sido excluído. O melhor fork disso parece ser github.com/phretor/django-repeatblock ; Também encontrei o github.com/ydm/django-sameas , que não requer um patch do Django 'wontfix'.
Natevw 02/09/19
4

Como uma atualização para quem se deparar com isso, peguei o trecho mencionado acima e o transformei em uma biblioteca de tags de modelo, django-macros, torna as macros mais poderosas e também implementa explicitamente um padrão de bloco repetido: django-macros .

usuario
fonte
4

Aqui está uma solução leve semelhante à resposta acima do_sete à do_gettag de modelo. O Django permite que você passe todo o contexto do modelo para uma tag, o que permite definir uma variável 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 %}

tag personalizada (teve a ideia aqui: https://stackoverflow.com/a/33564990/2747924 ):

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

Além disso, não se esqueça das {% load %}tags personalizadas ou adicione-as à lista de opções internas do modelo, para que você não precise carregá-las em todos os modelos. A única limitação a essa abordagem é a necessidade {% define %}de ser chamada de dentro de uma tag de bloco, porque os modelos filhos processam apenas tags de bloco que correspondem às tags pai. Não tenho certeza se existe uma maneira de contornar isso. Certifique-se também de que a definechamada é recebida antes de tentar usá-la obviamente.

manncito
fonte
3

Com base na sugestão de Van Gale, você pode criar tags get e set adicionando o seguinte ao seu arquivo 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)

Em seguida, defina valores em um modelo {% set foo %}put data here{% endset %}e obtenha-os {% get foo %}em outro.

Kieran Hervold
fonte
Eu acho que é a solução mais elegante de todas. Obrigado Kieran e Van Gale!
Robert Lacroix
Isso é muito bom, mas parece que pode ser ainda melhor renderizar todos os nós na tag Set, caso contrário, eles são renderizados repetidamente pelo Get. Posso pensar em razões que podem ser uma boa ideia (renderizar o mesmo bloco armazenado dentro de diferentes blocos em uma página), mas eu pensei em apontar isso.
acjay
3

Eu também encontrei a mesma necessidade de repetir {% block%} nos meus arquivos de modelo. O problema é que eu quero que um Django {% block%} seja usado nos dois casos de uma condição condicional do Django, e quero que o {% block%} seja sobrescrito pelos arquivos subseqüentes que podem estender o arquivo atual. (Portanto, nesse caso, o que eu quero é definitivamente mais um bloco do que uma variável, porque eu não estou tecnicamente reutilizando, apenas aparece nas duas extremidades de uma condicional.

O problema:

O código de modelo do Django a seguir resultará em um erro de sintaxe de modelo, mas acho que é um "desejo" válido ter um {% block%} definido reutilizado em uma condicional (IE, por que o analisador Django está validando a sintaxe em AMBOS OS TERMOS condicional, não deveria apenas validar a condição TRUTHY?)

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

A solução:

Você pode usar um {% include%} para inserir condicionalmente um {% block%} mais de uma vez. Isso funcionou para mim porque o verificador de sintaxe do Django inclui apenas o TRUTHY {% include%}. Veja o resultado abaixo:

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

Eu uso essa resposta para manter as coisas secas.

{% extends "base.html" %}

{% with "Entry Title" as title %}
    {% block title %}{{ title }}{% endblock %}
    {% block h1 %}{{ title }}{% endblock %}
{% endwith %}
Christian Long
fonte
1

Existem duas soluções fáceis para isso.

O mais fácil é colocar seu título em uma variável de contexto. Você definiria a variável de contexto em sua exibição.

Se você estiver usando algo como visualizações genéricas e não tiver um views.py para fotos, gatos etc., poderá seguir o caminho de uma tag de modelo personalizada que define uma variável no contexto .

Seguir essa rota permitiria fazer algo como:

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

Em seguida, no seu base.html:

...
{% block title %}{{ page_title }}{% endblock %}
...
<h1>{{ page_title }}</h1>
Van Gale
fonte
No entantoAny 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

A resposta selecionada faz alusão a uma solução fácil para quebrar uma marca dentro de outra no modelo filho para fornecer a ambos o mesmo valor. Eu uso isso para imagens sociais como essa.

Modelo filho:

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

Então no pai 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 %}">
...
Rich Ross
fonte
-3

No galho, você pode fazer assim:

# 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
fonte
3
Esta é uma pergunta sobre o Django.
François Constant