mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-19 22:41:00 +00:00
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:
parent
2eb4b4c2c1
commit
3d494fb24b
23 changed files with 802 additions and 2 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
------------
|
||||
|
||||
|
|
|
|||
128
docs/reference/contrib/settings.rst
Normal file
128
docs/reference/contrib/settings.rst
Normal 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.
|
||||
0
wagtail/contrib/settings/__init__.py
Normal file
0
wagtail/contrib/settings/__init__.py
Normal file
7
wagtail/contrib/settings/apps.py
Normal file
7
wagtail/contrib/settings/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WagtailSettingsAppConfig(AppConfig):
|
||||
name = 'wagtail.contrib.settings'
|
||||
label = 'wagtailsettings'
|
||||
verbose_name = "Wagtail site settings"
|
||||
56
wagtail/contrib/settings/context_processors.py
Normal file
56
wagtail/contrib/settings/context_processors.py
Normal 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)}
|
||||
25
wagtail/contrib/settings/models.py
Normal file
25
wagtail/contrib/settings/models.py
Normal 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
|
||||
19
wagtail/contrib/settings/permissions.py
Normal file
19
wagtail/contrib/settings/permissions.py
Normal 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
|
||||
62
wagtail/contrib/settings/registry.py
Normal file
62
wagtail/contrib/settings/registry.py
Normal 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
|
||||
29
wagtail/contrib/settings/templates/wagtailsettings/edit.html
Normal file
29
wagtail/contrib/settings/templates/wagtailsettings/edit.html
Normal 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 %}
|
||||
|
|
@ -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 %}
|
||||
0
wagtail/contrib/settings/templatetags/__init__.py
Normal file
0
wagtail/contrib/settings/templatetags/__init__.py
Normal 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 ''
|
||||
0
wagtail/contrib/settings/tests/__init__.py
Normal file
0
wagtail/contrib/settings/tests/__init__.py
Normal file
85
wagtail/contrib/settings/tests/test_admin.py
Normal file
85
wagtail/contrib/settings/tests/test_admin.py
Normal 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')
|
||||
22
wagtail/contrib/settings/tests/test_register.py
Normal file
22
wagtail/contrib/settings/tests/test_register.py
Normal 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')
|
||||
153
wagtail/contrib/settings/tests/test_templates.py
Normal file
153
wagtail/contrib/settings/tests/test_templates.py
Normal 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)
|
||||
7
wagtail/contrib/settings/urls.py
Normal file
7
wagtail/contrib/settings/urls.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^(\w+)/(\w+)/$', views.edit, name='wagtailsettings_edit'),
|
||||
]
|
||||
72
wagtail/contrib/settings/views.py
Normal file
72
wagtail/contrib/settings/views.py
Normal 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,
|
||||
})
|
||||
12
wagtail/contrib/settings/wagtail_hooks.py
Normal file
12
wagtail/contrib/settings/wagtail_hooks.py
Normal 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)),
|
||||
]
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue