Mercurial > libervia-backend
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) |