2
0
Эх сурвалжийг харах

Simplifies edit handlers by removing redundant classes.

This also allows to provide some missing arguments to panels like PageChooserPanel.
Matt Westcott 7 жил өмнө
parent
commit
5fc191b116

+ 2 - 0
CHANGELOG.txt

@@ -37,6 +37,7 @@ Changelog
  * Added `WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS` setting to determine whether superusers are included in moderation email notifications (Bruno Alla)
  * Added `WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS` setting to determine whether superusers are included in moderation email notifications (Bruno Alla)
  * Added a basic Dockerfile to the project template (Tom Dyson)
  * Added a basic Dockerfile to the project template (Tom Dyson)
  * StreamField blocks now allow custom `get_template` methods for overriding templates in instances (Christopher Bledsoe)
  * StreamField blocks now allow custom `get_template` methods for overriding templates in instances (Christopher Bledsoe)
+ * Simplified edit handler API (Florent Osmont, Bertrand Bordage)
  * Fix: Do not remove stopwords when generating slugs from non-ASCII titles, to avoid issues with incorrect word boundaries (Sævar Öfjörð Magnússon)
  * Fix: Do not remove stopwords when generating slugs from non-ASCII titles, to avoid issues with incorrect word boundaries (Sævar Öfjörð Magnússon)
  * Fix: The PostgreSQL search backend now preserves ordering of the `QuerySet` when searching with `order_by_relevance=False` (Bertrand Bordage)
  * Fix: The PostgreSQL search backend now preserves ordering of the `QuerySet` when searching with `order_by_relevance=False` (Bertrand Bordage)
  * Fix: Using `modeladmin_register` as a decorator no longer replaces the decorated class with `None` (Tim Heap)
  * Fix: Using `modeladmin_register` as a decorator no longer replaces the decorated class with `None` (Tim Heap)
@@ -64,6 +65,7 @@ Changelog
  * Fix: Style of the page unlock button was broken (Bertrand Bordage)
  * Fix: Style of the page unlock button was broken (Bertrand Bordage)
  * Fix: Admin search no longer floods browser history (Bertrand Bordage)
  * Fix: Admin search no longer floods browser history (Bertrand Bordage)
  * Fix: Version comparison now handles custom primary keys on inline models correctly (LB (Ben Johnston))
  * Fix: Version comparison now handles custom primary keys on inline models correctly (LB (Ben Johnston))
+ * Fixed error when inserting chooser panels into FieldRowPanel (Florent Osmont, Bertrand Bordage)
 
 
 
 
 1.13.1 (17.11.2017)
 1.13.1 (17.11.2017)

+ 1 - 0
CONTRIBUTORS.rst

@@ -268,6 +268,7 @@ Contributors
 * misraX
 * misraX
 * Bruno Alla
 * Bruno Alla
 * Christopher Bledsoe (The Motley Fool)
 * Christopher Bledsoe (The Motley Fool)
+* Florent Osmont
 
 
 Translators
 Translators
 ===========
 ===========

+ 2 - 0
docs/releases/2.0.rst

@@ -54,6 +54,7 @@ Other features
  * Added ``WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS`` setting to determine whether superusers are included in moderation email notifications (Bruno Alla)
  * Added ``WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS`` setting to determine whether superusers are included in moderation email notifications (Bruno Alla)
  * Added a basic Dockerfile to the project template (Tom Dyson)
  * Added a basic Dockerfile to the project template (Tom Dyson)
  * StreamField blocks now allow custom ``get_template`` methods for overriding templates in instances (Christopher Bledsoe)
  * StreamField blocks now allow custom ``get_template`` methods for overriding templates in instances (Christopher Bledsoe)
+ * Simplified edit handler API (Florent Osmont, Bertrand Bordage)
 
 
 
 
 Bug fixes
 Bug fixes
@@ -87,6 +88,7 @@ Bug fixes
  * Style of the page unlock button was broken (Bertrand Bordage)
  * Style of the page unlock button was broken (Bertrand Bordage)
  * Admin search no longer floods browser history (Bertrand Bordage)
  * Admin search no longer floods browser history (Bertrand Bordage)
  * Version comparison now handles custom primary keys on inline models correctly (LB (Ben Johnston))
  * Version comparison now handles custom primary keys on inline models correctly (LB (Ben Johnston))
+ * Fixed error when inserting chooser panels into FieldRowPanel (Florent Osmont, Bertrand Bordage)
 
 
 
 
 Upgrade considerations
 Upgrade considerations

+ 2 - 2
wagtail/admin/checks.py

@@ -60,9 +60,9 @@ def get_form_class_check(app_configs, **kwargs):
 
 
     for cls in get_page_models():
     for cls in get_page_models():
         edit_handler = cls.get_edit_handler()
         edit_handler = cls.get_edit_handler()
-        if not issubclass(edit_handler.get_form_class(cls), WagtailAdminPageForm):
+        if not issubclass(edit_handler.get_form_class(), WagtailAdminPageForm):
             errors.append(Error(
             errors.append(Error(
-                "{cls}.get_edit_handler().get_form_class({cls}) does not extend WagtailAdminPageForm".format(
+                "{cls}.get_edit_handler().get_form_class() does not extend WagtailAdminPageForm".format(
                     cls=cls.__name__),
                     cls=cls.__name__),
                 hint="Ensure that the EditHandler for {cls} creates a subclass of WagtailAdminPageForm".format(
                 hint="Ensure that the EditHandler for {cls} creates a subclass of WagtailAdminPageForm".format(
                     cls=cls.__name__),
                     cls=cls.__name__),

+ 300 - 365
wagtail/admin/edit_handlers.py

@@ -1,4 +1,3 @@
-import math
 import re
 import re
 
 
 from django import forms
 from django import forms
@@ -6,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured
 from django.db.models.fields import FieldDoesNotExist
 from django.db.models.fields import FieldDoesNotExist
 from django.forms.models import fields_for_model
 from django.forms.models import fields_for_model
 from django.template.loader import render_to_string
 from django.template.loader import render_to_string
+from django.utils.encoding import force_text
 from django.utils.functional import curry
 from django.utils.functional import curry
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy
 from django.utils.translation import ugettext_lazy
@@ -91,43 +91,74 @@ class EditHandler:
     the EditHandler API
     the EditHandler API
     """
     """
 
 
+    def __init__(self, heading='', classname='', help_text=''):
+        self.heading = heading
+        self.classname = classname
+        self.help_text = help_text
+
+    def clone(self):
+        return self.__class__(
+            heading=self.heading,
+            classname=self.classname,
+            help_text=self.help_text,
+        )
+
     # return list of widget overrides that this EditHandler wants to be in place
     # return list of widget overrides that this EditHandler wants to be in place
     # on the form it receives
     # on the form it receives
-    @classmethod
-    def widget_overrides(cls):
+    def widget_overrides(self):
         return {}
         return {}
 
 
     # return list of fields that this EditHandler expects to find on the form
     # return list of fields that this EditHandler expects to find on the form
-    @classmethod
-    def required_fields(cls):
+    def required_fields(self):
         return []
         return []
 
 
     # return a dict of formsets that this EditHandler requires to be present
     # return a dict of formsets that this EditHandler requires to be present
     # as children of the ClusterForm; the dict is a mapping from relation name
     # as children of the ClusterForm; the dict is a mapping from relation name
     # to parameters to be passed as part of get_form_for_model's 'formsets' kwarg
     # to parameters to be passed as part of get_form_for_model's 'formsets' kwarg
-    @classmethod
-    def required_formsets(cls):
+    def required_formsets(self):
         return {}
         return {}
 
 
     # return any HTML that needs to be output on the edit page once per edit handler definition.
     # return any HTML that needs to be output on the edit page once per edit handler definition.
     # Typically this will be used to define snippets of HTML within <script type="text/x-template"></script> blocks
     # Typically this will be used to define snippets of HTML within <script type="text/x-template"></script> blocks
     # for Javascript code to work with.
     # for Javascript code to work with.
-    @classmethod
-    def html_declarations(cls):
+    def html_declarations(self):
         return ''
         return ''
 
 
-    def __init__(self, instance=None, form=None):
+    def bind_to_model(self, model):
+        new = self.clone()
+        new.model = model
+        new.on_model_bound()
+        return new
+
+    def on_model_bound(self):
+        pass
+
+    def bind_to_instance(self, instance=None, form=None):
+        new = self.bind_to_model(self.model)
+
         if not instance:
         if not instance:
             raise ValueError("EditHandler did not receive an instance object")
             raise ValueError("EditHandler did not receive an instance object")
-        self.instance = instance
+        new.instance = instance
 
 
         if not form:
         if not form:
             raise ValueError("EditHandler did not receive a form object")
             raise ValueError("EditHandler did not receive a form object")
-        self.form = form
+        new.form = form
+
+        new.on_instance_bound()
 
 
-    # Heading / help text to display to the user
-    heading = ""
-    help_text = ""
+        return new
+
+    def on_instance_bound(self):
+        pass
+
+    def __repr__(self):
+        class_name = self.__class__.__name__
+        try:
+            bound_to = force_text(getattr(self, 'instance',
+                                          getattr(self, 'model')))
+        except AttributeError:
+            return '<%s>' % class_name
+        return '<%s bound to %s>' % (class_name, bound_to)
 
 
     def classes(self):
     def classes(self):
         """
         """
@@ -135,15 +166,9 @@ class EditHandler:
         Subclasses of EditHandler should override this, invoking super().classes() to
         Subclasses of EditHandler should override this, invoking super().classes() to
         append more classes specific to the situation.
         append more classes specific to the situation.
         """
         """
-
-        classes = []
-
-        try:
-            classes.append(self.classname)
-        except AttributeError:
-            pass
-
-        return classes
+        if self.classname:
+            return [self.classname]
+        return []
 
 
     def field_type(self):
     def field_type(self):
         """
         """
@@ -199,8 +224,7 @@ class EditHandler:
         """
         """
         return mark_safe(self.render_as_object() + self.render_missing_fields())
         return mark_safe(self.render_as_object() + self.render_missing_fields())
 
 
-    @classmethod
-    def get_comparison(cls):
+    def get_comparison(self):
         return []
         return []
 
 
 
 
@@ -209,71 +233,70 @@ class BaseCompositeEditHandler(EditHandler):
     Abstract class for EditHandlers that manage a set of sub-EditHandlers.
     Abstract class for EditHandlers that manage a set of sub-EditHandlers.
     Concrete subclasses must attach a 'children' property
     Concrete subclasses must attach a 'children' property
     """
     """
-    _widget_overrides = None
-
-    @classmethod
-    def widget_overrides(cls):
-        if cls._widget_overrides is None:
-            # build a collated version of all its children's widget lists
-            widgets = {}
-            for handler_class in cls.children:
-                widgets.update(handler_class.widget_overrides())
-            cls._widget_overrides = widgets
-
-        return cls._widget_overrides
-
-    _required_fields = None
-
-    @classmethod
-    def required_fields(cls):
-        if cls._required_fields is None:
-            fields = []
-            for handler_class in cls.children:
-                fields.extend(handler_class.required_fields())
-            cls._required_fields = fields
-
-        return cls._required_fields
-
-    _required_formsets = None
-
-    @classmethod
-    def required_formsets(cls):
-        if cls._required_formsets is None:
-            formsets = {}
-            for handler_class in cls.children:
-                formsets.update(handler_class.required_formsets())
-            cls._required_formsets = formsets
-
-        return cls._required_formsets
 
 
-    @classmethod
-    def html_declarations(cls):
-        return mark_safe(''.join([c.html_declarations() for c in cls.children]))
-
-    def __init__(self, instance=None, form=None):
-        super().__init__(instance=instance, form=form)
+    def __init__(self, children=(), *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.children = children
 
 
-        self.children = []
-        for child in self.__class__.children:
-            if not getattr(child, "children", None) and getattr(child, "field_name", None):
+    def clone(self):
+        return self.__class__(
+            children=self.children,
+            heading=self.heading,
+            classname=self.classname,
+            help_text=self.help_text,
+        )
+
+    def widget_overrides(self):
+        # build a collated version of all its children's widget lists
+        widgets = {}
+        for handler_class in self.children:
+            widgets.update(handler_class.widget_overrides())
+        widget_overrides = widgets
+
+        return widget_overrides
+
+    def required_fields(self):
+        fields = []
+        for handler in self.children:
+            fields.extend(handler.required_fields())
+        return fields
+
+    def required_formsets(self):
+        formsets = {}
+        for handler_class in self.children:
+            formsets.update(handler_class.required_formsets())
+        return formsets
+
+    def html_declarations(self):
+        return mark_safe(''.join([c.html_declarations() for c in self.children]))
+
+    def on_model_bound(self):
+        self.children = [child.bind_to_model(self.model)
+                         for child in self.children]
+
+    def on_instance_bound(self):
+        children = []
+        for child in self.children:
+            if isinstance(child, FieldPanel):
                 if self.form._meta.exclude:
                 if self.form._meta.exclude:
                     if child.field_name in self.form._meta.exclude:
                     if child.field_name in self.form._meta.exclude:
                         continue
                         continue
                 if self.form._meta.fields:
                 if self.form._meta.fields:
                     if child.field_name not in self.form._meta.fields:
                     if child.field_name not in self.form._meta.fields:
                         continue
                         continue
-            self.children.append(child(instance=self.instance, form=self.form))
+            children.append(child.bind_to_instance(instance=self.instance,
+                                                   form=self.form))
+        self.children = children
 
 
     def render(self):
     def render(self):
         return mark_safe(render_to_string(self.template, {
         return mark_safe(render_to_string(self.template, {
             'self': self
             'self': self
         }))
         }))
 
 
-    @classmethod
-    def get_comparison(cls):
+    def get_comparison(self):
         comparators = []
         comparators = []
 
 
-        for child in cls.children:
+        for child in self.children:
             comparators.extend(child.get_comparison())
             comparators.extend(child.get_comparison())
 
 
         return comparators
         return comparators
@@ -291,137 +314,94 @@ class BaseFormEditHandler(BaseCompositeEditHandler):
     # WagtailAdminModelForm
     # WagtailAdminModelForm
     base_form_class = None
     base_form_class = None
 
 
-    _form_class = None
-
-    @classmethod
-    def get_form_class(cls, model):
+    def get_form_class(self):
         """
         """
         Construct a form class that has all the fields and formsets named in
         Construct a form class that has all the fields and formsets named in
         the children of this edit handler.
         the children of this edit handler.
         """
         """
-        if cls._form_class is None:
-            # If a custom form class was passed to the EditHandler, use it.
-            # Otherwise, use the base_form_class from the model.
-            # If that is not defined, use WagtailAdminModelForm.
-            model_form_class = getattr(model, 'base_form_class', WagtailAdminModelForm)
-            base_form_class = cls.base_form_class or model_form_class
-
-            cls._form_class = get_form_for_model(
-                model,
-                form_class=base_form_class,
-                fields=cls.required_fields(),
-                formsets=cls.required_formsets(),
-                widgets=cls.widget_overrides())
-        return cls._form_class
-
-
-class BaseTabbedInterface(BaseFormEditHandler):
+        if not hasattr(self, 'model'):
+            raise AttributeError(
+                '%s is not bound to a model yet. Use `.bind_to_model(model)` '
+                'before using this method.' % self.__class__.__name__)
+        # If a custom form class was passed to the EditHandler, use it.
+        # Otherwise, use the base_form_class from the model.
+        # If that is not defined, use WagtailAdminModelForm.
+        model_form_class = getattr(self.model, 'base_form_class',
+                                   WagtailAdminModelForm)
+        base_form_class = self.base_form_class or model_form_class
+
+        return get_form_for_model(
+            self.model,
+            form_class=base_form_class,
+            fields=self.required_fields(),
+            formsets=self.required_formsets(),
+            widgets=self.widget_overrides())
+
+
+class TabbedInterface(BaseFormEditHandler):
     template = "wagtailadmin/edit_handlers/tabbed_interface.html"
     template = "wagtailadmin/edit_handlers/tabbed_interface.html"
 
 
+    def __init__(self, *args, **kwargs):
+        self.base_form_class = kwargs.pop('base_form_class', None)
+        super().__init__(*args, **kwargs)
 
 
-class TabbedInterface:
-    def __init__(self, children, base_form_class=None):
-        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,
-        })
+    def clone(self):
+        new = super().clone()
+        new.base_form_class = self.base_form_class
+        return new
 
 
 
 
-class BaseObjectList(BaseFormEditHandler):
+class ObjectList(TabbedInterface):
     template = "wagtailadmin/edit_handlers/object_list.html"
     template = "wagtailadmin/edit_handlers/object_list.html"
 
 
 
 
-class ObjectList:
-    def __init__(self, children, heading="", classname="",
-                 base_form_class=None):
-        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,), {
-            'model': model,
-            'children': [child.bind_to_model(model) for child in self.children],
-            'heading': self.heading,
-            'classname': self.classname,
-            'base_form_class': self.base_form_class,
-        })
-
-
-class BaseFieldRowPanel(BaseCompositeEditHandler):
+class FieldRowPanel(BaseCompositeEditHandler):
     template = "wagtailadmin/edit_handlers/field_row_panel.html"
     template = "wagtailadmin/edit_handlers/field_row_panel.html"
 
 
+    def on_instance_bound(self):
+        super().on_instance_bound()
 
 
-class FieldRowPanel:
-    def __init__(self, children, classname=""):
-        self.children = children
-        self.classname = classname
-
-    def bind_to_model(self, model):
-        col_count = " col" + str(int(math.floor(12 / len(self.children))))
-
+        col_count = ' col%s' % (12 // len(self.children))
         # If child panel doesn't have a col# class then append default based on
         # If child panel doesn't have a col# class then append default based on
         # number of columns
         # number of columns
         for child in self.children:
         for child in self.children:
             if not re.search(r'\bcol\d+\b', child.classname):
             if not re.search(r'\bcol\d+\b', child.classname):
                 child.classname += col_count
                 child.classname += col_count
 
 
-        return type(str('_FieldRowPanel'), (BaseFieldRowPanel,), {
-            'model': model,
-            'children': [child.bind_to_model(model) for child in self.children],
-            'classname': self.classname,
-        })
-
 
 
-class BaseMultiFieldPanel(BaseCompositeEditHandler):
+class MultiFieldPanel(BaseCompositeEditHandler):
     template = "wagtailadmin/edit_handlers/multi_field_panel.html"
     template = "wagtailadmin/edit_handlers/multi_field_panel.html"
 
 
     def classes(self):
     def classes(self):
         classes = super().classes()
         classes = super().classes()
         classes.append("multi-field")
         classes.append("multi-field")
-
         return classes
         return classes
 
 
 
 
-class MultiFieldPanel:
-    def __init__(self, children, heading="", classname=""):
-        self.children = children
-        self.heading = heading
-        self.classname = classname
-
-    def bind_to_model(self, model):
-        return type(str('_MultiFieldPanel'), (BaseMultiFieldPanel,), {
-            'model': model,
-            'children': [child.bind_to_model(model) for child in self.children],
-            'heading': self.heading,
-            'classname': self.classname,
-        })
-
-
-class BaseFieldPanel(EditHandler):
-
+class FieldPanel(EditHandler):
     TEMPLATE_VAR = 'field_panel'
     TEMPLATE_VAR = 'field_panel'
 
 
-    @classmethod
-    def widget_overrides(cls):
-        """check if a specific widget has been defined for this field"""
-        if hasattr(cls, 'widget'):
-            return {cls.field_name: cls.widget}
-        else:
-            return {}
+    def __init__(self, field_name, *args, **kwargs):
+        widget = kwargs.pop('widget', None)
+        if widget is not None:
+            self.widget = widget
+        super().__init__(*args, **kwargs)
+        self.field_name = field_name
 
 
-    def __init__(self, instance=None, form=None):
-        super().__init__(instance=instance, form=form)
-        self.bound_field = self.form[self.field_name]
+    def clone(self):
+        return self.__class__(
+            field_name=self.field_name,
+            widget=self.widget if hasattr(self, 'widget') else None,
+            heading=self.heading,
+            classname=self.classname,
+            help_text=self.help_text
+        )
 
 
-        self.heading = self.bound_field.label
-        self.help_text = self.bound_field.help_text
+    def widget_overrides(self):
+        """check if a specific widget has been defined for this field"""
+        if hasattr(self, 'widget'):
+            return {self.field_name: self.widget}
+        return {}
 
 
     def classes(self):
     def classes(self):
         classes = super().classes()
         classes = super().classes()
@@ -453,25 +433,22 @@ class BaseFieldPanel(EditHandler):
     field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
     field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
 
 
     def render_as_field(self):
     def render_as_field(self):
-        context = {
+        return mark_safe(render_to_string(self.field_template, {
             'field': self.bound_field,
             'field': self.bound_field,
             'field_type': self.field_type(),
             'field_type': self.field_type(),
-        }
-        return mark_safe(render_to_string(self.field_template, context))
+        }))
 
 
-    @classmethod
-    def required_fields(cls):
-        return [cls.field_name]
+    def required_fields(self):
+        return [self.field_name]
 
 
-    @classmethod
-    def get_comparison_class(cls):
+    def get_comparison_class(self):
         # Hide fields with hidden widget
         # Hide fields with hidden widget
-        widget_override = cls.widget_overrides().get(cls.field_name, None)
+        widget_override = self.widget_overrides().get(self.field_name, None)
         if widget_override and widget_override.is_hidden:
         if widget_override and widget_override.is_hidden:
             return
             return
 
 
         try:
         try:
-            field = cls.model._meta.get_field(cls.field_name)
+            field = self.db_field
 
 
             if field.choices:
             if field.choices:
                 return compare.ChoiceFieldComparison
                 return compare.ChoiceFieldComparison
@@ -491,54 +468,37 @@ class BaseFieldPanel(EditHandler):
 
 
         return compare.FieldComparison
         return compare.FieldComparison
 
 
-    @classmethod
-    def get_comparison(cls):
-        comparator_class = cls.get_comparison_class()
+    def get_comparison(self):
+        comparator_class = self.get_comparison_class()
 
 
         if comparator_class:
         if comparator_class:
-            field = cls.model._meta.get_field(cls.field_name)
-            return [curry(comparator_class, field)]
-        else:
-            return []
-
-
-class FieldPanel:
-    def __init__(self, field_name, classname="", widget=None):
-        self.field_name = field_name
-        self.classname = classname
-        self.widget = widget
+            return [curry(comparator_class, self.db_field)]
+        return []
 
 
-    def bind_to_model(self, model):
-        base = {
-            'model': model,
-            'field_name': self.field_name,
-            'classname': self.classname,
-        }
+    def on_model_bound(self):
+        self.db_field = self.model._meta.get_field(self.field_name)
 
 
-        if self.widget:
-            base['widget'] = self.widget
+    def on_instance_bound(self):
+        self.bound_field = self.form[self.field_name]
+        self.heading = self.bound_field.label
+        self.help_text = self.bound_field.help_text
 
 
-        return type(str('_FieldPanel'), (BaseFieldPanel,), base)
+    def __repr__(self):
+        class_name = self.__class__.__name__
+        try:
+            bound_to = force_text(getattr(self, 'instance',
+                                          getattr(self, 'model')))
+        except AttributeError:
+            return "<%s '%s'>" % (class_name, self.field_name)
+        return "<%s '%s' bound to %s>" % (class_name, self.field_name, bound_to)
 
 
 
 
-class BaseRichTextFieldPanel(BaseFieldPanel):
-    @classmethod
-    def get_comparison_class(cls):
+class RichTextFieldPanel(FieldPanel):
+    def get_comparison_class(self):
         return compare.RichTextFieldComparison
         return compare.RichTextFieldComparison
 
 
 
 
-class RichTextFieldPanel:
-    def __init__(self, field_name):
-        self.field_name = field_name
-
-    def bind_to_model(self, model):
-        return type(str('_RichTextFieldPanel'), (BaseRichTextFieldPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-        })
-
-
-class BaseChooserPanel(BaseFieldPanel):
+class BaseChooserPanel(FieldPanel):
     """
     """
     Abstract superclass for panels that provide a modal interface for choosing (or creating)
     Abstract superclass for panels that provide a modal interface for choosing (or creating)
     a database object such as an image, resulting in an ID that is used to populate
     a database object such as an image, resulting in an ID that is used to populate
@@ -559,7 +519,7 @@ class BaseChooserPanel(BaseFieldPanel):
             # if the ForeignKey is null=False, Django decides to raise
             # if the ForeignKey is null=False, Django decides to raise
             # a DoesNotExist exception here, rather than returning None
             # a DoesNotExist exception here, rather than returning None
             # like every other unpopulated field type. Yay consistency!
             # like every other unpopulated field type. Yay consistency!
-            return None
+            return
 
 
     def render_as_field(self):
     def render_as_field(self):
         instance_obj = self.get_chosen_item()
         instance_obj = self.get_chosen_item()
@@ -571,124 +531,131 @@ class BaseChooserPanel(BaseFieldPanel):
         return mark_safe(render_to_string(self.field_template, context))
         return mark_safe(render_to_string(self.field_template, context))
 
 
 
 
-class BasePageChooserPanel(BaseChooserPanel):
+class PageChooserPanel(BaseChooserPanel):
     object_type_name = "page"
     object_type_name = "page"
 
 
-    @classmethod
-    def widget_overrides(cls):
-        return {cls.field_name: widgets.AdminPageChooser(
-            target_models=cls.target_models(),
-            can_choose_root=cls.can_choose_root)}
+    def __init__(self, field_name, page_type=None, can_choose_root=False):
+        super().__init__(field_name=field_name)
+
+        if page_type:
+            # Convert single string/model into list
+            if not isinstance(page_type, (list, tuple)):
+                page_type = [page_type]
+        else:
+            page_type = []
 
 
-    @cached_classmethod
-    def target_models(cls):
-        if cls.page_type:
+        self.page_type = page_type
+        self.can_choose_root = can_choose_root
+
+    def clone(self):
+        return self.__class__(
+            field_name=self.field_name,
+            page_type=self.page_type,
+            can_choose_root=self.can_choose_root,
+        )
+
+    def widget_overrides(self):
+        return {self.field_name: widgets.AdminPageChooser(
+            target_models=self.target_models(),
+            can_choose_root=self.can_choose_root)}
+
+    def target_models(self):
+        if self.page_type:
             target_models = []
             target_models = []
 
 
-            for page_type in cls.page_type:
+            for page_type in self.page_type:
                 try:
                 try:
                     target_models.append(resolve_model_string(page_type))
                     target_models.append(resolve_model_string(page_type))
                 except LookupError:
                 except LookupError:
                     raise ImproperlyConfigured(
                     raise ImproperlyConfigured(
                         "{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
                         "{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
-                            cls.__name__, page_type
+                            self.__class__.__name__, page_type
                         )
                         )
                     )
                     )
                 except ValueError:
                 except ValueError:
                     raise ImproperlyConfigured(
                     raise ImproperlyConfigured(
                         "{0}.page_type refers to model {1!r} that has not been installed".format(
                         "{0}.page_type refers to model {1!r} that has not been installed".format(
-                            cls.__name__, page_type
+                            self.__class__.__name__, page_type
                         )
                         )
                     )
                     )
 
 
             return target_models
             return target_models
-        else:
-            return [cls.model._meta.get_field(cls.field_name).remote_field.model]
+        return [self.db_field.remote_field.model]
 
 
 
 
-class PageChooserPanel:
-    def __init__(self, field_name, page_type=None, can_choose_root=False):
-        self.field_name = field_name
-
-        if page_type:
-            # Convert single string/model into list
-            if not isinstance(page_type, (list, tuple)):
-                page_type = [page_type]
-        else:
-            page_type = []
-
-        self.page_type = page_type
-        self.can_choose_root = can_choose_root
-
-    def bind_to_model(self, model):
-        return type(str('_PageChooserPanel'), (BasePageChooserPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-            'page_type': self.page_type,
-            'can_choose_root': self.can_choose_root,
-        })
-
+class InlinePanel(EditHandler):
+    def __init__(self, relation_name, panels=None, heading='', label='',
+                 min_num=None, max_num=None, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.relation_name = relation_name
+        self.panels = panels
+        self.heading = heading or label
+        self.label = label
+        self.min_num = min_num
+        self.max_num = max_num
 
 
-class BaseInlinePanel(EditHandler):
-    @classmethod
-    def get_panel_definitions(cls):
+    def clone(self):
+        return self.__class__(
+            relation_name=self.relation_name,
+            panels=self.panels,
+            heading=self.heading,
+            label=self.label,
+            help_text=self.help_text,
+            min_num=self.min_num,
+            max_num=self.max_num,
+            classname=self.classname,
+        )
+
+    def get_panel_definitions(self):
         # Look for a panels definition in the InlinePanel declaration
         # Look for a panels definition in the InlinePanel declaration
-        if cls.panels is not None:
-            return cls.panels
+        if self.panels is not None:
+            return self.panels
         # Failing that, get it from the model
         # Failing that, get it from the model
-        else:
-            return extract_panel_definitions_from_model_class(
-                cls.related.related_model,
-                exclude=[cls.related.field.name]
-            )
-
-    _child_edit_handler_class = None
-
-    @classmethod
-    def get_child_edit_handler_class(cls):
-        if cls._child_edit_handler_class is None:
-            panels = cls.get_panel_definitions()
-            cls._child_edit_handler_class = MultiFieldPanel(
-                panels,
-                heading=cls.heading
-            ).bind_to_model(cls.related.related_model)
-
-        return cls._child_edit_handler_class
-
-    @classmethod
-    def required_formsets(cls):
-        child_edit_handler_class = cls.get_child_edit_handler_class()
+        return extract_panel_definitions_from_model_class(
+            self.related.related_model,
+            exclude=[self.related.field.name]
+        )
+
+    def get_child_edit_handler(self):
+        panels = self.get_panel_definitions()
+        child_edit_handler = MultiFieldPanel(panels, heading=self.heading)
+        return child_edit_handler.bind_to_model(self.related.related_model)
+
+    def required_formsets(self):
+        child_edit_handler = self.get_child_edit_handler()
         return {
         return {
-            cls.relation_name: {
-                'fields': child_edit_handler_class.required_fields(),
-                'widgets': child_edit_handler_class.widget_overrides(),
-                'min_num': cls.min_num,
-                'validate_min': cls.min_num is not None,
-                'max_num': cls.max_num,
-                'validate_max': cls.max_num is not None
+            self.relation_name: {
+                'fields': child_edit_handler.required_fields(),
+                'widgets': child_edit_handler.widget_overrides(),
+                'min_num': self.min_num,
+                'validate_min': self.min_num is not None,
+                'max_num': self.max_num,
+                'validate_max': self.max_num is not None
             }
             }
         }
         }
 
 
-    @classmethod
-    def html_declarations(cls):
-        return cls.get_child_edit_handler_class().html_declarations()
+    def html_declarations(self):
+        return self.get_child_edit_handler().html_declarations()
 
 
-    @classmethod
-    def get_comparison(cls):
-        field = cls.model._meta.get_field(cls.relation_name)
+    def get_comparison(self):
         field_comparisons = []
         field_comparisons = []
 
 
-        for panel in cls.get_panel_definitions():
-            field_comparisons.extend(panel.bind_to_model(cls.related.related_model).get_comparison())
+        for panel in self.get_panel_definitions():
+            field_comparisons.extend(
+                panel.bind_to_model(self.related.related_model)
+                .get_comparison())
 
 
-        return [curry(compare.ChildRelationComparison, field, field_comparisons)]
+        return [curry(compare.ChildRelationComparison, self.db_field,
+                      field_comparisons)]
 
 
-    def __init__(self, instance=None, form=None):
-        super().__init__(instance=instance, form=form)
+    def on_model_bound(self):
+        self.db_field = self.model._meta.get_field(self.relation_name)
+        manager = getattr(self.model, self.relation_name)
+        self.related = manager.rel
 
 
-        self.formset = form.formsets[self.__class__.relation_name]
+    def on_instance_bound(self):
+        self.formset = self.form.formsets[self.relation_name]
 
 
-        child_edit_handler_class = self.__class__.get_child_edit_handler_class()
         self.children = []
         self.children = []
         for subform in self.formset.forms:
         for subform in self.formset.forms:
             # override the DELETE field to have a hidden input
             # override the DELETE field to have a hidden input
@@ -698,9 +665,10 @@ class BaseInlinePanel(EditHandler):
             if self.formset.can_order:
             if self.formset.can_order:
                 subform.fields['ORDER'].widget = forms.HiddenInput()
                 subform.fields['ORDER'].widget = forms.HiddenInput()
 
 
+            child_edit_handler = self.get_child_edit_handler()
             self.children.append(
             self.children.append(
-                child_edit_handler_class(instance=subform.instance, form=subform)
-            )
+                child_edit_handler.bind_to_instance(instance=subform.instance,
+                                                    form=subform))
 
 
         # if this formset is valid, it may have been re-ordered; respect that
         # if this formset is valid, it may have been re-ordered; respect that
         # in case the parent form errored and we need to re-render
         # in case the parent form errored and we need to re-render
@@ -712,7 +680,9 @@ class BaseInlinePanel(EditHandler):
         if self.formset.can_order:
         if self.formset.can_order:
             empty_form.fields['ORDER'].widget = forms.HiddenInput()
             empty_form.fields['ORDER'].widget = forms.HiddenInput()
 
 
-        self.empty_child = child_edit_handler_class(instance=empty_form.instance, form=empty_form)
+        self.empty_child = self.get_child_edit_handler()
+        self.empty_child = self.empty_child.bind_to_instance(
+            instance=empty_form.instance, form=empty_form)
 
 
     template = "wagtailadmin/edit_handlers/inline_panel.html"
     template = "wagtailadmin/edit_handlers/inline_panel.html"
 
 
@@ -733,46 +703,23 @@ class BaseInlinePanel(EditHandler):
         }))
         }))
 
 
 
 
-class InlinePanel:
-    def __init__(self, relation_name, panels=None, classname='', heading='', label='', help_text='', min_num=None, max_num=None):
-        self.relation_name = relation_name
-        self.panels = panels
-        self.heading = heading or label
-        self.label = label
-        self.help_text = help_text
-        self.min_num = min_num
-        self.max_num = max_num
-        self.classname = classname
-
-    def bind_to_model(self, model):
-        related = getattr(model, self.relation_name).rel
-
-        return type(str('_InlinePanel'), (BaseInlinePanel,), {
-            'model': model,
-            'relation_name': self.relation_name,
-            'related': related,
-            'panels': self.panels,
-            'heading': self.heading,
-            'label': self.label,
-            'help_text': self.help_text,
-            # TODO: can we pick this out of the foreign key definition as an alternative?
-            # (with a bit of help from the inlineformset object, as we do for label/heading)
-            'min_num': self.min_num,
-            'max_num': self.max_num,
-            'classname': self.classname,
-        })
-
-
 # This allows users to include the publishing panel in their own per-model override
 # This allows users to include the publishing panel in their own per-model override
 # without having to write these fields out by hand, potentially losing 'classname'
 # without having to write these fields out by hand, potentially losing 'classname'
 # and therefore the associated styling of the publishing panel
 # and therefore the associated styling of the publishing panel
-def PublishingPanel():
-    return MultiFieldPanel([
-        FieldRowPanel([
-            FieldPanel('go_live_at'),
-            FieldPanel('expire_at'),
-        ], classname="label-above"),
-    ], ugettext_lazy('Scheduled publishing'), classname="publishing")
+class PublishingPanel(MultiFieldPanel):
+    def __init__(self, **kwargs):
+        updated_kwargs = {
+            'children': [
+                FieldRowPanel([
+                    FieldPanel('go_live_at'),
+                    FieldPanel('expire_at'),
+                ], classname="label-above"),
+            ],
+            'heading': ugettext_lazy('Scheduled publishing'),
+            'classname': 'publishing',
+        }
+        updated_kwargs.update(kwargs)
+        super().__init__(**updated_kwargs)
 
 
 
 
 # Now that we've defined EditHandlers, we can set up wagtailcore.Page to have some.
 # Now that we've defined EditHandlers, we can set up wagtailcore.Page to have some.
@@ -815,14 +762,14 @@ def get_edit_handler(cls):
     if cls.settings_panels:
     if cls.settings_panels:
         tabs.append(ObjectList(cls.settings_panels, heading=ugettext_lazy('Settings'), classname="settings"))
         tabs.append(ObjectList(cls.settings_panels, heading=ugettext_lazy('Settings'), classname="settings"))
 
 
-    EditHandler = TabbedInterface(tabs, base_form_class=cls.base_form_class)
-    return EditHandler.bind_to_model(cls)
+    edit_handler = TabbedInterface(tabs, base_form_class=cls.base_form_class)
+    return edit_handler.bind_to_model(cls)
 
 
 
 
 Page.get_edit_handler = get_edit_handler
 Page.get_edit_handler = get_edit_handler
 
 
 
 
-class BaseStreamFieldPanel(BaseFieldPanel):
+class StreamFieldPanel(FieldPanel):
     def classes(self):
     def classes(self):
         classes = super().classes()
         classes = super().classes()
         classes.append("stream-field")
         classes.append("stream-field")
@@ -834,12 +781,10 @@ class BaseStreamFieldPanel(BaseFieldPanel):
 
 
         return classes
         return classes
 
 
-    @classmethod
-    def html_declarations(cls):
-        return cls.block_def.all_html_declarations()
+    def html_declarations(self):
+        return self.block_def.all_html_declarations()
 
 
-    @classmethod
-    def get_comparison_class(cls):
+    def get_comparison_class(self):
         return compare.StreamFieldComparison
         return compare.StreamFieldComparison
 
 
     def id_for_label(self):
     def id_for_label(self):
@@ -847,16 +792,6 @@ class BaseStreamFieldPanel(BaseFieldPanel):
         # attach the label to any specific one
         # attach the label to any specific one
         return ""
         return ""
 
 
-
-class StreamFieldPanel:
-    def __init__(self, field_name, classname=''):
-        self.field_name = field_name
-        self.classname = classname
-
-    def bind_to_model(self, model):
-        return type(str('_StreamFieldPanel'), (BaseStreamFieldPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-            'block_def': model._meta.get_field(self.field_name).stream_block,
-            'classname': self.classname,
-        })
+    def on_model_bound(self):
+        super().on_model_bound()
+        self.block_def = self.db_field.stream_block

+ 120 - 64
wagtail/admin/tests/test_edit_handlers.py

@@ -3,7 +3,7 @@ from datetime import date
 import mock
 import mock
 from django import forms
 from django import forms
 from django.core import checks
 from django.core import checks
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
 
 
 from wagtail.tests.testapp.forms import ValidatedPageForm
 from wagtail.tests.testapp.forms import ValidatedPageForm
@@ -21,6 +21,14 @@ from wagtail.images.edit_handlers import ImageChooserPanel
 
 
 
 
 class TestGetFormForModel(TestCase):
 class TestGetFormForModel(TestCase):
+    def test_get_form_without_model(self):
+        edit_handler = ObjectList()
+        with self.assertRaisesMessage(
+                AttributeError,
+                'ObjectList is not bound to a model yet. '
+                'Use `.bind_to_model(model)` before using this method.'):
+            edit_handler.get_form_class()
+
     def test_get_form_for_model(self):
     def test_get_form_for_model(self):
         EventPageForm = get_form_for_model(EventPage, form_class=WagtailAdminPageForm)
         EventPageForm = get_form_for_model(EventPage, form_class=WagtailAdminPageForm)
         form = EventPageForm()
         form = EventPageForm()
@@ -128,8 +136,8 @@ class TestPageEditHandlers(TestCase):
         """
         """
         Forms for pages should have a base class of WagtailAdminPageForm.
         Forms for pages should have a base class of WagtailAdminPageForm.
         """
         """
-        EditHandler = EventPage.get_edit_handler()
-        EventPageForm = EditHandler.get_form_class(EventPage)
+        edit_handler = EventPage.get_edit_handler()
+        EventPageForm = edit_handler.get_form_class()
 
 
         # The generated form should inherit from WagtailAdminPageForm
         # The generated form should inherit from WagtailAdminPageForm
         self.assertTrue(issubclass(EventPageForm, WagtailAdminPageForm))
         self.assertTrue(issubclass(EventPageForm, WagtailAdminPageForm))
@@ -140,8 +148,8 @@ class TestPageEditHandlers(TestCase):
         ValidatedPage sets a custom base_form_class. This should be used as the
         ValidatedPage sets a custom base_form_class. This should be used as the
         base class when constructing a form for ValidatedPages
         base class when constructing a form for ValidatedPages
         """
         """
-        EditHandler = ValidatedPage.get_edit_handler()
-        GeneratedValidatedPageForm = EditHandler.get_form_class(ValidatedPage)
+        edit_handler = ValidatedPage.get_edit_handler()
+        GeneratedValidatedPageForm = edit_handler.get_form_class()
 
 
         # The generated form should inherit from ValidatedPageForm, because
         # The generated form should inherit from ValidatedPageForm, because
         # ValidatedPage.base_form_class == ValidatedPageForm
         # ValidatedPage.base_form_class == ValidatedPageForm
@@ -159,7 +167,7 @@ class TestPageEditHandlers(TestCase):
             id='wagtailadmin.E001')
             id='wagtailadmin.E001')
 
 
         invalid_edit_handler = checks.Error(
         invalid_edit_handler = checks.Error(
-            "ValidatedPage.get_edit_handler().get_form_class(ValidatedPage) does not extend WagtailAdminPageForm",
+            "ValidatedPage.get_edit_handler().get_form_class() does not extend WagtailAdminPageForm",
             hint="Ensure that the EditHandler for ValidatedPage creates a subclass of WagtailAdminPageForm",
             hint="Ensure that the EditHandler for ValidatedPage creates a subclass of WagtailAdminPageForm",
             obj=ValidatedPage,
             obj=ValidatedPage,
             id='wagtailadmin.E002')
             id='wagtailadmin.E002')
@@ -180,9 +188,9 @@ class TestPageEditHandlers(TestCase):
         ValidatedPage.base_form_class, or provide a custom form class for the
         ValidatedPage.base_form_class, or provide a custom form class for the
         edit handler. Check the generated form class is of the correct type.
         edit handler. Check the generated form class is of the correct type.
         """
         """
-        ValidatedPage.edit_handler = TabbedInterface([])
-        with mock.patch.object(ValidatedPage, 'edit_handler', new=TabbedInterface([]), create=True):
-            form_class = ValidatedPage.get_edit_handler().get_form_class(ValidatedPage)
+        ValidatedPage.edit_handler = TabbedInterface()
+        with mock.patch.object(ValidatedPage, 'edit_handler', new=TabbedInterface(), create=True):
+            form_class = ValidatedPage.get_edit_handler().get_form_class()
             self.assertTrue(issubclass(form_class, WagtailAdminPageForm))
             self.assertTrue(issubclass(form_class, WagtailAdminPageForm))
             errors = ValidatedPage.check()
             errors = ValidatedPage.check()
             self.assertEqual(errors, [])
             self.assertEqual(errors, [])
@@ -219,7 +227,7 @@ class TestExtractPanelDefinitionsFromModelClass(TestCase):
 class TestTabbedInterface(TestCase):
 class TestTabbedInterface(TestCase):
     def setUp(self):
     def setUp(self):
         # a custom tabbed interface for EventPage
         # a custom tabbed interface for EventPage
-        self.EventPageTabbedInterface = TabbedInterface([
+        self.event_page_tabbed_interface = TabbedInterface([
             ObjectList([
             ObjectList([
                 FieldPanel('title', widget=forms.Textarea),
                 FieldPanel('title', widget=forms.Textarea),
                 FieldPanel('date_from'),
                 FieldPanel('date_from'),
@@ -231,7 +239,7 @@ class TestTabbedInterface(TestCase):
         ]).bind_to_model(EventPage)
         ]).bind_to_model(EventPage)
 
 
     def test_get_form_class(self):
     def test_get_form_class(self):
-        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
+        EventPageForm = self.event_page_tabbed_interface.get_form_class()
         form = EventPageForm()
         form = EventPageForm()
 
 
         # form must include the 'speakers' formset required by the speakers InlinePanel
         # form must include the 'speakers' formset required by the speakers InlinePanel
@@ -241,11 +249,11 @@ class TestTabbedInterface(TestCase):
         self.assertEqual(type(form.fields['title'].widget), forms.Textarea)
         self.assertEqual(type(form.fields['title'].widget), forms.Textarea)
 
 
     def test_render(self):
     def test_render(self):
-        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
+        EventPageForm = self.event_page_tabbed_interface.get_form_class()
         event = EventPage(title='Abergavenny sheepdog trials')
         event = EventPage(title='Abergavenny sheepdog trials')
         form = EventPageForm(instance=event)
         form = EventPageForm(instance=event)
 
 
-        tabbed_interface = self.EventPageTabbedInterface(
+        tabbed_interface = self.event_page_tabbed_interface.bind_to_instance(
             instance=event,
             instance=event,
             form=form
             form=form
         )
         )
@@ -269,15 +277,15 @@ class TestTabbedInterface(TestCase):
 
 
     def test_required_fields(self):
     def test_required_fields(self):
         # required_fields should report the set of form fields to be rendered recursively by children of TabbedInterface
         # required_fields should report the set of form fields to be rendered recursively by children of TabbedInterface
-        result = set(self.EventPageTabbedInterface.required_fields())
+        result = set(self.event_page_tabbed_interface.required_fields())
         self.assertEqual(result, set(['title', 'date_from', 'date_to']))
         self.assertEqual(result, set(['title', 'date_from', 'date_to']))
 
 
     def test_render_form_content(self):
     def test_render_form_content(self):
-        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
+        EventPageForm = self.event_page_tabbed_interface.get_form_class()
         event = EventPage(title='Abergavenny sheepdog trials')
         event = EventPage(title='Abergavenny sheepdog trials')
         form = EventPageForm(instance=event)
         form = EventPageForm(instance=event)
 
 
-        tabbed_interface = self.EventPageTabbedInterface(
+        tabbed_interface = self.event_page_tabbed_interface.bind_to_instance(
             instance=event,
             instance=event,
             form=form
             form=form
         )
         )
@@ -293,7 +301,7 @@ class TestTabbedInterface(TestCase):
 class TestObjectList(TestCase):
 class TestObjectList(TestCase):
     def setUp(self):
     def setUp(self):
         # a custom ObjectList for EventPage
         # a custom ObjectList for EventPage
-        self.EventPageObjectList = ObjectList([
+        self.event_page_object_list = ObjectList([
             FieldPanel('title', widget=forms.Textarea),
             FieldPanel('title', widget=forms.Textarea),
             FieldPanel('date_from'),
             FieldPanel('date_from'),
             FieldPanel('date_to'),
             FieldPanel('date_to'),
@@ -301,7 +309,7 @@ class TestObjectList(TestCase):
         ], heading='Event details', classname="shiny").bind_to_model(EventPage)
         ], heading='Event details', classname="shiny").bind_to_model(EventPage)
 
 
     def test_get_form_class(self):
     def test_get_form_class(self):
-        EventPageForm = self.EventPageObjectList.get_form_class(EventPage)
+        EventPageForm = self.event_page_object_list.get_form_class()
         form = EventPageForm()
         form = EventPageForm()
 
 
         # form must include the 'speakers' formset required by the speakers InlinePanel
         # form must include the 'speakers' formset required by the speakers InlinePanel
@@ -311,11 +319,11 @@ class TestObjectList(TestCase):
         self.assertEqual(type(form.fields['title'].widget), forms.Textarea)
         self.assertEqual(type(form.fields['title'].widget), forms.Textarea)
 
 
     def test_render(self):
     def test_render(self):
-        EventPageForm = self.EventPageObjectList.get_form_class(EventPage)
+        EventPageForm = self.event_page_object_list.get_form_class()
         event = EventPage(title='Abergavenny sheepdog trials')
         event = EventPage(title='Abergavenny sheepdog trials')
         form = EventPageForm(instance=event)
         form = EventPageForm(instance=event)
 
 
-        object_list = self.EventPageObjectList(
+        object_list = self.event_page_object_list.bind_to_instance(
             instance=event,
             instance=event,
             form=form
             form=form
         )
         )
@@ -345,7 +353,12 @@ class TestFieldPanel(TestCase):
         self.event = EventPage(title='Abergavenny sheepdog trials',
         self.event = EventPage(title='Abergavenny sheepdog trials',
                                date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))
                                date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))
 
 
-        self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)
+        self.end_date_panel = (FieldPanel('date_to', classname='full-width')
+                               .bind_to_model(EventPage))
+
+    def test_invalid_field(self):
+        with self.assertRaises(FieldDoesNotExist):
+            FieldPanel('barbecue').bind_to_model(Page)
 
 
     def test_render_as_object(self):
     def test_render_as_object(self):
         form = self.EventPageForm(
         form = self.EventPageForm(
@@ -354,7 +367,7 @@ class TestFieldPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.EndDatePanel(
+        field_panel = self.end_date_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -381,7 +394,7 @@ class TestFieldPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.EndDatePanel(
+        field_panel = self.end_date_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -401,7 +414,7 @@ class TestFieldPanel(TestCase):
         self.assertNotIn('<p class="error-message">', result)
         self.assertNotIn('<p class="error-message">', result)
 
 
     def test_required_fields(self):
     def test_required_fields(self):
-        result = self.EndDatePanel.required_fields()
+        result = self.end_date_panel.required_fields()
         self.assertEqual(result, ['date_to'])
         self.assertEqual(result, ['date_to'])
 
 
     def test_error_message_is_rendered(self):
     def test_error_message_is_rendered(self):
@@ -411,7 +424,7 @@ class TestFieldPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.EndDatePanel(
+        field_panel = self.end_date_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -428,7 +441,7 @@ class TestFieldRowPanel(TestCase):
         self.event = EventPage(title='Abergavenny sheepdog trials',
         self.event = EventPage(title='Abergavenny sheepdog trials',
                                date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))
                                date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))
 
 
-        self.DatesPanel = FieldRowPanel([
+        self.dates_panel = FieldRowPanel([
             FieldPanel('date_from', classname='col4'),
             FieldPanel('date_from', classname='col4'),
             FieldPanel('date_to', classname='coltwo'),
             FieldPanel('date_to', classname='coltwo'),
         ]).bind_to_model(EventPage)
         ]).bind_to_model(EventPage)
@@ -440,7 +453,7 @@ class TestFieldRowPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.DatesPanel(
+        field_panel = self.dates_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -459,7 +472,7 @@ class TestFieldRowPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.DatesPanel(
+        field_panel = self.dates_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -485,7 +498,7 @@ class TestFieldRowPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.DatesPanel(
+        field_panel = self.dates_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -501,7 +514,7 @@ class TestFieldRowPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.DatesPanel(
+        field_panel = self.dates_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -517,7 +530,7 @@ class TestFieldRowPanel(TestCase):
 
 
         form.is_valid()
         form.is_valid()
 
 
-        field_panel = self.DatesPanel(
+        field_panel = self.dates_panel.bind_to_instance(
             instance=self.event,
             instance=self.event,
             form=form
             form=form
         )
         )
@@ -527,6 +540,38 @@ class TestFieldRowPanel(TestCase):
         self.assertIn('<li class="field-col col4', result)
         self.assertIn('<li class="field-col col4', result)
 
 
 
 
+class TestFieldRowPanelWithChooser(TestCase):
+    def setUp(self):
+        self.EventPageForm = get_form_for_model(
+            EventPage, form_class=WagtailAdminPageForm, formsets=[])
+        self.event = EventPage(title='Abergavenny sheepdog trials',
+                               date_from=date(2014, 7, 19), date_to=date(2014, 7, 21))
+
+        self.dates_panel = FieldRowPanel([
+            FieldPanel('date_from'),
+            ImageChooserPanel('feed_image'),
+        ]).bind_to_model(EventPage)
+
+    def test_render_as_object(self):
+        form = self.EventPageForm(
+            {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22'},
+            instance=self.event)
+
+        form.is_valid()
+
+        field_panel = self.dates_panel.bind_to_instance(
+            instance=self.event,
+            form=form
+        )
+        result = field_panel.render_as_object()
+
+        # check that the populated form field is included
+        self.assertIn('value="2014-07-20"', result)
+
+        # there should be no errors on this field
+        self.assertNotIn('<p class="error-message">', result)
+
+
 class TestPageChooserPanel(TestCase):
 class TestPageChooserPanel(TestCase):
     fixtures = ['test.json']
     fixtures = ['test.json']
 
 
@@ -534,11 +579,12 @@ class TestPageChooserPanel(TestCase):
         model = PageChooserModel  # a model with a foreign key to Page which we want to render as a page chooser
         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
         # a PageChooserPanel class that works on PageChooserModel's 'page' field
-        self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel)
-        self.MyPageChooserPanel = self.EditHandler.children[0]
+        self.edit_handler = (ObjectList([PageChooserPanel('page')])
+                             .bind_to_model(PageChooserModel))
+        self.my_page_chooser_panel = self.edit_handler.children[0]
 
 
         # build a form class containing the fields that MyPageChooserPanel wants
         # build a form class containing the fields that MyPageChooserPanel wants
-        self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel)
+        self.PageChooserForm = self.edit_handler.get_form_class()
 
 
         # a test instance of PageChooserModel, pointing to the 'christmas' page
         # a test instance of PageChooserModel, pointing to the 'christmas' page
         self.christmas_page = Page.objects.get(slug='christmas')
         self.christmas_page = Page.objects.get(slug='christmas')
@@ -546,7 +592,8 @@ class TestPageChooserPanel(TestCase):
         self.test_instance = model.objects.create(page=self.christmas_page)
         self.test_instance = model.objects.create(page=self.christmas_page)
 
 
         self.form = self.PageChooserForm(instance=self.test_instance)
         self.form = self.PageChooserForm(instance=self.test_instance)
-        self.page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=self.form)
+        self.page_chooser_panel = self.my_page_chooser_panel.bind_to_instance(
+            instance=self.test_instance, form=self.form)
 
 
     def test_page_chooser_uses_correct_widget(self):
     def test_page_chooser_uses_correct_widget(self):
         self.assertEqual(type(self.form.fields['page'].widget), AdminPageChooser)
         self.assertEqual(type(self.form.fields['page'].widget), AdminPageChooser)
@@ -561,14 +608,15 @@ class TestPageChooserPanel(TestCase):
     def test_render_js_init_with_can_choose_root_true(self):
     def test_render_js_init_with_can_choose_root_true(self):
         # construct an alternative page chooser panel object, with can_choose_root=True
         # construct an alternative page chooser panel object, with can_choose_root=True
 
 
-        MyPageObjectList = ObjectList([
+        my_page_object_list = ObjectList([
             PageChooserPanel('page', can_choose_root=True)
             PageChooserPanel('page', can_choose_root=True)
         ]).bind_to_model(PageChooserModel)
         ]).bind_to_model(PageChooserModel)
-        MyPageChooserPanel = MyPageObjectList.children[0]
-        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
+        my_page_chooser_panel = my_page_object_list.children[0]
+        PageChooserForm = my_page_object_list.get_form_class()
 
 
         form = PageChooserForm(instance=self.test_instance)
         form = PageChooserForm(instance=self.test_instance)
-        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
+        page_chooser_panel = my_page_chooser_panel.bind_to_instance(
+            instance=self.test_instance, form=form)
         result = page_chooser_panel.render_as_field()
         result = page_chooser_panel.render_as_field()
 
 
         # the canChooseRoot flag on createPageChooser should now be true
         # the canChooseRoot flag on createPageChooser should now be true
@@ -592,7 +640,8 @@ class TestPageChooserPanel(TestCase):
     def test_render_as_empty_field(self):
     def test_render_as_empty_field(self):
         test_instance = PageChooserModel()
         test_instance = PageChooserModel()
         form = self.PageChooserForm(instance=test_instance)
         form = self.PageChooserForm(instance=test_instance)
-        page_chooser_panel = self.MyPageChooserPanel(instance=test_instance, form=form)
+        page_chooser_panel = self.my_page_chooser_panel.bind_to_instance(
+            instance=test_instance, form=form)
         result = page_chooser_panel.render_as_field()
         result = page_chooser_panel.render_as_field()
 
 
         self.assertIn('<p class="help">help text</p>', result)
         self.assertIn('<p class="help">help text</p>', result)
@@ -603,19 +652,21 @@ class TestPageChooserPanel(TestCase):
         form = self.PageChooserForm({'page': ''}, instance=self.test_instance)
         form = self.PageChooserForm({'page': ''}, instance=self.test_instance)
         self.assertFalse(form.is_valid())
         self.assertFalse(form.is_valid())
 
 
-        page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)
+        page_chooser_panel = self.my_page_chooser_panel.bind_to_instance(
+            instance=self.test_instance, form=form)
         self.assertIn('<span>This field is required.</span>', page_chooser_panel.render_as_field())
         self.assertIn('<span>This field is required.</span>', page_chooser_panel.render_as_field())
 
 
     def test_override_page_type(self):
     def test_override_page_type(self):
         # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
         # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
         # to restrict the chooser to that page type
         # to restrict the chooser to that page type
-        MyPageObjectList = ObjectList([
+        my_page_object_list = ObjectList([
             PageChooserPanel('page', 'tests.EventPage')
             PageChooserPanel('page', 'tests.EventPage')
         ]).bind_to_model(EventPageChooserModel)
         ]).bind_to_model(EventPageChooserModel)
-        MyPageChooserPanel = MyPageObjectList.children[0]
-        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
+        my_page_chooser_panel = my_page_object_list.children[0]
+        PageChooserForm = my_page_object_list.get_form_class()
         form = PageChooserForm(instance=self.test_instance)
         form = PageChooserForm(instance=self.test_instance)
-        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
+        page_chooser_panel = my_page_chooser_panel.bind_to_instance(
+            instance=self.test_instance, form=form)
 
 
         result = page_chooser_panel.render_as_field()
         result = page_chooser_panel.render_as_field()
         expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
         expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
@@ -626,11 +677,13 @@ class TestPageChooserPanel(TestCase):
     def test_autodetect_page_type(self):
     def test_autodetect_page_type(self):
         # Model has a foreign key to EventPage, which we want to autodetect
         # Model has a foreign key to EventPage, which we want to autodetect
         # instead of specifying the page type in PageChooserPanel
         # instead of specifying the page type in PageChooserPanel
-        MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel)
-        MyPageChooserPanel = MyPageObjectList.children[0]
-        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
+        my_page_object_list = (ObjectList([PageChooserPanel('page')])
+                               .bind_to_model(EventPageChooserModel))
+        my_page_chooser_panel = my_page_object_list.children[0]
+        PageChooserForm = my_page_object_list.get_form_class()
         form = PageChooserForm(instance=self.test_instance)
         form = PageChooserForm(instance=self.test_instance)
-        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
+        page_chooser_panel = my_page_chooser_panel.bind_to_instance(
+            instance=self.test_instance, form=form)
 
 
         result = page_chooser_panel.render_as_field()
         result = page_chooser_panel.render_as_field()
         expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
         expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
@@ -640,14 +693,14 @@ class TestPageChooserPanel(TestCase):
 
 
     def test_target_models(self):
     def test_target_models(self):
         result = PageChooserPanel(
         result = PageChooserPanel(
-            'barbecue',
+            'page',
             'wagtailcore.site'
             'wagtailcore.site'
         ).bind_to_model(PageChooserModel).target_models()
         ).bind_to_model(PageChooserModel).target_models()
         self.assertEqual(result, [Site])
         self.assertEqual(result, [Site])
 
 
     def test_target_models_malformed_type(self):
     def test_target_models_malformed_type(self):
         result = PageChooserPanel(
         result = PageChooserPanel(
-            'barbecue',
+            'page',
             'snowman'
             'snowman'
         ).bind_to_model(PageChooserModel)
         ).bind_to_model(PageChooserModel)
         self.assertRaises(ImproperlyConfigured,
         self.assertRaises(ImproperlyConfigured,
@@ -655,7 +708,7 @@ class TestPageChooserPanel(TestCase):
 
 
     def test_target_models_nonexistent_type(self):
     def test_target_models_nonexistent_type(self):
         result = PageChooserPanel(
         result = PageChooserPanel(
-            'barbecue',
+            'page',
             'snowman.lorry'
             'snowman.lorry'
         ).bind_to_model(PageChooserModel)
         ).bind_to_model(PageChooserModel)
         self.assertRaises(ImproperlyConfigured,
         self.assertRaises(ImproperlyConfigured,
@@ -670,10 +723,10 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
         Check that the inline panel renders the panels set on the model
         Check that the inline panel renders the panels set on the model
         when no 'panels' parameter is passed in the InlinePanel definition
         when no 'panels' parameter is passed in the InlinePanel definition
         """
         """
-        SpeakerObjectList = ObjectList([
+        speaker_object_list = ObjectList([
             InlinePanel('speakers', label="Speakers", classname="classname-for-speakers")
             InlinePanel('speakers', label="Speakers", classname="classname-for-speakers")
         ]).bind_to_model(EventPage)
         ]).bind_to_model(EventPage)
-        EventPageForm = SpeakerObjectList.get_form_class(EventPage)
+        EventPageForm = speaker_object_list.get_form_class()
 
 
         # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
         # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
         self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
         self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
@@ -681,7 +734,8 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
         event_page = EventPage.objects.get(slug='christmas')
         event_page = EventPage.objects.get(slug='christmas')
 
 
         form = EventPageForm(instance=event_page)
         form = EventPageForm(instance=event_page)
-        panel = SpeakerObjectList(instance=event_page, form=form)
+        panel = speaker_object_list.bind_to_instance(instance=event_page,
+                                                     form=form)
 
 
         result = panel.render_as_field()
         result = panel.render_as_field()
 
 
@@ -720,22 +774,23 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
         Check that inline panel renders the panels listed in the InlinePanel definition
         Check that inline panel renders the panels listed in the InlinePanel definition
         where one is specified
         where one is specified
         """
         """
-        SpeakerObjectList = ObjectList([
+        speaker_object_list = ObjectList([
             InlinePanel('speakers', label="Speakers", panels=[
             InlinePanel('speakers', label="Speakers", panels=[
                 FieldPanel('first_name', widget=forms.Textarea),
                 FieldPanel('first_name', widget=forms.Textarea),
                 ImageChooserPanel('image'),
                 ImageChooserPanel('image'),
             ]),
             ]),
         ]).bind_to_model(EventPage)
         ]).bind_to_model(EventPage)
-        SpeakerInlinePanel = SpeakerObjectList.children[0]
-        EventPageForm = SpeakerObjectList.get_form_class(EventPage)
+        speaker_inline_panel = speaker_object_list.children[0]
+        EventPageForm = speaker_object_list.get_form_class()
 
 
-        # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
+        # speaker_inline_panel should instruct the form class to include a 'speakers' formset
         self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
         self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
 
 
         event_page = EventPage.objects.get(slug='christmas')
         event_page = EventPage.objects.get(slug='christmas')
 
 
         form = EventPageForm(instance=event_page)
         form = EventPageForm(instance=event_page)
-        panel = SpeakerInlinePanel(instance=event_page, form=form)
+        panel = speaker_inline_panel.bind_to_instance(
+            instance=event_page, form=form)
 
 
         result = panel.render_as_field()
         result = panel.render_as_field()
 
 
@@ -781,17 +836,18 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
         https://github.com/wagtail/wagtail/pull/2699
         https://github.com/wagtail/wagtail/pull/2699
         https://github.com/wagtail/wagtail/issues/3227
         https://github.com/wagtail/wagtail/issues/3227
         """
         """
-        SpeakerObjectList = ObjectList([
+        speaker_object_list = ObjectList([
             InlinePanel('speakers', label="Speakers", panels=[
             InlinePanel('speakers', label="Speakers", panels=[
                 FieldPanel('first_name', widget=forms.Textarea),
                 FieldPanel('first_name', widget=forms.Textarea),
                 ImageChooserPanel('image'),
                 ImageChooserPanel('image'),
             ]),
             ]),
         ]).bind_to_model(EventPage)
         ]).bind_to_model(EventPage)
-        SpeakerInlinePanel = SpeakerObjectList.children[0]
-        EventPageForm = SpeakerObjectList.get_form_class(EventPage)
+        speaker_inline_panel = speaker_object_list.children[0]
+        EventPageForm = speaker_object_list.get_form_class()
         event_page = EventPage.objects.get(slug='christmas')
         event_page = EventPage.objects.get(slug='christmas')
         form = EventPageForm(instance=event_page)
         form = EventPageForm(instance=event_page)
-        panel = SpeakerInlinePanel(instance=event_page, form=form)
+        panel = speaker_inline_panel.bind_to_instance(
+            instance=event_page, form=form)
 
 
         self.assertIn('maxForms: 1000', panel.render_js_init())
         self.assertIn('maxForms: 1000', panel.render_js_init())
 
 

+ 5 - 5
wagtail/admin/tests/test_rich_text.py

@@ -23,11 +23,11 @@ class BaseRichTextEditHandlerTestCase(TestCase):
         """
         """
         from wagtail.tests.testapp.models import DefaultRichBlockFieldPage
         from wagtail.tests.testapp.models import DefaultRichBlockFieldPage
 
 
-        block_page_edit_handler = DefaultRichBlockFieldPage.get_edit_handler()
-        if block_page_edit_handler._form_class:
-            rich_text_block = block_page_edit_handler._form_class.base_fields['body'].block.child_blocks['rich_text']
-            if hasattr(rich_text_block, 'field'):
-                del rich_text_block.field
+        rich_text_block = (DefaultRichBlockFieldPage.get_edit_handler()
+                           .get_form_class().base_fields['body'].block
+                           .child_blocks['rich_text'])
+        if hasattr(rich_text_block, 'field'):
+            del rich_text_block.field
 
 
         for page_class in get_page_models():
         for page_class in get_page_models():
             page_class.get_edit_handler.cache_clear()
             page_class.get_edit_handler.cache_clear()

+ 15 - 12
wagtail/admin/views/pages.py

@@ -183,8 +183,8 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
             return result
             return result
 
 
     page = page_class(owner=request.user)
     page = page_class(owner=request.user)
-    edit_handler_class = page_class.get_edit_handler()
-    form_class = edit_handler_class.get_form_class(page_class)
+    edit_handler = page_class.get_edit_handler()
+    form_class = edit_handler.get_form_class()
 
 
     next_url = get_valid_next_url_from_request(request)
     next_url = get_valid_next_url_from_request(request)
 
 
@@ -270,12 +270,13 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
             messages.validation_error(
             messages.validation_error(
                 request, _("The page could not be created due to validation errors"), form
                 request, _("The page could not be created due to validation errors"), form
             )
             )
-            edit_handler = edit_handler_class(instance=page, form=form)
+            edit_handler = edit_handler.bind_to_instance(instance=page,
+                                                         form=form)
             has_unsaved_changes = True
             has_unsaved_changes = True
     else:
     else:
         signals.init_new_page.send(sender=create, page=page, parent=parent_page)
         signals.init_new_page.send(sender=create, page=page, parent=parent_page)
         form = form_class(instance=page, parent_page=parent_page)
         form = form_class(instance=page, parent_page=parent_page)
-        edit_handler = edit_handler_class(instance=page, form=form)
+        edit_handler = edit_handler.bind_to_instance(instance=page, form=form)
         has_unsaved_changes = False
         has_unsaved_changes = False
 
 
     return render(request, 'wagtailadmin/pages/create.html', {
     return render(request, 'wagtailadmin/pages/create.html', {
@@ -307,8 +308,8 @@ def edit(request, page_id):
         if hasattr(result, 'status_code'):
         if hasattr(result, 'status_code'):
             return result
             return result
 
 
-    edit_handler_class = page_class.get_edit_handler()
-    form_class = edit_handler_class.get_form_class(page_class)
+    edit_handler = page_class.get_edit_handler()
+    form_class = edit_handler.get_form_class()
 
 
     next_url = get_valid_next_url_from_request(request)
     next_url = get_valid_next_url_from_request(request)
 
 
@@ -460,7 +461,8 @@ def edit(request, page_id):
                     request, _("The page could not be saved due to validation errors"), form
                     request, _("The page could not be saved due to validation errors"), form
                 )
                 )
 
 
-            edit_handler = edit_handler_class(instance=page, form=form)
+            edit_handler = edit_handler.bind_to_instance(instance=page,
+                                                         form=form)
             errors_debug = (
             errors_debug = (
                 repr(edit_handler.form.errors) +
                 repr(edit_handler.form.errors) +
                 repr([
                 repr([
@@ -472,7 +474,7 @@ def edit(request, page_id):
             has_unsaved_changes = True
             has_unsaved_changes = True
     else:
     else:
         form = form_class(instance=page, parent_page=parent)
         form = form_class(instance=page, parent_page=parent)
-        edit_handler = edit_handler_class(instance=page, form=form)
+        edit_handler = edit_handler.bind_to_instance(instance=page, form=form)
         has_unsaved_changes = False
         has_unsaved_changes = False
 
 
     # Check for revisions still undergoing moderation and warn
     # Check for revisions still undergoing moderation and warn
@@ -564,7 +566,7 @@ class PreviewOnEdit(View):
                                  id=self.args[0]).get_latest_revision_as_page()
                                  id=self.args[0]).get_latest_revision_as_page()
 
 
     def get_form(self, page, query_dict):
     def get_form(self, page, query_dict):
-        form_class = page.get_edit_handler().get_form_class(page._meta.model)
+        form_class = page.get_edit_handler().get_form_class()
         parent_page = page.get_parent().specific
         parent_page = page.get_parent().specific
 
 
         if self.session_key not in self.request.session:
         if self.session_key not in self.request.session:
@@ -1022,11 +1024,12 @@ def revisions_revert(request, page_id, revision_id):
     content_type = ContentType.objects.get_for_model(page)
     content_type = ContentType.objects.get_for_model(page)
     page_class = content_type.model_class()
     page_class = content_type.model_class()
 
 
-    edit_handler_class = page_class.get_edit_handler()
-    form_class = edit_handler_class.get_form_class(page_class)
+    edit_handler = page_class.get_edit_handler()
+    form_class = edit_handler.get_form_class()
 
 
     form = form_class(instance=revision_page)
     form = form_class(instance=revision_page)
-    edit_handler = edit_handler_class(instance=revision_page, form=form)
+    edit_handler = edit_handler.bind_to_instance(instance=revision_page,
+                                                 form=form)
 
 
     user_avatar = render_to_string('wagtailadmin/shared/user_avatar.html', {'user': revision.user})
     user_avatar = render_to_string('wagtailadmin/shared/user_avatar.html', {'user': revision.user})
 
 

+ 4 - 12
wagtail/contrib/forms/edit_handlers.py

@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
 from wagtail.admin.edit_handlers import EditHandler
 from wagtail.admin.edit_handlers import EditHandler
 
 
 
 
-class BaseFormSubmissionsPanel(EditHandler):
+class FormSubmissionsPanel(EditHandler):
     template = "wagtailforms/edit_handlers/form_responses_panel.html"
     template = "wagtailforms/edit_handlers/form_responses_panel.html"
 
 
     def render(self):
     def render(self):
@@ -23,14 +23,6 @@ class BaseFormSubmissionsPanel(EditHandler):
             'last_submit_time': submissions.order_by('submit_time').last().submit_time,
             'last_submit_time': submissions.order_by('submit_time').last().submit_time,
         }))
         }))
 
 
-
-class FormSubmissionsPanel:
-    def __init__(self, heading=None):
-        self.heading = heading
-
-    def bind_to_model(self, model):
-        heading = _('{} submissions').format(model.get_verbose_name())
-        return type(str('_FormResponsesPanel'), (BaseFormSubmissionsPanel,), {
-            'model': model,
-            'heading': self.heading or heading,
-        })
+    def on_model_bound(self):
+        if not self.heading:
+            self.heading = _('%s submissions') % self.model.get_verbose_name()

+ 4 - 2
wagtail/contrib/forms/tests/test_views.py

@@ -29,7 +29,8 @@ class TestFormResponsesPanel(TestCase):
 
 
         submissions_panel = FormSubmissionsPanel().bind_to_model(FormPage)
         submissions_panel = FormSubmissionsPanel().bind_to_model(FormPage)
 
 
-        self.panel = submissions_panel(self.form_page, self.FormPageForm())
+        self.panel = submissions_panel.bind_to_instance(
+            instance=self.form_page, form=self.FormPageForm())
 
 
     def test_render_with_submissions(self):
     def test_render_with_submissions(self):
         """Show the panel with the count of submission and a link to the list_submissions view."""
         """Show the panel with the count of submission and a link to the list_submissions view."""
@@ -67,7 +68,8 @@ class TestFormResponsesPanelWithCustomSubmissionClass(TestCase):
 
 
         submissions_panel = FormSubmissionsPanel().bind_to_model(FormPageWithCustomSubmission)
         submissions_panel = FormSubmissionsPanel().bind_to_model(FormPageWithCustomSubmission)
 
 
-        self.panel = submissions_panel(self.form_page, self.FormPageForm())
+        self.panel = submissions_panel.bind_to_instance(self.form_page,
+                                                        self.FormPageForm())
 
 
     def test_render_with_submissions(self):
     def test_render_with_submissions(self):
         """Show the panel with the count of submission and a link to the list_submissions view."""
         """Show the panel with the count of submission and a link to the list_submissions view."""

+ 6 - 4
wagtail/contrib/modeladmin/views.py

@@ -104,7 +104,7 @@ class WMABaseView(TemplateView):
 
 
 class ModelFormView(WMABaseView, FormView):
 class ModelFormView(WMABaseView, FormView):
 
 
-    def get_edit_handler_class(self):
+    def get_edit_handler(self):
         if hasattr(self.model, 'edit_handler'):
         if hasattr(self.model, 'edit_handler'):
             edit_handler = self.model.edit_handler
             edit_handler = self.model.edit_handler
         else:
         else:
@@ -114,7 +114,7 @@ class ModelFormView(WMABaseView, FormView):
         return edit_handler.bind_to_model(self.model)
         return edit_handler.bind_to_model(self.model)
 
 
     def get_form_class(self):
     def get_form_class(self):
-        return self.get_edit_handler_class().get_form_class(self.model)
+        return self.get_edit_handler().get_form_class()
 
 
     def get_success_url(self):
     def get_success_url(self):
         return self.index_url
         return self.index_url
@@ -136,11 +136,13 @@ class ModelFormView(WMABaseView, FormView):
 
 
     def get_context_data(self, **kwargs):
     def get_context_data(self, **kwargs):
         instance = self.get_instance()
         instance = self.get_instance()
-        edit_handler_class = self.get_edit_handler_class()
+        edit_handler = self.get_edit_handler()
         form = self.get_form()
         form = self.get_form()
+        edit_handler = edit_handler.bind_to_instance(
+            instance=instance, form=form)
         context = {
         context = {
             'is_multipart': form.is_multipart(),
             'is_multipart': form.is_multipart(),
-            'edit_handler': edit_handler_class(instance=instance, form=form),
+            'edit_handler': edit_handler,
             'form': form,
             'form': form,
         }
         }
         context.update(kwargs)
         context.update(kwargs)

+ 7 - 6
wagtail/contrib/settings/tests/test_admin.py

@@ -9,6 +9,7 @@ from wagtail.contrib.settings.views import get_setting_edit_handler
 from wagtail.tests.testapp.models import (
 from wagtail.tests.testapp.models import (
     FileUploadSetting, IconSetting, PanelSettings, TabbedSettings, TestSetting)
     FileUploadSetting, IconSetting, PanelSettings, TabbedSettings, TestSetting)
 from wagtail.tests.utils import WagtailTestUtils
 from wagtail.tests.utils import WagtailTestUtils
+from wagtail.admin.edit_handlers import FieldPanel, ObjectList, TabbedInterface
 from wagtail.core import hooks
 from wagtail.core import hooks
 from wagtail.core.models import Page, Site
 from wagtail.core.models import Page, Site
 
 
@@ -225,24 +226,24 @@ class TestEditHandlers(TestCase):
 
 
     def test_default_model_introspection(self):
     def test_default_model_introspection(self):
         handler = get_setting_edit_handler(TestSetting)
         handler = get_setting_edit_handler(TestSetting)
-        self.assertEqual(handler.__name__, '_ObjectList')
+        self.assertIsInstance(handler, ObjectList)
         self.assertEqual(len(handler.children), 2)
         self.assertEqual(len(handler.children), 2)
         first = handler.children[0]
         first = handler.children[0]
-        self.assertEqual(first.__name__, '_FieldPanel')
+        self.assertIsInstance(first, FieldPanel)
         self.assertEqual(first.field_name, 'title')
         self.assertEqual(first.field_name, 'title')
         second = handler.children[1]
         second = handler.children[1]
-        self.assertEqual(second.__name__, '_FieldPanel')
+        self.assertIsInstance(second, FieldPanel)
         self.assertEqual(second.field_name, 'email')
         self.assertEqual(second.field_name, 'email')
 
 
     def test_with_custom_panels(self):
     def test_with_custom_panels(self):
         handler = get_setting_edit_handler(PanelSettings)
         handler = get_setting_edit_handler(PanelSettings)
-        self.assertEqual(handler.__name__, '_ObjectList')
+        self.assertIsInstance(handler, ObjectList)
         self.assertEqual(len(handler.children), 1)
         self.assertEqual(len(handler.children), 1)
         first = handler.children[0]
         first = handler.children[0]
-        self.assertEqual(first.__name__, '_FieldPanel')
+        self.assertIsInstance(first, FieldPanel)
         self.assertEqual(first.field_name, 'title')
         self.assertEqual(first.field_name, 'title')
 
 
     def test_with_custom_edit_handler(self):
     def test_with_custom_edit_handler(self):
         handler = get_setting_edit_handler(TabbedSettings)
         handler = get_setting_edit_handler(TabbedSettings)
-        self.assertEqual(handler.__name__, '_TabbedInterface')
+        self.assertIsInstance(handler, TabbedInterface)
         self.assertEqual(len(handler.children), 2)
         self.assertEqual(len(handler.children), 2)

+ 8 - 6
wagtail/contrib/settings/views.py

@@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
 
 
 from wagtail.admin import messages
 from wagtail.admin import messages
 from wagtail.admin.edit_handlers import (
 from wagtail.admin.edit_handlers import (
-    ObjectList, extract_panel_definitions_from_model_class)
+    ObjectList, TabbedInterface, extract_panel_definitions_from_model_class)
 from wagtail.core.models import Site
 from wagtail.core.models import Site
 
 
 from .forms import SiteSwitchForm
 from .forms import SiteSwitchForm
@@ -51,8 +51,8 @@ def edit(request, app_name, model_name, site_pk):
     setting_type_name = model._meta.verbose_name
     setting_type_name = model._meta.verbose_name
 
 
     instance = model.for_site(site)
     instance = model.for_site(site)
-    edit_handler_class = get_setting_edit_handler(model)
-    form_class = edit_handler_class.get_form_class(model)
+    edit_handler = get_setting_edit_handler(model)
+    form_class = edit_handler.get_form_class()
 
 
     if request.method == 'POST':
     if request.method == 'POST':
         form = form_class(request.POST, request.FILES, instance=instance)
         form = form_class(request.POST, request.FILES, instance=instance)
@@ -70,10 +70,12 @@ def edit(request, app_name, model_name, site_pk):
             return redirect('wagtailsettings:edit', app_name, model_name, site.pk)
             return redirect('wagtailsettings:edit', app_name, model_name, site.pk)
         else:
         else:
             messages.error(request, _("The setting could not be saved due to errors."))
             messages.error(request, _("The setting could not be saved due to errors."))
-            edit_handler = edit_handler_class(instance=instance, form=form)
+            edit_handler = edit_handler.bind_to_instance(
+                instance=instance, form=form)
     else:
     else:
         form = form_class(instance=instance)
         form = form_class(instance=instance)
-        edit_handler = edit_handler_class(instance=instance, form=form)
+        edit_handler = edit_handler.bind_to_instance(
+            instance=instance, form=form)
 
 
     # Show a site switcher form if there are multiple sites
     # Show a site switcher form if there are multiple sites
     site_switcher = None
     site_switcher = None
@@ -88,5 +90,5 @@ def edit(request, app_name, model_name, site_pk):
         'form': form,
         'form': form,
         'site': site,
         'site': site,
         'site_switcher': site_switcher,
         'site_switcher': site_switcher,
-        'tabbed': edit_handler_class.__name__ == '_TabbedInterface',
+        'tabbed': isinstance(edit_handler, TabbedInterface),
     })
     })

+ 3 - 15
wagtail/documents/edit_handlers.py

@@ -3,20 +3,8 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
 from .widgets import AdminDocumentChooser
 from .widgets import AdminDocumentChooser
 
 
 
 
-class BaseDocumentChooserPanel(BaseChooserPanel):
+class DocumentChooserPanel(BaseChooserPanel):
     object_type_name = "document"
     object_type_name = "document"
 
 
-    @classmethod
-    def widget_overrides(cls):
-        return {cls.field_name: AdminDocumentChooser}
-
-
-class DocumentChooserPanel:
-    def __init__(self, field_name):
-        self.field_name = field_name
-
-    def bind_to_model(self, model):
-        return type(str('_DocumentChooserPanel'), (BaseDocumentChooserPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-        })
+    def widget_overrides(self):
+        return {self.field_name: AdminDocumentChooser}

+ 4 - 17
wagtail/images/edit_handlers.py

@@ -6,29 +6,16 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
 from .widgets import AdminImageChooser
 from .widgets import AdminImageChooser
 
 
 
 
-class BaseImageChooserPanel(BaseChooserPanel):
+class ImageChooserPanel(BaseChooserPanel):
     object_type_name = "image"
     object_type_name = "image"
 
 
-    @classmethod
-    def widget_overrides(cls):
-        return {cls.field_name: AdminImageChooser}
+    def widget_overrides(self):
+        return {self.field_name: AdminImageChooser}
 
 
-    @classmethod
-    def get_comparison_class(cls):
+    def get_comparison_class(self):
         return ImageFieldComparison
         return ImageFieldComparison
 
 
 
 
-class ImageChooserPanel:
-    def __init__(self, field_name):
-        self.field_name = field_name
-
-    def bind_to_model(self, model):
-        return type(str('_ImageChooserPanel'), (BaseImageChooserPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-        })
-
-
 class ImageFieldComparison(ForeignObjectComparison):
 class ImageFieldComparison(ForeignObjectComparison):
     def htmldiff(self):
     def htmldiff(self):
         image_a, image_b = self.get_objects()
         image_a, image_b = self.get_objects()

+ 6 - 23
wagtail/snippets/edit_handlers.py

@@ -6,21 +6,11 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
 from .widgets import AdminSnippetChooser
 from .widgets import AdminSnippetChooser
 
 
 
 
-class BaseSnippetChooserPanel(BaseChooserPanel):
+class SnippetChooserPanel(BaseChooserPanel):
     object_type_name = 'item'
     object_type_name = 'item'
 
 
-    _target_model = None
-
-    @classmethod
-    def widget_overrides(cls):
-        return {cls.field_name: AdminSnippetChooser(model=cls.target_model())}
-
-    @classmethod
-    def target_model(cls):
-        if cls._target_model is None:
-            cls._target_model = cls.model._meta.get_field(cls.field_name).remote_field.model
-
-        return cls._target_model
+    def widget_overrides(self):
+        return {self.field_name: AdminSnippetChooser(model=self.target_model)}
 
 
     def render_as_field(self):
     def render_as_field(self):
         instance_obj = self.get_chosen_item()
         instance_obj = self.get_chosen_item()
@@ -29,13 +19,6 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
             self.object_type_name: instance_obj,
             self.object_type_name: instance_obj,
         }))
         }))
 
 
-
-class SnippetChooserPanel:
-    def __init__(self, field_name):
-        self.field_name = field_name
-
-    def bind_to_model(self, model):
-        return type(str('_SnippetChooserPanel'), (BaseSnippetChooserPanel,), {
-            'model': model,
-            'field_name': self.field_name,
-        })
+    def on_model_bound(self):
+        super().on_model_bound()
+        self.target_model = self.db_field.remote_field.model

+ 18 - 21
wagtail/snippets/tests.py

@@ -370,18 +370,18 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
         test_snippet = model.objects.create(
         test_snippet = model.objects.create(
             advert=Advert.objects.create(text=self.advert_text))
             advert=Advert.objects.create(text=self.advert_text))
 
 
-        self.edit_handler_class = get_snippet_edit_handler(model)
-        self.form_class = self.edit_handler_class.get_form_class(model)
+        self.edit_handler = get_snippet_edit_handler(model)
+        self.form_class = self.edit_handler.get_form_class()
         form = self.form_class(instance=test_snippet)
         form = self.form_class(instance=test_snippet)
-        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
+        edit_handler = self.edit_handler.bind_to_instance(instance=test_snippet,
+                                                          form=form)
 
 
         self.snippet_chooser_panel = [
         self.snippet_chooser_panel = [
             panel for panel in edit_handler.children
             panel for panel in edit_handler.children
             if getattr(panel, 'field_name', None) == 'advert'][0]
             if getattr(panel, 'field_name', None) == 'advert'][0]
 
 
     def test_create_snippet_chooser_panel_class(self):
     def test_create_snippet_chooser_panel_class(self):
-        self.assertEqual(type(self.snippet_chooser_panel).__name__,
-                         '_SnippetChooserPanel')
+        self.assertIsInstance(self.snippet_chooser_panel, SnippetChooserPanel)
 
 
     def test_render_as_field(self):
     def test_render_as_field(self):
         field_html = self.snippet_chooser_panel.render_as_field()
         field_html = self.snippet_chooser_panel.render_as_field()
@@ -392,7 +392,8 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
     def test_render_as_empty_field(self):
     def test_render_as_empty_field(self):
         test_snippet = SnippetChooserModel()
         test_snippet = SnippetChooserModel()
         form = self.form_class(instance=test_snippet)
         form = self.form_class(instance=test_snippet)
-        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
+        edit_handler = self.edit_handler.bind_to_instance(instance=test_snippet,
+                                                          form=form)
 
 
         snippet_chooser_panel = [
         snippet_chooser_panel = [
             panel for panel in edit_handler.children
             panel for panel in edit_handler.children
@@ -410,7 +411,7 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
     def test_target_model_autodetected(self):
     def test_target_model_autodetected(self):
         result = SnippetChooserPanel(
         result = SnippetChooserPanel(
             'advert'
             'advert'
-        ).bind_to_model(SnippetChooserModel).target_model()
+        ).bind_to_model(SnippetChooserModel).target_model
         self.assertEqual(result, Advert)
         self.assertEqual(result, Advert)
 
 
 
 
@@ -694,14 +695,14 @@ class TestDeleteOnlyPermissions(TestCase, WagtailTestUtils):
 
 
 class TestSnippetEditHandlers(TestCase, WagtailTestUtils):
 class TestSnippetEditHandlers(TestCase, WagtailTestUtils):
     def test_standard_edit_handler(self):
     def test_standard_edit_handler(self):
-        edit_handler_class = get_snippet_edit_handler(StandardSnippet)
-        form_class = edit_handler_class.get_form_class(StandardSnippet)
+        edit_handler = get_snippet_edit_handler(StandardSnippet)
+        form_class = edit_handler.get_form_class()
         self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
         self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
         self.assertFalse(issubclass(form_class, FancySnippetForm))
         self.assertFalse(issubclass(form_class, FancySnippetForm))
 
 
     def test_fancy_edit_handler(self):
     def test_fancy_edit_handler(self):
-        edit_handler_class = get_snippet_edit_handler(FancySnippet)
-        form_class = edit_handler_class.get_form_class(FancySnippet)
+        edit_handler = get_snippet_edit_handler(FancySnippet)
+        form_class = edit_handler.get_form_class()
         self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
         self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
         self.assertTrue(issubclass(form_class, FancySnippetForm))
         self.assertTrue(issubclass(form_class, FancySnippetForm))
 
 
@@ -941,18 +942,17 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
             )
             )
         )
         )
 
 
-        self.edit_handler_class = get_snippet_edit_handler(model)
-        self.form_class = self.edit_handler_class.get_form_class(model)
+        self.edit_handler = get_snippet_edit_handler(model)
+        self.form_class = self.edit_handler.get_form_class()
         form = self.form_class(instance=test_snippet)
         form = self.form_class(instance=test_snippet)
-        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
+        edit_handler = self.edit_handler.bind_to_instance(instance=test_snippet, form=form)
 
 
         self.snippet_chooser_panel = [
         self.snippet_chooser_panel = [
             panel for panel in edit_handler.children
             panel for panel in edit_handler.children
             if getattr(panel, 'field_name', None) == 'advertwithcustomprimarykey'][0]
             if getattr(panel, 'field_name', None) == 'advertwithcustomprimarykey'][0]
 
 
     def test_create_snippet_chooser_panel_class(self):
     def test_create_snippet_chooser_panel_class(self):
-        self.assertEqual(type(self.snippet_chooser_panel).__name__,
-                         '_SnippetChooserPanel')
+        self.assertIsInstance(self.snippet_chooser_panel, SnippetChooserPanel)
 
 
     def test_render_as_field(self):
     def test_render_as_field(self):
         field_html = self.snippet_chooser_panel.render_as_field()
         field_html = self.snippet_chooser_panel.render_as_field()
@@ -963,7 +963,7 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
     def test_render_as_empty_field(self):
     def test_render_as_empty_field(self):
         test_snippet = SnippetChooserModelWithCustomPrimaryKey()
         test_snippet = SnippetChooserModelWithCustomPrimaryKey()
         form = self.form_class(instance=test_snippet)
         form = self.form_class(instance=test_snippet)
-        edit_handler = self.edit_handler_class(instance=test_snippet, form=form)
+        edit_handler = self.edit_handler.bind_to_instance(instance=test_snippet, form=form)
 
 
         snippet_chooser_panel = [
         snippet_chooser_panel = [
             panel for panel in edit_handler.children
             panel for panel in edit_handler.children
@@ -981,12 +981,10 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
     def test_target_model_autodetected(self):
     def test_target_model_autodetected(self):
         result = SnippetChooserPanel(
         result = SnippetChooserPanel(
             'advertwithcustomprimarykey'
             'advertwithcustomprimarykey'
-        ).bind_to_model(SnippetChooserModelWithCustomPrimaryKey).target_model()
+        ).bind_to_model(SnippetChooserModelWithCustomPrimaryKey).target_model
         self.assertEqual(result, AdvertWithCustomPrimaryKey)
         self.assertEqual(result, AdvertWithCustomPrimaryKey)
 
 
 
 
-
-
 class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
 class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
     fixtures = ['test.json']
     fixtures = ['test.json']
 
 
@@ -1014,7 +1012,6 @@ class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
         self.assertEqual(response.context['items'][0].text, "advert 1")
         self.assertEqual(response.context['items'][0].text, "advert 1")
 
 
 
 
-
 class TestSnippetChosenWithCustomPrimaryKey(TestCase, WagtailTestUtils):
 class TestSnippetChosenWithCustomPrimaryKey(TestCase, WagtailTestUtils):
     fixtures = ['test.json']
     fixtures = ['test.json']
 
 

+ 12 - 8
wagtail/snippets/views/snippets.py

@@ -129,8 +129,8 @@ def create(request, app_label, model_name):
         return permission_denied(request)
         return permission_denied(request)
 
 
     instance = model()
     instance = model()
-    edit_handler_class = get_snippet_edit_handler(model)
-    form_class = edit_handler_class.get_form_class(model)
+    edit_handler = get_snippet_edit_handler(model)
+    form_class = edit_handler.get_form_class()
 
 
     if request.method == 'POST':
     if request.method == 'POST':
         form = form_class(request.POST, request.FILES, instance=instance)
         form = form_class(request.POST, request.FILES, instance=instance)
@@ -153,10 +153,12 @@ def create(request, app_label, model_name):
             return redirect('wagtailsnippets:list', app_label, model_name)
             return redirect('wagtailsnippets:list', app_label, model_name)
         else:
         else:
             messages.error(request, _("The snippet could not be created due to errors."))
             messages.error(request, _("The snippet could not be created due to errors."))
-            edit_handler = edit_handler_class(instance=instance, form=form)
+            edit_handler = edit_handler.bind_to_instance(instance=instance,
+                                                         form=form)
     else:
     else:
         form = form_class(instance=instance)
         form = form_class(instance=instance)
-        edit_handler = edit_handler_class(instance=instance, form=form)
+        edit_handler = edit_handler.bind_to_instance(instance=instance,
+                                                     form=form)
 
 
     return render(request, 'wagtailsnippets/snippets/create.html', {
     return render(request, 'wagtailsnippets/snippets/create.html', {
         'model_opts': model._meta,
         'model_opts': model._meta,
@@ -173,8 +175,8 @@ def edit(request, app_label, model_name, pk):
         return permission_denied(request)
         return permission_denied(request)
 
 
     instance = get_object_or_404(model, pk=unquote(pk))
     instance = get_object_or_404(model, pk=unquote(pk))
-    edit_handler_class = get_snippet_edit_handler(model)
-    form_class = edit_handler_class.get_form_class(model)
+    edit_handler = get_snippet_edit_handler(model)
+    form_class = edit_handler.get_form_class()
 
 
     if request.method == 'POST':
     if request.method == 'POST':
         form = form_class(request.POST, request.FILES, instance=instance)
         form = form_class(request.POST, request.FILES, instance=instance)
@@ -197,10 +199,12 @@ def edit(request, app_label, model_name, pk):
             return redirect('wagtailsnippets:list', app_label, model_name)
             return redirect('wagtailsnippets:list', app_label, model_name)
         else:
         else:
             messages.error(request, _("The snippet could not be saved due to errors."))
             messages.error(request, _("The snippet could not be saved due to errors."))
-            edit_handler = edit_handler_class(instance=instance, form=form)
+            edit_handler = edit_handler.bind_to_instance(instance=instance,
+                                                         form=form)
     else:
     else:
         form = form_class(instance=instance)
         form = form_class(instance=instance)
-        edit_handler = edit_handler_class(instance=instance, form=form)
+        edit_handler = edit_handler.bind_to_instance(instance=instance,
+                                                     form=form)
 
 
     return render(request, 'wagtailsnippets/snippets/edit.html', {
     return render(request, 'wagtailsnippets/snippets/edit.html', {
         'model_opts': model._meta,
         'model_opts': model._meta,