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.