I'm really new to Wagtail. I've been trying to find a way to filter a the values in a chooser (PageChooserPanel). I'm building a story site where authors can create non-linear stories. I followed a blog model to build this and expanded on it. I've gotten to the point where authors can link up pages through an orderable. The problem is the orderable shows pages of other stories. Is there a way to filter out the unrelated pages. I appreciate any help on this. Thank you in advance!
Here's my code:
class StoryPath(models.Model):
route = models.ForeignKey('story.StoryPage', on_delete=models.SET_NULL, null=True, related_name='next_path', verbose_name='Story Path')
panels = [
PageChooserPanel('route', page_type='story.StoryPage'),
FieldPanel('correct'),
]
class Meta:
abstract = True
class StoryPathOrderable(Orderable, StoryPath):
page = ParentalKey('story.StoryPage', on_delete=models.CASCADE, related_name='story_paths')
class StoryPage(Page):
template = 'story/story_page.html'
body = RichTextField()
content_panels = [
FieldPanel('title', heading='Section Title', classname='collapsible'),
FieldPanel('body', classname='collapsible'),
MultiFieldPanel(
[
InlinePanel('story_paths'),
],
heading = 'Story Paths',
classname = 'collapsible'
)
]
parent_page_type =['story.Story']
subpage_types = []
def __str__(self):
return '%s' % (self.title)
class Story(Page):
subtitle = models.CharField(max_length=250, null=True, blank=True)
content_panels = Page.content_panels + [
FieldPanel('subtitle'),
]
subpage_types = ['story.StoryPage']
def __str__(self):
return '%s' % (self.title)
EDIT: Here's the template I'm using:
{% extends "base.html" %}
{% load static wagtailcore_tags %}
{% block body_class %}{{self.title}}{% endblock %}
{% block extra_css %}{% endblock extra_css %}
{% block content %}
<div class="d-flex justify-content-center flex-column">
<div class="fs-3">{{page.title}}</div>
<div>{{page.body|richtext}}</div>
</div>
{% endblock content %}
This is not easy to do with the PageChooserPanel
, the one that opens a modal with a search interface, however you an achieve this goal much easier if you are happy for the field to just show a drop down of 'sibling' pages.
A bit of an overview of how this all works;
InlinePanel('story_paths'),
this leverages a few parts of Wagtail, Django and also Django Modelcluster to set up a dynamic formset.InlinePanel
allows you to create sub 'forms' for multiple additions of similar objects, in this case the objects that are created inline are the StoryPathOrderable
instances.InlinePanel
it will look for a panels
attribute on the inner models being created/managed inline.PageChooserPanel('route', page_type='story.StoryPage'),
.PageChooserPanel
is really great for a lot of use cases but somewhat hard to customise for edge cases (as it creates a field that triggers a modal that has its own search/listing interface). It is possible to modify the results of this modals using Wagtail Hooks - see `construct_page_chooser_queryset', however this is global and does not have any ability to know 'which' page or field is requesting the linked page.PageChooserPanel
, you can use a basic FieldPanel
which provides a dropdown of pages available across the whole application, from there we can customise this field's query much easier.FieldPanel
that modifies the behaviour of its on_form_bound
, this gets called when the form is being built up for the editor once the form is available.page = self.page
will work when you are NOT using an InlinePanel
as the instance will be the currently edited page.InlinePanel
, we need to consider the case of the 'initial' form that gets prepared as a template so you can add items dynamically.InlinePanel
and basic field usage we can grab the current request that is bound to the instance of the custom Panel and infer the page from there.Page
and the field, we can modify the queryset to suit our needs, Wagtail extends the ability of querysets to add child_of
and sibling_of
.some-app/models.py
from wagtail.admin.edit_handlers import FieldPanel
class SiblingOnlyPageFieldPanel(FieldPanel):
def on_form_bound(self):
super().on_form_bound()
field = self.form[self.field_name].field
# when creating a new page, check for the parent page ID & refine field queryset
parent_page_id = self.request.resolver_match.kwargs.get("parent_page_id", None)
if parent_page_id:
parent_page = Page.objects.filter(pk=parent_page_id).first()
field.queryset = field.queryset.child_of(parent_page)
return
# when editing a page, get the ID of the page currently being edited
page_id = self.request.resolver_match.kwargs.get("page_id", None)
if not page_id:
return
page = Page.objects.filter(pk=page_id).first()
if not page:
return
field = self.form[self.field_name].field
field.queryset = field.queryset.sibling_of(page)
class StoryPath(models.Model):
route = models.ForeignKey('story.StoryPage', on_delete=models.SET_NULL, null=True, related_name='next_path', verbose_name='Story Path')
panels = [
SibingOnlyPageFieldPanel('route'), # replaced PageChooserPanel & removed `page_type` as that will no longer work for a normal FieldPanel
FieldPanel('correct'),
]
class Meta:
abstract = True