Simplifies edit handlers by removing redundant classes.

This also allows to provide some missing arguments to panels like PageChooserPanel.
pull/4196/head
Matt Westcott 2018-01-12 16:54:01 +00:00
rodzic 3d945d0255
commit 5fc191b116
18 zmienionych plików z 517 dodań i 560 usunięć

Wyświetl plik

@ -37,6 +37,7 @@ Changelog
* 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)
* 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: 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)
@ -64,6 +65,7 @@ Changelog
* Fix: Style of the page unlock button was broken (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))
* Fixed error when inserting chooser panels into FieldRowPanel (Florent Osmont, Bertrand Bordage)
1.13.1 (17.11.2017)

Wyświetl plik

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

Wyświetl plik

@ -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 a basic Dockerfile to the project template (Tom Dyson)
* 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
@ -87,6 +88,7 @@ Bug fixes
* Style of the page unlock button was broken (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))
* Fixed error when inserting chooser panels into FieldRowPanel (Florent Osmont, Bertrand Bordage)
Upgrade considerations

Wyświetl plik

@ -60,9 +60,9 @@ def get_form_class_check(app_configs, **kwargs):
for cls in get_page_models():
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(
"{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__),
hint="Ensure that the EditHandler for {cls} creates a subclass of WagtailAdminPageForm".format(
cls=cls.__name__),

Wyświetl plik

@ -1,4 +1,3 @@
import math
import re
from django import forms
@ -6,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db.models.fields import FieldDoesNotExist
from django.forms.models import fields_for_model
from django.template.loader import render_to_string
from django.utils.encoding import force_text
from django.utils.functional import curry
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy
@ -91,43 +91,74 @@ class EditHandler:
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
# on the form it receives
@classmethod
def widget_overrides(cls):
def widget_overrides(self):
return {}
# return list of fields that this EditHandler expects to find on the form
@classmethod
def required_fields(cls):
def required_fields(self):
return []
# 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
# 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 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
# for Javascript code to work with.
@classmethod
def html_declarations(cls):
def html_declarations(self):
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:
raise ValueError("EditHandler did not receive an instance object")
self.instance = instance
new.instance = instance
if not form:
raise ValueError("EditHandler did not receive a form object")
self.form = form
new.form = form
# Heading / help text to display to the user
heading = ""
help_text = ""
new.on_instance_bound()
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):
"""
@ -135,15 +166,9 @@ class EditHandler:
Subclasses of EditHandler should override this, invoking super().classes() to
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):
"""
@ -199,8 +224,7 @@ class EditHandler:
"""
return mark_safe(self.render_as_object() + self.render_missing_fields())
@classmethod
def get_comparison(cls):
def get_comparison(self):
return []
@ -209,71 +233,70 @@ class BaseCompositeEditHandler(EditHandler):
Abstract class for EditHandlers that manage a set of sub-EditHandlers.
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
def __init__(self, children=(), *args, **kwargs):
super().__init__(*args, **kwargs)
self.children = children
return cls._widget_overrides
def clone(self):
return self.__class__(
children=self.children,
heading=self.heading,
classname=self.classname,
help_text=self.help_text,
)
_required_fields = None
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
@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 widget_overrides
return cls._required_fields
def required_fields(self):
fields = []
for handler in self.children:
fields.extend(handler.required_fields())
return fields
_required_formsets = None
def required_formsets(self):
formsets = {}
for handler_class in self.children:
formsets.update(handler_class.required_formsets())
return formsets
@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
def html_declarations(self):
return mark_safe(''.join([c.html_declarations() for c in self.children]))
return cls._required_formsets
def on_model_bound(self):
self.children = [child.bind_to_model(self.model)
for child in self.children]
@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)
self.children = []
for child in self.__class__.children:
if not getattr(child, "children", None) and getattr(child, "field_name", None):
def on_instance_bound(self):
children = []
for child in self.children:
if isinstance(child, FieldPanel):
if self.form._meta.exclude:
if child.field_name in self.form._meta.exclude:
continue
if self.form._meta.fields:
if child.field_name not in self.form._meta.fields:
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):
return mark_safe(render_to_string(self.template, {
'self': self
}))
@classmethod
def get_comparison(cls):
def get_comparison(self):
comparators = []
for child in cls.children:
for child in self.children:
comparators.extend(child.get_comparison())
return comparators
@ -291,137 +314,94 @@ class BaseFormEditHandler(BaseCompositeEditHandler):
# WagtailAdminModelForm
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
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
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
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
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 BaseTabbedInterface(BaseFormEditHandler):
class TabbedInterface(BaseFormEditHandler):
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"
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"
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
# number of columns
for child in self.children:
if not re.search(r'\bcol\d+\b', child.classname):
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"
def classes(self):
classes = super().classes()
classes.append("multi-field")
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'
@classmethod
def widget_overrides(cls):
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 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
)
def widget_overrides(self):
"""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, instance=None, form=None):
super().__init__(instance=instance, form=form)
self.bound_field = self.form[self.field_name]
self.heading = self.bound_field.label
self.help_text = self.bound_field.help_text
if hasattr(self, 'widget'):
return {self.field_name: self.widget}
return {}
def classes(self):
classes = super().classes()
@ -453,25 +433,22 @@ class BaseFieldPanel(EditHandler):
field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
def render_as_field(self):
context = {
return mark_safe(render_to_string(self.field_template, {
'field': self.bound_field,
'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
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:
return
try:
field = cls.model._meta.get_field(cls.field_name)
field = self.db_field
if field.choices:
return compare.ChoiceFieldComparison
@ -491,54 +468,37 @@ class BaseFieldPanel(EditHandler):
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:
field = cls.model._meta.get_field(cls.field_name)
return [curry(comparator_class, field)]
else:
return []
return [curry(comparator_class, self.db_field)]
return []
def on_model_bound(self):
self.db_field = self.model._meta.get_field(self.field_name)
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
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 FieldPanel:
def __init__(self, field_name, classname="", widget=None):
self.field_name = field_name
self.classname = classname
self.widget = widget
def bind_to_model(self, model):
base = {
'model': model,
'field_name': self.field_name,
'classname': self.classname,
}
if self.widget:
base['widget'] = self.widget
return type(str('_FieldPanel'), (BaseFieldPanel,), base)
class BaseRichTextFieldPanel(BaseFieldPanel):
@classmethod
def get_comparison_class(cls):
class RichTextFieldPanel(FieldPanel):
def get_comparison_class(self):
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)
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
# a DoesNotExist exception here, rather than returning None
# like every other unpopulated field type. Yay consistency!
return None
return
def render_as_field(self):
instance_obj = self.get_chosen_item()
@ -571,44 +531,11 @@ class BaseChooserPanel(BaseFieldPanel):
return mark_safe(render_to_string(self.field_template, context))
class BasePageChooserPanel(BaseChooserPanel):
class PageChooserPanel(BaseChooserPanel):
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)}
@cached_classmethod
def target_models(cls):
if cls.page_type:
target_models = []
for page_type in cls.page_type:
try:
target_models.append(resolve_model_string(page_type))
except LookupError:
raise ImproperlyConfigured(
"{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
cls.__name__, page_type
)
)
except ValueError:
raise ImproperlyConfigured(
"{0}.page_type refers to model {1!r} that has not been installed".format(
cls.__name__, page_type
)
)
return target_models
else:
return [cls.model._meta.get_field(cls.field_name).remote_field.model]
class PageChooserPanel:
def __init__(self, field_name, page_type=None, can_choose_root=False):
self.field_name = field_name
super().__init__(field_name=field_name)
if page_type:
# Convert single string/model into list
@ -620,75 +547,115 @@ class PageChooserPanel:
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,
})
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 = []
for page_type in self.page_type:
try:
target_models.append(resolve_model_string(page_type))
except LookupError:
raise ImproperlyConfigured(
"{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
self.__class__.__name__, page_type
)
)
except ValueError:
raise ImproperlyConfigured(
"{0}.page_type refers to model {1!r} that has not been installed".format(
self.__class__.__name__, page_type
)
)
return target_models
return [self.db_field.remote_field.model]
class BaseInlinePanel(EditHandler):
@classmethod
def get_panel_definitions(cls):
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
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
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
else:
return extract_panel_definitions_from_model_class(
cls.related.related_model,
exclude=[cls.related.field.name]
)
return extract_panel_definitions_from_model_class(
self.related.related_model,
exclude=[self.related.field.name]
)
_child_edit_handler_class = None
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)
@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()
def required_formsets(self):
child_edit_handler = self.get_child_edit_handler()
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 = []
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 = []
for subform in self.formset.forms:
# override the DELETE field to have a hidden input
@ -698,9 +665,10 @@ class BaseInlinePanel(EditHandler):
if self.formset.can_order:
subform.fields['ORDER'].widget = forms.HiddenInput()
child_edit_handler = self.get_child_edit_handler()
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
# in case the parent form errored and we need to re-render
@ -712,7 +680,9 @@ class BaseInlinePanel(EditHandler):
if self.formset.can_order:
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"
@ -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
# without having to write these fields out by hand, potentially losing 'classname'
# 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.
@ -815,14 +762,14 @@ def get_edit_handler(cls):
if cls.settings_panels:
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
class BaseStreamFieldPanel(BaseFieldPanel):
class StreamFieldPanel(FieldPanel):
def classes(self):
classes = super().classes()
classes.append("stream-field")
@ -834,12 +781,10 @@ class BaseStreamFieldPanel(BaseFieldPanel):
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
def id_for_label(self):
@ -847,16 +792,6 @@ class BaseStreamFieldPanel(BaseFieldPanel):
# attach the label to any specific one
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

Wyświetl plik

@ -3,7 +3,7 @@ from datetime import date
import mock
from django import forms
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 wagtail.tests.testapp.forms import ValidatedPageForm
@ -21,6 +21,14 @@ from wagtail.images.edit_handlers import ImageChooserPanel
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):
EventPageForm = get_form_for_model(EventPage, form_class=WagtailAdminPageForm)
form = EventPageForm()
@ -128,8 +136,8 @@ class TestPageEditHandlers(TestCase):
"""
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
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
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
# ValidatedPage.base_form_class == ValidatedPageForm
@ -159,7 +167,7 @@ class TestPageEditHandlers(TestCase):
id='wagtailadmin.E001')
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",
obj=ValidatedPage,
id='wagtailadmin.E002')
@ -180,9 +188,9 @@ class TestPageEditHandlers(TestCase):
ValidatedPage.base_form_class, or provide a custom form class for the
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))
errors = ValidatedPage.check()
self.assertEqual(errors, [])
@ -219,7 +227,7 @@ class TestExtractPanelDefinitionsFromModelClass(TestCase):
class TestTabbedInterface(TestCase):
def setUp(self):
# a custom tabbed interface for EventPage
self.EventPageTabbedInterface = TabbedInterface([
self.event_page_tabbed_interface = TabbedInterface([
ObjectList([
FieldPanel('title', widget=forms.Textarea),
FieldPanel('date_from'),
@ -231,7 +239,7 @@ class TestTabbedInterface(TestCase):
]).bind_to_model(EventPage)
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 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)
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')
form = EventPageForm(instance=event)
tabbed_interface = self.EventPageTabbedInterface(
tabbed_interface = self.event_page_tabbed_interface.bind_to_instance(
instance=event,
form=form
)
@ -269,15 +277,15 @@ class TestTabbedInterface(TestCase):
def test_required_fields(self):
# 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']))
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')
form = EventPageForm(instance=event)
tabbed_interface = self.EventPageTabbedInterface(
tabbed_interface = self.event_page_tabbed_interface.bind_to_instance(
instance=event,
form=form
)
@ -293,7 +301,7 @@ class TestTabbedInterface(TestCase):
class TestObjectList(TestCase):
def setUp(self):
# a custom ObjectList for EventPage
self.EventPageObjectList = ObjectList([
self.event_page_object_list = ObjectList([
FieldPanel('title', widget=forms.Textarea),
FieldPanel('date_from'),
FieldPanel('date_to'),
@ -301,7 +309,7 @@ class TestObjectList(TestCase):
], heading='Event details', classname="shiny").bind_to_model(EventPage)
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 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)
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')
form = EventPageForm(instance=event)
object_list = self.EventPageObjectList(
object_list = self.event_page_object_list.bind_to_instance(
instance=event,
form=form
)
@ -345,7 +353,12 @@ class TestFieldPanel(TestCase):
self.event = EventPage(title='Abergavenny sheepdog trials',
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):
form = self.EventPageForm(
@ -354,7 +367,7 @@ class TestFieldPanel(TestCase):
form.is_valid()
field_panel = self.EndDatePanel(
field_panel = self.end_date_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -381,7 +394,7 @@ class TestFieldPanel(TestCase):
form.is_valid()
field_panel = self.EndDatePanel(
field_panel = self.end_date_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -401,7 +414,7 @@ class TestFieldPanel(TestCase):
self.assertNotIn('<p class="error-message">', result)
def test_required_fields(self):
result = self.EndDatePanel.required_fields()
result = self.end_date_panel.required_fields()
self.assertEqual(result, ['date_to'])
def test_error_message_is_rendered(self):
@ -411,7 +424,7 @@ class TestFieldPanel(TestCase):
form.is_valid()
field_panel = self.EndDatePanel(
field_panel = self.end_date_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -428,7 +441,7 @@ class TestFieldRowPanel(TestCase):
self.event = EventPage(title='Abergavenny sheepdog trials',
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_to', classname='coltwo'),
]).bind_to_model(EventPage)
@ -440,7 +453,7 @@ class TestFieldRowPanel(TestCase):
form.is_valid()
field_panel = self.DatesPanel(
field_panel = self.dates_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -459,7 +472,7 @@ class TestFieldRowPanel(TestCase):
form.is_valid()
field_panel = self.DatesPanel(
field_panel = self.dates_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -485,7 +498,7 @@ class TestFieldRowPanel(TestCase):
form.is_valid()
field_panel = self.DatesPanel(
field_panel = self.dates_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -501,7 +514,7 @@ class TestFieldRowPanel(TestCase):
form.is_valid()
field_panel = self.DatesPanel(
field_panel = self.dates_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -517,7 +530,7 @@ class TestFieldRowPanel(TestCase):
form.is_valid()
field_panel = self.DatesPanel(
field_panel = self.dates_panel.bind_to_instance(
instance=self.event,
form=form
)
@ -527,6 +540,38 @@ class TestFieldRowPanel(TestCase):
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):
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
# 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
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
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.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):
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):
# 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)
]).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)
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()
# the canChooseRoot flag on createPageChooser should now be true
@ -592,7 +640,8 @@ class TestPageChooserPanel(TestCase):
def test_render_as_empty_field(self):
test_instance = PageChooserModel()
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()
self.assertIn('<p class="help">help text</p>', result)
@ -603,19 +652,21 @@ class TestPageChooserPanel(TestCase):
form = self.PageChooserForm({'page': ''}, instance=self.test_instance)
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())
def test_override_page_type(self):
# Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
# to restrict the chooser to that page type
MyPageObjectList = ObjectList([
my_page_object_list = ObjectList([
PageChooserPanel('page', 'tests.EventPage')
]).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)
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()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
@ -626,11 +677,13 @@ class TestPageChooserPanel(TestCase):
def test_autodetect_page_type(self):
# Model has a foreign key to EventPage, which we want to autodetect
# instead of specifying the page type in PageChooserPanel
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)
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()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
@ -640,14 +693,14 @@ class TestPageChooserPanel(TestCase):
def test_target_models(self):
result = PageChooserPanel(
'barbecue',
'page',
'wagtailcore.site'
).bind_to_model(PageChooserModel).target_models()
self.assertEqual(result, [Site])
def test_target_models_malformed_type(self):
result = PageChooserPanel(
'barbecue',
'page',
'snowman'
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
@ -655,7 +708,7 @@ class TestPageChooserPanel(TestCase):
def test_target_models_nonexistent_type(self):
result = PageChooserPanel(
'barbecue',
'page',
'snowman.lorry'
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
@ -670,10 +723,10 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
Check that the inline panel renders the panels set on the model
when no 'panels' parameter is passed in the InlinePanel definition
"""
SpeakerObjectList = ObjectList([
speaker_object_list = ObjectList([
InlinePanel('speakers', label="Speakers", classname="classname-for-speakers")
]).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
self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))
@ -681,7 +734,8 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
event_page = EventPage.objects.get(slug='christmas')
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()
@ -720,22 +774,23 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
Check that inline panel renders the panels listed in the InlinePanel definition
where one is specified
"""
SpeakerObjectList = ObjectList([
speaker_object_list = ObjectList([
InlinePanel('speakers', label="Speakers", panels=[
FieldPanel('first_name', widget=forms.Textarea),
ImageChooserPanel('image'),
]),
]).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()))
event_page = EventPage.objects.get(slug='christmas')
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()
@ -781,17 +836,18 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
https://github.com/wagtail/wagtail/pull/2699
https://github.com/wagtail/wagtail/issues/3227
"""
SpeakerObjectList = ObjectList([
speaker_object_list = ObjectList([
InlinePanel('speakers', label="Speakers", panels=[
FieldPanel('first_name', widget=forms.Textarea),
ImageChooserPanel('image'),
]),
]).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')
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())

Wyświetl plik

@ -23,11 +23,11 @@ class BaseRichTextEditHandlerTestCase(TestCase):
"""
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():
page_class.get_edit_handler.cache_clear()

Wyświetl plik

@ -183,8 +183,8 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
return result
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)
@ -270,12 +270,13 @@ def create(request, content_type_app_name, content_type_model_name, parent_page_
messages.validation_error(
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
else:
signals.init_new_page.send(sender=create, page=page, parent=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
return render(request, 'wagtailadmin/pages/create.html', {
@ -307,8 +308,8 @@ def edit(request, page_id):
if hasattr(result, 'status_code'):
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)
@ -460,7 +461,8 @@ def edit(request, page_id):
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 = (
repr(edit_handler.form.errors) +
repr([
@ -472,7 +474,7 @@ def edit(request, page_id):
has_unsaved_changes = True
else:
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
# Check for revisions still undergoing moderation and warn
@ -564,7 +566,7 @@ class PreviewOnEdit(View):
id=self.args[0]).get_latest_revision_as_page()
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
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)
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)
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})

Wyświetl plik

@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from wagtail.admin.edit_handlers import EditHandler
class BaseFormSubmissionsPanel(EditHandler):
class FormSubmissionsPanel(EditHandler):
template = "wagtailforms/edit_handlers/form_responses_panel.html"
def render(self):
@ -23,14 +23,6 @@ class BaseFormSubmissionsPanel(EditHandler):
'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()

Wyświetl plik

@ -29,7 +29,8 @@ class TestFormResponsesPanel(TestCase):
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):
"""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)
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):
"""Show the panel with the count of submission and a link to the list_submissions view."""

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
from wagtail.admin import messages
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 .forms import SiteSwitchForm
@ -51,8 +51,8 @@ def edit(request, app_name, model_name, site_pk):
setting_type_name = model._meta.verbose_name
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':
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)
else:
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:
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
site_switcher = None
@ -88,5 +90,5 @@ def edit(request, app_name, model_name, site_pk):
'form': form,
'site': site,
'site_switcher': site_switcher,
'tabbed': edit_handler_class.__name__ == '_TabbedInterface',
'tabbed': isinstance(edit_handler, TabbedInterface),
})

Wyświetl plik

@ -3,20 +3,8 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
from .widgets import AdminDocumentChooser
class BaseDocumentChooserPanel(BaseChooserPanel):
class DocumentChooserPanel(BaseChooserPanel):
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}

Wyświetl plik

@ -6,29 +6,16 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
from .widgets import AdminImageChooser
class BaseImageChooserPanel(BaseChooserPanel):
class ImageChooserPanel(BaseChooserPanel):
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
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):
def htmldiff(self):
image_a, image_b = self.get_objects()

Wyświetl plik

@ -6,21 +6,11 @@ from wagtail.admin.edit_handlers import BaseChooserPanel
from .widgets import AdminSnippetChooser
class BaseSnippetChooserPanel(BaseChooserPanel):
class SnippetChooserPanel(BaseChooserPanel):
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):
instance_obj = self.get_chosen_item()
@ -29,13 +19,6 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
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

Wyświetl plik

@ -370,18 +370,18 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
test_snippet = model.objects.create(
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)
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 = [
panel for panel in edit_handler.children
if getattr(panel, 'field_name', None) == 'advert'][0]
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):
field_html = self.snippet_chooser_panel.render_as_field()
@ -392,7 +392,8 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
def test_render_as_empty_field(self):
test_snippet = SnippetChooserModel()
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 = [
panel for panel in edit_handler.children
@ -410,7 +411,7 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
def test_target_model_autodetected(self):
result = SnippetChooserPanel(
'advert'
).bind_to_model(SnippetChooserModel).target_model()
).bind_to_model(SnippetChooserModel).target_model
self.assertEqual(result, Advert)
@ -694,14 +695,14 @@ class TestDeleteOnlyPermissions(TestCase, WagtailTestUtils):
class TestSnippetEditHandlers(TestCase, WagtailTestUtils):
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.assertFalse(issubclass(form_class, FancySnippetForm))
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, 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)
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 = [
panel for panel in edit_handler.children
if getattr(panel, 'field_name', None) == 'advertwithcustomprimarykey'][0]
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):
field_html = self.snippet_chooser_panel.render_as_field()
@ -963,7 +963,7 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
def test_render_as_empty_field(self):
test_snippet = SnippetChooserModelWithCustomPrimaryKey()
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 = [
panel for panel in edit_handler.children
@ -981,12 +981,10 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
def test_target_model_autodetected(self):
result = SnippetChooserPanel(
'advertwithcustomprimarykey'
).bind_to_model(SnippetChooserModelWithCustomPrimaryKey).target_model()
).bind_to_model(SnippetChooserModelWithCustomPrimaryKey).target_model
self.assertEqual(result, AdvertWithCustomPrimaryKey)
class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
fixtures = ['test.json']
@ -1014,7 +1012,6 @@ class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
self.assertEqual(response.context['items'][0].text, "advert 1")
class TestSnippetChosenWithCustomPrimaryKey(TestCase, WagtailTestUtils):
fixtures = ['test.json']

Wyświetl plik

@ -129,8 +129,8 @@ def create(request, app_label, model_name):
return permission_denied(request)
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':
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)
else:
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:
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', {
'model_opts': model._meta,
@ -173,8 +175,8 @@ def edit(request, app_label, model_name, pk):
return permission_denied(request)
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':
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)
else:
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:
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', {
'model_opts': model._meta,