kopia lustrzana https://github.com/wagtail/wagtail
Allows binding instance and request to EditHandler before form.
rodzic
f271c40cea
commit
b599fce714
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Ładowanie…
Reference in New Issue