问题
I have an abstract Wagtail model with a few StreamFields. Two of these StreamFields are in a separate tab in the admin view, which are added to the edit_handler.
class AbstractHomePage(Page):
    body = StreamField(
        HomePageStreamBlock(),
        default=''
    )
    headingpanel = StreamField(
        HeadingPanelStreamBlock(),
        default=''
    )
    sidepanel = StreamField(
        SidePanelStreamBlock(),
        default=''
    )
    class Meta:
        abstract = True
    search_fields = Page.search_fields + [index.SearchField('body')]
    content_panels = Page.content_panels + [
        StreamFieldPanel('body'),
    ]
    pagesection_panels = [
        StreamFieldPanel('headingpanel'),
        StreamFieldPanel('sidepanel'),
    ]
    edit_handler = TabbedInterface([
        ObjectList(content_panels),
        ObjectList(pagesection_panels, heading='Page sections'),
        ObjectList(Page.promote_panels),
        ObjectList(Page.settings_panels, classname='settings'),
    ])
I want to extend this model and add a field:
class Foo(AbstractHomePage):
    extra = models.TextField()
    Meta:
        verbose_name='Foo'
    content_panels = [
        AbstractHomePage.content_panels[0],     # title
        FieldPanel('extra'),
        AbstractHomePage.content_panels[-1]     # streamfield
    ]
When adding a new Foo page, the only fields available in the admin panel are the fields from the AbstractHomePage. The newly added field isn't available until I update Foo's edit_handler:
class Foo(AbstractHomePage):
    extra = models.TextField()
    Meta:
        verbose_name='Foo'
    content_panels = [
        AbstractHomePage.content_panels[0],     # title
        FieldPanel('extra'),
        AbstractHomePage.content_panels[-1]     # streamfield
    ]
    edit_handler = TabbedInterface([
        ObjectList(content_panels),
        ObjectList(AbstractHomePage.pagesection_panels, heading='Page sections'),
        ObjectList(Page.promote_panels),
        ObjectList(Page.settings_panels, classname='settings'),
    ])
And now to my question: Have I done something wrong or not followed good coding practices?
If I do have to update the edit_handler for each extending model, is there a better way to do this? Having to ensure that newly added fields for models extending the AbstractHomePage gets the explicit "boilerplate" edit_handler block each time feels really ugly. I see it as a massive violation of the DRY principle.
回答1:
The reason you have to redefine edit_handler in Foo is that Python evaluates the AbstractHomePage class definition from top to bottom - at the point that it encounters the line:
ObjectList(content_panels),
content_panels is treated as a variable, not a class property, and so the edit handler is built based on the content_panels list as it existed at that point. Redefining content_panels in the subclass can't override this.
Essentially, you're looking for a way to defer constructing the edit_handler until the subclass has been defined. I can't see a good way of doing that directly, but I think you could achieve it with a bit of digging into Wagtail internals, by overriding the Page.get_edit_handler method:
from wagtail.utils.decorators import cached_classmethod
class AbstractHomePage(Page):
    ...
    @cached_classmethod
    def get_edit_handler(cls):
        edit_handler = TabbedInterface([
            ObjectList(cls.content_panels),
            ObjectList(cls.pagesection_panels, heading='Page sections'),
            ObjectList(cls.promote_panels),
            ObjectList(cls.settings_panels, classname='settings'),
        ])
        return edit_handler.bind_to_model(cls)
    来源:https://stackoverflow.com/questions/41668167/what-is-the-correct-way-to-extend-wagtail-abstract-models-with-extra-fields