Thema's toevoegen aan artikelen in Wagtail

Als je veel artikelen op je Wagtail-site hebt, kan het handig zijn om ze in thema's te ordenen. In deze tutorial leggen we uit hoe je thema's en themapagina's maakt met alle artikelen per thema.

9 juli 2020 13:40

We maken een themamodel door een Wagtail-fragment (snippet) in onze models.py vóór ArticlePage te plaatsen:

from wagtail.snippets.models import register_snippet

@register_snippet
class Theme(models.Model):
    name = models.CharField(max_length=255)

    panels = [
        FieldPanel('name'),
    ]

    def __str__(self):
        return self.name

Het model heeft slechts één veld genaamd name, dat kan worden gemaakt en bewerkt in admin, in een apart menu Snippets. We creëren een relatie tussen een artikelpagina en een of meer thema's door het volgende veld toe te voegen aan ons ArticlePage-model (importeer ParentalManyToManyField van modelcluster.fields):

themes = ParentalManyToManyField(Theme, blank=True,  related_name='articlepages', verbose_name=_("Themes"))

en aan de content_panels van ArticlePage (importeer forms uit Django):

FieldPanel('themes', widget=forms.CheckboxSelectMultiple),

Dit stelt ons in staat om voor elk artikel de relevante thema's te kiezen en alle artikelen met betrekking tot een thema op te halen via de related_name articlepages. Merk op dat het niet nuttig is om null = True toe te voegen aan een ManyToManyField, zoals vermeld in de documentatie. Definieer nu een ThemePage-model:

from wagtail.snippets.edit_handlers import SnippetChooserPanel

class ThemePage(TranslatablePage):
    theme = models.ForeignKey(Theme, on_delete=models.SET_NULL, null=True, related_name='themepages')
    intro = RichTextField(blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image', blank=True, null=True, on_delete=models.SET_NULL, related_name='+'
    )
    caption = models.CharField(blank=True, null=True, max_length=250)

    def articlepages(self):
        return self.theme.articlepages.filter(language=self.language).live().order_by('-first_published_at')

    content_panels = TranslatablePage.content_panels + [
        SnippetChooserPanel('theme'),
        FieldPanel('intro', classname='full'),
        ImageChooserPanel('image'),
        FieldPanel('caption'),
    ]

Hier gebruiken we een ForeignKey om thema en themapagina te verbinden; de reden hiervoor is dat we pagina's in meerdere talen hebben die betrekking hebben op één thema. Als je maar één taal zou hebben, dan zou je kunnen kiezen voor een OneToOneField. We definiëren een methode articlepages om de artikelen in omgekeerde chronologische volgorde te plaatsen, net zoals we in een eerdere tutorial hebben gedaan met ons ArticleIndexPage-model. We willen alleen artikelpagina's in dezelfde taal, daarom filteren we op het veld language van het model TranslatablePage. Aangezien we exact dezelfde veldnamen en related_name hebben gekozen als voor het model ArticleIndexPage, kan de template theme_page.html voor een themapagina met alle artikelen over dat thema identiek zijn aan de article_index_page.html-template die we in een eerdere tutorial hebben gemaakt:

{% include "article_index_page.html" %}

We willen ook een indexpagina met een overzicht van al onze thema's. Ons model kan eenvoudig zijn:

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

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

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

Het lijkt op het model van ArticleIndexPage, met subpage_types die aangeeft dat we alleen ThemePage-pagina's als kinderen willen. De template theme_index_page.html is bijna hetzelfde als die van ArticleIndexPage, alleen zullen we hier page.get_children doorlopen om alle themapagina's weer te geven; je kunt het vinden in de repository.

We willen de thema's ook graag op onze homepage weergeven. Voeg aan het startpaginamodel een thema-sectietitel, een inleiding en een link naar een pagina toe waar we alle thema's plaatsen:

theme_section_title = models.CharField(
    null=True,
    blank=True,
    max_length=255,
    help_text=_("Title to display above the theme section"),
)
theme_section_intro = RichTextField(blank=True)
theme_section = models.ForeignKey(
    TranslatablePage,
    null=True,
    blank=True,
    on_delete=models.SET_NULL,
    related_name='+',
    help_text=_("Featured section for the homepage. Will display all themes."),
    verbose_name=_("Theme section"),
)

Voeg aan de template home_page.html de titel en de inleiding toe en een container met de volgende code (volledige code in repository):

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

Dit lijkt veel op wat we in een eerdere tutorial hebben gemaakt om een ​​aantal artikelen op de startpagina weer te geven: we gebruiken de afbeelding van de themapagina om een ​​kaart te maken met een link naar dat thema, en we creëren een overlay-effect met een tweede transparante afbeelding die we stylen in onze CSS. De kaarten zijn dit keer ellipsen.

Om onze homepage wat levendiger te maken, kunnen we een carrousel toevoegen met afbeeldingen van alle thema's en hun titels. Dit is een standaard Bootstrap-component, dus we zullen de html-code hier niet herhalen. Om de thema's te herhalen gebruiken we een standaard Django for loop. We halen de thema's op met page.theme_section.get_children, gebruiken de image-tag om de afbeeldingen weer te geven en de pageurl-tag om te linken naar de url van een pagina.

We willen ook kleine badges tonen met alle relevante thema's boven elk artikel en met een link naar de relevante themapagina in dezelfde taal. Nu komen we een klein probleem tegen: we weten welke thema's bij een bepaalde artikelpagina horen, maar aangezien we themapagina's in verschillende talen hebben, weten we niet naar welke van die te linken. We kunnen dit oplossen door de volgende methode toe te voegen aan het model ArticlePage:

def themepages(self):
    return ThemePage.objects.filter(theme__in=self.themes.all(), language=self.language)

Dit retourneert exact alle themapagina's die tot de gerelateerde thema's behoren in dezelfde taal als de artikelpagina. Nu is de code om de thema's op de artikelpagina weer te geven:

{% if page.themes.all %}
    <div class="container-fluid mt-4">
        {% trans "Themes:" %}
        {% for themepage in page.themepages %}
                <a href="{{ themepage.url }}" class="badge badge-primary">{{ themepage.theme.name }}</a>
        {% endfor %}
    </div>
{% endif %}

Modellen en templates zijn klaar, dus tijd om het uit te proberen. Migreer de database, ga naar de editor, maak thema's via het Snippets-menu, selecteer relevante thema's voor je artikelen, maak een indexpagina voor al je thema's met de template voor ThemeIndexPage: alles zou rechttoe-rechtaan moeten zijn, check de video als je wilt.

Voor degenen die niet alleen pagina's maar ook thema's in meerdere talen willen, bezoek deze tutorial. En iedereen die navigatie aan zijn site wil toevoegen, lees verder.

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