Skip to content

Commit 6cec67c

Browse files
committed
chore: deprecate loader_tags
BREAKING CHANGE: mezzanine.template.loader_tags has been deprecated. If you're still using the {% overextends %} tag provided by it switch to Django's {% extend %} tag for identical results. Fixes #1974.
1 parent c2ee5b4 commit 6cec67c

File tree

4 files changed

+20
-304
lines changed

4 files changed

+20
-304
lines changed

mezzanine/core/checks.py

+15-156
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,39 @@
1-
import pprint
2-
3-
from django import VERSION as DJANGO_VERSION
4-
from django.conf import global_settings
51
from django.core.checks import Warning, register
62

73
from mezzanine.conf import settings
84
from mezzanine.utils.conf import middlewares_or_subclasses_installed
95
from mezzanine.utils.sites import SITE_PERMISSION_MIDDLEWARE
106

7+
LOADER_TAGS_WARNING = (
8+
"You have included 'mezzanine.template.loader_tags' as a builtin in your template "
9+
"configuration. 'loader_tags' no longer exists and should be removed. If you're "
10+
"still using the {% overextends %} tag please replace it with Django's "
11+
"{% extend %} for identical results."
12+
)
13+
1114

1215
@register()
1316
def check_template_settings(app_configs, **kwargs):
14-
1517
issues = []
1618

17-
if not settings.TEMPLATES:
18-
19-
suggested_config = _build_suggested_template_config(settings)
20-
21-
declaration = "TEMPLATES = "
22-
config_formatted = pprint.pformat(suggested_config)
23-
config_formatted = "\n".join(
24-
" " * len(declaration) + line for line in config_formatted.splitlines()
25-
)
26-
config_formatted = declaration + config_formatted[len(declaration) :]
27-
28-
issues.append(
29-
Warning(
30-
"Please update your settings to use the TEMPLATES setting rather "
31-
"than the deprecated individual TEMPLATE_ settings. The latter "
32-
"are unsupported and correct behaviour is not guaranteed. Here's "
33-
"a suggestion based on on your existing configuration:\n\n%s\n"
34-
% config_formatted,
35-
id="mezzanine.core.W01",
36-
)
37-
)
38-
39-
if settings.DEBUG != settings.TEMPLATE_DEBUG:
40-
issues.append(
41-
Warning(
42-
"TEMPLATE_DEBUG and DEBUG settings have different values, "
43-
"which may not be what you want. Mezzanine used to fix this "
44-
"for you, but doesn't any more. Update your settings.py to "
45-
"use the TEMPLATES setting to have template debugging "
46-
"controlled by the DEBUG setting.",
47-
id="mezzanine.core.W02",
48-
)
49-
)
50-
51-
else:
52-
loader_tags_built_in = any(
53-
"mezzanine.template.loader_tags"
54-
in config.get("OPTIONS", {}).get("builtins", {})
55-
for config in settings.TEMPLATES
56-
)
57-
if not DJANGO_VERSION < (1, 9) and not loader_tags_built_in:
58-
issues.append(
59-
Warning(
60-
"You haven't included 'mezzanine.template.loader_tags' as a "
61-
"builtin in any of your template configurations. Mezzanine's "
62-
"'overextends' tag will not be available in your templates.",
63-
id="mezzanine.core.W03",
64-
)
65-
)
19+
if any(
20+
"mezzanine.template.loader_tags"
21+
in config.get("OPTIONS", {}).get("builtins", {})
22+
for config in settings.TEMPLATES
23+
):
24+
issues.append(Warning(LOADER_TAGS_WARNING, id="mezzanine.core.W05"))
6625

6726
return issues
6827

6928

70-
def _build_suggested_template_config(settings):
71-
72-
suggested_templates_config = {
73-
"BACKEND": "django.template.backends.django.DjangoTemplates",
74-
"OPTIONS": {
75-
"builtins": [
76-
"mezzanine.template.loader_tags",
77-
],
78-
},
79-
}
80-
81-
def set_setting(name, value, unconditional=False):
82-
if value or unconditional:
83-
suggested_templates_config[name] = value
84-
85-
def set_option(name, value):
86-
if value:
87-
suggested_templates_config["OPTIONS"][name.lower()] = value
88-
89-
def get_debug(_):
90-
if settings.TEMPLATE_DEBUG != settings.DEBUG:
91-
return settings.TEMPLATE_DEBUG
92-
93-
def get_default(default):
94-
def getter(name):
95-
value = getattr(settings, name)
96-
if value == getattr(global_settings, name):
97-
value = default
98-
return value
99-
100-
return getter
101-
102-
default_context_processors = [
103-
"django.contrib.auth.context_processors.auth",
104-
"django.contrib.messages.context_processors.messages",
105-
"django.core.context_processors.debug",
106-
"django.core.context_processors.i18n",
107-
"django.core.context_processors.static",
108-
"django.core.context_processors.media",
109-
"django.core.context_processors.request",
110-
"django.core.context_processors.tz",
111-
"mezzanine.conf.context_processors.settings",
112-
"mezzanine.pages.context_processors.page",
113-
]
114-
115-
def get_loaders(_):
116-
"""
117-
Django's default TEMPLATES setting doesn't specify loaders, instead
118-
dynamically sets a default based on whether or not APP_DIRS is True.
119-
We check here if the existing TEMPLATE_LOADERS setting matches one
120-
of those default cases, and omit the 'loaders' option if so.
121-
"""
122-
template_loaders = list(settings.TEMPLATE_LOADERS)
123-
default_loaders = list(global_settings.TEMPLATE_LOADERS)
124-
125-
if template_loaders == default_loaders:
126-
# Equivalent to Django's default with APP_DIRS True
127-
template_loaders = None
128-
app_dirs = True
129-
elif template_loaders == default_loaders[:1]:
130-
# Equivalent to Django's default with APP_DIRS False
131-
template_loaders = None
132-
app_dirs = False
133-
else:
134-
# This project has a custom loaders setting, which we'll use.
135-
# Custom loaders are incompatible with APP_DIRS.
136-
app_dirs = False
137-
138-
return template_loaders, app_dirs
139-
140-
def set_loaders(name, value):
141-
template_loaders, app_dirs = value
142-
set_option(name, template_loaders)
143-
set_setting("APP_DIRS", app_dirs, unconditional=True)
144-
145-
old_settings = [
146-
("ALLOWED_INCLUDE_ROOTS", settings.__getattr__, set_option),
147-
("TEMPLATE_STRING_IF_INVALID", settings.__getattr__, set_option),
148-
("TEMPLATE_DIRS", settings.__getattr__, set_setting),
149-
(
150-
"TEMPLATE_CONTEXT_PROCESSORS",
151-
get_default(default_context_processors),
152-
set_option,
153-
),
154-
("TEMPLATE_DEBUG", get_debug, set_option),
155-
("TEMPLATE_LOADERS", get_loaders, set_loaders),
156-
]
157-
158-
def convert_setting_name(old_name):
159-
return old_name.rpartition("TEMPLATE_")[2]
160-
161-
for setting_name, getter, setter in old_settings:
162-
value = getter(setting_name)
163-
new_setting_name = convert_setting_name(setting_name)
164-
setter(new_setting_name, value)
165-
166-
return [suggested_templates_config]
167-
168-
16929
@register()
17030
def check_sites_middleware(app_configs, **kwargs):
17131

17232
if not middlewares_or_subclasses_installed([SITE_PERMISSION_MIDDLEWARE]):
17333
return [
17434
Warning(
175-
SITE_PERMISSION_MIDDLEWARE
176-
+ " missing from settings.MIDDLEWARE - per site"
177-
" permissions not applied",
35+
f"{SITE_PERMISSION_MIDDLEWARE} missing from settings.MIDDLEWARE - "
36+
"per site permissions not applied",
17837
id="mezzanine.core.W04",
17938
)
18039
]

mezzanine/project_template/project_name/settings.py

-3
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,6 @@
207207
"mezzanine.conf.context_processors.settings",
208208
"mezzanine.pages.context_processors.page",
209209
],
210-
"builtins": [
211-
"mezzanine.template.loader_tags",
212-
],
213210
"loaders": [
214211
"mezzanine.template.loaders.host_themes.Loader",
215212
"django.template.loaders.filesystem.Loader",

mezzanine/template/loader_tags.py

+4-144
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,7 @@
1-
import os
2-
import warnings
3-
from itertools import chain
4-
5-
from django import VERSION as DJANGO_VERSION
6-
from django.template import Template, TemplateDoesNotExist, TemplateSyntaxError
7-
from django.template.loader_tags import ExtendsNode
8-
9-
from mezzanine import template
1+
from django import template
102

113
register = template.Library()
124

13-
14-
class OverExtendsNode(ExtendsNode):
15-
"""
16-
Allows the template ``foo/bar.html`` to extend ``foo/bar.html``,
17-
given that there is another version of it that can be loaded. This
18-
allows templates to be created in a project that extend their app
19-
template counterparts, or even app templates that extend other app
20-
templates with the same relative name/path.
21-
22-
We use our own version of ``find_template``, that uses an explict
23-
list of template directories to search for the template, based on
24-
the directories that the known template loaders
25-
(``app_directories`` and ``filesystem``) use. This list gets stored
26-
in the template context, and each time a template is found, its
27-
absolute path gets removed from the list, so that subsequent
28-
searches for the same relative name/path can find parent templates
29-
in other directories, which allows circular inheritance to occur.
30-
31-
Django's ``app_directories``, ``filesystem``, and ``cached``
32-
loaders are supported. The ``eggs`` loader, and any loader that
33-
implements ``load_template_source`` with a source string returned,
34-
should also theoretically work.
35-
"""
36-
37-
def find_template(self, name, context, peeking=False):
38-
"""
39-
Replacement for Django's ``find_template`` that uses the current
40-
template context to keep track of which template directories it
41-
has used when finding a template. This allows multiple templates
42-
with the same relative name/path to be discovered, so that
43-
circular template inheritance can occur.
44-
"""
45-
46-
# These imports want settings, which aren't available when this
47-
# module is imported to ``add_to_builtins``, so do them here.
48-
import django.template.loaders.app_directories as app_directories
49-
50-
from mezzanine.conf import settings
51-
52-
# Store a dictionary in the template context mapping template
53-
# names to the lists of template directories available to
54-
# search for that template. Each time a template is loaded, its
55-
# origin directory is removed from its directories list.
56-
context_name = "OVEREXTENDS_DIRS"
57-
if context_name not in context:
58-
context[context_name] = {}
59-
if name not in context[context_name]:
60-
all_dirs = list(
61-
chain.from_iterable(
62-
[
63-
template_engine.get("DIRS", [])
64-
for template_engine in settings.TEMPLATES
65-
]
66-
)
67-
) + list(app_directories.get_app_template_dirs("templates"))
68-
# os.path.abspath is needed under uWSGI, and also ensures we
69-
# have consistent path separators across different OSes.
70-
context[context_name][name] = list(map(os.path.abspath, all_dirs))
71-
72-
# Build a list of template loaders to use. For loaders that wrap
73-
# other loaders like the ``cached`` template loader, unwind its
74-
# internal loaders and add those instead.
75-
loaders = []
76-
for loader in context.template.engine.template_loaders:
77-
loaders.extend(getattr(loader, "loaders", [loader]))
78-
79-
# Go through the loaders and try to find the template. When
80-
# found, removed its absolute path from the context dict so
81-
# that it won't be used again when the same relative name/path
82-
# is requested.
83-
for loader in loaders:
84-
dirs = context[context_name][name]
85-
try:
86-
source, path = loader.load_template_source(name, dirs)
87-
except TemplateDoesNotExist:
88-
pass
89-
else:
90-
# Only remove the absolute path for the initial call in
91-
# get_parent, and not when we're peeking during the
92-
# second call.
93-
if not peeking:
94-
remove_path = os.path.abspath(path[: -len(name) - 1])
95-
context[context_name][name].remove(remove_path)
96-
return Template(source)
97-
raise TemplateDoesNotExist(name)
98-
99-
def get_parent(self, context):
100-
"""
101-
Load the parent template using our own ``find_template``, which
102-
will cause its absolute path to not be used again. Then peek at
103-
the first node, and if its parent arg is the same as the
104-
current parent arg, we know circular inheritance is going to
105-
occur, in which case we try and find the template again, with
106-
the absolute directory removed from the search list.
107-
"""
108-
parent = self.parent_name.resolve(context)
109-
# If parent is a template object, just return it.
110-
if hasattr(parent, "render"):
111-
return parent
112-
template = self.find_template(parent, context)
113-
for node in template.nodelist:
114-
if (
115-
isinstance(node, ExtendsNode)
116-
and node.parent_name.resolve(context) == parent
117-
):
118-
return self.find_template(parent, context, peeking=True)
119-
return template
120-
121-
122-
@register.tag
123-
def overextends(parser, token):
124-
"""
125-
Extended version of Django's ``extends`` tag that allows circular
126-
inheritance to occur, eg a template can both be overridden and
127-
extended at once.
128-
"""
129-
if DJANGO_VERSION >= (1, 9):
130-
warnings.warn(
131-
"The `overextends` template tag is deprecated in favour of "
132-
"Django's built-in `extends` tag, which supports recursive "
133-
"extension in Django 1.9 and above.",
134-
DeprecationWarning,
135-
stacklevel=2,
136-
)
137-
138-
bits = token.split_contents()
139-
if len(bits) != 2:
140-
raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
141-
parent_name = parser.compile_filter(bits[1])
142-
nodelist = parser.parse()
143-
if nodelist.get_nodes_by_type(ExtendsNode):
144-
raise TemplateSyntaxError(
145-
"'%s' cannot appear more than once " "in the same template" % bits[0]
146-
)
147-
return OverExtendsNode(nodelist, parent_name, None)
5+
# TODO: Remove this file a couple releases after Mezzanine 5
6+
# We've kept this file because users upgrading to Mezzanine 5 might still refer to it.
7+
# However, mezzanine.core.checks should warn them about it being deprecated.

pytest.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ addopts =
55
--cov-report html
66
--cov-report term:skip-covered
77
# Original coverage was 54% (not great), but at least ensure we don't go below
8-
--cov-fail-under 54
8+
--cov-fail-under 55

0 commit comments

Comments
 (0)