Allow overriding base form class for EditHandlers

EditHandlers that can produce forms can now specify which base form
class to use. This defaults to WagtailAdminModelForm, which was the hard
coded base class previously.

Only TabbedInterface and ObjectList EditHandlers now support generating
a form. Other EditHandlers generating forms was not used anywhere except
in the tests.
This commit is contained in:
Tim Heap 2015-10-29 10:20:20 +11:00 committed by Matt Westcott
parent 73b6ba01a5
commit 92d507752f
2 changed files with 63 additions and 33 deletions

View file

@ -92,7 +92,7 @@ WagtailAdminModelForm = WagtailAdminModelFormMetaclass(str('WagtailAdminModelFor
def get_form_for_model(
model,
model, form_class=WagtailAdminModelForm,
fields=None, exclude=None, formsets=None, exclude_formsets=None, widgets=None
):
@ -124,7 +124,8 @@ def get_form_for_model(
'Meta': type(str('Meta'), (object,), attrs)
}
return WagtailAdminModelFormMetaclass(class_name, (WagtailAdminModelForm,), form_class_attrs)
metaclass = type(form_class)
return metaclass(class_name, (form_class,), form_class_attrs)
def extract_panel_definitions_from_model_class(model, exclude=None):
@ -184,19 +185,6 @@ class EditHandler(object):
def html_declarations(cls):
return ''
# the top-level edit handler is responsible for providing a form class that can produce forms
# acceptable to the edit handler
_form_class = None
@classmethod
def get_form_class(cls, model):
if cls._form_class is None:
cls._form_class = get_form_for_model(
model,
fields=cls.required_fields(),
formsets=cls.required_formsets(), widgets=cls.widget_overrides())
return cls._form_class
def __init__(self, instance=None, form=None):
if not instance:
raise ValueError("EditHandler did not receive an instance object")
@ -341,30 +329,62 @@ class BaseCompositeEditHandler(EditHandler):
}))
class BaseTabbedInterface(BaseCompositeEditHandler):
class BaseFormEditHandler(BaseCompositeEditHandler):
"""
Base class for edit handlers that can construct a form class for all their
child edit handlers.
"""
# The form class used as the base for constructing specific forms for this
# edit handler. Subclasses can override this attribute to provide a form
# with custom validation, for example. Custom forms must subclass
# WagtailAdminModelForm
base_form_class = WagtailAdminModelForm
@classmethod
@lru_cache()
def get_form_class(cls, model):
"""
Construct a form class that has all the fields and formsets named in
the children of this edit handler.
"""
return get_form_for_model(
model,
form_class=cls.base_form_class,
fields=cls.required_fields(),
formsets=cls.required_formsets(),
widgets=cls.widget_overrides())
class BaseTabbedInterface(BaseFormEditHandler):
template = "wagtailadmin/edit_handlers/tabbed_interface.html"
class TabbedInterface(object):
def __init__(self, children):
def __init__(self, children, base_form_class=BaseFormEditHandler.base_form_class):
self.children = children
self.base_form_class = base_form_class
def bind_to_model(self, model):
return type(str('_TabbedInterface'), (BaseTabbedInterface,), {
'model': model,
'children': [child.bind_to_model(model) for child in self.children],
'base_form_class': self.base_form_class,
})
class BaseObjectList(BaseCompositeEditHandler):
class BaseObjectList(BaseFormEditHandler):
template = "wagtailadmin/edit_handlers/object_list.html"
class ObjectList(object):
def __init__(self, children, heading="", classname=""):
def __init__(self, children, heading="", classname="",
base_form_class=BaseFormEditHandler.base_form_class):
self.children = children
self.heading = heading
self.classname = classname
self.base_form_class = base_form_class
def bind_to_model(self, model):
return type(str('_ObjectList'), (BaseObjectList,), {
@ -372,6 +392,7 @@ class ObjectList(object):
'children': [child.bind_to_model(model) for child in self.children],
'heading': self.heading,
'classname': self.classname,
'base_form_class': self.base_form_class,
})

View file

@ -348,10 +348,11 @@ class TestPageChooserPanel(TestCase):
model = PageChooserModel # a model with a foreign key to Page which we want to render as a page chooser
# a PageChooserPanel class that works on PageChooserModel's 'page' field
self.MyPageChooserPanel = PageChooserPanel('page').bind_to_model(PageChooserModel)
self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel)
self.MyPageChooserPanel = self.EditHandler.children[0]
# build a form class containing the fields that MyPageChooserPanel wants
self.PageChooserForm = self.MyPageChooserPanel.get_form_class(PageChooserModel)
self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel)
# a test instance of PageChooserModel, pointing to the 'christmas' page
self.christmas_page = Page.objects.get(slug='christmas')
@ -418,10 +419,13 @@ class TestPageChooserPanel(TestCase):
def test_override_page_type(self):
# Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
# to restrict the chooser to that page type
MyPageChooserPanel = PageChooserPanel('page', 'tests.EventPage').bind_to_model(EventPageChooserModel)
PageChooserForm = MyPageChooserPanel.get_form_class(EventPageChooserModel)
MyPageObjectList = ObjectList([
PageChooserPanel('page', 'tests.EventPage')
]).bind_to_model(EventPageChooserModel)
MyPageChooserPanel = MyPageObjectList.children[0]
PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
form = PageChooserForm(instance=self.test_instance)
page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)
page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format(
@ -432,10 +436,11 @@ class TestPageChooserPanel(TestCase):
def test_autodetect_page_type(self):
# Model has a foreign key to EventPage, which we want to autodetect
# instead of specifying the page type in PageChooserPanel
MyPageChooserPanel = PageChooserPanel('page').bind_to_model(EventPageChooserModel)
PageChooserForm = MyPageChooserPanel.get_form_class(EventPageChooserModel)
MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel)
MyPageChooserPanel = MyPageObjectList.children[0]
PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
form = PageChooserForm(instance=self.test_instance)
page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)
page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format(
@ -475,8 +480,9 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
Check that the inline panel renders the panels set on the model
when no 'panels' parameter is passed in the InlinePanel definition
"""
SpeakerInlinePanel = InlinePanel('speakers', label="Speakers").bind_to_model(EventPage)
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
SpeakerObjectList = ObjectList([InlinePanel('speakers', label="Speakers")]).bind_to_model(EventPage)
SpeakerInlinePanel = SpeakerObjectList.children[0]
EventPageForm = SpeakerObjectList.get_form_class(EventPage)
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
@ -510,11 +516,14 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
Check that inline panel renders the panels listed in the InlinePanel definition
where one is specified
"""
SpeakerInlinePanel = InlinePanel('speakers', label="Speakers", panels=[
FieldPanel('first_name', widget=forms.Textarea),
ImageChooserPanel('image'),
SpeakerObjectList = ObjectList([
InlinePanel('speakers', label="Speakers", panels=[
FieldPanel('first_name', widget=forms.Textarea),
ImageChooserPanel('image'),
]),
]).bind_to_model(EventPage)
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
SpeakerInlinePanel = SpeakerObjectList.children[0]
EventPageForm = SpeakerObjectList.get_form_class(EventPage)
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))