Merge pull request #960 from gasman/feature/model-aware-edithandlers

Refactor edit handler classes so that they can have a persistent reference to their corresponding model
pull/964/head
Karl Hobley 2015-02-04 16:12:43 +00:00
commit ed9a8d1169
8 zmienionych plików z 148 dodań i 79 usunięć

Wyświetl plik

@ -310,31 +310,51 @@ class BaseTabbedInterface(BaseCompositeEditHandler):
template = "wagtailadmin/edit_handlers/tabbed_interface.html"
def TabbedInterface(children):
return type(str('_TabbedInterface'), (BaseTabbedInterface,), {'children': children})
class TabbedInterface(object):
def __init__(self, children):
self.children = children
def bind_to_model(self, model):
return type(str('_TabbedInterface'), (BaseTabbedInterface,), {
'model': model,
'children': [child.bind_to_model(model) for child in self.children],
})
class BaseObjectList(BaseCompositeEditHandler):
template = "wagtailadmin/edit_handlers/object_list.html"
def ObjectList(children, heading="", classname=""):
return type(str('_ObjectList'), (BaseObjectList,), {
'children': children,
'heading': heading,
'classname': classname
})
class ObjectList(object):
def __init__(self, children, heading="", classname=""):
self.children = children
self.heading = heading
self.classname = classname
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,
})
class BaseFieldRowPanel(BaseCompositeEditHandler):
template = "wagtailadmin/edit_handlers/field_row_panel.html"
def FieldRowPanel(children, classname=""):
return type(str('_FieldRowPanel'), (BaseFieldRowPanel,), {
'children': children,
'classname': classname,
})
class FieldRowPanel(object):
def __init__(self, children, classname=""):
self.children = children
self.classname = classname
def bind_to_model(self, model):
return type(str('_FieldRowPanel'), (BaseFieldRowPanel,), {
'model': model,
'children': [child.bind_to_model(model) for child in self.children],
'classname': self.classname,
})
class BaseMultiFieldPanel(BaseCompositeEditHandler):
@ -347,12 +367,19 @@ class BaseMultiFieldPanel(BaseCompositeEditHandler):
return classes
def MultiFieldPanel(children, heading="", classname=""):
return type(str('_MultiFieldPanel'), (BaseMultiFieldPanel,), {
'children': children,
'heading': heading,
'classname': classname,
})
class MultiFieldPanel(object):
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):
@ -412,26 +439,38 @@ class BaseFieldPanel(EditHandler):
return [self.field_name]
def FieldPanel(field_name, classname="", widget=None):
base = {
'field_name': field_name,
'classname': classname,
}
class FieldPanel(object):
def __init__(self, field_name, classname="", widget=None):
self.field_name = field_name
self.classname = classname
self.widget = widget
if widget:
base['widget'] = widget
def bind_to_model(self, model):
base = {
'model': model,
'field_name': self.field_name,
'classname': self.classname,
}
return type(str('_FieldPanel'), (BaseFieldPanel,), base)
if self.widget:
base['widget'] = self.widget
return type(str('_FieldPanel'), (BaseFieldPanel,), base)
class BaseRichTextFieldPanel(BaseFieldPanel):
pass
def RichTextFieldPanel(field_name):
return type(str('_RichTextFieldPanel'), (BaseRichTextFieldPanel,), {
'field_name': field_name,
})
class RichTextFieldPanel(object):
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):
@ -507,11 +546,17 @@ class BasePageChooserPanel(BaseChooserPanel):
return super(BasePageChooserPanel, self).render_as_field(show_help_text, context)
def PageChooserPanel(field_name, page_type=None):
return type(str('_PageChooserPanel'), (BasePageChooserPanel,), {
'field_name': field_name,
'page_type': page_type,
})
class PageChooserPanel(object):
def __init__(self, field_name, page_type=None):
self.field_name = field_name
self.page_type = page_type
def bind_to_model(self, model):
return type(str('_PageChooserPanel'), (BasePageChooserPanel,), {
'model': model,
'field_name': self.field_name,
'page_type': self.page_type,
})
class BaseInlinePanel(EditHandler):
@ -530,7 +575,7 @@ class BaseInlinePanel(EditHandler):
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)
cls._child_edit_handler_class = MultiFieldPanel(panels, heading=cls.heading).bind_to_model(cls.related.model)
return cls._child_edit_handler_class
@ -594,15 +639,24 @@ class BaseInlinePanel(EditHandler):
}))
def InlinePanel(base_model, relation_name, panels=None, label='', help_text=''):
rel = getattr(base_model, relation_name).related
return type(str('_InlinePanel'), (BaseInlinePanel,), {
'relation_name': relation_name,
'related': rel,
'panels': panels,
'heading': label,
'help_text': 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)
})
class InlinePanel(object):
def __init__(self, base_model, relation_name, panels=None, label='', help_text=''):
# the base_model param is now redundant; we set up relations based on the model passed to
# bind_to_model instead
self.relation_name = relation_name
self.panels = panels
self.label = label
self.help_text = help_text
def bind_to_model(self, model):
return type(str('_InlinePanel'), (BaseInlinePanel,), {
'model': model,
'relation_name': self.relation_name,
'related': getattr(model, self.relation_name).related,
'panels': self.panels,
'heading': 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)
})
# This allows users to include the publishing panel in their own per-model override

Wyświetl plik

@ -7,9 +7,8 @@ from django import forms
from wagtail.wagtailadmin.edit_handlers import (
get_form_for_model,
extract_panel_definitions_from_model_class,
BaseFieldPanel,
FieldPanel,
BaseRichTextFieldPanel,
RichTextFieldPanel,
TabbedInterface,
ObjectList,
PageChooserPanel,
@ -17,7 +16,7 @@ from wagtail.wagtailadmin.edit_handlers import (
)
from wagtail.wagtailadmin.widgets import AdminPageChooser, AdminDateInput
from wagtail.wagtailimages.edit_handlers import BaseImageChooserPanel, ImageChooserPanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailcore.models import Page, Site
from wagtail.tests.models import PageChooserModel, EventPage, EventPageSpeaker
@ -91,7 +90,7 @@ class TestExtractPanelDefinitionsFromModelClass(TestCase):
result = extract_panel_definitions_from_model_class(EventPageSpeaker)
self.assertEqual(len(result), 4)
#print repr(result)
self.assertTrue(any([issubclass(panel, BaseImageChooserPanel) for panel in result]))
self.assertTrue(any([isinstance(panel, ImageChooserPanel) for panel in result]))
def test_exclude(self):
panels = extract_panel_definitions_from_model_class(Site, exclude=['hostname'])
@ -103,19 +102,19 @@ class TestExtractPanelDefinitionsFromModelClass(TestCase):
panels = extract_panel_definitions_from_model_class(EventPage)
self.assertTrue(any([
issubclass(panel, BaseFieldPanel) and panel.field_name == 'date_from'
isinstance(panel, FieldPanel) and panel.field_name == 'date_from'
for panel in panels
]))
# returned panel types should respect modelfield.get_panel() - used on RichTextField
self.assertTrue(any([
issubclass(panel, BaseRichTextFieldPanel) and panel.field_name == 'body'
isinstance(panel, RichTextFieldPanel) and panel.field_name == 'body'
for panel in panels
]))
# treebeard fields should be excluded
self.assertFalse(any([
issubclass(panel, BaseFieldPanel) and panel.field_name == 'path'
panel.field_name == 'path'
for panel in panels
]))
@ -132,7 +131,7 @@ class TestTabbedInterface(TestCase):
ObjectList([
InlinePanel(EventPage, 'speakers', label="Speakers"),
], heading='Speakers'),
])
]).bind_to_model(EventPage)
def test_get_form_class(self):
EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
@ -202,7 +201,7 @@ class TestObjectList(TestCase):
FieldPanel('date_from'),
FieldPanel('date_to'),
InlinePanel(EventPage, 'speakers', label="Speakers"),
], heading='Event details', classname="shiny")
], heading='Event details', classname="shiny").bind_to_model(EventPage)
def test_get_form_class(self):
EventPageForm = self.EventPageObjectList.get_form_class(EventPage)
@ -245,7 +244,7 @@ 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')
self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)
def test_render_as_object(self):
form = self.EventPageForm(
@ -327,7 +326,7 @@ class TestPageChooserPanel(TestCase):
model = PageChooserModel # a model with a foreign key to Page which we want to render as a page chooser
# a PageChooserPanel class that works on PageChooserModel's 'page' field
self.MyPageChooserPanel = PageChooserPanel('page', 'tests.EventPage')
self.MyPageChooserPanel = PageChooserPanel('page', 'tests.EventPage').bind_to_model(PageChooserModel)
# build a form class containing the fields that MyPageChooserPanel wants
self.PageChooserForm = self.MyPageChooserPanel.get_form_class(PageChooserModel)
@ -372,14 +371,14 @@ class TestPageChooserPanel(TestCase):
result = PageChooserPanel(
'barbecue',
'wagtailcore.site'
).target_content_type()
).bind_to_model(PageChooserModel).target_content_type()
self.assertEqual(result.name, 'site')
def test_target_content_type_malformed_type(self):
result = PageChooserPanel(
'barbecue',
'snowman'
)
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
result.target_content_type)
@ -387,7 +386,7 @@ class TestPageChooserPanel(TestCase):
result = PageChooserPanel(
'barbecue',
'snowman.lorry'
)
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
result.target_content_type)
@ -400,7 +399,7 @@ class TestInlinePanel(TestCase):
Check that the inline panel renders the panels set on the model
when no 'panels' parameter is passed in the InlinePanel definition
"""
SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers")
SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers").bind_to_model(EventPage)
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
@ -438,7 +437,7 @@ class TestInlinePanel(TestCase):
SpeakerInlinePanel = InlinePanel(EventPage, 'speakers', label="Speakers", panels=[
FieldPanel('first_name', widget=forms.Textarea),
ImageChooserPanel('image'),
])
]).bind_to_model(EventPage)
EventPageForm = SpeakerInlinePanel.get_form_class(EventPage)
# SpeakerInlinePanel should instruct the form class to include a 'speakers' formset

Wyświetl plik

@ -712,7 +712,7 @@ def get_page_edit_handler(page_class):
ObjectList(page_class.content_panels, heading='Content'),
ObjectList(page_class.promote_panels, heading='Promote'),
ObjectList(page_class.settings_panels, heading='Settings', classname="settings")
])
]).bind_to_model(page_class)
return PAGE_EDIT_HANDLERS[page_class]

Wyświetl plik

@ -13,7 +13,12 @@ class BaseDocumentChooserPanel(BaseChooserPanel):
return {cls.field_name: AdminDocumentChooser}
def DocumentChooserPanel(field_name):
return type(str('_DocumentChooserPanel'), (BaseDocumentChooserPanel,), {
'field_name': field_name,
})
class DocumentChooserPanel(object):
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,
})

Wyświetl plik

@ -13,7 +13,12 @@ class BaseImageChooserPanel(BaseChooserPanel):
return {cls.field_name: AdminImageChooser}
def ImageChooserPanel(field_name):
return type(str('_ImageChooserPanel'), (BaseImageChooserPanel,), {
'field_name': field_name,
})
class ImageChooserPanel(object):
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,
})

Wyświetl plik

@ -12,7 +12,7 @@ from wagtail.wagtailadmin import messages
from wagtail.wagtailredirects import models
REDIRECT_EDIT_HANDLER = ObjectList(models.Redirect.content_panels)
REDIRECT_EDIT_HANDLER = ObjectList(models.Redirect.content_panels).bind_to_model(models.Redirect)
@permission_required('wagtailredirects.change_redirect')

Wyświetl plik

@ -39,9 +39,15 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
}))
def SnippetChooserPanel(field_name, snippet_type):
return type(str('_SnippetChooserPanel'), (BaseSnippetChooserPanel,), {
'field_name': field_name,
'snippet_type_name': force_text(snippet_type._meta.verbose_name),
'snippet_type': snippet_type,
})
class SnippetChooserPanel(object):
def __init__(self, field_name, snippet_type):
self.field_name = field_name
self.snippet_type = snippet_type
def bind_to_model(self, model):
return type(str('_SnippetChooserPanel'), (BaseSnippetChooserPanel,), {
'model': model,
'field_name': self.field_name,
'snippet_type_name': force_text(self.snippet_type._meta.verbose_name),
'snippet_type': self.snippet_type,
})

Wyświetl plik

@ -59,7 +59,7 @@ SNIPPET_EDIT_HANDLERS = {}
def get_snippet_edit_handler(model):
if model not in SNIPPET_EDIT_HANDLERS:
panels = extract_panel_definitions_from_model_class(model)
edit_handler = ObjectList(panels)
edit_handler = ObjectList(panels).bind_to_model(model)
SNIPPET_EDIT_HANDLERS[model] = edit_handler