Home > front end >  How to pass "self" or Page.id to a custom admin Panel in the Wagtail CMS?
How to pass "self" or Page.id to a custom admin Panel in the Wagtail CMS?

Time:06-28

More generally speaking I want add a custom admin Panel to list some related content. To lookup this related content I need to pass the current instance of the model or at least its ID to this panel. How can I do that within these lists in which these admin panels are noted?

Here is my specific example of an ArtistPage. In the editor I would like to add a panel to list WorkPages that are related to this ArtistPage:

from wagtail.models import Page

class ArtistPage(Page):
# ...
content_panels = [
        # ...
        ListWorksPanel(artist=self),  # This doesn’t work
    ]

The panel itself is defined like that, mostly copied from the HelpPanel:

from wagtail.admin.panels import Panel

class ListWorksPanel(Panel):
    def __init__(self, artist="", template="admin/list_works_panel.html", **kwargs,):
        super().__init__(**kwargs)
        self.artist = artist
        self.template = template

    def clone_kwargs(self):
        kwargs = super().clone_kwargs()
        del kwargs["help_text"]
        kwargs.update(
            artist=self.artist,
            template=self.template,
        )
        return kwargs

    class BoundPanel(Panel.BoundPanel):
        def __init__(self, panel, instance, request, form):
            super().__init__(panel, instance, request, form)
            self.template_name = self.panel.template
            self.artist = self.panel.artist

This is more a general Python question, I think. I know how to pass "self" in functions. But how does that work here with this class as element of a list? I reckon that the __init__() method of the ArtistPage is the way to go, but I cannot figure out how exactly.

What is the pythonic way of passing "self" to another class?

Update (Solution):

Following @gasman’s aswer, I just added the get_context_data method to the BoundPanel class. The works are accessible in the template of the panel now!

class ListWorksPanel(Panel):
    def __init__(self, artist="", template="admin/list_works_panel.html", **kwargs,):
        super().__init__(**kwargs)
        self.artist = artist
        self.template = template

    def clone_kwargs(self):
        kwargs = super().clone_kwargs()
        del kwargs["help_text"]
        kwargs.update(
            artist=self.artist,
            template=self.template,
        )
        return kwargs

    class BoundPanel(Panel.BoundPanel):
        def __init__(self, panel, instance, request, form):
            super().__init__(panel, instance, request, form)
            self.template_name = self.panel.template
            self.artist = self.panel.artist
            
        def get_context_data(self, parent_context):
            context = super().get_context_data(parent_context)
            context['works'] = self.instance.works.all()  # exactly what I needed
            return context

CodePudding user response:

The ArtistPage instance is passed to BoundPanel.__init__ as the keyword argument instance. All code that deals with an individual ArtistPage needs to be written inside the BoundPanel class.

When you write ListWorksPanel() as part of a content_panels definition, you're creating a ListWorksPanel instance that then becomes part of the definition of the ArtistPage class. At this point in the code, no actual instance of ArtistPage exists, so there's no self to refer to. Effectively, there's a single ListWorksPanel object shared by all ArtistPage instances that will ever be created.

When the time comes to render the edit form for an individual page, Wagtail calls get_bound_panel on the ListWorksPanel object, passing the page instance along with the form and request objects. (The full process is explained here.) This returns an instance of BoundPanel, which is a template component that performs the final rendering. In this case, you probably want to define a get_context_data method on BoundPanel that does something like context['works'] = self.instance.works.all() - this will then make the variable works available on the template.

  • Related