comparison src/tools/common/template.py @ 2245:e09048cb7595

core (tools/common/template): helping methods/filters for templates: - Indexer class (used with next_gidx/cur_gidx) allows to get per page indexes, usefull to have unique id to reference - blog_date is a Q&D filter to get relative dates, should be replaced/improved in the future - ScriptsHandler (used with scripts_handler) is intended to be a way to add scripts from template, and group them at a specific location of the page. It is not used yet and may change heavily or disappear in the future.
author Goffi <goffi@goffi.org>
date Fri, 19 May 2017 12:54:31 +0200
parents f472179305a1
children e572482f6cbd
comparison
equal deleted inserted replaced
2244:9d49e66bdbf2 2245:e09048cb7595
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 """ template generation """ 20 """ template generation """
21 21
22 from sat.core.constants import Const as C 22 from sat.core.constants import Const as C
23 from sat.core.i18n import _
23 from sat.core import exceptions 24 from sat.core import exceptions
24 from sat.core.log import getLogger 25 from sat.core.log import getLogger
25 log = getLogger(__name__) 26 log = getLogger(__name__)
26 import os.path 27 import os.path
28 from collections import OrderedDict
29 from xml.sax.saxutils import quoteattr
30 import time
27 try: 31 try:
28 import sat_templates 32 import sat_templates
29 except ImportError: 33 except ImportError:
30 raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine') 34 raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine')
31 else: 35 else:
111 return super(TemplateLoader, self).get_source(environment, default_path) 115 return super(TemplateLoader, self).get_source(environment, default_path)
112 # if no default template is found, we re-raise the error 116 # if no default template is found, we re-raise the error
113 raise e 117 raise e
114 118
115 119
120 class Indexer(object):
121 """Index global to a page"""
122
123 def __init__(self):
124 self._idx = 0
125
126 def next(self):
127 self._idx+=1
128 return self._idx
129
130 def current(self):
131 return self._idx
132
133
134 class ScriptsHandler(object):
135 # TODO: this class is not finished/used yet, and add_script is referenced in default/script/base.html
136 # but doesn't exist yet here
137
138 def __init__(self, renderer, template_path, template_root_dir):
139 self.renderer = renderer
140 self.template_root_dir = template_root_dir
141 self.scripts = OrderedDict
142 dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path)
143
144 def import_script(self, library_name):
145 if library_name.endswith('.js'):
146 library_name = library_name[:-3]
147 if library_name not in self.scripts:
148 self.scripts[library_name] = {}
149
150 def generate(self):
151 """Generate the <scripts> elements
152 @return (unicode): <scripts> HTML tags
153 """
154 scripts = []
155 tpl = u'<script src="{src}"></script>'
156 for library,data in self.scripts:
157 path = self.renderer.getStaticPath(library, self.template_root_dir, self.theme, self.is_default_theme, '.js')
158 if path is None:
159 log.warning(_(u"Can't find {}.js javascript library").format(library))
160 continue
161 scripts.append(tpl.format(src=quoteattr(path)))
162 return u'\n'.join(scripts)
163
164
116 class Renderer(object): 165 class Renderer(object):
117 166
118 def __init__(self, host): 167 def __init__(self, host):
119 self.host = host 168 self.host = host
120 self.base_dir = os.path.dirname(sat_templates.__file__) # FIXME: should be modified if we handle use extra dirs 169 self.base_dir = os.path.dirname(sat_templates.__file__) # FIXME: should be modified if we handle use extra dirs
124 trim_blocks=True, 173 trim_blocks=True,
125 lstrip_blocks=True, 174 lstrip_blocks=True,
126 ) 175 )
127 # we want to have access to SàT constants in templates 176 # we want to have access to SàT constants in templates
128 self.env.globals[u'C'] = C 177 self.env.globals[u'C'] = C
178 # custom filters
179 self.env.filters['next_gidx'] = self._next_gidx
180 self.env.filters['cur_gidx'] = self._cur_gidx
181 self.env.filters['blog_date'] = self._blog_date
129 182
130 def getThemeAndRoot(self, template): 183 def getThemeAndRoot(self, template):
131 """retrieve theme and root dir of a given tempalte 184 """retrieve theme and root dir of a given tempalte
132 185
133 @param template(unicode): template to parse 186 @param template(unicode): template to parse
134 @return (tuple[unicode, unicode]): theme and absolute path to theme's root dir 187 @return (tuple[unicode, unicode]): theme and absolute path to theme's root dir
135 """ 188 """
136 theme, dummy = self.env.loader.parse_template(template) 189 theme, dummy = self.env.loader.parse_template(template)
137 return theme, os.path.join(self.base_dir, theme) 190 return theme, os.path.join(self.base_dir, theme)
138 191
139 def _appendCSSIfExists(self, css_files, template_root_dir, theme, name, is_default): 192 def getStaticPath(self, name, template_root_dir, theme, is_default, ext='.css'):
140 """append CSS file to list if it exists, else try with default theme 193 """retrieve path of a static file if it exists with current theme or default
141 194
142 CSS file will be looked at [theme]/static/[name].css, and then default 195 File will be looked at [theme]/static/[name][ext], and then default
143 if not found. 196 if not found.
144 @param css_files(list): list of CSS file to be completed 197 @param name(unicode): name of the file to look for
145 @param template_root_dir(unicode): absolute path to template root used 198 @param template_root_dir(unicode): absolute path to template root used
146 @param theme(unicode): name of the template theme used 199 @param theme(unicode): name of the template theme used
147 @param name(unicode): name of the CSS file to look for
148 @param is_default(bool): True if theme is the default theme 200 @param is_default(bool): True if theme is the default theme
149 """ 201 @return (unicode, None): relative path if found, else None
150 css_path = os.path.join(theme, C.TEMPLATE_STATIC_DIR, name + '.css') 202 """
151 if os.path.exists(os.path.join(template_root_dir, css_path)): 203 file_ = None
152 css_files.append(css_path) 204 path = os.path.join(theme, C.TEMPLATE_STATIC_DIR, name + ext)
205 if os.path.exists(os.path.join(template_root_dir, path)):
206 file_ = path
153 elif not is_default: 207 elif not is_default:
154 css_path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + '.css') 208 path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext)
155 if os.path.exists(os.path.join(template_root_dir, css_path)): 209 if os.path.exists(os.path.join(template_root_dir, path)):
156 css_files.append(css_path) 210 file_.append(path)
211 return file_
212
213 def getThemeData(self, template_path):
214 """return template data got from template_path
215
216 @return tuple(unicde, unicode, bool):
217 path_elems: elements of the path
218 theme: theme of the page
219 is_default: True if the theme is the default theme
220 """
221 path_elems = template_path.split(u'/')
222 theme = path_elems.pop(0)
223 is_default = theme == C.TEMPLATE_THEME_DEFAULT
224 return (path_elems, theme, is_default)
157 225
158 def getCSSFiles(self, template_path, template_root_dir): 226 def getCSSFiles(self, template_path, template_root_dir):
159 """retrieve CSS files to use according to theme and template path 227 """retrieve CSS files to use according to theme and template path
160 228
161 for each element of the path, a .css file is looked for in /static, and returned if it exists. 229 for each element of the path, a .css file is looked for in /static, and returned if it exists.
168 @param template_root_dir(unicode): absolute path of the theme root dir used 236 @param template_root_dir(unicode): absolute path of the theme root dir used
169 @return list[unicode]: relative path to CSS files to use 237 @return list[unicode]: relative path to CSS files to use
170 """ 238 """
171 # TODO: some caching would be nice 239 # TODO: some caching would be nice
172 css_files = [] 240 css_files = []
173 path_elems = template_path.split(u'/') 241 path_elems, theme, is_default = self.getThemeData(template_path)
174 theme = path_elems.pop(0) 242 for css in (u'fonts', u'styles'):
175 is_default = theme == C.TEMPLATE_THEME_DEFAULT 243 css_path = self.getStaticPath(css, template_root_dir, theme, is_default)
176 self._appendCSSIfExists(css_files, template_root_dir, theme, u'styles', is_default) 244 if css_path is not None:
245 css_files.append(css_path)
177 246
178 for idx, path in enumerate(path_elems): 247 for idx, path in enumerate(path_elems):
179 self._appendCSSIfExists(css_files, template_root_dir, theme, u'_'.join(path_elems[:idx+1]), is_default) 248 css_path = self.getStaticPath(u'_'.join(path_elems[:idx+1]), template_root_dir, theme, is_default)
249 if css_path is not None:
250 css_files.append(css_path)
180 251
181 return css_files 252 return css_files
253
254 @jinja2.contextfilter
255 def _next_gidx(self, ctx, value):
256 """Use next current global index as suffix"""
257 return u"{}_{}".format(value, ctx['gidx'].next())
258
259 @jinja2.contextfilter
260 def _cur_gidx(self, ctx, value):
261 """Use current current global index as suffix"""
262 return u"{}_{}".format(value, ctx['gidx'].current())
263
264 def _blog_date(self, timestamp):
265 # FIXME: Q&D, need to be done properly
266 return unicode(int(time.time() - int(timestamp))/(3600*24)) + u" days ago"
182 267
183 def render(self, template, theme=None, root_path=u'', css_files=None, css_inline=False, **kwargs): 268 def render(self, template, theme=None, root_path=u'', css_files=None, css_inline=False, **kwargs):
184 """render a template 269 """render a template
185 270
186 @param template(unicode): template to render (e.g. blog/articles.html) 271 @param template(unicode): template to render (e.g. blog/articles.html)
220 css_file_path = os.path.join(template_root_dir, css_file) 305 css_file_path = os.path.join(template_root_dir, css_file)
221 with open(css_file_path) as f: 306 with open(css_file_path) as f:
222 css_contents.append(f.read()) 307 css_contents.append(f.read())
223 if css_contents: 308 if css_contents:
224 kwargs['css_content'] = '\n'.join(css_contents) 309 kwargs['css_content'] = '\n'.join(css_contents)
310
311 scripts_handler = ScriptsHandler(self, template_path, template_root_dir)
225 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme 312 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme
226 # if the template doesn't exist in the requested theme. 313 # if the template doesn't exist in the requested theme.
227 return template_source.render(theme=theme, root_path=root_path, css_files=css_files, **kwargs) 314 return template_source.render(theme=theme, root_path=root_path, css_files=css_files, gidx=Indexer(), scripts_handler=scripts_handler, **kwargs)