user3170530
user3170530

Reputation: 436

Wagtail : How do I query related names (inline models) of a child page?

I have two pages:

ArticlePage and TimelinePage.

TimelinePage has an inline panel which is related to the model TimelinePageEntry.

TimelinePage displays all of it's child TimelinePageEntry(s) just fine. However when I try to get all the TimelinePageEntry(s) from ArticlePage, it fails with an exception.

ArticlePage fails with an exception when I try to do:

context['timeline_entries'] = timeline_page.timeline_entries.all()

Here's the source code for the three classes in models.py:

import datetime

from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
from wagtail.core.fields import RichTextField
from wagtail.core.models import Orderable
from wagtail.core.models import Page


class ArticlePage(Page):
    date = models.DateField("Creation date", default=datetime.date.today)
    hero_biography = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('hero_biography'),
    ]

    subpage_types = [
        'articles.TimelinePage'
    ]

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request)
        timeline_pages = self.get_children().type(TimelinePage)
        timeline_page = timeline_pages[0]

        # prints "Page"
        print(type(timeline_page).__name__)
        
        # prints the title of the timeline page, so I know I'm getting a query result
        print(timeline_page.title)

        context['timeline_page'] = timeline_page

        # Throws an exception
        context['timeline_entries'] = timeline_page.timeline_entries.all()

        return context


class TimelinePage(Page):
    max_count_per_parent = 1
    subpage_types = []

    content_panels = Page.content_panels + [
        InlinePanel('timeline_entries', label="Timeline Entries")
    ]

    parent_page_type = [
        'articles.ArticlePage'
    ]

    def get_context(self, request, *args, **kwargs):
        # Prints "TimelinePage"
        print(type(self).__name__)

        # Prints a list of timeline entries
        print(repr(self.timeline_entries.all()))

        return super().get_context(request)


class TimelinePageEntry(Orderable):
    page = ParentalKey(TimelinePage, on_delete=models.CASCADE, related_name='timeline_entries')

    start_date = models.DateField()
    end_date = models.DateField(null=True, blank=True)

    line = RichTextField(max_length=90)

    panels = [
        FieldPanel('start_date'),
        FieldPanel('end_date'),
        FieldPanel('line'),
    ]

    class Meta(Orderable.Meta):
        ordering = ['-start_date']

It seems like what I am trying to do should be really simple. TimelinePage is a child of ArticlePage. I am able to access other attributes of TimelinePage (the title) from ArticlePage, but I'm stumped on how to get the TimelinePageEntries.

The exception that I get is:

    context['timeline_entries'] = timeline_page.timeline_entries.all()
AttributeError: 'Page' object has no attribute 'timeline_entries'

Upvotes: 0

Views: 416

Answers (1)

gasman
gasman

Reputation: 25292

This is due to the distinction between Page instances (which just contain the data common to all page types, such as title) and instances of your specific TimelinePage subclass (which provides access to all the methods, fields and relations defined on that class).

self.get_children() returns a queryset of type Page; this is necessary because the query doesn't know in advance what page types will be included in the results. .type(TimelinePage) filters this to just pages of the given type, but this only acts as a filter on the existing queryset - the results will still remain as Page objects, which are missing the timeline_entries relation.

If you rewrite the line

timeline_pages = self.get_children().type(TimelinePage)

into

timeline_pages = TimelinePage.objects.child_of(self)

then this will ensure that the results are returned as TimelinePage instances, and the timeline_entries relation will then work as intended.

Upvotes: 3

Related Questions