Skip to content

Commit

Permalink
Revert "Multiple page types in page chooser"
Browse files Browse the repository at this point in the history
  • Loading branch information
gasman committed Aug 11, 2015
1 parent 31c6198 commit 85707d5
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 147 deletions.
9 changes: 2 additions & 7 deletions docs/reference/pages/editing_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,10 @@ PageChooserPanel
)
content_panels = Page.content_panels + [
PageChooserPanel('related_page', ['demo.PublisherPage', 'demo.EventPage']),
PageChooserPanel('related_page', 'demo.PublisherPage'),
]
``PageChooserPanel`` takes two arguments: a field name and an optional list of page types.

The ``page_types`` parameter restricts which page types can be selected in this field. It defaults to just the model that the ``ForeignKey`` is pointing at. A ``ForeignKey`` pointing at ``wagtailcore.Page``, such as the one in the example above, will allow all pages to be selected, as all pages must subclass ``wagtailcore.Page``.

This parameter will take a Page subclass, a string (in the form of an ``"app_name.ModelName"`` string) or a list of strings/model classes.

``PageChooserPanel`` takes two arguments: a field name and an optional page type. Specifying a page type (in the form of an ``"appname.modelname"`` string) will filter the chooser to display only pages of that type.

ImageChooserPanel
-----------------
Expand Down
64 changes: 21 additions & 43 deletions wagtail/wagtailadmin/edit_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,66 +544,44 @@ def render_as_field(self):
class BasePageChooserPanel(BaseChooserPanel):
object_type_name = "page"

_target_content_types = None
_target_content_type = None

@classmethod
def widget_overrides(cls):
return {cls.field_name: widgets.AdminPageChooser(
content_types=cls.target_content_types())}
content_type=cls.target_content_type())}

@classmethod
def target_content_types(cls):
if cls._target_content_types is None:
if cls.page_types:
models = []

for index, page_type in enumerate(cls.page_types):
try:
models.append(resolve_model_string(page_type))
except LookupError:
raise ImproperlyConfigured("{0}.page_types[{1}] must be of the form 'app_label.model_name', given {2!r}".format(
cls.__name__, index, page_type))
except ValueError:
raise ImproperlyConfigured("{0}.page_types[{1}] refers to model {2!r} that has not been installed".format(
cls.__name__, index, page_type))

# Get a mapping of ContentType objects for the models
content_types = ContentType.objects.get_for_models(*models)

# Convert content types into a list and set it to _target_content_types
# As the content types were in a dict, the order would be randomised but
# we need the ordering on _target_content_types to be deterministic to
# simplify testing
cls._target_content_types = [
content_types[model]
for model in models
]
def target_content_type(cls):
if cls._target_content_type is None:
if cls.page_type:
try:
model = resolve_model_string(cls.page_type)
except LookupError:
raise ImproperlyConfigured("{0}.page_type must be of the form 'app_label.model_name', given {1!r}".format(
cls.__name__, cls.page_type))
except ValueError:
raise ImproperlyConfigured("{0}.page_type refers to model {1!r} that has not been installed".format(
cls.__name__, cls.page_type))

cls._target_content_type = ContentType.objects.get_for_model(model)
else:
# Find model by introspecting the ForeignKey
model = cls.model._meta.get_field(cls.field_name).rel.to
cls._target_content_types = [ContentType.objects.get_for_model(model)]
target_model = cls.model._meta.get_field(cls.field_name).rel.to
cls._target_content_type = ContentType.objects.get_for_model(target_model)

return cls._target_content_types
return cls._target_content_type


class PageChooserPanel(object):
def __init__(self, field_name, page_types=None):
def __init__(self, field_name, page_type=None):
self.field_name = field_name

# Make sure page_types is a list
if page_types is None:
page_types = []

if not isinstance(page_types, (list, tuple)):
page_types = [page_types]

self.page_types = page_types
self.page_type = page_type

def bind_to_model(self, model):
return type(str('_PageChooserPanel'), (BasePageChooserPanel,), {
'model': model,
'field_name': self.field_name,
'page_types': self.page_types,
'page_type': self.page_type,
})


Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
{% load i18n %}
{% if page_types_restricted %}
{% trans "Choose" as choose_str %}
{% trans page_type_name as subtitle %}
{% else %}
{% trans "Choose a page" as choose_str %}
{% endif %}

{% include "wagtailadmin/shared/header.html" with title=choose_str subtitle=page_type_names|join:", " search_url="wagtailadmin_choose_page_search" query_parameters="page_types="|add:page_type_string icon="doc-empty-inverse" %}
{% include "wagtailadmin/shared/header.html" with title=choose_str subtitle=subtitle search_url="wagtailadmin_choose_page_search" query_parameters="page_type="|add:page_type_string icon="doc-empty-inverse" %}

<div class="nice-padding">
{% include 'wagtailadmin/chooser/_link_types.html' with current='internal' %}

{% if page_types_restricted %}
<p class="help-block help-warning">
{% blocktrans with type=page_type_names|join:", " count counter=page_type_names|length %}
{% blocktrans with type=page_type_name %}
Only pages of type "{{ type }}" may be chosen for this field. Search results will exclude pages of other types.
{% plural %}
Only the following page types may be chosen for this field: {{ type }}. Search results will exclude pages of other types.
{% endblocktrans %}
</p>
{% endif %}
Expand Down
44 changes: 11 additions & 33 deletions wagtail/wagtailadmin/tests/test_edit_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def test_page_chooser_uses_correct_widget(self):

def test_render_js_init(self):
result = self.page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent});'.format(
expected_js = 'createPageChooser("{id}", "{model}", {parent});'.format(
id="id_page", model="wagtailcore.page", parent=self.events_index_page.id)

self.assertIn(expected_js, result)
Expand Down Expand Up @@ -400,25 +400,11 @@ def test_override_page_type(self):
page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)

result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent});'.format(
expected_js = 'createPageChooser("{id}", "{model}", {parent});'.format(
id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

self.assertIn(expected_js, result)

def test_multiple_page_types(self):
# Model has a foreign key to Page, but we specify EventPage, and SimplePage in
# the PageChooserPanel to restrict the chooser to that page type
MyPageChooserPanel = PageChooserPanel('page', ['tests.EventPage', 'tests.SimplePage']).bind_to_model(EventPageChooserModel)
PageChooserForm = MyPageChooserPanel.get_form_class(EventPageChooserModel)
form = PageChooserForm(instance=self.test_instance)
page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)

result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model1}", "{model2}"], {parent});'.format(
id="id_page", model1="tests.eventpage", model2="tests.simplepage", parent=self.events_index_page.id)

self.assertIn(expected_js, result)

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
Expand All @@ -428,41 +414,33 @@ def test_autodetect_page_type(self):
page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)

result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", ["{model}"], {parent});'.format(
expected_js = 'createPageChooser("{id}", "{model}", {parent});'.format(
id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

self.assertIn(expected_js, result)

def test_target_content_types(self):
result = PageChooserPanel(
'barbecue',
'tests.simplepage'
).bind_to_model(PageChooserModel).target_content_types()
self.assertEqual(result[0].name, 'simple page')

def test_target_content_types_multiple_page_types(self):
def test_target_content_type(self):
result = PageChooserPanel(
'barbecue',
['tests.simplepage', 'tests.eventpage'],
).bind_to_model(PageChooserModel).target_content_types()
self.assertEqual(result[0].name, 'simple page')
self.assertEqual(result[1].name, 'event page')
'wagtailcore.site'
).bind_to_model(PageChooserModel).target_content_type()
self.assertEqual(result.name, 'Site')

def test_target_content_types_malformed_type(self):
def test_target_content_type_malformed_type(self):
result = PageChooserPanel(
'barbecue',
'snowman'
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
result.target_content_types)
result.target_content_type)

def test_target_content_types_nonexistent_type(self):
def test_target_content_type_nonexistent_type(self):
result = PageChooserPanel(
'barbecue',
'snowman.lorry'
).bind_to_model(PageChooserModel)
self.assertRaises(ImproperlyConfigured,
result.target_content_types)
result.target_content_type)


class TestInlinePanel(TestCase, WagtailTestUtils):
Expand Down
2 changes: 1 addition & 1 deletion wagtail/wagtailadmin/tests/test_page_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_search_no_results(self):
self.assertContains(response, "There are 0 matches")

def test_get_invalid(self):
response = self.search({'page_types': 'foo.bar'})
response = self.search({'page_type': 'foo.bar'})
self.assertEqual(response.status_code, 404)


Expand Down
61 changes: 23 additions & 38 deletions wagtail/wagtailadmin/views/chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
from wagtail.wagtailadmin.modal_workflow import render_modal_workflow
from wagtail.wagtailadmin.forms import SearchForm, ExternalLinkChooserForm, ExternalLinkChooserWithLinkTextForm, EmailLinkChooserForm, EmailLinkChooserWithLinkTextForm

from wagtail.wagtailcore.utils import resolve_model_string
from wagtail.wagtailcore.models import Page


def get_querystring(request):
return urlencode({
'page_types': request.GET.get('page_types', ''),
'page_type': request.GET.get('page_type', ''),
'allow_external_link': request.GET.get('allow_external_link', ''),
'allow_email_link': request.GET.get('allow_email_link', ''),
'prompt_for_link_text': request.GET.get('prompt_for_link_text', ''),
Expand All @@ -33,27 +32,25 @@ def shared_context(request, extra_context={}):
def browse(request, parent_page_id=None):
ITEMS_PER_PAGE = 25

page_types = request.GET.get('page_types', 'wagtailcore.page').split(',')
page_type = request.GET.get('page_type') or 'wagtailcore.page'
content_type_app_name, content_type_model_name = page_type.split('.')

desired_classes = []
for page_type in page_types:
try:
content_type = resolve_model_string(page_type)
except LookupError:
raise Http404

desired_classes.append(content_type)
try:
content_type = ContentType.objects.get_by_natural_key(content_type_app_name, content_type_model_name)
except ContentType.DoesNotExist:
raise Http404
desired_class = content_type.model_class()

if parent_page_id:
parent_page = get_object_or_404(Page, id=parent_page_id)
else:
parent_page = Page.get_first_root_node()

parent_page.can_choose = issubclass(parent_page.specific_class, tuple(desired_classes))
parent_page.can_choose = issubclass(parent_page.specific_class, desired_class)
search_form = SearchForm()
pages = parent_page.get_children()

if desired_classes == [Page]:
if desired_class == Page:
# apply pagination first, since we know that the page listing won't
# have to be filtered, and that saves us walking the entire list
p = request.GET.get('p', 1)
Expand All @@ -76,7 +73,7 @@ def browse(request, parent_page_id=None):

shown_pages = []
for page in pages:
page.can_choose = issubclass(page.specific_class or Page, tuple(desired_classes))
page.can_choose = issubclass(page.specific_class or Page, desired_class)
page.can_descend = page.get_children_count()

if page.can_choose or page.can_descend:
Expand All @@ -99,42 +96,30 @@ def browse(request, parent_page_id=None):
'parent_page': parent_page,
'pages': pages,
'search_form': search_form,
'page_type_string': ','.join(page_types),
'page_type_names': [desired_class.get_verbose_name() for desired_class in desired_classes],
'page_type_string': page_type,
'page_type_name': desired_class.get_verbose_name(),
'page_types_restricted': (page_type != 'wagtailcore.page')
})
)


def search(request, parent_page_id=None):
page_types = request.GET.get('page_types')
content_types = []
page_type = request.GET.get('page_type') or 'wagtailcore.page'
content_type_app_name, content_type_model_name = page_type.split('.')

# Convert page_types string into list of ContentType objects
if page_types:
try:
content_types = ContentType.objects.get_for_models(*[
resolve_model_string(page_type) for page_type in page_types.split(',')])
except LookupError:
raise Http404
try:
content_type = ContentType.objects.get_by_natural_key(content_type_app_name, content_type_model_name)
except ContentType.DoesNotExist:
raise Http404
desired_class = content_type.model_class()

search_form = SearchForm(request.GET)
if search_form.is_valid() and search_form.cleaned_data['q']:
pages = Page.objects.exclude(
pages = desired_class.objects.exclude(
depth=1 # never include root
)

# Restrict content types
if content_types:
pages = pages.filter(content_type__in=content_types)

# Do search
pages = pages.filter(title__icontains=search_form.cleaned_data['q'])

# Truncate results
pages = pages[:10]
).filter(title__icontains=search_form.cleaned_data['q'])[:10]
else:
pages = Page.objects.none()
pages = desired_class.objects.none()

shown_pages = []
for page in pages:
Expand Down
31 changes: 10 additions & 21 deletions wagtail/wagtailadmin/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,17 @@ def __init__(self, **kwargs):


class AdminPageChooser(AdminChooser):
target_content_types = None
target_content_type = None
choose_one_text = _('Choose a page')
choose_another_text = _('Choose another page')
link_to_chosen_text = _('Edit this page')

def __init__(self, content_types=None, **kwargs):
def __init__(self, content_type=None, **kwargs):
super(AdminPageChooser, self).__init__(**kwargs)
self.target_content_types = content_types or [ContentType.objects.get_for_model(Page)]
self.target_content_type = content_type or ContentType.objects.get_for_model(Page)

def render_html(self, name, value, attrs):
if len(self.target_content_types) == 1:
model_class = self.target_content_types[0].model_class()
else:
model_class = Page

model_class = self.target_content_type.model_class()
instance, value = self.get_instance_and_id(model_class, value)

original_field_html = super(AdminPageChooser, self).render_html(name, value, attrs)
Expand All @@ -138,24 +134,17 @@ def render_html(self, name, value, attrs):
})

def render_js_init(self, id_, name, value):
if isinstance(value, Page):
model_class = self.target_content_type.model_class()
if isinstance(value, model_class):
page = value
else:
if len(self.target_content_types) == 1:
model_class = self.target_content_types[0].model_class()
else:
model_class = Page

page = self.get_instance(model_class, value)

parent = page.get_parent() if page else None
content_type = self.target_content_type

return "createPageChooser({id}, {content_type}, {parent});".format(
id=json.dumps(id_),
content_type=json.dumps([
'{app}.{model}'.format(
app=content_type.app_label,
model=content_type.model)
for content_type in self.target_content_types
]),
content_type=json.dumps('{app}.{model}'.format(
app=content_type.app_label,
model=content_type.model)),
parent=json.dumps(parent.id if parent else None))

0 comments on commit 85707d5

Please sign in to comment.