From eb47526888ff8d92daaf023f81bce6e3b86607a8 Mon Sep 17 00:00:00 2001 From: Mikalai Radchuk <radchuk.m@gmail.com> Date: Tue, 24 Jan 2017 20:03:21 +0300 Subject: [PATCH] Enhancement for PageChooserBlock Allow the target_model argument to be a list or tuple. --- CHANGELOG.txt | 1 + docs/releases/1.10.rst | 1 + docs/topics/streamfield.rst | 2 +- wagtail/wagtailcore/blocks/field_block.py | 49 ++++++++++++++++--- wagtail/wagtailcore/tests/test_blocks.py | 58 +++++++++++++++++++++-- 5 files changed, 100 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index afb5bdfdb8..316f3b8403 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -10,6 +10,7 @@ Changelog * Added `construct_image_chooser_queryset`, `construct_document_chooser_queryset` and `construct_page_chooser_queryset` hooks (Gagaro) * The homepage created in the project template is now titled "Home" rather than "Homepage" (Karl Hobley) * Signal receivers for custom `Image` and `Rendition` models are connected automatically (Mike Dingjan) + * `PageChooserBlock` can now accept a list/tuple of page models as `target_model` (Mikalai Radchuk) * Fix: Marked 'Date from' / 'Date to' strings in wagtailforms for translation (Vorlif) * Fix: Unreliable preview is now reliable by always opening in a new window (Kjartan Sverrisson) * Fix: Fixed placement of `{{ block.super }}` in `snippets/type_index.html` (LB (Ben Johnston)) diff --git a/docs/releases/1.10.rst b/docs/releases/1.10.rst index 47eeca369a..5be8b77560 100644 --- a/docs/releases/1.10.rst +++ b/docs/releases/1.10.rst @@ -20,6 +20,7 @@ Other features * Added ``construct_image_chooser_queryset``, ``construct_document_chooser_queryset`` and ``construct_page_chooser_queryset`` hooks (Gagaro) * The homepage created in the project template is now titled "Home" rather than "Homepage" (Karl Hobley) * Signal receivers for custom ``Image`` and ``Rendition`` models are connected automatically (Mike Dingjan) + * ``PageChooserBlock`` can now accept a list/tuple of page models as ``target_model`` (Mikalai Radchuk) Bug fixes diff --git a/docs/topics/streamfield.rst b/docs/topics/streamfield.rst index 51f50fc4c6..412627ce7c 100644 --- a/docs/topics/streamfield.rst +++ b/docs/topics/streamfield.rst @@ -251,7 +251,7 @@ A control for selecting a page object, using Wagtail's page browser. The followi If true, the field cannot be left blank. ``target_model`` (default: Page) - Restrict choices to a single Page type. + Restrict choices to one or more specific page types. Accepts a page model class, model name (as a string), or a list or tuple of these. ``can_choose_root`` (default: False) If true, the editor can choose the tree root as a page. Normally this would be undesirable, since the tree root is never a usable page, but in some specialised cases it may be appropriate. For example, a block providing a feed of related articles could use a PageChooserBlock to select which subsection of the site articles will be taken from, with the root corresponding to 'everywhere'. diff --git a/wagtail/wagtailcore/blocks/field_block.py b/wagtail/wagtailcore/blocks/field_block.py index d4afbf4c2e..435cd7a130 100644 --- a/wagtail/wagtailcore/blocks/field_block.py +++ b/wagtail/wagtailcore/blocks/field_block.py @@ -581,20 +581,48 @@ class ChooserBlock(FieldBlock): class PageChooserBlock(ChooserBlock): - def __init__(self, target_model='wagtailcore.Page', can_choose_root=False, + # TODO: rename target_model to page_type + def __init__(self, target_model=None, can_choose_root=False, **kwargs): - self._target_model = target_model + if target_model: + # Convert single string/model into a list + if not isinstance(target_model, (list, tuple)): + target_model = [target_model] + else: + target_model = [] + + self._target_models = target_model self.can_choose_root = can_choose_root super(PageChooserBlock, self).__init__(**kwargs) @cached_property def target_model(self): - return resolve_model_string(self._target_model) + """ + Defines the model used by the base ChooserBlock for ID <-> instance + conversions. If a single page type is specified in target_model, + we can use that to get the more specific instance "for free"; otherwise + use the generic Page model. + """ + if len(self.target_models) == 1: + return self.target_models[0] + + return resolve_model_string('wagtailcore.Page') + + @cached_property + def target_models(self): + target_models = [] + + for target_model in self._target_models: + target_models.append( + resolve_model_string(target_model) + ) + + return target_models @cached_property def widget(self): from wagtail.wagtailadmin.widgets import AdminPageChooser - return AdminPageChooser(target_models=[self.target_model], + return AdminPageChooser(target_models=self.target_models, can_choose_root=self.can_choose_root) def render_basic(self, value, context=None): @@ -605,9 +633,18 @@ class PageChooserBlock(ChooserBlock): def deconstruct(self): name, args, kwargs = super(PageChooserBlock, self).deconstruct() + if 'target_model' in kwargs: - opts = self.target_model._meta - kwargs['target_model'] = '{}.{}'.format(opts.app_label, opts.object_name) + target_models = [] + + for target_model in self.target_models: + opts = target_model._meta + target_models.append( + '{}.{}'.format(opts.app_label, opts.object_name) + ) + + kwargs['target_model'] = target_models + return name, args, kwargs class Meta: diff --git a/wagtail/wagtailcore/tests/test_blocks.py b/wagtail/wagtailcore/tests/test_blocks.py index 69f3831f32..b27c06b487 100644 --- a/wagtail/wagtailcore/tests/test_blocks.py +++ b/wagtail/wagtailcore/tests/test_blocks.py @@ -21,7 +21,7 @@ from django.utils.translation import ugettext_lazy as __ from wagtail.tests.testapp.blocks import LinkBlock as CustomLinkBlock from wagtail.tests.testapp.blocks import SectionBlock -from wagtail.tests.testapp.models import SimplePage +from wagtail.tests.testapp.models import EventPage, SimplePage from wagtail.utils.deprecation import RemovedInWagtail111Warning from wagtail.wagtailcore import blocks from wagtail.wagtailcore.models import Page @@ -2265,11 +2265,31 @@ class TestPageChooserBlock(TestCase): self.assertIn(expected_html, christmas_form_html) self.assertIn("pick a page, any page", christmas_form_html) - def test_form_render_with_target_model(self): + def test_form_render_with_target_model_default(self): + block = blocks.PageChooserBlock() + empty_form_html = block.render_form(None, 'page') + self.assertIn('createPageChooser("page", ["wagtailcore.page"], null, false);', empty_form_html) + + def test_form_render_with_target_model_string(self): block = blocks.PageChooserBlock(help_text="pick a page, any page", target_model='tests.SimplePage') empty_form_html = block.render_form(None, 'page') self.assertIn('createPageChooser("page", ["tests.simplepage"], null, false);', empty_form_html) + def test_form_render_with_target_model_literal(self): + block = blocks.PageChooserBlock(help_text="pick a page, any page", target_model=SimplePage) + empty_form_html = block.render_form(None, 'page') + self.assertIn('createPageChooser("page", ["tests.simplepage"], null, false);', empty_form_html) + + def test_form_render_with_target_model_multiple_strings(self): + block = blocks.PageChooserBlock(help_text="pick a page, any page", target_model=['tests.SimplePage', 'tests.EventPage']) + empty_form_html = block.render_form(None, 'page') + self.assertIn('createPageChooser("page", ["tests.simplepage", "tests.eventpage"], null, false);', empty_form_html) + + def test_form_render_with_target_model_multiple_literals(self): + block = blocks.PageChooserBlock(help_text="pick a page, any page", target_model=[SimplePage, EventPage]) + empty_form_html = block.render_form(None, 'page') + self.assertIn('createPageChooser("page", ["tests.simplepage", "tests.eventpage"], null, false);', empty_form_html) + def test_form_render_with_can_choose_root(self): block = blocks.PageChooserBlock(help_text="pick a page, any page", can_choose_root=True) empty_form_html = block.render_form(None, 'page') @@ -2297,6 +2317,10 @@ class TestPageChooserBlock(TestCase): self.assertEqual(nonrequired_block.clean(christmas_page), christmas_page) self.assertEqual(nonrequired_block.clean(None), None) + def test_target_model_default(self): + block = blocks.PageChooserBlock() + self.assertEqual(block.target_model, Page) + def test_target_model_string(self): block = blocks.PageChooserBlock(target_model='tests.SimplePage') self.assertEqual(block.target_model, SimplePage) @@ -2305,17 +2329,43 @@ class TestPageChooserBlock(TestCase): block = blocks.PageChooserBlock(target_model=SimplePage) self.assertEqual(block.target_model, SimplePage) + def test_target_model_multiple_strings(self): + block = blocks.PageChooserBlock(target_model=['tests.SimplePage', 'tests.EventPage']) + self.assertEqual(block.target_model, Page) + + def test_target_model_multiple_literals(self): + block = blocks.PageChooserBlock(target_model=[SimplePage, EventPage]) + self.assertEqual(block.target_model, Page) + + def test_deconstruct_target_model_default(self): + block = blocks.PageChooserBlock() + self.assertEqual(block.deconstruct(), ( + 'wagtail.wagtailcore.blocks.PageChooserBlock', + (), {})) + def test_deconstruct_target_model_string(self): block = blocks.PageChooserBlock(target_model='tests.SimplePage') self.assertEqual(block.deconstruct(), ( 'wagtail.wagtailcore.blocks.PageChooserBlock', - (), {'target_model': 'tests.SimplePage'})) + (), {'target_model': ['tests.SimplePage']})) def test_deconstruct_target_model_literal(self): block = blocks.PageChooserBlock(target_model=SimplePage) self.assertEqual(block.deconstruct(), ( 'wagtail.wagtailcore.blocks.PageChooserBlock', - (), {'target_model': 'tests.SimplePage'})) + (), {'target_model': ['tests.SimplePage']})) + + def test_deconstruct_target_model_multiple_strings(self): + block = blocks.PageChooserBlock(target_model=['tests.SimplePage', 'tests.EventPage']) + self.assertEqual(block.deconstruct(), ( + 'wagtail.wagtailcore.blocks.PageChooserBlock', + (), {'target_model': ['tests.SimplePage', 'tests.EventPage']})) + + def test_deconstruct_target_model_multiple_literals(self): + block = blocks.PageChooserBlock(target_model=[SimplePage, EventPage]) + self.assertEqual(block.deconstruct(), ( + 'wagtail.wagtailcore.blocks.PageChooserBlock', + (), {'target_model': ['tests.SimplePage', 'tests.EventPage']})) class TestStaticBlock(unittest.TestCase):