mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
Add wagtail.contrib.settings.jinja2tags (#2451)
This commit is contained in:
parent
eda39c85c8
commit
31d2d43d5b
@ -90,8 +90,8 @@ If access to a setting is required in the code, the :func:`~wagtail.contrib.sett
|
||||
social_media_settings = SocialMediaSettings.for_site(request.site)
|
||||
...
|
||||
|
||||
Using in templates
|
||||
------------------
|
||||
Using in Django templates
|
||||
-------------------------
|
||||
|
||||
Add the ``settings`` context processor to your settings:
|
||||
|
||||
@ -137,3 +137,54 @@ If there is no ``request`` available in the template at all, you can use the set
|
||||
{{ 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.
|
||||
|
||||
Using in Jinja2 templates
|
||||
-------------------------
|
||||
|
||||
Add ``wagtail.contrib.settings.jinja2tags.settings`` extension to your Jinja2 settings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
TEMPLATES = [
|
||||
# ...
|
||||
{
|
||||
'BACKEND': 'django.template.backends.jinja2.Jinja2',
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'extensions': [
|
||||
# ...
|
||||
'wagtail.contrib.settings.jinja2tags.settings',
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Then access the settings through the ``settings()`` template function:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{{ settings("app_label.SocialMediaSettings").twitter }}
|
||||
|
||||
(Replace ``app_label`` with the label of the app containing your settings model.)
|
||||
|
||||
This will look for a ``request`` variable in the template context, and find the correct site to use from that. If for some reason you do not have a ``request`` available, you can instead use the settings defined for the default site:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{{ settings("app_label.SocialMediaSettings", use_default_site=True).instagram }}
|
||||
|
||||
You can store the settings instance in a variable to save some typing, if you have to use multiple values from one model:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% with social_settings=settings("app_label.SocialMediaSettings") %}
|
||||
Follow us on Twitter at @{{ social_settings.twitter }},
|
||||
or Instagram at @{{ social_settings.Instagram }}.
|
||||
{% endwith %}
|
||||
|
||||
Or, alternately, using the ``set`` tag:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% set social_settings=settings("app_label.SocialMediaSettings") %}
|
||||
|
88
wagtail/contrib/settings/jinja2tags.py
Normal file
88
wagtail/contrib/settings/jinja2tags.py
Normal file
@ -0,0 +1,88 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
import jinja2
|
||||
from django.utils.encoding import force_str
|
||||
from jinja2.ext import Extension
|
||||
|
||||
from wagtail.contrib.settings.registry import registry
|
||||
from wagtail.wagtailcore.models import Site
|
||||
|
||||
# Settings are cached per template context, to prevent excessive database
|
||||
# lookups. The cached settings are disposed of once the template context is no
|
||||
# longer used.
|
||||
settings_cache = WeakKeyDictionary()
|
||||
|
||||
|
||||
class ContextCache(dict):
|
||||
"""
|
||||
A cache of Sites and their Settings for a template Context
|
||||
"""
|
||||
def __missing__(self, key):
|
||||
"""
|
||||
Make a SiteSetting for a new Site
|
||||
"""
|
||||
if not(isinstance(key, Site)):
|
||||
raise TypeError
|
||||
out = self[key] = SiteSettings(key)
|
||||
return out
|
||||
|
||||
|
||||
class SiteSettings(dict):
|
||||
"""
|
||||
A cache of Settings for a specific Site
|
||||
"""
|
||||
def __init__(self, site):
|
||||
super(SiteSettings, self).__init__()
|
||||
self.site = site
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Normalise all keys to lowercase
|
||||
return super(SiteSettings, self).__getitem__(force_str(key).lower())
|
||||
|
||||
def __missing__(self, key):
|
||||
"""
|
||||
Get the settings instance for this site, and store it for later
|
||||
"""
|
||||
try:
|
||||
app_label, model_name = key.split('.', 1)
|
||||
except ValueError:
|
||||
raise KeyError('Invalid model name: {}'.format(key))
|
||||
Model = registry.get_by_natural_key(app_label, model_name)
|
||||
if Model is None:
|
||||
raise KeyError('Unknown setting: {}'.format(key))
|
||||
|
||||
out = self[key] = Model.for_site(self.site)
|
||||
return out
|
||||
|
||||
|
||||
@jinja2.contextfunction
|
||||
def get_setting(context, model_string, 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')
|
||||
|
||||
# Sadly, WeakKeyDictionary can not implement __missing__, so we have to do
|
||||
# this one manually
|
||||
try:
|
||||
context_cache = settings_cache[context]
|
||||
except KeyError:
|
||||
context_cache = settings_cache[context] = ContextCache()
|
||||
# These ones all implement __missing__ in a useful way though
|
||||
return context_cache[site][model_string]
|
||||
|
||||
|
||||
class SettingsExtension(Extension):
|
||||
def __init__(self, environment):
|
||||
super(SettingsExtension, self).__init__(environment)
|
||||
self.environment.globals.update({
|
||||
'settings': get_setting,
|
||||
})
|
||||
|
||||
|
||||
settings = SettingsExtension
|
@ -1,6 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.template import Context, RequestContext, Template
|
||||
from django.template import Context, RequestContext, Template, engines
|
||||
from django.test import TestCase
|
||||
|
||||
from wagtail.tests.testapp.models import TestSetting
|
||||
@ -153,3 +153,121 @@ class TestTemplateTag(TemplateTestCase):
|
||||
'{{ settings.tests.testsetting.title}}')
|
||||
with self.assertRaises(RuntimeError):
|
||||
template.render(context)
|
||||
|
||||
|
||||
class TestSettingsJinja(TemplateTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSettingsJinja, self).setUp()
|
||||
self.engine = engines['jinja2']
|
||||
|
||||
def render(self, string, context=None, request_context=True):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
# Add a request to the template, to simulate a RequestContext
|
||||
if request_context:
|
||||
if 'site' in context:
|
||||
site = context['site']
|
||||
else:
|
||||
site = Site.objects.get(is_default_site=True)
|
||||
|
||||
request = self.client.get('/test/', HTTP_HOST=site.hostname)
|
||||
request.site = site
|
||||
context['request'] = request
|
||||
|
||||
template = self.engine.from_string(string)
|
||||
return template.render(context)
|
||||
|
||||
def test_accessing_setting(self):
|
||||
""" Check that the context processor works """
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.TestSetting").title }}'),
|
||||
self.test_setting.title)
|
||||
|
||||
def test_multisite(self):
|
||||
""" Check that the correct setting for the current site is returned """
|
||||
context = {'site': self.default_site}
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.TestSetting").title }}', context),
|
||||
self.test_setting.title)
|
||||
|
||||
context = {'site': self.other_site}
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.TestSetting").title }}', context),
|
||||
self.other_setting.title)
|
||||
|
||||
def test_model_case_insensitive(self):
|
||||
""" Model names should be case insensitive """
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.testsetting").title }}'),
|
||||
self.test_setting.title)
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.TESTSETTING").title }}'),
|
||||
self.test_setting.title)
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.TestSetting").title }}'),
|
||||
self.test_setting.title)
|
||||
self.assertEqual(
|
||||
self.render('{{ settings("tests.tEstsEttIng").title }}'),
|
||||
self.test_setting.title)
|
||||
|
||||
def test_models_cached(self):
|
||||
""" Accessing a setting should only hit the DB once per render """
|
||||
get_title = '{{ settings("tests.testsetting").title }}'
|
||||
|
||||
# Cant use the default 'self.render()' as it does DB queries to get
|
||||
# site, dummy request
|
||||
site = Site.objects.get(is_default_site=True)
|
||||
request = self.client.get('/test/', HTTP_HOST=site.hostname)
|
||||
request.site = site
|
||||
|
||||
for i in range(1, 4):
|
||||
with self.assertNumQueries(1):
|
||||
context = {'request': request}
|
||||
template = self.engine.from_string(get_title * i)
|
||||
self.assertEqual(
|
||||
template.render(context),
|
||||
self.test_setting.title * i)
|
||||
|
||||
def test_settings_use_default_site_override(self):
|
||||
"""
|
||||
Check that {{ settings(use_default_site=True) }} overrides a site in
|
||||
the context.
|
||||
"""
|
||||
request = self.get_request(site=self.other_site)
|
||||
context = {'request': request}
|
||||
|
||||
# This should use the default site, ignoring the site in the request
|
||||
template = '{{ settings("tests.testsetting", use_default_site=True).title }}'
|
||||
|
||||
self.assertEqual(
|
||||
self.render(template, context),
|
||||
self.test_setting.title)
|
||||
|
||||
def test_settings_use_default_site(self):
|
||||
"""
|
||||
Check that the {{ settings(use_default_site=True) }} option works with
|
||||
no site in the context
|
||||
"""
|
||||
context = {}
|
||||
|
||||
# This should use the default site
|
||||
template = '{{ settings("tests.testsetting", use_default_site=True).title}}'
|
||||
|
||||
self.assertEqual(
|
||||
self.render(template, context, request_context=False),
|
||||
self.test_setting.title)
|
||||
|
||||
def test_settings_no_request_no_use_default(self):
|
||||
"""
|
||||
Check that {{ settings }} throws an error if it can not find a
|
||||
site to work with
|
||||
"""
|
||||
context = {}
|
||||
|
||||
# Without a request in the context, and without use_default_site, this
|
||||
# should bail with an error
|
||||
template = '{{ settings("tests.testsetting").title}}'
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.render(template, context, request_context=False)
|
||||
|
@ -61,6 +61,7 @@ TEMPLATES = [
|
||||
'wagtail.wagtailcore.jinja2tags.core',
|
||||
'wagtail.wagtailadmin.jinja2tags.userbar',
|
||||
'wagtail.wagtailimages.jinja2tags.images',
|
||||
'wagtail.contrib.settings.jinja2tags.settings',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user