Accueil / Blog / Métier / 2016 / Wagtail : Créer ses modèles de type de contenu (partie 1)

Wagtail : Créer ses modèles de type de contenu (partie 1)

Par Yann Fouillat — publié 05/07/2016, édité le 18/07/2016
Contributeurs : Yannick Chabbert
On commence à avoir l'habitude d'initialiser son CMS directement depuis une interface web, cela incluant souvent de nombreux panneaux complexes. Wagtail reprend plutôt la philosophie Django et nous propose de définir les types de contenus comme des modèles.
Wagtail : Créer ses modèles de type de contenu (partie 1)

Wagtail

Une connaissance des principes de base de Django est requise pour la lecture de cette article. Si ce n'est pas votre cas, je vous invite à lire l'introduction présente dans la documentation officiel.

Initialisation de Wagtail

L'initialisation d'un site Wagtail est très similaire à celle d'un site Django : créer un environnement virtuel, installer wagtail, puis utiliser la commande wagtail. Il suffit ensuite de créer la base de données et un compte d'administrateur :

$ mkvirtualenv mon_site_wagtail
$ pip install wagtail
$ wagtail start mon_site_wagtail
$ cd mon_site_wagtail
$ python manage.py migrate
$ python manage.py createsuperuser

Nous avons deux applications créées par défaut: home et search. home contient un type de contenu tandis que search définis une vue permettant de faire des recherches.

Champs

Chaque type de contenu est en fait un modèle Django héritant du modèle Page. Ce modèle possède un certain nombre de champs par défaut (tel qu'un titre, un slug, ...). La liste des champs par défaut est présent dans la documentation.

Les champs utilisés sont les mêmes que Django (CharField, DateTimeField, ...), en plus des champs RichTextField, permettant l'utilisation d'un WYSIWYG, et StreamField, que nous verrons juste après. Créons notre premier type de contenu en modifiant HomePage :

class HomePage(Page):
    sous_titre = models.CharField(max_length=255)
    intro = RichTextField()
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

L'ajout d'une image est une simple ForeignKey vers le modèle wagtailimages.Image de Wagtail. Il est possible de lier un document de la même facon avec le modèle wagtaildocs.Document.

StreamField

Un StreamField est un champ un peu particulier permettant le mélange de plusieurs type de champs de manière organisée. Une présentation est disponible sur YouTube. Voyons comment créer un StreamField :

class HomePage(Page):
    [...]
    corps = StreamField([
        ('titre', blocks.CharBlock(classname="full title")),
        ('paragraphe', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
    ])

Un StreamField est une liste de blocks. La liste complète des blocks disponibles est dans la documentation officielle.

Bien, maintenant que nous avons définis les champs de notre type de contenu, il ne nous reste plus qu'a choisir où les afficher dans l'écran d'édition.

Panels

/blog/metier/2016/images-presentation-wagtail/panels

Wagtail propose plusieurs panneaux (onglets) pour organiser les champs. content_panels pour le contenu en lui même, promote_panels pour les métadonnés et settings_panels pour les paramètres :

class HomePage(Page):
    # [...]

    content_panels = Page.content_panels + [
        FieldPanel('sous_titre'),
        ImageChooserPanel('image'),
        FieldPanel('intro', classname="full"),
        StreamFieldPanel('corps')
    ]

Un panneau est une liste de FieldPanels. Il en existe plusieurs types.

FieldPanel

FieldPanel pour tous les champs de base. FieldPanel peut prendre le paramètre classname pour ajouter une classe au champ. Wagtail met plusieurs classes utiles à disposition : - title, indiquant un champ titre. - full, permettant au champs de prendre toute la place à disposition. - col1 à col12, indiquant la taille en nombre de colonnes (sur douze) que le champ prendra.

/blog/metier/2016/images-presentation-wagtail/field-row-panel

StreamFieldPanel

StreamFieldPanel pour les StreamField :

/blog/metier/2016/images-presentation-wagtail/stream-field-panel

ChooserPanel

Les ChooserPanel pour selectionner un objet déjà existant. Il en existe quatre différents : PageChooserPanel, ImageChooserPanel, DocumentChooserPanel ou SnippetChooserPanel :

/blog/metier/2016/images-presentation-wagtail/image-chooser-panel

Organiser les champs

Il existe trois types de FieldPanel permettant de mieux organiser les champs :

MultiFieldPanel

MultiFieldPanel permet de grouper des champs ensemble. Ajouter collapsible en classe permet de créer un accordéon pour ces champs. Ajouter collapsed permet d'avoir l'accordéon fermé par défaut.

content_panels = Page.content_panels + [
    MultiFieldPanel([
            FieldPanel('sous_titre'),
            ImageChooserPanel('image'),
        ],
        heading="Un MultiFieldPanel",
        classname="collapsible collapsed",
    ),
    FieldPanel('intro', classname="full"),
    StreamFieldPanel('corps')
]
/blog/metier/2016/images-presentation-wagtail/multi-field-panel

FieldRowPanel

FieldRowPanel permet de réunir plusieurs champs sur une même ligne. Il est important de préciser classname avec la taille des colonnes pour que les champs s'affichent correctement :

content_panels = Page.content_panels + [
    FieldRowPanel([
        FieldPanel('sous_titre', classname="col6"),
        FieldPanel('intro', classname="col6"),
    ]),
    ImageChooserPanel('image'),
    StreamFieldPanel('corps')
]
/blog/metier/2016/images-presentation-wagtail/field-row-panel

InlinePanel

InlinePanel permet de gérer un autre modèle, et permet notamment de créer un champ multiple.

Les InlinePanels utilisent un modèle spécial, Orderable (Documentation) :

class HomePage(Page):
    [...]

    content_panels = Page.content_panels + [
        [...]
        InlinePanel('liens', label="Liens")
    ]

class HomePageLien(Orderable):
    page = ParentalKey(HomePage, related_name='liens')
    nom = models.CharField(max_length=255)
    url = models.URLField()

    panels = [
        FieldPanel('nom'),
        FieldPanel('url'),
    ]
/blog/metier/2016/images-presentation-wagtail/inline-panel

ParentalKey est juste une ForeignKey utilisé pour django-modelcluster (pour plus d'information, voir la documentation).

Comme tout modèle Django, nous devons d'abord créer et faire tourner les migrations :

$ python manage.py makemigrations
$ python manage.py migrate

N'hésitez pas à consulter la Documentation officielle pour pousser plus loin.

Nous pouvons dès à présent créer nos premiers contenus. Mais il reste un dernier point à comprendre avant de se jeter la-dessus, et pas des moindres.

Hiérarchie

Wagtail organise les contenus par arborescence (hiérarchie). Comme tout arbre, il existe par défaut une page racine d'un site (oui, Wagtail est multisite par défaut, mais nous reviendrons sur ce point dans un prochain article). À cette page racine, on y rattache traditionnellement une page enfant de type page d'accueil. C'est le premier niveau. Puis à partir de cette page d'accueil, on aura des pages enfants de type listes (deuxième niveau) et enfin des pages de détails. Pour reprendre notre exemple, on va considérer que notre page d'accueil va contenir une page de blog qui listera les billets de blog en mode aperçu. Pour cela, il faut que cette page d'index de blog n'accepte que les pages de type blog comme enfant. Inversement, on peut définir dans les pages de blog que seule la page d'index peut être la parente. Pour y parvenir, Wagtail met à disposition deux attributs parent_page_types et subpage_types à définir dans les modèles de pages (Documentation).

Pour notre page d'accueil, nous allons donc créer un autre modèle BlogIndex qui n'acceptera que les pages de type blog en enfant :

class BlogIndex(Page):
    [...]
    subpage_types = ['app.BlogPage']

Et ajouter un modèle BlogPage qui n'autorisera que la(es) page(s) de type BlogIndex à être parente, et qui ne pourra pas avoir elle-même d'enfant :

class BlogPage(Page):
    [...]
    parent_page_types = ['app.BlogIndex']
    subpage_types = []

Conclusion

Vous savez désormais tout sur les Pages. Ou presque. Nous verrons dans la prochaine partie comment utiliser ce modèle ainsi que son manager.

ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Django Rest Framework : les tests (partie 8) Django Rest Framework : les tests (partie 8) 22/02/2016

Avec les API REST, développer très rapidement des tests fonctionnels complets qui frôlent les ...

Formation Django initiation à Toulouse du 13 au 15 mars Formation Django initiation à Toulouse du 13 au 15 mars 26/01/2017

Entrez de plain-pied dans l'univers de Django aux côtés de développeurs ayant une expérience de ...

Python : Bien configurer son environnement de développement Python : Bien configurer son environnement de développement 07/12/2015

Comment utiliser les bonnes pratiques de développement Python.

Internationalisation avec Django Internationalisation avec Django 27/11/2018

En tant que développeurs nous sommes parfois confronté à la problématique de l'accessibilité ...

Retour sur la PyConFr 2016 Retour sur la PyConFr 2016 18/10/2016

Nous étions présents à Rennes pour PyConFr 2016. Voici notre compte-rendu à chaud.