0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Merge branch 'takeflight-merge-settings'

This commit is contained in:
Matt Westcott 2015-10-23 11:54:47 +01:00
commit dc02375cc8
29 changed files with 1019 additions and 2 deletions

View File

@ -4,6 +4,7 @@ Changelog
1.2 (xx.xx.xxxx)
~~~~~~~~~~~~~~~~
* Added `wagtail.contrib.settings`, a module to allow administrators to edit site-specific settings
* Core templatetags (pageurl, image, wagtailuserbar, etc) are now compatible with Jinja2
* Image and document models now provide a `search` method on their QuerySets
* Search methods now accept an `operator` argument to determine whether multiple terms are ORed or ANDed together

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

@ -10,6 +10,14 @@ Wagtail 1.2 release notes - IN DEVELOPMENT
What's new
==========
Site settings module
~~~~~~~~~~~~~~~~~~~~
Wagtail now includes a contrib module (previously available as the `wagtailsettings <https://pypi.python.org/pypi/wagtailsettings/>`_ package) to allow administrators to edit site-specific settings.
See: :doc:`/reference/contrib/settings`
Jinja2 support
~~~~~~~~~~~~~~

View File

@ -54,6 +54,9 @@ var apps = [
'wagtailstyleguide/scss/styleguide.scss'
],
}),
new App('wagtail/contrib/settings', {
'appName': 'wagtailsettings',
}),
];
module.exports = {

1
wagtail/contrib/settings/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
static/

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,27 @@
from __future__ import absolute_import, unicode_literals
from django import forms
from django.core.urlresolvers import reverse
from wagtail.wagtailcore.models import Site
class SiteSwitchForm(forms.Form):
site = forms.ChoiceField(choices=[])
class Media:
js = [
'wagtailsettings/js/site-switcher.js',
]
def __init__(self, current_site, model, **kwargs):
initial_data = {'site': self.get_change_url(current_site, model)}
super(SiteSwitchForm, self).__init__(initial=initial_data, **kwargs)
sites = [(self.get_change_url(site, model), site)
for site in Site.objects.all()]
self.fields['site'].choices = sites
@classmethod
def get_change_url(cls, site, model):
return reverse('wagtailsettings:edit', args=[
site.pk, model._meta.app_label, model._meta.model_name])

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,4 @@
def user_can_edit_setting_type(user, model):
""" Check if a user has permission to edit this setting type """
return user.has_perm("{}.change_{}".format(
model._meta.app_label, model._meta.model_name))

View File

@ -0,0 +1,81 @@
from django.apps import apps
from django.contrib.auth.models import Permission
from django.core.urlresolvers import reverse
from django.utils.text import capfirst
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailcore import hooks
from .permissions import user_can_edit_setting_type
class SettingMenuItem(MenuItem):
def __init__(self, model, icon='cog', classnames='', **kwargs):
icon_classes = 'icon icon-' + icon
if classnames:
classnames += ' ' + icon_classes
else:
classnames = icon_classes
self.model = model
super(SettingMenuItem, self).__init__(
label=capfirst(model._meta.verbose_name),
url=reverse('wagtailsettings:edit', args=[
model._meta.app_label, model._meta.model_name]),
classnames=classnames,
**kwargs)
def is_shown(self, request):
return user_can_edit_setting_type(request.user, self.model)
class Registry(list):
def register(self, model, **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)
# Register a new menu item in the settings menu
@hooks.register('register_settings_menu_item')
def menu_hook():
return SettingMenuItem(model, **kwargs)
@hooks.register('register_permissions')
def permissions_hook():
return Permission.objects.filter(
content_type__app_label=model._meta.app_label,
codename='change_{}'.format(model._meta.model_name))
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,12 @@
$(function() {
var $switcher = $('form#settings-site-switch select');
if (!$switcher.length) return;
var initial = $switcher.val();
$switcher.on('change', function() {
var url = $switcher.val();
if (url != initial) {
window.location = url;
}
});
});

View File

@ -0,0 +1,53 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{% blocktrans %}Editing {{ setting_type_name}} - {{ instance }}{% endblocktrans %}{% endblock %}
{% block bodyclass %}menu-settings{% endblock %}
{% block content %}
<header class="nice-padding">
<div class="row">
<div class="left">
<div class="col">
<h1 class="icon icon-cogs">
{% trans "Editing" %}
<span>{{ setting_type_name|capfirst }}</span>
</h1>
</div>
</div>
<div class="right">
{% if site_switcher %}
<form method="get" id="settings-site-switch">
<label for="{{ site_switcher.site.id_for_label }}">
Site:
</label>
{{ site_switcher.site }}
</form>
{% endif %}
</div>
</div>
</header>
<form action="{% url 'wagtailsettings:edit' site.pk 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" %}
{{ form.media.css }}
{{ site_switcher.media.css }}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ form.media.js }}
{{ site_switcher.media.js }}
{% 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,206 @@
from __future__ import absolute_import, unicode_literals
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils.text import capfirst
from wagtail.contrib.settings.registry import SettingMenuItem
from wagtail.tests.testapp.models import IconSetting, TestSetting
from wagtail.tests.utils import WagtailTestUtils
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Page, Site
class TestSettingMenu(TestCase, WagtailTestUtils):
def login_only_admin(self):
""" Log in with a user that only has permission to access the admin """
user = get_user_model().objects.create_user(
username='test', email='test@email.com', password='password')
user.user_permissions.add(Permission.objects.get_by_natural_key(
codename='access_admin', app_label='wagtailadmin', model='admin'))
self.client.login(username='test', password='password')
return user
def test_menu_item_in_admin(self):
self.login()
response = self.client.get(reverse('wagtailadmin_home'))
self.assertContains(response, capfirst(TestSetting._meta.verbose_name))
self.assertContains(response, reverse('wagtailsettings:edit', args=('tests', 'testsetting')))
def test_menu_item_no_permissions(self):
self.login_only_admin()
response = self.client.get(reverse('wagtailadmin_home'))
self.assertNotContains(response, TestSetting._meta.verbose_name)
self.assertNotContains(response, reverse('wagtailsettings:edit', args=('tests', 'testsetting')))
def test_menu_item_icon(self):
menu_item = SettingMenuItem(IconSetting, icon='tag', classnames='test-class')
classnames = set(menu_item.classnames.split(' '))
self.assertEqual(classnames, {'icon', 'icon-tag', 'test-class'})
class BaseTestSettingView(TestCase, WagtailTestUtils):
def get(self, site_pk=1, params={}):
url = self.edit_url('tests', 'testsetting', site_pk=site_pk)
return self.client.get(url, params)
def post(self, site_pk=1, post_data={}):
url = self.edit_url('tests', 'testsetting', site_pk=site_pk)
return self.client.post(url, post_data)
def edit_url(self, app, model, site_pk=1):
return reverse('wagtailsettings:edit', args=[site_pk, app, model])
class TestSettingCreateView(BaseTestSettingView):
def setUp(self):
self.login()
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(BaseTestSettingView):
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 test_status_code(self):
self.assertEqual(self.get().status_code, 200)
def test_non_existant_model(self):
response = self.client.get(self.edit_url('test', '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')
class TestMultiSite(BaseTestSettingView):
def setUp(self):
self.default_site = Site.objects.get(is_default_site=True)
self.other_site = Site.objects.create(hostname='example.com', root_page=Page.objects.get(pk=2))
self.login()
def test_redirect_to_default(self):
"""
Should redirect to the setting for the default site.
"""
start_url = reverse('wagtailsettings:edit', args=[
'tests', 'testsetting'])
dest_url = 'http://testserver' + reverse('wagtailsettings:edit', args=[
self.default_site.pk, 'tests', 'testsetting'])
response = self.client.get(start_url, follow=True)
self.assertEqual([(dest_url, 302)], response.redirect_chain)
def test_redirect_to_current(self):
"""
Should redirect to the setting for the current site taken from the URL,
by default
"""
start_url = reverse('wagtailsettings:edit', args=[
'tests', 'testsetting'])
dest_url = 'http://example.com' + reverse('wagtailsettings:edit', args=[
self.other_site.pk, 'tests', 'testsetting'])
response = self.client.get(start_url, follow=True, HTTP_HOST=self.other_site.hostname)
self.assertEqual([(dest_url, 302)], response.redirect_chain)
def test_with_no_current_site(self):
"""
Redirection should not break if the current request does not correspond to a site
"""
self.default_site.is_default_site = False
self.default_site.save()
start_url = reverse('wagtailsettings:edit', args=[
'tests', 'testsetting'])
response = self.client.get(start_url, follow=True, HTTP_HOST="noneoftheabove.example.com")
self.assertEqual(302, response.redirect_chain[0][1])
def test_switcher(self):
""" Check that the switcher form exists in the page """
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'id="settings-site-switch"')
def test_unknown_site(self):
""" Check that unknown sites throw a 404 """
response = self.get(site_pk=3)
self.assertEqual(response.status_code, 404)
def test_edit(self):
"""
Check that editing settings in multi-site mode edits the correct
setting, and leaves the other ones alone
"""
TestSetting.objects.create(
title='default',
email='default@example.com',
site=self.default_site)
TestSetting.objects.create(
title='other',
email='other@example.com',
site=self.other_site)
response = self.post(site_pk=self.other_site.pk, post_data={
'title': 'other-new', 'email': 'other-other@example.com'})
self.assertEqual(response.status_code, 302)
# Check that the correct setting was updated
other_setting = TestSetting.for_site(self.other_site)
self.assertEqual(other_setting.title, 'other-new')
self.assertEqual(other_setting.email, 'other-other@example.com')
# Check that the other setting was not updated
default_setting = TestSetting.for_site(self.default_site)
self.assertEqual(default_setting.title, 'default')
self.assertEqual(default_setting.email, 'default@example.com')
class TestAdminPermission(TestCase, WagtailTestUtils):
def test_registered_permission(self):
permission = Permission.objects.get_by_natural_key(
app_label='tests', model='testsetting', codename='change_testsetting')
for fn in hooks.get_hooks('register_permissions'):
if permission in fn():
break
else:
self.fail('Change permission for tests.TestSetting not registered')

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,8 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^(\w+)/(\w+)/$', views.edit_current_site, name='edit'),
url(r'^(\d+)/(\w+)/(\w+)/$', views.edit, name='edit'),
]

View File

@ -0,0 +1,87 @@
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.shortcuts import redirect, render, get_object_or_404
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 wagtail.wagtailcore.models import Site
from .forms import SiteSwitchForm
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)
def edit_current_site(request, app_name, model_name):
# Redirect the user to the edit page for the current site
# (or the current request does not correspond to a site, the first site in the list)
site = request.site or Site.objects.first()
return redirect('wagtailsettings:edit', site.pk, app_name, model_name)
def edit(request, site_pk, 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
site = get_object_or_404(Site, pk=site_pk)
setting_type_name = model._meta.verbose_name
instance = model.for_site(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', site.pk, 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)
# Show a site switcher form if there are multiple sites
site_switcher = None
if Site.objects.count() > 1:
site_switcher = SiteSwitchForm(site, model)
return render(request, 'wagtailsettings/edit.html', {
'opts': model._meta,
'setting_type_name': setting_type_name,
'instance': instance,
'edit_handler': edit_handler,
'site': site,
'site_switcher': site_switcher,
})

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, namespace='wagtailsettings')),
]

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