Wagtail: Get previous or next sibling
Asked Answered
H

5

7

I'm creating a page with wagtail where I need to know the previous and next sibling of the current page:

In my portrait page model, I tried to define two methods to find the correct urls, but I'm missing a crucial part. To get the first sibling, I can just do the following:

class PortraitPage(Page):

    ...

    def first_portrait(self):
        return self.get_siblings().live().first().url

There is the first() and last() method, but there doesn't seem to be a next() or previous() method to get the direct neighbours (in the order that they are arranged in the wagtail admin).

Is there any way to achieve this?

Howerton answered 20/5, 2015 at 9:13 Comment(0)
H
5

After going through the debugger for a while, I found out that wagtail already has two methods: get_prev_sibling() and get_next_sibling().

So the methods could look like this (accounting for the first page in the previous method and the last item in the next method):

def prev_portrait(self):
    if self.get_prev_sibling():
        return self.get_prev_sibling().url
    else:
        return self.get_siblings().last().url

def next_portrait(self):
    if self.get_next_sibling():
        return self.get_next_sibling().url
    else:
        return self.get_siblings().first().url
Howerton answered 20/5, 2015 at 10:1 Comment(7)
I imagine that it comes from the page model, but I haven't looked into it. I just did a dir(self) and looked through the available methods. And it works. Which version of wagtail do you have? I'm working on v.1.0b1Howerton
@rnevius Ah, here we go, it is only mentioned in the release notes of wagtail 0.4: docs.wagtail.io/en/latest/releases/0.4.htmlHowerton
Hi @Howerton and @rnevius, the get_prev_sibling() and get_next_sibling() methods come from Treebeard, which handles Wagtail's page hierarchy, and is documented here: tabo.pe/projects/django-treebeard/docs/1.61/… P.S. @nils, if you're using 1.0b1 you should check out 1.0b2 which we released today: github.com/torchbox/wagtail/releases/tag/v1.0b2Royal
Thanks for the info @tomd! I'm running beta2 as we speak ;-)Brawner
@Royal I just realized that "get_next_sibling()" doesn't contain the fields that I defined in the Page-model (eg. "header_image"). Is there a way to access these as well?Howerton
@Howerton `.specific. will give you what you're after.Conspicuous
get_next_sibling() will also give you unpublished pages. I think this is the reason why it is not documented. What is the correct way doing this?Multiplier
C
11

Django-Treebeard provides get_next_sibling and get_prev_sibling which will return your direct siblings in the tree, but these are not necessarily your next published sibling. To request those you can use:

prev = page.get_prev_siblings().live().first()
next = page.get_next_siblings().live().first()

Which can obviously also be chained with any other queryset operations.

Conspicuous answered 22/6, 2016 at 5:41 Comment(1)
is there any way to achieve the same result in a Django template?Dameron
H
5

After going through the debugger for a while, I found out that wagtail already has two methods: get_prev_sibling() and get_next_sibling().

So the methods could look like this (accounting for the first page in the previous method and the last item in the next method):

def prev_portrait(self):
    if self.get_prev_sibling():
        return self.get_prev_sibling().url
    else:
        return self.get_siblings().last().url

def next_portrait(self):
    if self.get_next_sibling():
        return self.get_next_sibling().url
    else:
        return self.get_siblings().first().url
Howerton answered 20/5, 2015 at 10:1 Comment(7)
I imagine that it comes from the page model, but I haven't looked into it. I just did a dir(self) and looked through the available methods. And it works. Which version of wagtail do you have? I'm working on v.1.0b1Howerton
@rnevius Ah, here we go, it is only mentioned in the release notes of wagtail 0.4: docs.wagtail.io/en/latest/releases/0.4.htmlHowerton
Hi @Howerton and @rnevius, the get_prev_sibling() and get_next_sibling() methods come from Treebeard, which handles Wagtail's page hierarchy, and is documented here: tabo.pe/projects/django-treebeard/docs/1.61/… P.S. @nils, if you're using 1.0b1 you should check out 1.0b2 which we released today: github.com/torchbox/wagtail/releases/tag/v1.0b2Royal
Thanks for the info @tomd! I'm running beta2 as we speak ;-)Brawner
@Royal I just realized that "get_next_sibling()" doesn't contain the fields that I defined in the Page-model (eg. "header_image"). Is there a way to access these as well?Howerton
@Howerton `.specific. will give you what you're after.Conspicuous
get_next_sibling() will also give you unpublished pages. I think this is the reason why it is not documented. What is the correct way doing this?Multiplier
M
1

Here's a version handling non-published siblings.

def next_portrait(self):
    next_sibling = self.get_next_sibling()
    if next_sibling and next_sibling.live:
        return next_sibling.url
    else:
        next_published_siblings = self.get_next_siblings(
            inclusive=False
        ).live()
        if len(next_published_siblings):
            return next_published_siblings[0].url
        return self.get_siblings().live().first().url

def prev_portrait(self):
    previous_sibling = self.get_prev_sibling()
    if previous_sibling and previous_sibling.live:
        return previous_sibling.url
    else:
        previous_published_siblings = self.get_prev_siblings(
            inclusive=False
        ).live()
        if len(previous_published_siblings):
            return previous_published_siblings[0].url
        return self.get_siblings().live().last().url
Mylonite answered 6/7, 2016 at 10:24 Comment(0)
D
0

You can define properties in your class that inherits from Page

Siblings

If you want the siblings of an instance of Page, you can use the following (based on Danielle Madeley's answer):

class PortraitPage(Page):
    # ...
    @property
    def next_sibling(self):
        return self.get_next_siblings().live().first()

    @property
    def prev_sibling(self):
        return self.get_prev_siblings().live().first()

Siblings of the same class

If you want the the siblings of PortraitPage, specify self.__class__ in the type method as follows:

class PortraitPage(Page):
    # ...
    @property
    def next_sibling(self):
        return self.get_next_siblings().type(self.__class__).live().first()

    @property
    def prev_sibling(self):
        return self.get_prev_siblings().type(self.__class__).live().first()

Template

If you want to use them in a template, after defining the properties, do the following:

{# This is a template #}
Previous Sibling: {{ page.next_sibling }}
Next Sibling: {{ page.prev_sibling }}
Dameron answered 31/3, 2019 at 3:17 Comment(0)
T
0

self.get_siblings() falls over if you want to do any filtering based on properties only found in the subclass since the PageQuerySet results are of type Page.

For me, I have a blog index page that can be filtered by category or tag. The bog detail page has cards for the next and previous blog post at the end. I wanted those prev/next posts to be according to the filter I landed on that page from.

To get around this, you need to query the objects belonging to the subclass (eg BlogDetailPage), filter those, then get the prev/next post using class.objects.sibling_of(self):

def get_context(self, request, *args, **kwargs):
    context = super().get_context(request, *args, **kwargs)
    siblings = self.__class__.objects.sibling_of(self).live()
    category_filter = request.GET.get("category", None)
    tag_filter = request.GET.get("tag", None)
    if category_filter:
        siblings = siblings.filter(categories__slug__in=category_filter.split(","))
        context["filter"] = '?category=' + category_filter
    elif tag_filter:
        siblings = siblings.filter(tags__slug__in=tag_filter.split(','))
        context["filter"] = '?tag=' + tag_filter
    else:
        context["filter"] = ''
    context["next_post"] = siblings.filter(path__gt=self.path).first()
    context["previous_post"] = siblings.filter(path__lt=self.path).first()

    return context

I used self.__class__.objects.sibling_of(self) as this is in a super class with sub classed blog pages.

Trilateral answered 13/7, 2021 at 16:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.