Skip to content

Instantly share code, notes, and snippets.

@VincentLoy
Last active June 19, 2024 16:52
Show Gist options
  • Save VincentLoy/e693aa1f149a59f465a5a71b6d937b9a to your computer and use it in GitHub Desktop.
Save VincentLoy/e693aa1f149a59f465a5a71b6d937b9a to your computer and use it in GitHub Desktop.
Easy snippet to get breadcrumb in a Wagtail page
<div class="breadcrumb-content">
{% if self.get_ancestors|length > 1 %}
<ul class="breadcrumb">
{% for p in self.get_ancestors %}
{% if p.is_root == False %}
<li><a href="{{ p.url }}">{{ p.title }}</a></li>
{% endif %}
{% endfor %}
<li class="active">{{ self.title }}</li>
</ul>
{% endif %}
</div>
@blerdijankoliqi
Copy link

Thank you!

@simzam
Copy link

simzam commented Nov 19, 2023

Really nice! Thanks.

@ch12i5
Copy link

ch12i5 commented Mar 4, 2024

This is a super helpful snippet, thank you! :)

@Nigel2392
Copy link

Nigel2392 commented Apr 18, 2024

As of Wagtail 6.1 this should get updated to populate an array inside of a view; to then use in a template.
For the routed page; this will make sure the query count will be zero.

The database should NOT be hit. AT ALL.

Storage of pages happens in routing.

Rough sketch:

class MyPage(...):
    def get_context(self, ...):
        context = super().get_context(...)
        breadcrumbs = []
        
        page = self
        while page.get_parent() is not None:
            if page.is_root():
                break
            # Note how we do not add self.
            page = page.get_parent()
            breadcrumbs.append(page)

        breadcrumbs.reverse()
        context["breadcrumbs"] = breadcrumbs
        return context

And then in the template:

<div class="breadcrumb-content">
    {% if breadcrumbs|length > 1 %}
        <ul class="breadcrumb">
            {% for p in breadcrumbs %}
                <li><a href="{{ p.url }}">{{ p.title }}</a></li>
            {% endfor %}

            <li class="active">{{ self.title }}</li>
        </ul>
    {% endif %}
</div>

@ismayil-ismayilov
Copy link

ismayil-ismayilov commented Jun 19, 2024

If you have articles in multiple depths (under category/subcategory/topic/...), then the following method can be useful.

  • It hits DB only once, no matter the depth.
  • It excludes the Root page (depth__gt=1 -> depth 0 is Root, so we only get the higher).
  • Homepage is included in the list.
  • It does not include the current page itself. If you want it to include, then set include_self to True.
def construct_breadcrumbs_list(page, include_self=False):
    """
    Constructs a breadcrumb list for the given page, excluding the root.

    Args:
        page: The page object for which to construct the breadcrumb list.
        include_self (bool): If True, includes the page itself in the breadcrumb list.

    Returns:
        list: A list of ancestor pages, excluding the root and self.
    """
    # Retrieve all ancestors of the page, optionally including the page itself
    ancestors = page.get_ancestors(inclusive=include_self).filter(depth__gt=1)

    # Convert the queryset to a list to return the breadcrumbs
    breadcrumbs = list(ancestors)

    return breadcrumbs

@Nigel2392
Copy link

Nigel2392 commented Jun 19, 2024

If you have articles in multiple depths (under category/subcategory/topic/...), then the following method can be useful.

  • It hits DB only once, no matter the depth.
  • It excludes the Root page (depth__gt=1 -> depth 0 is Root, so we only get the higher).
  • Homepage is included in the list.
  • It does not include the current page itself. If you want it to include, then set include_self to True.
def construct_breadcrumbs_list(page, include_self=False):
    """
    Constructs a breadcrumb list for the given page, excluding the root.

    Args:
        page: The page object for which to construct the breadcrumb list.
        include_self (bool): If True, includes the page itself in the breadcrumb list.

    Returns:
        list: A list of ancestor pages, excluding the root and self.
    """
    # Retrieve all ancestors of the page, optionally including the page itself
    ancestors = page.get_ancestors(inclusive=include_self).filter(depth__gt=1)

    # Convert the queryset to a list to return the breadcrumbs
    breadcrumbs = list(ancestors)

    return breadcrumbs

Still does a query though... ❤️ 😉

@ismayil-ismayilov
Copy link

ismayil-ismayilov commented Jun 19, 2024

Still does a query though... ❤️ 😉

True. However, the following method also hits one query in my case.

Edit: as I assumed, the following does not hit the db if the page is in the first depth. Otherwise, it will need to hit DB.
That's why I mentioned:

If you have articles in multiple depths (under category/subcategory/topic/...), then the following method can be useful.

    breadcrumbs = []
    
    page = self
    while page.get_parent() is not None:
        if page.is_root():
            break
        # Note how we do not add self.
        page = page.get_parent()
        breadcrumbs.append(page)

    breadcrumbs.reverse()

@Nigel2392
Copy link

Nigel2392 commented Jun 19, 2024 via email

@ismayil-ismayilov
Copy link

ismayil-ismayilov commented Jun 19, 2024

what version of wagtail are you using?
v 6.1.

It does not hit no matter the depth of page in your case? So if we assume there is such tree: FirstPage > SecondPage > SecondPage, and you try to make breadcrumb in the ThirdPage, it is still 0?

Sorry about confusion, it actually hits one time no matter the depth in my case. I am not sure what may cause such difference. It has to get "parent" data from somewhere, either it has to be prefetched already and cached or it has to be requested.

How it would else be possible not to hit DB using get_ancestors()?

@Nigel2392
Copy link

Nigel2392 commented Jun 19, 2024

what version of wagtail are you using?
v 6.1.

It does not hit no matter the depth of page in your case? So if we assume there is such tree: FirstPage > SecondPage > SecondPage, and you try to make breadcrumb in the ThirdPage, it is still 0? In my case, in SecondPage, it does not hit, but in SecondPage it does.

Hmm - very odd. No - it really shouldn't hit the DB, no matter the depth...

Possibly I made a mistake in my PR... I'll have to revisit.

I'll submit a fix - my bad. In the future you should then have zero query breadcrumbs - I think I made a mistake not setting the cache on the specific instance of the page.

Thanks for the help! :)

[EDIT]
@ismayil-ismayilov would you mind trying this repo to see if it still queries?
https://github.com/Nigel2392/wagtail/tree/update_page_routing

can be done with pip install git+https://github.com/Nigel2392/wagtail.git@update_page_routing

@ismayil-ismayilov
Copy link

ismayil-ismayilov commented Jun 19, 2024

Thanks for the help! :)

Glad to be helpful!

Possibly I made a mistake in my PR... I'll have to revisit.

Maybe, it is my confusion. Just to make it clear, I do not used/tested routed pages, I just implemented your method to send context in a Page. Let's say there is a Blog Index Page, and I show breadcrumb Homepage>Blog Index.

@Nigel2392
Copy link

Nigel2392 commented Jun 19, 2024

@ismayil-ismayilov To be clear, you're not going from your browser to the page? IE the page.route method does not get invoked?

Sorry about confusion, it actually hits one time no matter the depth in my case. I am not sure what may cause such difference. It has to get > "parent" data from somewhere, either it has to be prefetched already and cached or it has to be requested.
How it would else be possible not to hit DB using get_ancestors()?

Get ancestors will for now always perform a query - it is not cached.

The parent objects however are cached upon routing to that page.

IE.
Homepage
> BlogPage
> > BlogIndexPage

You should be able to travel from BlogIndexPage to Homepage using get_parent without it performing any queries (inside the serve method of BlogIndexPage, after page.route has been invoked by the page serve view)

Can you tell me if this is the case for you?

@ismayil-ismayilov
Copy link

ismayil-ismayilov commented Jun 19, 2024

Unfortunately, I do not think that's a case for me. I strongly believe I caused a confusion since I may not have the same application method as you refer, sorry about it.

I used your Wagtail repo. It still hits DB.

This is my "simplified" Page class. I basically, applied your method to create a breadcrumb. Nothing more or less.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment