Move wagtailsettings into contrib module

The `wagtailsettings` module is useful enough that it should be included
in the Wagtail contrib section, to make it available to all Wagtail
developers.

All the code has been given a once-over to make sure it is nice and
polished before being copied in. As such, this is not a direct copy of
the `wagtailsettings` module. It should be backwards compatible though,
excepting the new location.

It has been moved to `wagtail.contrib.settings`, following the naming
scheme set out in #1504.

Documentation has been concatenated in to a single page, and added to
the contrib reference section.
This commit is contained in:
Tim Heap 2015-09-30 15:28:33 +10:00
parent 2eb4b4c2c1
commit 3d494fb24b
23 changed files with 802 additions and 2 deletions

View file

@ -1,3 +1,5 @@
.. _styleguide:
UI Styleguide
=============
@ -15,4 +17,4 @@ To install the styleguide module on your site, add it to the list of ``INSTALLED
At present the styleguide is static: new UI components must be added to it manually, and there are no hooks into it for other modules to use. We hope to support hooks in the future.
The styleguide doesn't currently provide examples of all the core interface components; notably the Page, Document, Image and Snippet chooser interfaces are not currently represented.
The styleguide doesn't currently provide examples of all the core interface components; notably the Page, Document, Image and Snippet chooser interfaces are not currently represented.

View file

@ -7,6 +7,7 @@ Wagtail ships with a variety of extra optional modules.
.. toctree::
:maxdepth: 2
settings
forms
staticsitegen
sitemaps
@ -16,6 +17,12 @@ Wagtail ships with a variety of extra optional modules.
searchpromotions
:doc:`settings`
---------------
Site-wide settings that are editable by administrators in the Wagtail admin.
:doc:`forms`
------------

View file

@ -0,0 +1,128 @@
.. _settings:
=============
Site settings
=============
You can define settings for your site that are editable by administrators in the Wagtail admin. These settings can be accessed in code, as well as in templates.
To use these settings, you must add ``wagtail.contrib.settings`` to your ``INSTALLED_APPS``:
.. code-block:: python
INSTALLED_APPS += [
'wagtail.contrib.settings',
]
Defining settings
=================
Create a model that inherits from ``BaseSetting``, and register it using the ``register_setting`` decorator:
.. code-block:: python
from wagtail.contrib.settings.models import BaseSetting, register_setting
@register_setting
class SocialMediaSettings(BaseSetting):
facebook = models.URLField(
help_text='Your Facebook page URL')
instagram = models.CharField(
max_length=255, help_text='Your Instagram username, without the @')
trip_advisor = models.URLField(
help_text='Your Trip Advisor page URL')
youtube = models.URLField(
help_text='Your YouTube channel or user account URL')
A 'Social media settings' link will appear in the Wagtail admin 'Settings' menu.
Edit handlers
-------------
Settings use edit handlers much like the rest of Wagtail. Add a ``panels`` setting to your model defining all the edit handlers required:
.. code-block:: python
@register_setting
class ImportantPages(BaseSetting):
donate_page = models.ForeignKey(
'wagtailcore.Page', null=True, on_delete=models.SET_NULL)
sign_up_page = models.ForeignKey(
'wagtailcore.Page', null=True, on_delete=models.SET_NULL)
panels = [
PageChooserPanel('donate_page'),
PageChooserPanel('sign_up_page'),
]
Appearance
----------
You can change the label used in the menu by changing the `verbose_name <https://docs.djangoproject.com/en/dev/ref/models/options/#verbose-name>`_ of your model.
You can add an icon to the menu by passing an 'icon' argument to the ``register_setting`` decorator:
.. code-block:: python
@register_setting(icon='icon-placeholder')
class SocialMediaSettings(BaseSetting):
class Meta:
verbose_name = 'Social media accounts'
...
For a list of all available icons, please see the :ref:`styleguide`.
Using the settings
==================
Settings are designed to be used both in Python code, and in templates.
Using in Python
---------------
If access to a setting is required in the code, the :func:`~wagtail.contrib.settings.models.BaseSetting.for_site` method will retrieve the setting for the supplied site:
.. code-block:: python
def view(request):
social_media_settings = SocialMediaSettings.for_site(request.site)
...
Using in templates
------------------
Add the ``request`` and ``settings`` context processors to your settings:
.. code-block:: python
from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + [
'django.core.context_processors.request',
'wagtail.contrib.settings.context_processors.settings',
]
Then access the settings through ``{{ settings }}``:
.. code-block:: html+django
{{ settings.app_label.SocialMediaSettings.instagram }}
If you are not in a ``RequestContext``, then context processors will not have run, and the ``settings`` variable will not be availble. To get the ``settings``, use the provided ``{% get_settings %}`` template tag. If a ``request`` is in the template context, but for some reason it is not a ``RequestContext``, just use ``{{ get_settings }}``:
.. code-block:: html+django
{% load wagtailsettings_tags %}
{% get_settings %}
{{ settings.app_label.SocialMediaSettings.instagram }}
If there is no ``request`` available in the template at all, you can use the settings for the default Wagtail site instead:
.. code-block:: html+django
{% load wagtailsettings_tags %}
{% get_settings use_default_site=True %}
{{ settings.app_label.SocialMediaSettings.instagram }}
.. note:: You can not reliably get the correct settings instance for the current site from this template tag if the request object is not available. This is only relevant for multisite instances of Wagtail.

View file

View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class WagtailSettingsAppConfig(AppConfig):
name = 'wagtail.contrib.settings'
label = 'wagtailsettings'
verbose_name = "Wagtail site settings"

View file

@ -0,0 +1,56 @@
from django.utils.encoding import python_2_unicode_compatible
from .registry import registry
@python_2_unicode_compatible
class SettingsProxy(dict):
"""
Get a SettingModuleProxy for an app using proxy['app_label']
"""
def __init__(self, site):
self.site = site
def __missing__(self, app_label):
self[app_label] = value = SettingModuleProxy(self.site, app_label)
return value
def __str__(self):
return 'SettingsProxy'
@python_2_unicode_compatible
class SettingModuleProxy(dict):
"""
Get a setting instance using proxy['modelname']
"""
def __init__(self, site, app_label):
self.site = site
self.app_label = app_label
def __getitem__(self, model_name):
""" Get a setting instance for a model """
# Model names are treated as case-insensitive
return super(SettingModuleProxy, self).__getitem__(model_name.lower())
def __missing__(self, model_name):
""" Get and cache settings that have not been looked up yet """
self[model_name] = value = self.get_setting(model_name)
return value
def get_setting(self, model_name):
"""
Get a setting instance
"""
Model = registry.get_by_natural_key(self.app_label, model_name)
if Model is None:
return None
return Model.for_site(self.site)
def __str__(self):
return 'SettingsModuleProxy({0})'.format(self.app_label)
def settings(request):
return {'settings': SettingsProxy(request.site)}

View file

@ -0,0 +1,25 @@
from django.db import models
from .registry import register_setting
__all__ = ['BaseSetting', 'register_setting']
class BaseSetting(models.Model):
"""
The abstract base model for settings. Subclasses must be registered using
:func:`~wagtail.contrib.settings.registry.register_setting`
"""
site = models.OneToOneField(
'wagtailcore.Site', unique=True, db_index=True, editable=False)
class Meta:
abstract = True
@classmethod
def for_site(cls, site):
"""
Get an instance of this setting for the site.
"""
instance, created = cls.objects.get_or_create(site=site)
return instance

View file

@ -0,0 +1,19 @@
from django.contrib.contenttypes.models import ContentType
def user_can_edit_setting_type(user, model):
""" Check if a user has any permission related to a content type """
if not user.is_active:
return False
if user.is_superuser:
return True
content_type = ContentType.objects.get_for_model(model)
permission_codenames = content_type.permission_set.values_list('codename', flat=True)
for codename in permission_codenames:
permission_name = "%s.%s" % (content_type.app_label, codename)
if user.has_perm(permission_name):
return True
return False

View file

@ -0,0 +1,62 @@
from django.apps import apps
from django.core.urlresolvers import reverse
from django.utils.text import capfirst
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailcore import hooks
class Registry(list):
def register(self, model, icon='cog', **kwargs):
"""
Register a model as a setting, adding it to the wagtail admin menu
"""
# Don't bother registering this if it is already registered
if model in self:
return model
self.append(model)
icon_classes = 'icon icon-' + icon
if 'classnames' in kwargs:
kwargs['classnames'] += ' ' + icon_classes
else:
kwargs['classnames'] = icon_classes
# Register a new menu item in the settings menu
@hooks.register('register_settings_menu_item')
def hook():
return MenuItem(
capfirst(model._meta.verbose_name),
reverse('wagtailsettings_edit', args=[
model._meta.app_label, model._meta.model_name]),
**kwargs)
return model
def register_decorator(self, model=None, **kwargs):
"""
Register a model as a setting in the Wagtail admin
"""
if model is None:
return lambda model: self.register(model, **kwargs)
return self.register(model, **kwargs)
def get_by_natural_key(self, app_label, model_name):
"""
Get a setting model using its app_label and model_name.
If the app_label.model_name combination is not a valid model, or the
model is not registered as a setting, returns None.
"""
try:
Model = apps.get_model(app_label, model_name)
except LookupError:
return None
if Model not in registry:
return None
return Model
registry = Registry()
register_setting = registry.register_decorator

View file

@ -0,0 +1,29 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% blocktrans %}Editing {{ setting_type_name}} - {{ instance }}{% endblocktrans %}{% endblock %}
{% block bodyclass %}menu-settings{% endblock %}
{% block content %}
{% trans "Editing" as editing_str %}
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=setting_type_name|capfirst icon="cogs" %}
<form action="{% url 'wagtailsettings_edit' opts.app_label opts.model_name %}" method="POST">
{% csrf_token %}
{{ edit_handler.render_form_content }}
<footer>
<ul>
<li class="actions">
<input type="submit" value="{% trans 'Save' %}" class="button" />
</li>
</ul>
</footer>
</form>
{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% trans "Settings" %}{% endblock %}
{% block bodyclass %}menu-settings{% endblock %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title="Settings" icon="cogs" %}
<div class="nice-padding">
<ul class="listing">
{% for name, description, content_type in setting_types %}
<li>
<div class="row row-flush title">
<h2>
<a href="{% url 'wagtailsettings_edit' content_type.app_label content_type.model %}" class="col6">
{{ name|capfirst }}
</a>
</h2>
<small class="col6">{{ description }}</small>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
from django.template import Library
from wagtail.wagtailcore.models import Site
from ..context_processors import SettingsProxy
register = Library()
@register.simple_tag(takes_context=True)
def get_settings(context, use_default_site=False):
if use_default_site:
site = Site.objects.get(is_default_site=True)
elif 'request' in context:
site = context['request'].site
else:
raise RuntimeError('No request found in context, and use_default_site '
'flag not set')
context['settings'] = SettingsProxy(site)
return ''

View file

@ -0,0 +1,85 @@
from django.core.urlresolvers import reverse
from django.test import TestCase
from wagtail.tests.testapp.models import TestSetting
from wagtail.tests.utils import WagtailTestUtils
from wagtail.wagtailcore.models import Site
class TestSettingCreateView(TestCase, WagtailTestUtils):
def setUp(self):
self.login()
def get(self, params={}):
return self.client.get(
reverse('wagtailsettings_edit', args=('tests', 'testsetting')),
params)
def post(self, post_data={}):
return self.client.post(
reverse('wagtailsettings_edit', args=('tests', 'testsetting')),
post_data)
def test_status_code(self):
self.assertEqual(self.get().status_code, 200)
def test_edit_invalid(self):
response = self.post(post_data={'foo': 'bar'})
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(response, "This field is required.")
def test_edit(self):
response = self.post(post_data={'title': 'Edited site title',
'email': 'test@example.com'})
self.assertEqual(response.status_code, 302)
default_site = Site.objects.get(is_default_site=True)
setting = TestSetting.objects.get(site=default_site)
self.assertEqual(setting.title, 'Edited site title')
self.assertEqual(setting.email, 'test@example.com')
class TestSettingEditView(TestCase, WagtailTestUtils):
def setUp(self):
default_site = Site.objects.get(is_default_site=True)
self.test_setting = TestSetting()
self.test_setting.title = 'Site title'
self.test_setting.email = 'initial@example.com'
self.test_setting.site = default_site
self.test_setting.save()
self.login()
def get(self, params={}):
return self.client.get(
reverse('wagtailsettings_edit', args=('tests', 'testsetting')),
params)
def post(self, post_data={}):
return self.client.post(
reverse('wagtailsettings_edit', args=('tests', 'testsetting')),
post_data)
def test_status_code(self):
self.assertEqual(self.get().status_code, 200)
def test_non_existant_model(self):
response = self.client.get(
reverse('wagtailsettings_edit', args=('tests', 'foo')))
self.assertEqual(response.status_code, 404)
def test_edit_invalid(self):
response = self.post(post_data={'foo': 'bar'})
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(response, "This field is required.")
def test_edit(self):
response = self.post(post_data={'title': 'Edited site title',
'email': 'test@example.com'})
self.assertEqual(response.status_code, 302)
default_site = Site.objects.get(is_default_site=True)
setting = TestSetting.objects.get(site=default_site)
self.assertEqual(setting.title, 'Edited site title')
self.assertEqual(setting.email, 'test@example.com')

View file

@ -0,0 +1,22 @@
from django.core.urlresolvers import reverse
from django.test import TestCase
from wagtail.contrib.settings.registry import Registry
from wagtail.tests.testapp.models import NotYetRegisteredSetting
from wagtail.tests.utils import WagtailTestUtils
class TestRegister(TestCase, WagtailTestUtils):
def setUp(self):
self.registry = Registry()
self.login()
def test_register(self):
self.assertNotIn(NotYetRegisteredSetting, self.registry)
NowRegisteredSetting = self.registry.register_decorator(NotYetRegisteredSetting)
self.assertIn(NotYetRegisteredSetting, self.registry)
self.assertIs(NowRegisteredSetting, NotYetRegisteredSetting)
def test_icon(self):
admin = self.client.get(reverse('wagtailadmin_home'))
self.assertContains(admin, 'icon icon-tag')

View file

@ -0,0 +1,153 @@
from django.template import Context, RequestContext, Template
from django.test import TestCase
from wagtail.tests.testapp.models import TestSetting
from wagtail.tests.utils import WagtailTestUtils
from wagtail.wagtailcore.models import Page, Site
class TemplateTestCase(TestCase, WagtailTestUtils):
def setUp(self):
root = Page.objects.first()
other_home = Page(title='Other Root', slug='other')
root.add_child(instance=other_home)
self.default_site = Site.objects.get(is_default_site=True)
self.other_site = Site.objects.create(hostname='other', root_page=other_home)
self.test_setting = TestSetting.objects.create(
title='Site title',
email='initial@example.com',
site=self.default_site)
self.other_setting = TestSetting.objects.create(
title='Other title',
email='other@example.com',
site=self.other_site)
def get_request(self, site=None):
if site is None:
site = self.default_site
request = self.client.get('/test/', HTTP_HOST=site.hostname)
request.site = site
return request
def render(self, request, string, context=None, site=None):
template = Template(string)
context = RequestContext(request, context)
return template.render(context)
class TestContextProcessor(TemplateTestCase):
def test_accessing_setting(self):
""" Check that the context processor works """
request = self.get_request()
self.assertEqual(
self.render(request, '{{ settings.tests.TestSetting.title }}'),
self.test_setting.title)
def test_multisite(self):
""" Check that the correct setting for the current site is returned """
request = self.get_request(site=self.default_site)
self.assertEqual(
self.render(request, '{{ settings.tests.TestSetting.title }}'),
self.test_setting.title)
request = self.get_request(site=self.other_site)
self.assertEqual(
self.render(request, '{{ settings.tests.TestSetting.title }}'),
self.other_setting.title)
def test_model_case_insensitive(self):
""" Model names should be case insensitive """
request = self.get_request()
self.assertEqual(
self.render(request, '{{ settings.tests.testsetting.title }}'),
self.test_setting.title)
self.assertEqual(
self.render(request, '{{ settings.tests.TESTSETTING.title }}'),
self.test_setting.title)
self.assertEqual(
self.render(request, '{{ settings.tests.TestSetting.title }}'),
self.test_setting.title)
self.assertEqual(
self.render(request, '{{ settings.tests.tEstsEttIng.title }}'),
self.test_setting.title)
def test_models_cached(self):
""" Accessing a setting should only hit the DB once per render """
request = self.get_request()
get_title = '{{ settings.tests.testsetting.title }}'
for i in range(1, 4):
with self.assertNumQueries(1):
self.assertEqual(
self.render(request, get_title * i),
self.test_setting.title * i)
class TestTemplateTag(TemplateTestCase):
def test_no_context_processor(self):
"""
Assert that not running the context processor means settings are not in
the context, as expected.
"""
template = Template('{{ settings.tests.TestSetting.title }}')
context = Context()
self.assertEqual(template.render(context), '')
def test_get_settings_request_context(self):
""" Check that the {% get_settings %} tag works """
request = self.get_request(site=self.other_site)
context = Context({'request': request})
# This should use the site in the request
template = Template('{% load wagtailsettings_tags %}'
'{% get_settings %}'
'{{ settings.tests.testsetting.title}}')
self.assertEqual(template.render(context), self.other_setting.title)
def test_get_settings_request_context_use_default(self):
"""
Check that the {% get_settings use_default_site=True %} option
overrides a request in the context.
"""
request = self.get_request(site=self.other_site)
context = Context({'request': request})
# This should use the default site, ignoring the site in the request
template = Template('{% load wagtailsettings_tags %}'
'{% get_settings use_default_site=True %}'
'{{ settings.tests.testsetting.title}}')
self.assertEqual(template.render(context), self.test_setting.title)
def test_get_settings_use_default(self):
"""
Check that the {% get_settings use_default_site=True %} option works
"""
context = Context()
# This should use the default site
template = Template('{% load wagtailsettings_tags %}'
'{% get_settings use_default_site=True %}'
'{{ settings.tests.testsetting.title}}')
self.assertEqual(template.render(context), self.test_setting.title)
def test_get_settings_no_request_no_default(self):
"""
Check that the {% get_settings %} throws an error if it can not find a
site to work with
"""
context = Context()
# Without a request in the context, and without use_default_site, this
# should bail with an error
template = Template('{% load wagtailsettings_tags %}'
'{% get_settings %}'
'{{ settings.tests.testsetting.title}}')
with self.assertRaises(RuntimeError):
template.render(context)

View file

@ -0,0 +1,7 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^(\w+)/(\w+)/$', views.edit, name='wagtailsettings_edit'),
]

View file

@ -0,0 +1,72 @@
from django.contrib.auth.decorators import permission_required
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.shortcuts import redirect, render
from django.utils.lru_cache import lru_cache
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
from wagtail.wagtailadmin import messages
from wagtail.wagtailadmin.edit_handlers import (
ObjectList, extract_panel_definitions_from_model_class)
from .permissions import user_can_edit_setting_type
from .registry import registry
def get_model_from_url_params(app_name, model_name):
"""
retrieve a content type from an app_name / model_name combo.
Throw Http404 if not a valid setting type
"""
model = registry.get_by_natural_key(app_name, model_name)
if model is None:
raise Http404
return model
@lru_cache()
def get_setting_edit_handler(model):
panels = extract_panel_definitions_from_model_class(model, ['site'])
return ObjectList(panels).bind_to_model(model)
@permission_required('wagtailadmin.access_admin') # further permissions are enforced within the view
def edit(request, app_name, model_name):
model = get_model_from_url_params(app_name, model_name)
if not user_can_edit_setting_type(request.user, model):
raise PermissionDenied
setting_type_name = model._meta.verbose_name
instance = model.for_site(request.site)
edit_handler_class = get_setting_edit_handler(model)
form_class = edit_handler_class.get_form_class(model)
if request.POST:
form = form_class(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
messages.success(
request,
_("{setting_type} updated.").format(
setting_type=capfirst(setting_type_name),
instance=instance
)
)
return redirect('wagtailsettings_edit', app_name, model_name)
else:
messages.error(request, _("The setting could not be saved due to errors."))
edit_handler = edit_handler_class(instance=instance, form=form)
else:
form = form_class(instance=instance)
edit_handler = edit_handler_class(instance=instance, form=form)
return render(request, 'wagtailsettings/edit.html', {
'opts': model._meta,
'setting_type_name': setting_type_name,
'instance': instance,
'edit_handler': edit_handler,
})

View file

@ -0,0 +1,12 @@
from django.conf.urls import include, url
from wagtail.wagtailcore import hooks
from . import urls
@hooks.register('register_admin_urls')
def register_admin_urls():
return [
url(r'^settings/', include(urls)),
]

View file

@ -53,6 +53,7 @@ if django.VERSION >= (1, 8):
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
'wagtail.tests.context_processors.do_not_use_static_url',
'wagtail.contrib.settings.context_processors.settings',
],
},
},
@ -72,6 +73,7 @@ else:
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.request',
'wagtail.tests.context_processors.do_not_use_static_url',
'wagtail.contrib.settings.context_processors.settings',
)
MIDDLEWARE_CLASSES = (
@ -106,6 +108,7 @@ INSTALLED_APPS = (
'wagtail.contrib.wagtailfrontendcache',
'wagtail.contrib.wagtailapi',
'wagtail.contrib.wagtailsearchpromotions',
'wagtail.contrib.settings',
'wagtail.wagtailforms',
'wagtail.wagtailsearch',
'wagtail.wagtailembeds',

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0019_verbose_names_cleanup'),
('tests', '0012_filepage'),
]
operations = [
migrations.CreateModel(
name='IconSetting',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('site', models.OneToOneField(editable=False, to='wagtailcore.Site')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='NotYetRegisteredSetting',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('site', models.OneToOneField(editable=False, to='wagtailcore.Site')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TestSetting',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=100)),
('email', models.EmailField(max_length=50)),
('site', models.OneToOneField(editable=False, to='wagtailcore.Site')),
],
options={
'abstract': False,
},
),
]

View file

@ -11,6 +11,7 @@ from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from modelcluster.contrib.taggit import ClusterTaggableManager
from wagtail.contrib.settings.models import BaseSetting, register_setting
from wagtail.wagtailcore.models import Page, Orderable
from wagtail.wagtailcore.fields import RichTextField, StreamField
from wagtail.wagtailcore.blocks import CharBlock, RichTextBlock
@ -319,7 +320,6 @@ FormPage.content_panels = [
]
# Snippets
class AdvertPlacement(models.Model):
page = ParentalKey('wagtailcore.Page', related_name='advert_placements')
@ -378,6 +378,7 @@ StandardChild.edit_handler = TabbedInterface([
ObjectList([], heading='Dinosaurs'),
])
class BusinessIndex(Page):
""" Can be placed anywhere, can only have Business children """
subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex']
@ -411,9 +412,11 @@ TaggedPage.content_panels = [
class PageChooserModel(models.Model):
page = models.ForeignKey('wagtailcore.Page', help_text='help text')
class EventPageChooserModel(models.Model):
page = models.ForeignKey('tests.EventPage', help_text='more help text')
class SnippetChooserModel(models.Model):
advert = models.ForeignKey(Advert, help_text='help text')
@ -461,3 +464,18 @@ class MTIChildPage(MTIBasePage):
class AbstractPage(Page):
class Meta:
abstract = True
@register_setting
class TestSetting(BaseSetting):
title = models.CharField(max_length=100)
email = models.EmailField(max_length=50)
@register_setting(icon="tag")
class IconSetting(BaseSetting):
pass
class NotYetRegisteredSetting(BaseSetting):
pass