Allows binding instance and request to EditHandler before form.

pull/4987/head
Bertrand Bordage 2018-08-25 00:18:42 +02:00 zatwierdzone przez Matt Westcott
rodzic f271c40cea
commit b599fce714
2 zmienionych plików z 124 dodań i 102 usunięć

Wyświetl plik

@ -1,5 +1,6 @@
import functools import functools
import re import re
from warnings import warn
from django import forms from django import forms
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -7,7 +8,6 @@ from django.db.models.fields import FieldDoesNotExist
from django.forms.formsets import DELETION_FIELD_NAME, ORDERING_FIELD_NAME from django.forms.formsets import DELETION_FIELD_NAME, ORDERING_FIELD_NAME
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 cached_property from django.utils.functional import cached_property
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
@ -18,6 +18,7 @@ from wagtail.core.fields import RichTextField
from wagtail.core.models import Page from wagtail.core.models import Page
from wagtail.core.utils import camelcase_to_underscore, resolve_model_string from wagtail.core.utils import camelcase_to_underscore, resolve_model_string
from wagtail.utils.decorators import cached_classmethod from wagtail.utils.decorators import cached_classmethod
from wagtail.utils.deprecation import RemovedInWagtail27Warning
# DIRECT_FORM_FIELD_OVERRIDES, FORM_FIELD_OVERRIDES are imported for backwards # DIRECT_FORM_FIELD_OVERRIDES, FORM_FIELD_OVERRIDES are imported for backwards
# compatibility, as people are likely importing them from here and then # compatibility, as people are likely importing them from here and then
@ -97,13 +98,20 @@ class EditHandler:
self.heading = heading self.heading = heading
self.classname = classname self.classname = classname
self.help_text = help_text self.help_text = help_text
self.model = None
self.instance = None
self.request = None
self.form = None
def clone(self): def clone(self):
return self.__class__( return self.__class__(**self.clone_kwargs())
heading=self.heading,
classname=self.classname, def clone_kwargs(self):
help_text=self.help_text, return {
) '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
@ -126,45 +134,58 @@ class EditHandler:
def html_declarations(self): def html_declarations(self):
return '' return ''
def bind_to_model(self, model): def bind_to(self, model=None, instance=None, request=None, form=None):
if model is None and instance is not None and self.model is None:
model = instance._meta.model
new = self.clone() new = self.clone()
new.model = model new.model = self.model if model is None else model
new.on_model_bound() new.instance = self.instance if instance is None else instance
new.request = self.request if request is None else request
new.form = self.form if form is None else form
if new.model is not None:
new.on_model_bound()
if new.instance is not None:
new.on_instance_bound()
if new.request is not None:
new.on_request_bound()
if new.form is not None:
new.on_form_bound()
return new return new
def bind_to_model(self, model):
warn('EditHandler.bind_to_model(model) is deprecated. '
'Use EditHandler.bind_to(model=model) instead',
category=RemovedInWagtail27Warning)
return self.bind_to(model=model)
def bind_to_instance(self, instance, form, request):
warn('EditHandler.bind_to_instance(instance, request, form) is deprecated. '
'Use EditHandler.bind_to(instance=instance, request=request, form=form) instead',
category=RemovedInWagtail27Warning)
return self.bind_to(instance=instance, request=request, form=form)
def on_model_bound(self): def on_model_bound(self):
pass pass
def bind_to_instance(self, instance=None, form=None, request=None):
new = self.bind_to_model(self.model)
if not instance:
raise ValueError("EditHandler did not receive an instance object")
new.instance = instance
if not form:
raise ValueError("EditHandler did not receive a form object")
new.form = form
if request is None:
raise ValueError("EditHandler did not receive a request object")
new.request = request
new.on_instance_bound()
return new
def on_instance_bound(self): def on_instance_bound(self):
pass pass
def on_request_bound(self):
pass
def on_form_bound(self):
pass
def __repr__(self): def __repr__(self):
class_name = self.__class__.__name__ return '<%s with model=%s instance=%s request=%s form=%s>' % (
try: self.__class__.__name__,
bound_to = force_text(getattr(self, 'instance', self.model, self.instance, self.request, self.form)
getattr(self, 'model')))
except AttributeError:
return '<%s>' % class_name
return '<%s bound to %s>' % (class_name, bound_to)
def classes(self): def classes(self):
""" """
@ -244,13 +265,10 @@ class BaseCompositeEditHandler(EditHandler):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.children = children self.children = children
def clone(self): def clone_kwargs(self):
return self.__class__( kwargs = super().clone_kwargs()
children=self.children, kwargs['children'] = self.children
heading=self.heading, return kwargs
classname=self.classname,
help_text=self.help_text,
)
def widget_overrides(self): def widget_overrides(self):
# build a collated version of all its children's widget lists # build a collated version of all its children's widget lists
@ -277,10 +295,18 @@ class BaseCompositeEditHandler(EditHandler):
return mark_safe(''.join([c.html_declarations() for c in self.children])) return mark_safe(''.join([c.html_declarations() for c in self.children]))
def on_model_bound(self): def on_model_bound(self):
self.children = [child.bind_to_model(self.model) self.children = [child.bind_to(model=self.model)
for child in self.children] for child in self.children]
def on_instance_bound(self): def on_instance_bound(self):
self.children = [child.bind_to(instance=self.instance)
for child in self.children]
def on_request_bound(self):
self.children = [child.bind_to(request=self.request)
for child in self.children]
def on_form_bound(self):
children = [] children = []
for child in self.children: for child in self.children:
if isinstance(child, FieldPanel): if isinstance(child, FieldPanel):
@ -290,9 +316,7 @@ class BaseCompositeEditHandler(EditHandler):
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
children.append(child.bind_to_instance(instance=self.instance, children.append(child.bind_to(form=self.form))
form=self.form,
request=self.request))
self.children = children self.children = children
def render(self): def render(self):
@ -326,9 +350,9 @@ class BaseFormEditHandler(BaseCompositeEditHandler):
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 not hasattr(self, 'model'): if self.model is None:
raise AttributeError( raise AttributeError(
'%s is not bound to a model yet. Use `.bind_to_model(model)` ' '%s is not bound to a model yet. Use `.bind_to(model=model)` '
'before using this method.' % self.__class__.__name__) 'before using this method.' % self.__class__.__name__)
# If a custom form class was passed to the EditHandler, use it. # If a custom form class was passed to the EditHandler, use it.
# Otherwise, use the base_form_class from the model. # Otherwise, use the base_form_class from the model.
@ -352,10 +376,10 @@ class TabbedInterface(BaseFormEditHandler):
self.base_form_class = kwargs.pop('base_form_class', None) self.base_form_class = kwargs.pop('base_form_class', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def clone(self): def clone_kwargs(self):
new = super().clone() kwargs = super().clone_kwargs()
new.base_form_class = self.base_form_class kwargs['base_form_class'] = self.base_form_class
return new return kwargs
class ObjectList(TabbedInterface): class ObjectList(TabbedInterface):
@ -392,13 +416,14 @@ class HelpPanel(EditHandler):
self.content = content self.content = content
self.template = template self.template = template
def clone(self): def clone_kwargs(self):
return self.__class__( kwargs = super().clone_kwargs()
del kwargs['help_text']
kwargs.update(
content=self.content, content=self.content,
template=self.template, template=self.template,
heading=self.heading,
classname=self.classname,
) )
return kwargs
def render(self): def render(self):
return mark_safe(render_to_string(self.template, { return mark_safe(render_to_string(self.template, {
@ -416,14 +441,13 @@ class FieldPanel(EditHandler):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.field_name = field_name self.field_name = field_name
def clone(self): def clone_kwargs(self):
return self.__class__( kwargs = super().clone_kwargs()
kwargs.update(
field_name=self.field_name, field_name=self.field_name,
widget=self.widget if hasattr(self, 'widget') else None, widget=self.widget if hasattr(self, 'widget') else None,
heading=self.heading,
classname=self.classname,
help_text=self.help_text
) )
return kwargs
def widget_overrides(self): def widget_overrides(self):
"""check if a specific widget has been defined for this field""" """check if a specific widget has been defined for this field"""
@ -515,19 +539,15 @@ class FieldPanel(EditHandler):
return model._meta.get_field(self.field_name) return model._meta.get_field(self.field_name)
def on_instance_bound(self): def on_form_bound(self):
self.bound_field = self.form[self.field_name] self.bound_field = self.form[self.field_name]
self.heading = self.bound_field.label self.heading = self.bound_field.label
self.help_text = self.bound_field.help_text self.help_text = self.bound_field.help_text
def __repr__(self): def __repr__(self):
class_name = self.__class__.__name__ return "<%s '%s' with model=%s instance=%s request=%s form=%s>" % (
try: self.__class__.__name__, self.field_name,
bound_to = force_text(getattr(self, 'instance', self.model, self.instance, self.request, self.form)
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 RichTextFieldPanel(FieldPanel): class RichTextFieldPanel(FieldPanel):
@ -584,12 +604,12 @@ class PageChooserPanel(BaseChooserPanel):
self.page_type = page_type self.page_type = page_type
self.can_choose_root = can_choose_root self.can_choose_root = can_choose_root
def clone(self): def clone_kwargs(self):
return self.__class__( return {
field_name=self.field_name, 'field_name': self.field_name,
page_type=self.page_type, 'page_type': self.page_type,
can_choose_root=self.can_choose_root, 'can_choose_root': self.can_choose_root,
) }
def widget_overrides(self): def widget_overrides(self):
return {self.field_name: widgets.AdminPageChooser( return {self.field_name: widgets.AdminPageChooser(
@ -631,17 +651,16 @@ class InlinePanel(EditHandler):
self.min_num = min_num self.min_num = min_num
self.max_num = max_num self.max_num = max_num
def clone(self): def clone_kwargs(self):
return self.__class__( kwargs = super().clone_kwargs()
kwargs.update(
relation_name=self.relation_name, relation_name=self.relation_name,
panels=self.panels, panels=self.panels,
heading=self.heading,
label=self.label, label=self.label,
help_text=self.help_text,
min_num=self.min_num, min_num=self.min_num,
max_num=self.max_num, max_num=self.max_num,
classname=self.classname,
) )
return kwargs
def get_panel_definitions(self): def get_panel_definitions(self):
# Look for a panels definition in the InlinePanel declaration # Look for a panels definition in the InlinePanel declaration
@ -656,7 +675,7 @@ class InlinePanel(EditHandler):
def get_child_edit_handler(self): def get_child_edit_handler(self):
panels = self.get_panel_definitions() panels = self.get_panel_definitions()
child_edit_handler = MultiFieldPanel(panels, heading=self.heading) child_edit_handler = MultiFieldPanel(panels, heading=self.heading)
return child_edit_handler.bind_to_model(self.db_field.related_model) return child_edit_handler.bind_to(model=self.db_field.related_model)
def required_formsets(self): def required_formsets(self):
child_edit_handler = self.get_child_edit_handler() child_edit_handler = self.get_child_edit_handler()
@ -679,7 +698,7 @@ class InlinePanel(EditHandler):
for panel in self.get_panel_definitions(): for panel in self.get_panel_definitions():
field_comparisons.extend( field_comparisons.extend(
panel.bind_to_model(self.db_field.related_model) panel.bind_to(model=self.db_field.related_model)
.get_comparison()) .get_comparison())
return [functools.partial(compare.ChildRelationComparison, self.db_field, field_comparisons)] return [functools.partial(compare.ChildRelationComparison, self.db_field, field_comparisons)]
@ -688,7 +707,7 @@ class InlinePanel(EditHandler):
manager = getattr(self.model, self.relation_name) manager = getattr(self.model, self.relation_name)
self.db_field = manager.rel self.db_field = manager.rel
def on_instance_bound(self): def on_form_bound(self):
self.formset = self.form.formsets[self.relation_name] self.formset = self.form.formsets[self.relation_name]
self.children = [] self.children = []
@ -701,10 +720,8 @@ class InlinePanel(EditHandler):
subform.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput() subform.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput()
child_edit_handler = self.get_child_edit_handler() child_edit_handler = self.get_child_edit_handler()
self.children.append( self.children.append(child_edit_handler.bind_to(
child_edit_handler.bind_to_instance(instance=subform.instance, instance=subform.instance, request=self.request, form=subform))
form=subform,
request=self.request))
# 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
@ -718,8 +735,8 @@ class InlinePanel(EditHandler):
empty_form.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput() empty_form.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput()
self.empty_child = self.get_child_edit_handler() self.empty_child = self.get_child_edit_handler()
self.empty_child = self.empty_child.bind_to_instance( self.empty_child = self.empty_child.bind_to(
instance=empty_form.instance, form=empty_form, request=self.request) instance=empty_form.instance, request=self.request, form=empty_form)
template = "wagtailadmin/edit_handlers/inline_panel.html" template = "wagtailadmin/edit_handlers/inline_panel.html"
@ -786,21 +803,26 @@ def get_edit_handler(cls):
Get the EditHandler to use in the Wagtail admin when editing this page type. Get the EditHandler to use in the Wagtail admin when editing this page type.
""" """
if hasattr(cls, 'edit_handler'): if hasattr(cls, 'edit_handler'):
return cls.edit_handler.bind_to_model(cls) edit_handler = cls.edit_handler
else:
# construct a TabbedInterface made up of content_panels, promote_panels
# and settings_panels, skipping any which are empty
tabs = []
# construct a TabbedInterface made up of content_panels, promote_panels if cls.content_panels:
# and settings_panels, skipping any which are empty tabs.append(ObjectList(cls.content_panels,
tabs = [] heading=ugettext_lazy('Content')))
if cls.promote_panels:
tabs.append(ObjectList(cls.promote_panels,
heading=ugettext_lazy('Promote')))
if cls.settings_panels:
tabs.append(ObjectList(cls.settings_panels,
heading=ugettext_lazy('Settings'),
classname='settings'))
if cls.content_panels: edit_handler = TabbedInterface(tabs, base_form_class=cls.base_form_class)
tabs.append(ObjectList(cls.content_panels, heading=ugettext_lazy('Content')))
if cls.promote_panels:
tabs.append(ObjectList(cls.promote_panels, heading=ugettext_lazy('Promote')))
if cls.settings_panels:
tabs.append(ObjectList(cls.settings_panels, heading=ugettext_lazy('Settings'), classname="settings"))
edit_handler = TabbedInterface(tabs, base_form_class=cls.base_form_class) return edit_handler.bind_to(model=cls)
return edit_handler.bind_to_model(cls)
Page.get_edit_handler = get_edit_handler Page.get_edit_handler = get_edit_handler

Wyświetl plik

@ -27,7 +27,7 @@ class TestGetFormForModel(TestCase):
with self.assertRaisesMessage( with self.assertRaisesMessage(
AttributeError, AttributeError,
'ObjectList is not bound to a model yet. ' 'ObjectList is not bound to a model yet. '
'Use `.bind_to_model(model)` before using this method.'): 'Use `.bind_to(model=model)` before using this method.'):
edit_handler.get_form_class() edit_handler.get_form_class()
def test_get_form_for_model(self): def test_get_form_for_model(self):