diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7da7827d66..aa5942058f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -16,6 +16,7 @@ Changelog * Modeladmin forms now respect `fields` / `exclude` options passed on custom model forms (Thejaswi Puthraya) * Added new StreamField block type `StaticBlock` (Benoît Vogel) * Updated Cloudflare cache module to use the v4 API (Albert O'Connor) + * Added `exclude_from_explorer` attribute to the `ModelAdmin` class to allow hiding instances of a page type from Wagtail's explorer views (Andy Babic) * Fix: `AbstractForm` now respects custom `get_template` methods on the page model (Gagaro) * Fix: Use specific page model for the parent page in the explore index (Gagaro) * Fix: Remove responsive styles in embed when there is no ratio available (Gagaro) diff --git a/docs/reference/contrib/modeladmin/index.rst b/docs/reference/contrib/modeladmin/index.rst index baf31bea13..fe6fe04682 100644 --- a/docs/reference/contrib/modeladmin/index.rst +++ b/docs/reference/contrib/modeladmin/index.rst @@ -98,6 +98,7 @@ can get to it. menu_icon = 'date' # change as required menu_order = 200 # will put in 3rd place (000 being 1st, 100 2nd) add_to_settings_menu = False # or True to add your model to the Settings sub-menu + exclude_from_explorer = False # or True to exclude pages of this type from Wagtail's explorer view list_display = ('title', 'example_field2', 'example_field3', 'live') list_filter = ('live', 'example_field2', 'example_field3') search_fields = ('title',) diff --git a/docs/releases/1.8.rst b/docs/releases/1.8.rst index 28a6034a1e..232cb4f4d4 100644 --- a/docs/releases/1.8.rst +++ b/docs/releases/1.8.rst @@ -42,6 +42,7 @@ Minor features * Modeladmin forms now respect ``fields`` / ``exclude`` options passed on custom model forms (Thejaswi Puthraya) * Added new StreamField block type ``StaticBlock`` for blocks that occupy a position in a stream but otherwise have no configuration; see :ref:`streamfield_staticblock` (Benoît Vogel) * Updated Cloudflare cache module to use the v4 API (Albert O'Connor) + * Added `exclude_from_explorer` attribute to the `ModelAdmin` class to allow hiding instances of a page type from Wagtail's explorer views (Andy Babic) Bug fixes diff --git a/wagtail/contrib/modeladmin/options.py b/wagtail/contrib/modeladmin/options.py index 6af68cc492..cdd07a9f47 100644 --- a/wagtail/contrib/modeladmin/options.py +++ b/wagtail/contrib/modeladmin/options.py @@ -25,6 +25,7 @@ class WagtailRegisterable(object): ModelAdminGroup instances to be registered with Wagtail's admin area. """ add_to_settings_menu = False + exclude_from_explorer = False def register_with_wagtail(self): @@ -45,6 +46,18 @@ class WagtailRegisterable(object): def register_admin_menu_item(): return self.get_menu_item() + # Overriding the explorer page queryset is a somewhat 'niche' / experimental + # operation, so only attach that hook if we specifically opt into it + # by returning True from will_modify_explorer_page_queryset + if self.will_modify_explorer_page_queryset(): + @hooks.register('construct_explorer_page_queryset') + def construct_explorer_page_queryset(parent_page, queryset, request): + return self.modify_explorer_page_queryset( + parent_page, queryset, request) + + def will_modify_explorer_page_queryset(self): + return False + class ThumbnailMixin(object): """ @@ -512,6 +525,14 @@ class ModelAdmin(WagtailRegisterable): ) return urls + def will_modify_explorer_page_queryset(self): + return (self.is_pagemodel and self.exclude_from_explorer) + + def modify_explorer_page_queryset(self, parent_page, queryset, request): + if self.is_pagemodel and self.exclude_from_explorer: + queryset = queryset.not_type(self.model) + return queryset + class ModelAdminGroup(WagtailRegisterable): """ @@ -587,6 +608,18 @@ class ModelAdminGroup(WagtailRegisterable): urls += instance.get_admin_urls_for_registration() return urls + def will_modify_explorer_page_queryset(self): + return any( + instance.will_modify_explorer_page_queryset() + for instance in self.modeladmin_instances + ) + + def modify_explorer_page_queryset(self, parent_page, queryset, request): + for instance in self.modeladmin_instances: + queryset = instance.modify_explorer_page_queryset( + parent_page, queryset, request) + return queryset + def modeladmin_register(modeladmin_class): """ diff --git a/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py b/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py index bf160f2721..fb13004880 100644 --- a/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py +++ b/wagtail/contrib/modeladmin/tests/test_page_modeladmin.py @@ -58,6 +58,27 @@ class TestIndexView(TestCase, WagtailTestUtils): self.assertEqual(response.context['result_count'], 4) +class TestExcludeFromExplorer(TestCase, WagtailTestUtils): + fixtures = ['modeladmintest_test.json'] + + def setUp(self): + self.login() + + def test_attribute_effects_explorer(self): + # The two VenuePages should appear in the venuepage list + response = self.client.get('/admin/modeladmintest/venuepage/') + self.assertContains(response, "Santa's Grotto") + self.assertContains(response, "Santa's Workshop") + + # But when viewing the children of 'Christmas' event in explorer + response = self.client.get('/admin/pages/4/') + self.assertNotContains(response, "Santa's Grotto") + self.assertNotContains(response, "Santa's Workshop") + + # But the other test page should... + self.assertContains(response, "Claim your free present!") + + class TestCreateView(TestCase, WagtailTestUtils): fixtures = ['test_specific.json'] diff --git a/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json b/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json index ef78942f47..7ebf7a4c1c 100644 --- a/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json +++ b/wagtail/tests/modeladmintest/fixtures/modeladmintest_test.json @@ -69,5 +69,147 @@ "fields": { "key": "boom" } +}, +{ + "pk": 1, + "model": "wagtailcore.page", + "fields": { + "title": "Root", + "numchild": 1, + "show_in_menus": false, + "live": true, + "depth": 1, + "content_type": ["wagtailcore", "page"], + "path": "0001", + "url_path": "/", + "slug": "root" + } +}, + +{ + "pk": 2, + "model": "wagtailcore.page", + "fields": { + "title": "Welcome to the Wagtail test site!", + "numchild": 5, + "show_in_menus": false, + "live": true, + "depth": 2, + "content_type": ["wagtailcore", "page"], + "path": "00010001", + "url_path": "/home/", + "slug": "home" + } +}, + +{ + "pk": 3, + "model": "wagtailcore.page", + "fields": { + "title": "Events", + "numchild": 4, + "show_in_menus": true, + "live": true, + "depth": 3, + "content_type": ["tests", "eventindex"], + "path": "000100010001", + "url_path": "/home/events/", + "slug": "events" + } +}, +{ + "pk": 3, + "model": "tests.eventindex", + "fields": { + "intro": "Look at our lovely events." + } +}, + +{ + "pk": 4, + "model": "wagtailcore.page", + "fields": { + "title": "Christmas", + "numchild": 3, + "show_in_menus": true, + "live": true, + "depth": 4, + "content_type": ["tests", "eventpage"], + "path": "0001000100010001", + "url_path": "/home/events/christmas/", + "slug": "christmas" + } +}, +{ + "pk": 4, + "model": "tests.eventpage", + "fields": { + "date_from": "2014-12-25", + "audience": "public", + "location": "The North Pole", + "body": "

Chestnuts roasting on an open fire

", + "cost": "Free" + } +}, +{ + "pk": 5, + "model": "wagtailcore.page", + "fields": { + "title": "Santa's Grotto", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["modeladmintest", "venuepage"], + "path": "00010001000100010001", + "url_path": "/home/events/christmas/santas-grotto/", + "slug": "santas-grotto" + } +}, +{ + "pk": 5, + "model": "modeladmintest.venuepage", + "fields": { + "address": "The North Pole", + "capacity": 3 + } +}, +{ + "pk": 6, + "model": "wagtailcore.page", + "fields": { + "title": "Santa's Workshop", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["modeladmintest", "venuepage"], + "path": "00010001000100010002", + "url_path": "/home/events/christmas/santas-workshop/", + "slug": "santas-workshop" + } +}, +{ + "pk": 6, + "model": "modeladmintest.venuepage", + "fields": { + "address": "The North Pole", + "capacity": 25 + } +}, +{ + "pk": 7, + "model": "wagtailcore.page", + "fields": { + "title": "Claim your free present!", + "numchild": 0, + "show_in_menus": true, + "live": true, + "depth": 5, + "content_type": ["wagtailcore", "page"], + "path": "00010001000100010003", + "url_path": "/home/events/christmas/claim-free-present/", + "slug": "claim-free-present" + } } ] diff --git a/wagtail/tests/modeladmintest/migrations/0004_venuepage.py b/wagtail/tests/modeladmintest/migrations/0004_venuepage.py new file mode 100644 index 0000000000..97193bcad5 --- /dev/null +++ b/wagtail/tests/modeladmintest/migrations/0004_venuepage.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-07 11:22 +from __future__ import unicode_literals + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modeladmintest', '0003_publisher'), + ] + + operations = [ + migrations.CreateModel( + name='VenuePage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('address', models.CharField(max_length=300)), + ('capacity', models.IntegerField()), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/wagtail/tests/modeladmintest/models.py b/wagtail/tests/modeladmintest/models.py index 38c4126a60..ae8d216b42 100644 --- a/wagtail/tests/modeladmintest/models.py +++ b/wagtail/tests/modeladmintest/models.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible +from wagtail.wagtailcore.models import Page from wagtail.wagtailsearch import index @@ -39,3 +40,8 @@ class Publisher(models.Model): def __str__(self): return self.name + + +class VenuePage(Page): + address = models.CharField(max_length=300) + capacity = models.IntegerField() diff --git a/wagtail/tests/modeladmintest/wagtail_hooks.py b/wagtail/tests/modeladmintest/wagtail_hooks.py index 769b4792f1..c5606e5ed6 100644 --- a/wagtail/tests/modeladmintest/wagtail_hooks.py +++ b/wagtail/tests/modeladmintest/wagtail_hooks.py @@ -5,7 +5,7 @@ from wagtail.contrib.modeladmin.views import CreateView from wagtail.tests.testapp.models import BusinessChild, EventPage, SingleEventPage from .forms import PublisherModelAdminForm -from .models import Author, Book, Publisher, Token +from .models import Author, Book, Publisher, Token, VenuePage class AuthorModelAdmin(ModelAdmin): @@ -63,9 +63,14 @@ class SingleEventPageAdmin(EventPageAdmin): model = SingleEventPage +class VenuePageAdmin(ModelAdmin): + model = VenuePage + exclude_from_explorer = True + + class EventsAdminGroup(ModelAdminGroup): menu_label = "Events" - items = (EventPageAdmin, SingleEventPageAdmin) + items = (EventPageAdmin, SingleEventPageAdmin, VenuePageAdmin) menu_order = 500