Comment répéter un «bloc» dans un modèle django

126

Je veux utiliser le même {% block%} deux fois dans le même modèle django. Je veux que ce bloc apparaisse plus d'une fois dans mon modèle de 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>

Et puis prolongez-le:

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

J'obtiendrai une exception, car Django souhaite que le bloc n'apparaisse qu'une seule fois:

TemplateSyntaxError à /

La balise 'block' avec le nom 'title' apparaît plus d'une fois

Une solution rapide et sale consisterait à dupliquer le titre du bloc dans title1 et title2 :

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

Mais c'est une violation du principe DRY . Ce serait très difficile car j'ai beaucoup de modèles héritiers, et aussi parce que je ne veux pas aller en enfer ;-)

Y a-t-il une astuce ou une solution à ce problème? Comment puis-je répéter le même bloc dans mon modèle, sans dupliquer tout le code?

David Arcos
la source
1
voir aussi la solution sur cette question stackoverflow.com/q/1178743/168034
phunehehe
2
Voir cette réponse en particulier à la question à laquelle phunehehe renvoie.
Tobu

Réponses:

69

Je pense que l'utilisation du processeur de contexte est dans ce cas exagéré. Vous pouvez facilement faire ceci:

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

puis:

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

et ainsi de suite ... Ressemble à DRY-compatible.

dqd
la source
1
Je pourrais essayer cela demain - je me suis demandé comment enregistrer un peu de répétition dans les modèles et cela semble être une bonne approche. Merci.
thebiglife le
1
Cette approche est excellente. J'ai divisé mon base.html en base.html et superbase.html, donc cela fonctionne également si vous souhaitez mettre un balisage de titre standard (comme un h1) dans vos modèles partagés. Les pages peuvent toujours remplacer le contenu du cartouche et il sera mis à jour aux deux emplacements.
SystemParadox
2
Cela ne permet pas d'utiliser le texte plus de deux fois, n'est-ce pas?
Dennis Golomazov
1
Denis Golomazov: Non. Dans ce cas, il vaut mieux utiliser le plugin macro (voir ci-dessous).
dqd
1
Peut également être appliqué dans l'autre sens: définir le h1contenu à l'intérieur du bloc qui définit le title. Ou un bloc qui définit une partie du fichier title.
Mikael Lindlöf
83

Utilisez le plugin de macros de modèle 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>

et

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

et

# blog.html
{% extends 'base.html' %}
{% block title %}My Blog{% endblock %}
John R Perry
la source
2
C'est fantastique! Cela peut vraiment résoudre les problèmes que je rencontre avec le partage de modèles avec des boucles django et des boucles de données ajax.
Glycerine le
1
Bonne solution. Cependant, c'est "use_macro". "usemacro" est faux.
Ramtin
Doit définitivement être intégré à Django par défaut.
zepp.lee
19

Vous ne voulez probablement pas utiliser un bloc mais plutôt simplement utiliser une variable:

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

Vous définissez ensuite le titre dans le contexte.

Aaron Maenpaa
la source
17
Probablement sec. Mais vous ne voudriez pas définir le titre dans la vue; mais dans les modèles.
Lakshman Prasad du
6
Les titres doivent être définis à partir des modèles, non fournis par le contexte, vous devez avoir un moyen de définir cette variable "titre", sinon ce n'est pas une bonne solution.
Guillaume Esquevin
C'est ce que font les modèles d'administration de django (pour {{title}}), mais définir le titre lors d'une suppression n'est pas pratique.
Tobu
13

Voici une façon que j'ai découverte en essayant de faire la même chose moi-même:

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

Nécessite malheureusement un fichier supplémentaire, mais ne nécessite pas que vous passiez le titre de la vue.

Roman Starkov
la source
Au final, j'ai opté pour la solution {% macro%}, qui ne nécessite pas de nouveau fichier, et globalement me permet d'exprimer exactement ce que je veux exprimer.
Roman Starkov
simple et efficace. contrairement à la réponse de @dqd, les blocs n'ont pas besoin d'être imbriqués, très utile par exemple pour les balises facebook og, qui peuvent avoir le même contenu que d'autres attributs head. upvote!
benzkji
2
Très bonne réponse. Cela semble encore plus sec que la réponse de @ dqd puisque vous n'avez pas à répéter la balise <h1> dans chaque modèle qui hérite de base. Cela pourrait être la réponse acceptée.
Anupam
Excellent! J'ai utilisé cela dans des scénarios plus complexes où je voulais répéter la ligne de pied de page d'un tableau en haut. Et la <tr>rangée était plutôt complexe.
caram
12

vous pouvez utiliser {% include subtemplate.html %}plusieurs fois. ce n'est pas la même chose que les blocs, mais ça fait l'affaire.

Javier
la source
Cela pose le même problème. Le modèle de base ne saura pas quel sous-modèle inclure.
Van Gale
Veuillez noter que includec'est plus lent que block. docs.djangoproject.com/en/1.10/topics/performance/…
Wtower
5

Il y a quelques discussions ici: http://code.djangoproject.com/ticket/4529 De toute évidence, l'équipe de base de django rejette ce ticket car ils pensent que ce n'est pas un scénario couramment utilisé, mais je ne suis pas d'accord.

Le bloc de répétition est une implémentation simple et propre pour cela: https://github.com/SmileyChris/django-repeatblock

les macros de modèles en sont une autre, mais l'auteur a mentionné qu'elles n'avaient pas été soigneusement testées: http://www.djangosnippets.org/snippets/363/

J'ai utilisé repeatblock.

Robert Mao
la source
4
Le dépôt d'origine django-repeatblock semble avoir été supprimé. Le meilleur fork de cela semble être github.com/phretor/django-repeatblock ; J'ai également trouvé github.com/ydm/django-sameas , qui ne nécessite pas de patch Django «wontfix».
natevw le
4

En tant que mise à jour pour quiconque rencontre cela, j'ai pris l'extrait de code mentionné ci-dessus et l'ai transformé en une bibliothèque de balises de modèles, django-macros, rend les macros plus puissantes et implémente également un modèle de bloc répété explicitement: django-macros .

pseudo
la source
4

Voici une solution légère similaire à la réponse de balise ci-dessus do_setet do_getmodèle. Django vous permet de passer tout le contexte du modèle dans une balise qui peut vous permettre de définir une variable globale.

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 personnalisé (j'ai eu l'idée ici: https://stackoverflow.com/a/33564990/2747924 ):

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

N'oubliez pas non plus {% load %}vos balises personnalisées ou ajoutez-les à la liste intégrée des options de modèle afin de ne pas avoir à les charger dans chaque modèle. La seule limitation de cette approche est le {% define %}doit être appelé à partir d'une balise de bloc car les modèles enfants ne rendent que les balises de bloc qui correspondent aux balises parentes. Je ne sais pas s'il existe un moyen de contourner cela. Assurez-vous également que l' defineappel arrive avant d'essayer de l'utiliser évidemment.

manncito
la source
3

En vous basant sur la suggestion de Van Gale, vous pouvez créer des balises get et set en ajoutant ce qui suit à votre fichier 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)

Ensuite, définissez les valeurs dans un modèle via {% set foo %}put data here{% endset %}et récupérez-les via {% get foo %}un autre.

kieran hervold
la source
Je pense que c'est la solution la plus élégante de toutes. Merci Kieran et Van Gale!
Robert Lacroix
C'est assez simple, mais il semble qu'il serait peut-être encore mieux de rendre tous les nœuds de la balise Set, sinon ils sont rendus encore et encore par Get. Je peux penser à des raisons qui pourraient être une bonne idée (rendre le même bloc stocké à l'intérieur de différents blocs sur une page), mais j'ai juste pensé le souligner.
acjay du
3

Moi aussi, j'ai rencontré le même besoin de répéter {% block%} dans mes fichiers de modèle. Le problème est que je veux qu'un Django {% block%} soit utilisé dans les deux cas d'un conditionnel Django, et je veux que le {% block%} soit écrasable par les fichiers suivants qui peuvent étendre le fichier actuel. (Donc dans ce cas, ce que je veux est définitivement plus un bloc qu'une variable parce que je ne le réutilise pas techniquement, il apparaît juste à chaque extrémité d'un conditionnel.

Le problème:

Le code de modèle Django suivant entraînera une erreur de syntaxe de modèle, mais je pense que c'est un "souhait" valide d'avoir un {% block%} défini réutilisé dans un conditionnel (IE, pourquoi l'analyseur Django valide la syntaxe aux DEUX extrémités d'un conditionnel, ne devrait-il pas seulement valider la condition VRAIE?)

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

Vous pouvez utiliser un {% include%} pour insérer conditionnellement un {% block%} plusieurs fois. Cela a fonctionné pour moi car le vérificateur de syntaxe de Django n'inclut que TRUTHY {% include%}. Voir le résultat ci-dessous:

# 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
la source
2

J'utilise cette réponse pour garder les choses au sec.

{% extends "base.html" %}

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

Il existe deux solutions simples pour cela.

Le plus simple est de mettre votre titre dans une variable de contexte. Vous définiriez la variable de contexte dans votre vue.

Si vous utilisez quelque chose comme des vues génériques et que vous n'avez pas de views.py pour les images, les chats, etc., vous pouvez opter pour une balise de modèle personnalisée qui définit une variable dans le contexte .

Suivre cette route vous permettrait de faire quelque chose comme:

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

Puis dans votre base.html:

...
{% block title %}{{ page_title }}{% endblock %}
...
<h1>{{ page_title }}</h1>
Van Gale
la source
CependantAny 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 réponse sélectionnée fait allusion à une solution de contournement simple pour envelopper une balise dans une autre dans le modèle enfant pour leur donner à tous les deux la même valeur. J'utilise ceci pour des images sociales comme ça.

Modèle enfant:

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

Puis chez le parent 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
la source
-3

En brindille, vous pouvez faire ceci comme:

# 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 %}
Mars
la source
3
C'est une question sur Django.
François Constant