-
Notifications
You must be signed in to change notification settings - Fork 18
/
loader.py
130 lines (115 loc) · 4.26 KB
/
loader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Template loader for app-namespace"""
import errno
import io
import os
from collections import OrderedDict
import django
from django.apps import apps
try:
from django.template import Origin
except ImportError: # pragma: no cover
class Origin(object):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
from django.template import TemplateDoesNotExist
from django.template.loaders.base import Loader as BaseLoader
from django.utils._os import safe_join
from django.utils._os import upath
from django.utils.functional import cached_property
class NamespaceOrigin(Origin):
def __init__(self, app_name, *args, **kwargs):
self.app_name = app_name
super(NamespaceOrigin, self).__init__(*args, **kwargs)
class Loader(BaseLoader):
"""
App namespace loader for allowing you to both extend and override
a template provided by an app at the same time.
"""
is_usable = True
def __init__(self, *args, **kwargs):
super(Loader, self).__init__(*args, **kwargs)
self._already_used = []
def reset(self, mandatory_on_django_18):
"""
Empty the cache of paths already used.
"""
if django.VERSION[1] == 8:
if not mandatory_on_django_18:
return
self._already_used = []
def get_app_template_path(self, app, template_name):
"""
Return the full path of a template name located in an app.
"""
return safe_join(self.app_templates_dirs[app], template_name)
@cached_property
def app_templates_dirs(self):
"""
Build a cached dict with settings.INSTALLED_APPS as keys
and the 'templates' directory of each application as values.
"""
app_templates_dirs = OrderedDict()
for app_config in apps.get_app_configs():
templates_dir = os.path.join(
getattr(app_config, 'path', '/'), 'templates')
if os.path.isdir(templates_dir):
templates_dir = upath(templates_dir)
app_templates_dirs[app_config.name] = templates_dir
app_templates_dirs[app_config.label] = templates_dir
return app_templates_dirs
def get_contents(self, origin):
"""
Try to load the origin.
"""
try:
path = self.get_app_template_path(
origin.app_name, origin.template_name)
with io.open(path, encoding=self.engine.file_charset) as fp:
return fp.read()
except KeyError:
raise TemplateDoesNotExist(origin)
except IOError as error:
if error.errno == errno.ENOENT:
raise TemplateDoesNotExist(origin)
raise
def get_template_sources(self, template_name):
"""
Build a list of Origin to load 'template_name' splitted with ':'.
The first item is the name of the application and the last item
is the true value of 'template_name' provided by the specified
application.
"""
if ':' not in template_name:
self.reset(True)
return
app, template_path = template_name.split(':')
if app:
yield NamespaceOrigin(
app_name=app,
name='app_namespace:%s:%s' % (app, template_name),
template_name=template_path,
loader=self)
return
self.reset(False)
for app in self.app_templates_dirs:
file_path = self.get_app_template_path(app, template_path)
if file_path in self._already_used:
continue
self._already_used.append(file_path)
yield NamespaceOrigin(
app_name=app,
name='app_namespace:%s:%s' % (app, template_name),
template_name=template_path,
loader=self)
def load_template_source(self, *ka):
"""
Backward compatible method for Django < 2.0.
"""
template_name = ka[0]
for origin in self.get_template_sources(template_name):
try:
return self.get_contents(origin), origin.name
except TemplateDoesNotExist:
pass
raise TemplateDoesNotExist(template_name)