StreamField toevoegen aan een Wagtail-pagina

In dit artikel zullen we een StreamField toevoegen aan een Wagtail-pagina, zodat we tekst en inline afbeeldingen kunnen invoeren via de CMS-editor.

8 juli 2020 12:25
Thema's: Wagtail Streamfield opties

Stel dat je Wagtail hebt geïnstalleerd en een heel eenvoudige startpagina hebt gemaakt. Door gebruik te maken van het StreamField van Wagtail kunnen we op een zeer flexibele manier tekst en afbeeldingen aan pagina's toevoegen. We zullen een artikelpaginamodel maken dat een overzicht geeft van alle artikelen. De artikelen staan in omgekeerde chronologische volgorde. Omdat de Wagtail-docs een zeer goede tutorial bevatten, zullen we direct wat dieper duiken.

Voeg in models.py een model ArticlePage toe (als je al imports hebt, verwijder dan duplicaten):

from django.db import models
from django.utils.translation import gettext_lazy as _
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, StreamFieldPanel
from wagtail.core import blocks
from wagtail.core.fields import RichTextField, StreamField
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtailtrans.models import TranslatablePage
from .blocks import InlineImageBlock


class ArticlePage(TranslatablePage):
    intro = RichTextField(blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image', blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("Image")
    )
    featured = models.BooleanField(default=False)
    body = StreamField([
        ('paragraph', blocks.RichTextBlock()),
        ('image', InlineImageBlock()),
   ])

    content_panels = TranslatablePage.content_panels + [
        FieldPanel('intro'),
        FieldPanel('featured'),
        ImageChooserPanel('image'),
        StreamFieldPanel('body'),
   ]

Het afbeeldingsveld is geen verplicht veld, daarom is blank = True, null = True en on_delete = models.SET_NULL. We stellen related_name gelijk aan '+', omdat we geen achterwaartse relatie willen creëren van de afbeelding naar de pagina. Het veld featured wordt gebruikt om artikelen voor onze homepage te selecteren. StreamField maakt het combineren van tekst, afbeeldingen, video's, citaten, codefragmenten etc. in één entiteit mogelijk. Het bestaat uit (naam, block_type) tuples. Wagtail biedt veel blokken standaard aan. Het RichTextBlock is standaard Wagtail.

Het InlineImageBlock is een aangepast blok dat we gaan maken, met behulp van StructBlock. Hoewel het niet strikt noodzakelijk is, is het verstandig om StructBlock-modellen in een apart bestand te plaatsen; de demosite van Wagtail doet het zo. Maak dus een blocks.py in je app en voeg de volgende inhoud toe:

from django.utils.translation import gettext_lazy as _
from wagtail.core import blocks
from wagtail.core.blocks import CharBlock
from wagtail.images.blocks import ImageChooserBlock


class InlineImageBlock(blocks.StructBlock):
    image = ImageChooserBlock(label=_("Image"))
    caption = CharBlock(required=False, label=_("Caption"))
    float = blocks.ChoiceBlock(
        required=False,
        choices=[('right', _("Right")), ('left', _("Left")), ('center', _("Center"))],
        default='right',
        label=_("Float"),
    )
    size = blocks.ChoiceBlock(
        required=False,
        choices=[('small', _("Small")), ('medium', _("Medium")), ('large', _("Large"))],
        default='small',
        label=_("Size"),
    )

    class Meta:
        icon = 'image'

Alle voor mensen leesbare strings worden vertaald met gettext_lazy. image en caption spreken voor zich. Het veld float wordt in de template gebruikt om de afbeelding rechts, links of in het midden van de pagina te plaatsen; de parameter size werkt op dezelfde manier. Beide zijn ChoiceBlocks. Met de klasse Meta kan het pictogram worden ingesteld. Je kunt een lijst met alle beschikbare pictogrammen bekijken in de Wagtail styleguide; Als je dat niet wilt installeren, zoek dan naar Wagtail StreamField-pictogrammen op internet. Voor ons doel voldoet image.

Het model is klaar, tijd om de template te maken. Zoals we eerder bij de startpagina hebben gezien, moet de naam van de template article_page.html zijn. Je kunt de inhoud op Github bekijken, of je kunt de video in dit artikel bekijken. De gebruikelijke elementen zoals het laden van bibliotheken, paginatitel, intro, datum worden geconfigureerd zoals in home_page.html. De enige nieuwe elementen zijn de volgende:

{%  include "streamfield.html" %}
<a href="{{ page.get_parent.url }}">{% trans "Return to articles" %}</a>

d.w.z. een template streamfield.html en een link naar de bovenliggende pagina (die we hieronder zullen maken). Beide zijn verpakt in een container zodat ze er mooier uitzien op de pagina. Het bestand streamfield.html wordt elke keer gebruikt dat een blok moet worden gerenderd en heeft de volgende inhoud:

{% load wagtailcore_tags wagtailimages_tags %}

{% for block in page.body %}
    {% if block.block_type == 'image' %}
        <div class="block-{{ block.block_type }}-{{ block.value.float }}">
            <!-- make use of specific properties of Wagtail 'image' tag -->
            {% if block.value.size == "small" %}
                {% image block.value.image width-240 class="img-fluid" %}
            {% elif block.value.size == "medium" %}
                {% image block.value.image width-480 class="img-fluid" %}
            {% else %}
                {% image block.value.image width-2400 class="img-fluid" %}
            {% endif %}
            {{ block.value.caption }}
        </div>
   {% else %}
        <div class="block-{{ block.block_type }}">
            {% include_block block %}
        </div>
    {% endif %}
{% endfor %}

Er wordt onderscheid gemaakt tussen de verschillende soorten blokken: afbeelding en andere. We verwijzen naar de individuele velden van de StructBlocks via de eigenschap value; hetzelfde voor de eigenschap block_type. Voor afbeeldingen maken we gebruik van de image-templatetag en beeldweergave-opties van Wagtail. De img-fluid klasse van Bootstrap zorgt ervoor dat alle afbeeldingen mooi in de viewport passen. De include_block tag zorgt voor de weergave van alle andere (niet-beeld) blokken.

Het is tijd om wat styling aan onze CSS toe te voegen (voor de eerste keer in ons project, voor degenen onder jullie die het hebben gevolgd). De essentiële stylingelementen voor de bovenstaande templates zijn (ik heb er enkele weggelaten die eenvoudig zijn, bijv. block-paragraph, block-image-right/left, block-video-right/left, block-small/medium/large, ze zijn in de repository):

.img-fluid {
    max-width: 100%;
    height: auto;
}
/* for the images and text inside streamfields */
.block-image-center {
    display: grid;
    /* justify-content works here because Wagtail creates an image of fixed dimensions */
    justify-content: center;
    overflow: hidden;
    font: italic 12px Georgia, serif;
}

Een afbeelding centreren werkt het beste met justify-content omdat Wagtail een afbeelding met vaste afmetingen maakt die vervolgens wordt gecentreerd binnen de div met de eigenschap justify-content.

Onze artikelpagina is nu klaar. Nu maken we een indexpagina met alle artikelen die we willen schrijven in onze models.py:

class ArticleIndexPage(TranslatablePage):
    intro = RichTextField(blank=True)

    # Specifies that only ArticlePage objects can live under this index page
    subpage_types = ['ArticlePage']

    # A method to access and reorder the children of the page (i.e. ArticlePage objects)
    def articlepages(self):
        return ArticlePage.objects.child_of(self).live().order_by('-first_published_at')

    def featured_articlepages(self):
        return self.articlepages().filter(featured=True)

    content_panels = TranslatablePage.content_panels + [
        FieldPanel('intro', classname='full'),
    ]

De parameter subpage_types zorgt ervoor dat we alleen ArticlePage-instanties kunnen maken onder deze indexpagina. De methode articlepages selecteert de live onderliggende pagina's van deze indexpagina en rangschikt ze antichronologisch. Het veld first_published_at is een standaardveld van het paginamodel. De methode featured_articlepages is een subset: alleen degene die zijn gemarkeerd als featured voor de startpagina. We gebruiken de methode articlepages in onze template article_index_page.html; het interessante deel van de template is:

{% for childpage in page.articlepages %}
    <div class="col-auto mb-3">
        <div class="card article">
            <a href="{{ childpage.url }}">
                {% if childpage.specific.image %}
                    {% image childpage.specific.image fill-320x240 class="img-front rounded" %}
                {% else %}
                    <img alt="" src="{% static 'images/transparent.png' %}" width="320" height="240" class="img-default rounded">
                {% endif %}
                <img alt="" src="{% static 'images/transparent.png' %}" width="320" height="240" class="img-background rounded">
                <div class="card-img-overlay">
                    <h5 class="card-title">{{ childpage.title }}</h5>
                    <p class="card-subtitle">{{ childpage.specific.intro|striptags|safe|truncatewords:15 }}</p>
                    <p class="card-text small">{{ childpage.specific.first_published_at }}</p>
                </div>
            </a>
        </div>
    </div>
{% endfor %}

Voor elke onderliggende pagina wordt een link gemaakt in de vorm van een afbeelding van 320 x 240 pixels. We gebruiken Bootstrap's klassen voor kaarten, zoals card-img-overlay. Op de afbeelding staat de titel van de pagina plus de eerste 15 woorden van de intro en de datum. We gebruiken Django's filter striptags om alle html-tags in de titel te verwijderen, de filter safe om escapen van aanhalingstekens in de tekst te voorkomen en de filter truncatewords om de intro na 15 woorden af ​​te kappen. Zoals uitgelegd in de Wagtail-documentatie, moeten we de methode specific gebruiken om te verwijzen naar de velden van de kinderen van de ArticlePage instantie.

Als de onderliggende pagina een afbeelding bevat, wordt deze afbeelding gebruikt, zo niet, dan wordt de afbeelding images/transparent.png gebruikt. Dit is een volledig transparant beeld (vergelijk het met een glasplaat) dat gemakkelijk te maken of te downloaden is; zet het in de subdirectory images van cms/static/cms en voeg deze directory toe aan STATICFILES_DIRS in de instellingen. Door de eigenschap background-color te gebruiken, kunnen we deze in elke kleur veranderen; de klasse image-default in onze CSS-instellingen maakt deze grijs. De tweede afbeelding met de klasse img-background is een overlay die van kleur verandert wanneer je er met de muis overheen beweegt. Beeldoverlay wordt bereikt door de combinatie van de position: relative en position: absolute declaraties. We gebruiken Bootstrap's klasse rounded om de hoeken van de afbeeldingen af te ronden. De CSS-instellingen zijn als volgt:

.article, .theme {
    width: 320px;
    height: 240px;
}
.img-front {
    position: relative;
    max-width: 100%;
}
.img-default {
    position: relative;
    max-width: 100%;
    background-color: grey;
}
.img-background {
    position: absolute;
    max-width: 100%;
    max-height: 100%;
    left: 0;
    background-color: navy;
    opacity: 0;
    transition: opacity 300ms;
}
a:hover .img-background {
    opacity: 0.6;
}

Nu willen we een link naar de artikelindexpagina plus de aanbevolen artikelen op onze startpagina. Voeg toe aan het HomePage-model:

article_section_title = models.CharField(
    null=True,
    blank=True,
    max_length=255,
    help_text=_("Title to display above the article section"),
)
article_section_intro = RichTextField(blank=True)
article_section = models.ForeignKey(
    TranslatablePage,
    null=True,
    blank=True,
    on_delete=models.SET_NULL,
    related_name='+',
    help_text=_("Featured articles for the homepage"),
    verbose_name=_("Article section"),
)

Voeg de imports MultiFieldPanel en PageChooserPanel toe aan de content_panels van het HomePage model:

MultiFieldPanel([
        FieldPanel('article_section_title'),
        FieldPanel('article_section_intro', classname='full'),
        PageChooserPanel('article_section'),
        ], heading=_("Article section"), classname='collapsible'),

Hierdoor kunnen we een artikelsectie maken met een titel, inleiding en een link naar een TranslatablePage; in admin zullen we hier de artikelindexpagina aan toewijzen, zodat we er in onze template toegang toe hebben. Daarin voegen we een stuk html toe dat erg lijkt op wat we zojuist hebben gemaakt voor onze article_page.html, alleen de loop over alle artikelen is anders:

{% for childpage in page.article_section.specific.featured_articlepages %}
{% endfor %}

We moeten wagtailimages_tags en static laden omdat we Wagtail's image-tag en de afbeelding transparent.png in onze directory static gebruiken.

Oef, we zijn klaar! Migreer de database om alle modelwijzigingen op te nemen. Ga dan naar Wagtail's admin, naar Pagina's en naar onze homepage en maak een onderliggende pagina met het model ArticleIndexPage. Geef het een titel en typ wat tekst in het introveld; publiceer het. Maak vervolgens een onderliggende pagina van deze indexpagina (deze gebruikt automatisch het model ArticlePage), voeg tekst toe, upload en voeg afbeeldingen in en publiceer. Maak een andere artikelpagina. Bekijk de afbeeldingslinks op de startpagina en indexpagina. Voel je vrij om de styling te veranderen.

Hiermee is de basis gelegd voor het plaatsen van inhoud op onze site. Je kunt meer lezen over het embedden van video, thema's toevoegen en navigatie.

Reageer op dit artikel (log eerst in of bevestig hieronder met naam en email)