0
0
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:
Tim Heap 2016-04-12 15:49:32 +10:00 committed by Karl Hobley
parent eda39c85c8
commit 31d2d43d5b
4 changed files with 261 additions and 3 deletions

View File

@ -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") %}

View 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

View File

@ -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)

View File

@ -61,6 +61,7 @@ TEMPLATES = [
'wagtail.wagtailcore.jinja2tags.core',
'wagtail.wagtailadmin.jinja2tags.userbar',
'wagtail.wagtailimages.jinja2tags.images',
'wagtail.contrib.settings.jinja2tags.settings',
],
},
},