Mercurial > libervia-backend
comparison sat/tools/common/template.py @ 2624:56f94936df1e
code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Jun 2018 20:14:46 +0200 |
parents | 5b26033c49a8 |
children | 0fa217fafabf |
comparison
equal
deleted
inserted
replaced
2623:49533de4540b | 2624:56f94936df1e |
---|---|
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.i18n import _ |
24 from sat.core import exceptions | 24 from sat.core import exceptions |
25 from sat.tools.common import date_utils | 25 from sat.tools.common import date_utils |
26 from sat.core.log import getLogger | 26 from sat.core.log import getLogger |
27 | |
27 log = getLogger(__name__) | 28 log = getLogger(__name__) |
28 import os.path | 29 import os.path |
29 from xml.sax.saxutils import quoteattr | 30 from xml.sax.saxutils import quoteattr |
30 import time | 31 import time |
31 import re | 32 import re |
33 from babel import Locale | 34 from babel import Locale |
34 from babel.core import UnknownLocaleError | 35 from babel.core import UnknownLocaleError |
35 import pygments | 36 import pygments |
36 from pygments import lexers | 37 from pygments import lexers |
37 from pygments import formatters | 38 from pygments import formatters |
39 | |
38 try: | 40 try: |
39 import sat_templates | 41 import sat_templates |
40 except ImportError: | 42 except ImportError: |
41 raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine') | 43 raise exceptions.MissingModule( |
44 u"sat_templates module is not available, please install it or check your path to use template engine" | |
45 ) | |
42 else: | 46 else: |
43 sat_templates # to avoid pyflakes warning | 47 sat_templates # to avoid pyflakes warning |
44 | 48 |
45 try: | 49 try: |
46 import jinja2 | 50 import jinja2 |
47 except: | 51 except: |
48 raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2') | 52 raise exceptions.MissingModule( |
53 u"Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2" | |
54 ) | |
49 | 55 |
50 from jinja2 import Markup as safe | 56 from jinja2 import Markup as safe |
51 from jinja2 import is_undefined | 57 from jinja2 import is_undefined |
52 from lxml import etree | 58 from lxml import etree |
53 | 59 |
54 HTML_EXT = ('html', 'xhtml') | 60 HTML_EXT = ("html", "xhtml") |
55 RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]') | 61 RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]") |
56 # TODO: handle external path (an additional search path for templates should be settable by user | 62 # TODO: handle external path (an additional search path for templates should be settable by user |
57 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason | 63 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason |
58 | 64 |
59 | 65 |
60 class TemplateLoader(jinja2.FileSystemLoader): | 66 class TemplateLoader(jinja2.FileSystemLoader): |
61 | |
62 def __init__(self): | 67 def __init__(self): |
63 searchpath = os.path.dirname(sat_templates.__file__) | 68 searchpath = os.path.dirname(sat_templates.__file__) |
64 super(TemplateLoader, self).__init__(searchpath, followlinks=True) | 69 super(TemplateLoader, self).__init__(searchpath, followlinks=True) |
65 | 70 |
66 def parse_template(self, template): | 71 def parse_template(self, template): |
70 @return (tuple[(unicode,None),unicode]): theme and template_path | 75 @return (tuple[(unicode,None),unicode]): theme and template_path |
71 theme can be None if relative path is used | 76 theme can be None if relative path is used |
72 relative path is the path from search path with theme specified | 77 relative path is the path from search path with theme specified |
73 e.g. default/blog/articles.html | 78 e.g. default/blog/articles.html |
74 """ | 79 """ |
75 if template.startswith(u'('): | 80 if template.startswith(u"("): |
76 try: | 81 try: |
77 theme_end = template.index(u')') | 82 theme_end = template.index(u")") |
78 except IndexError: | 83 except IndexError: |
79 raise ValueError(u"incorrect theme in template") | 84 raise ValueError(u"incorrect theme in template") |
80 theme = template[1:theme_end] | 85 theme = template[1:theme_end] |
81 template = template[theme_end+1:] | 86 template = template[theme_end + 1 :] |
82 if not template or template.startswith(u'/'): | 87 if not template or template.startswith(u"/"): |
83 raise ValueError(u"incorrect path after template name") | 88 raise ValueError(u"incorrect path after template name") |
84 template = os.path.join(theme, template) | 89 template = os.path.join(theme, template) |
85 elif template.startswith(u'/'): | 90 elif template.startswith(u"/"): |
86 # absolute path means no template | 91 # absolute path means no template |
87 theme = None | 92 theme = None |
88 raise NotImplementedError(u'absolute path is not implemented yet') | 93 raise NotImplementedError(u"absolute path is not implemented yet") |
89 else: | 94 else: |
90 theme = C.TEMPLATE_THEME_DEFAULT | 95 theme = C.TEMPLATE_THEME_DEFAULT |
91 template = os.path.join(theme, template) | 96 template = os.path.join(theme, template) |
92 return theme, template | 97 return theme, template |
93 | 98 |
97 @param theme(unicode): theme used | 102 @param theme(unicode): theme used |
98 @param template_path(unicode): path to the not found template | 103 @param template_path(unicode): path to the not found template |
99 @return (unicode, None): default path or None if there is not | 104 @return (unicode, None): default path or None if there is not |
100 """ | 105 """ |
101 ext = os.path.splitext(template_path)[1][1:] | 106 ext = os.path.splitext(template_path)[1][1:] |
102 path_elems = template_path.split(u'/') | 107 path_elems = template_path.split(u"/") |
103 if ext in HTML_EXT: | 108 if ext in HTML_EXT: |
104 if path_elems[1] == u'error': | 109 if path_elems[1] == u"error": |
105 # if an inexisting error page is requested, we return base page | 110 # if an inexisting error page is requested, we return base page |
106 default_path = os.path.join(theme, u'error/base.html') | 111 default_path = os.path.join(theme, u"error/base.html") |
107 return default_path | 112 return default_path |
108 if theme != C.TEMPLATE_THEME_DEFAULT: | 113 if theme != C.TEMPLATE_THEME_DEFAULT: |
109 # if template doesn't exists for this theme, we try with default | 114 # if template doesn't exists for this theme, we try with default |
110 return os.path.join(C.TEMPLATE_THEME_DEFAULT, path_elems[1:]) | 115 return os.path.join(C.TEMPLATE_THEME_DEFAULT, path_elems[1:]) |
111 | 116 |
122 except jinja2.exceptions.TemplateNotFound as e: | 127 except jinja2.exceptions.TemplateNotFound as e: |
123 # in some special cases, a defaut template is returned if nothing is found | 128 # in some special cases, a defaut template is returned if nothing is found |
124 if theme is not None: | 129 if theme is not None: |
125 default_path = self.get_default_template(theme, template_path) | 130 default_path = self.get_default_template(theme, template_path) |
126 if default_path is not None: | 131 if default_path is not None: |
127 return super(TemplateLoader, self).get_source(environment, default_path) | 132 return super(TemplateLoader, self).get_source( |
133 environment, default_path | |
134 ) | |
128 # if no default template is found, we re-raise the error | 135 # if no default template is found, we re-raise the error |
129 raise e | 136 raise e |
130 | 137 |
131 | 138 |
132 class Indexer(object): | 139 class Indexer(object): |
145 def current(self, value): | 152 def current(self, value): |
146 return self._indexes.get(value) | 153 return self._indexes.get(value) |
147 | 154 |
148 | 155 |
149 class ScriptsHandler(object): | 156 class ScriptsHandler(object): |
150 | |
151 def __init__(self, renderer, template_path, template_root_dir, root_path): | 157 def __init__(self, renderer, template_path, template_root_dir, root_path): |
152 self.renderer = renderer | 158 self.renderer = renderer |
153 self.template_root_dir = template_root_dir | 159 self.template_root_dir = template_root_dir |
154 self.root_path = root_path | 160 self.root_path = root_path |
155 self.scripts = [] # we don't use a set because order may be important | 161 self.scripts = [] # we don't use a set because order may be important |
156 dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path) | 162 dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path) |
157 | 163 |
158 def include(self, library_name, attribute='defer'): | 164 def include(self, library_name, attribute="defer"): |
159 """Mark that a script need to be imported. | 165 """Mark that a script need to be imported. |
160 | 166 |
161 Must be used before base.html is extended, as <script> are generated there. | 167 Must be used before base.html is extended, as <script> are generated there. |
162 If called several time with the same library, it will be imported once. | 168 If called several time with the same library, it will be imported once. |
163 @param library_name(unicode): name of the library to import | 169 @param library_name(unicode): name of the library to import |
164 @param loading: | 170 @param loading: |
165 """ | 171 """ |
166 if attribute not in ('defer', 'async', ''): | 172 if attribute not in ("defer", "async", ""): |
167 raise exceptions.DataError(_(u'Invalid attribute, please use one of "defer", "async" or ""')) | 173 raise exceptions.DataError( |
168 if library_name.endswith('.js'): | 174 _(u'Invalid attribute, please use one of "defer", "async" or ""') |
175 ) | |
176 if library_name.endswith(".js"): | |
169 library_name = library_name[:-3] | 177 library_name = library_name[:-3] |
170 if library_name not in self.scripts: | 178 if library_name not in self.scripts: |
171 self.scripts.append((library_name, attribute)) | 179 self.scripts.append((library_name, attribute)) |
172 return u'' | 180 return u"" |
173 | 181 |
174 def generate_scripts(self): | 182 def generate_scripts(self): |
175 """Generate the <script> elements | 183 """Generate the <script> elements |
176 | 184 |
177 @return (unicode): <scripts> HTML tags | 185 @return (unicode): <scripts> HTML tags |
178 """ | 186 """ |
179 scripts = [] | 187 scripts = [] |
180 tpl = u'<script src={src} {attribute}></script>' | 188 tpl = u"<script src={src} {attribute}></script>" |
181 for library, attribute in self.scripts: | 189 for library, attribute in self.scripts: |
182 path = self.renderer.getStaticPath(library, self.template_root_dir, self.theme, self.is_default_theme, '.js') | 190 path = self.renderer.getStaticPath( |
191 library, self.template_root_dir, self.theme, self.is_default_theme, ".js" | |
192 ) | |
183 if path is None: | 193 if path is None: |
184 log.warning(_(u"Can't find {}.js javascript library").format(library)) | 194 log.warning(_(u"Can't find {}.js javascript library").format(library)) |
185 continue | 195 continue |
186 path = os.path.join(self.root_path, path) | 196 path = os.path.join(self.root_path, path) |
187 scripts.append(tpl.format( | 197 scripts.append(tpl.format(src=quoteattr(path), attribute=attribute)) |
188 src = quoteattr(path), | 198 return safe(u"\n".join(scripts)) |
189 attribute = attribute, | |
190 )) | |
191 return safe(u'\n'.join(scripts)) | |
192 | 199 |
193 | 200 |
194 class Renderer(object): | 201 class Renderer(object): |
195 | |
196 def __init__(self, host): | 202 def __init__(self, host): |
197 self.host = host | 203 self.host = host |
198 self.base_dir = os.path.dirname(sat_templates.__file__) # FIXME: should be modified if we handle use extra dirs | 204 self.base_dir = os.path.dirname( |
205 sat_templates.__file__ | |
206 ) # FIXME: should be modified if we handle use extra dirs | |
199 self.env = jinja2.Environment( | 207 self.env = jinja2.Environment( |
200 loader=TemplateLoader(), | 208 loader=TemplateLoader(), |
201 autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']), | 209 autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]), |
202 trim_blocks=True, | 210 trim_blocks=True, |
203 lstrip_blocks=True, | 211 lstrip_blocks=True, |
204 extensions=['jinja2.ext.i18n'], | 212 extensions=["jinja2.ext.i18n"], |
205 ) | 213 ) |
206 self._locale_str = C.DEFAULT_LOCALE | 214 self._locale_str = C.DEFAULT_LOCALE |
207 self._locale = Locale.parse(self._locale_str) | 215 self._locale = Locale.parse(self._locale_str) |
208 self.installTranslations() | 216 self.installTranslations() |
209 # we want to have access to SàT constants in templates | 217 # we want to have access to SàT constants in templates |
210 self.env.globals[u'C'] = C | 218 self.env.globals[u"C"] = C |
211 # custom filters | 219 # custom filters |
212 self.env.filters['next_gidx'] = self._next_gidx | 220 self.env.filters["next_gidx"] = self._next_gidx |
213 self.env.filters['cur_gidx'] = self._cur_gidx | 221 self.env.filters["cur_gidx"] = self._cur_gidx |
214 self.env.filters['date_fmt'] = self._date_fmt | 222 self.env.filters["date_fmt"] = self._date_fmt |
215 self.env.filters['xmlui_class'] = self._xmlui_class | 223 self.env.filters["xmlui_class"] = self._xmlui_class |
216 self.env.filters['attr_escape'] = self.attr_escape | 224 self.env.filters["attr_escape"] = self.attr_escape |
217 self.env.filters['item_filter'] = self._item_filter | 225 self.env.filters["item_filter"] = self._item_filter |
218 self.env.filters['adv_format'] = self._adv_format | 226 self.env.filters["adv_format"] = self._adv_format |
219 self.env.filters['dict_ext'] = self._dict_ext | 227 self.env.filters["dict_ext"] = self._dict_ext |
220 self.env.filters['highlight'] = self.highlight | 228 self.env.filters["highlight"] = self.highlight |
221 # custom tests | 229 # custom tests |
222 self.env.tests['in_the_past'] = self._in_the_past | 230 self.env.tests["in_the_past"] = self._in_the_past |
223 self.icons_path = os.path.join(host.media_dir, u'fonts/fontello/svg') | 231 self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg") |
224 | 232 |
225 def installTranslations(self): | 233 def installTranslations(self): |
226 i18n_dir = os.path.join(self.base_dir, 'i18n') | 234 i18n_dir = os.path.join(self.base_dir, "i18n") |
227 self.translations = {} | 235 self.translations = {} |
228 for lang_dir in os.listdir(i18n_dir): | 236 for lang_dir in os.listdir(i18n_dir): |
229 lang_path = os.path.join(i18n_dir, lang_dir) | 237 lang_path = os.path.join(i18n_dir, lang_dir) |
230 if not os.path.isdir(lang_path): | 238 if not os.path.isdir(lang_path): |
231 continue | 239 continue |
232 po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo') | 240 po_path = os.path.join(lang_path, "LC_MESSAGES/sat.mo") |
233 try: | 241 try: |
234 with open(po_path, 'rb') as f: | 242 with open(po_path, "rb") as f: |
235 self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat') | 243 self.translations[Locale.parse(lang_dir)] = support.Translations( |
244 f, "sat" | |
245 ) | |
236 except EnvironmentError: | 246 except EnvironmentError: |
237 log.error(_(u"Can't find template translation at {path}").format(path = po_path)) | 247 log.error( |
248 _(u"Can't find template translation at {path}").format(path=po_path) | |
249 ) | |
238 except UnknownLocaleError as e: | 250 except UnknownLocaleError as e: |
239 log.error(_(u"Invalid locale name: {msg}").format(msg=e)) | 251 log.error(_(u"Invalid locale name: {msg}").format(msg=e)) |
240 else: | 252 else: |
241 log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir)) | 253 log.info(_(u"loaded {lang} templates translations").format(lang=lang_dir)) |
242 self.env.install_null_translations(True) | 254 self.env.install_null_translations(True) |
243 | 255 |
244 def setLocale(self, locale_str): | 256 def setLocale(self, locale_str): |
245 """set current locale | 257 """set current locale |
246 | 258 |
247 change current translation locale and self self._locale and self._locale_str | 259 change current translation locale and self self._locale and self._locale_str |
248 """ | 260 """ |
249 if locale_str == self._locale_str: | 261 if locale_str == self._locale_str: |
250 return | 262 return |
251 if locale_str == 'en': | 263 if locale_str == "en": |
252 # we default to GB English when it's not specified | 264 # we default to GB English when it's not specified |
253 # one of the main reason is to avoid the nonsense U.S. short date format | 265 # one of the main reason is to avoid the nonsense U.S. short date format |
254 locale_str = 'en_GB' | 266 locale_str = "en_GB" |
255 try: | 267 try: |
256 locale = Locale.parse(locale_str) | 268 locale = Locale.parse(locale_str) |
257 except ValueError as e: | 269 except ValueError as e: |
258 log.warning(_(u"invalid locale value: {msg}").format(msg=e)) | 270 log.warning(_(u"invalid locale value: {msg}").format(msg=e)) |
259 locale_str = self._locale_str = C.DEFAULT_LOCALE | 271 locale_str = self._locale_str = C.DEFAULT_LOCALE |
267 log.warning(_(u"Can't find locale {locale}".format(locale=locale))) | 279 log.warning(_(u"Can't find locale {locale}".format(locale=locale))) |
268 locale_str = C.DEFAULT_LOCALE | 280 locale_str = C.DEFAULT_LOCALE |
269 locale = Locale.parse(self._locale_str) | 281 locale = Locale.parse(self._locale_str) |
270 else: | 282 else: |
271 self.env.install_gettext_translations(translations, True) | 283 self.env.install_gettext_translations(translations, True) |
272 log.debug(_(u'Switched to {lang}').format(lang=locale.english_name)) | 284 log.debug(_(u"Switched to {lang}").format(lang=locale.english_name)) |
273 | 285 |
274 if locale_str == C.DEFAULT_LOCALE: | 286 if locale_str == C.DEFAULT_LOCALE: |
275 self.env.install_null_translations(True) | 287 self.env.install_null_translations(True) |
276 | 288 |
277 self._locale = locale | 289 self._locale = locale |
284 @return (tuple[unicode, unicode]): theme and absolute path to theme's root dir | 296 @return (tuple[unicode, unicode]): theme and absolute path to theme's root dir |
285 """ | 297 """ |
286 theme, dummy = self.env.loader.parse_template(template) | 298 theme, dummy = self.env.loader.parse_template(template) |
287 return theme, os.path.join(self.base_dir, theme) | 299 return theme, os.path.join(self.base_dir, theme) |
288 | 300 |
289 def getStaticPath(self, name, template_root_dir, theme, is_default, ext='.css'): | 301 def getStaticPath(self, name, template_root_dir, theme, is_default, ext=".css"): |
290 """retrieve path of a static file if it exists with current theme or default | 302 """retrieve path of a static file if it exists with current theme or default |
291 | 303 |
292 File will be looked at [theme]/static/[name][ext], and then default | 304 File will be looked at [theme]/static/[name][ext], and then default |
293 if not found. | 305 if not found. |
294 @param name(unicode): name of the file to look for | 306 @param name(unicode): name of the file to look for |
300 file_ = None | 312 file_ = None |
301 path = os.path.join(theme, C.TEMPLATE_STATIC_DIR, name + ext) | 313 path = os.path.join(theme, C.TEMPLATE_STATIC_DIR, name + ext) |
302 if os.path.exists(os.path.join(template_root_dir, path)): | 314 if os.path.exists(os.path.join(template_root_dir, path)): |
303 file_ = path | 315 file_ = path |
304 elif not is_default: | 316 elif not is_default: |
305 path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext) | 317 path = os.path.join( |
318 C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext | |
319 ) | |
306 if os.path.exists(os.path.join(template_root_dir, path)): | 320 if os.path.exists(os.path.join(template_root_dir, path)): |
307 file_.append(path) | 321 file_.append(path) |
308 return file_ | 322 return file_ |
309 | 323 |
310 def getThemeData(self, template_path): | 324 def getThemeData(self, template_path): |
313 @return tuple(unicode, unicode, bool): | 327 @return tuple(unicode, unicode, bool): |
314 path_elems: elements of the path | 328 path_elems: elements of the path |
315 theme: theme of the page | 329 theme: theme of the page |
316 is_default: True if the theme is the default theme | 330 is_default: True if the theme is the default theme |
317 """ | 331 """ |
318 path_elems = [os.path.splitext(p)[0] for p in template_path.split(u'/')] | 332 path_elems = [os.path.splitext(p)[0] for p in template_path.split(u"/")] |
319 theme = path_elems.pop(0) | 333 theme = path_elems.pop(0) |
320 is_default = theme == C.TEMPLATE_THEME_DEFAULT | 334 is_default = theme == C.TEMPLATE_THEME_DEFAULT |
321 return (path_elems, theme, is_default) | 335 return (path_elems, theme, is_default) |
322 | 336 |
323 def getCSSFiles(self, template_path, template_root_dir): | 337 def getCSSFiles(self, template_path, template_root_dir): |
334 @return list[unicode]: relative path to CSS files to use | 348 @return list[unicode]: relative path to CSS files to use |
335 """ | 349 """ |
336 # TODO: some caching would be nice | 350 # TODO: some caching would be nice |
337 css_files = [] | 351 css_files = [] |
338 path_elems, theme, is_default = self.getThemeData(template_path) | 352 path_elems, theme, is_default = self.getThemeData(template_path) |
339 for css in (u'fonts', u'styles'): | 353 for css in (u"fonts", u"styles"): |
340 css_path = self.getStaticPath(css, template_root_dir, theme, is_default) | 354 css_path = self.getStaticPath(css, template_root_dir, theme, is_default) |
341 if css_path is not None: | 355 if css_path is not None: |
342 css_files.append(css_path) | 356 css_files.append(css_path) |
343 | 357 |
344 for idx, path in enumerate(path_elems): | 358 for idx, path in enumerate(path_elems): |
345 css_path = self.getStaticPath(u'_'.join(path_elems[:idx+1]), template_root_dir, theme, is_default) | 359 css_path = self.getStaticPath( |
360 u"_".join(path_elems[: idx + 1]), template_root_dir, theme, is_default | |
361 ) | |
346 if css_path is not None: | 362 if css_path is not None: |
347 css_files.append(css_path) | 363 css_files.append(css_path) |
348 | 364 |
349 return css_files | 365 return css_files |
350 | |
351 | 366 |
352 ## custom filters ## | 367 ## custom filters ## |
353 | 368 |
354 @jinja2.contextfilter | 369 @jinja2.contextfilter |
355 def _next_gidx(self, ctx, value): | 370 def _next_gidx(self, ctx, value): |
356 """Use next current global index as suffix""" | 371 """Use next current global index as suffix""" |
357 next_ = ctx['gidx'].next(value) | 372 next_ = ctx["gidx"].next(value) |
358 return value if next_ == 0 else u"{}_{}".format(value, next_) | 373 return value if next_ == 0 else u"{}_{}".format(value, next_) |
359 | 374 |
360 @jinja2.contextfilter | 375 @jinja2.contextfilter |
361 def _cur_gidx(self, ctx, value): | 376 def _cur_gidx(self, ctx, value): |
362 """Use current current global index as suffix""" | 377 """Use current current global index as suffix""" |
363 current = ctx['gidx'].current(value) | 378 current = ctx["gidx"].current(value) |
364 return value if not current else u"{}_{}".format(value, current) | 379 return value if not current else u"{}_{}".format(value, current) |
365 | 380 |
366 def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None): | 381 def _date_fmt( |
382 self, timestamp, fmt="short", date_only=False, auto_limit=None, auto_old_fmt=None | |
383 ): | |
367 if is_undefined(fmt): | 384 if is_undefined(fmt): |
368 fmt = u'short' | 385 fmt = u"short" |
369 | 386 |
370 try: | 387 try: |
371 return date_utils.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt) | 388 return date_utils.date_fmt( |
389 timestamp, fmt, date_only, auto_limit, auto_old_fmt | |
390 ) | |
372 except Exception as e: | 391 except Exception as e: |
373 log.warning(_(u"Can't parse date: {msg}").format(msg=e)) | 392 log.warning(_(u"Can't parse date: {msg}").format(msg=e)) |
374 return timestamp | 393 return timestamp |
375 | 394 |
376 def attr_escape(self, text): | 395 def attr_escape(self, text): |
377 """escape a text to a value usable as an attribute | 396 """escape a text to a value usable as an attribute |
378 | 397 |
379 remove spaces, and put in lower case | 398 remove spaces, and put in lower case |
380 """ | 399 """ |
381 return RE_ATTR_ESCAPE.sub(u'_', text.strip().lower())[:50] | 400 return RE_ATTR_ESCAPE.sub(u"_", text.strip().lower())[:50] |
382 | 401 |
383 def _xmlui_class(self, xmlui_item, fields): | 402 def _xmlui_class(self, xmlui_item, fields): |
384 """return classes computed from XMLUI fields name | 403 """return classes computed from XMLUI fields name |
385 | 404 |
386 will return a string with a series of escaped {name}_{value} separated by spaces. | 405 will return a string with a series of escaped {name}_{value} separated by spaces. |
392 classes = [] | 411 classes = [] |
393 for name in fields: | 412 for name in fields: |
394 escaped_name = self.attr_escape(name) | 413 escaped_name = self.attr_escape(name) |
395 try: | 414 try: |
396 for value in xmlui_item.widgets[name].values: | 415 for value in xmlui_item.widgets[name].values: |
397 classes.append(escaped_name + '_' + self.attr_escape(value)) | 416 classes.append(escaped_name + "_" + self.attr_escape(value)) |
398 except KeyError: | 417 except KeyError: |
399 log.debug(_(u"ignoring field \"{name}\": it doesn't exists").format(name=name)) | 418 log.debug( |
419 _(u'ignoring field "{name}": it doesn\'t exists').format(name=name) | |
420 ) | |
400 continue | 421 continue |
401 return u' '.join(classes) or None | 422 return u" ".join(classes) or None |
402 | 423 |
403 @jinja2.contextfilter | 424 @jinja2.contextfilter |
404 def _item_filter(self, ctx, item, filters): | 425 def _item_filter(self, ctx, item, filters): |
405 """return item's value, filtered if suitable | 426 """return item's value, filtered if suitable |
406 | 427 |
418 value = item.value | 439 value = item.value |
419 filter_ = filters.get(item.name, None) | 440 filter_ = filters.get(item.name, None) |
420 if filter_ is None: | 441 if filter_ is None: |
421 return value | 442 return value |
422 elif isinstance(filter_, dict): | 443 elif isinstance(filter_, dict): |
423 filters_args = filter_.get(u'filters_args') | 444 filters_args = filter_.get(u"filters_args") |
424 for idx, f_name in enumerate(filter_.get(u'filters', [])): | 445 for idx, f_name in enumerate(filter_.get(u"filters", [])): |
425 kwargs = filters_args[idx] if filters_args is not None else {} | 446 kwargs = filters_args[idx] if filters_args is not None else {} |
426 filter_func = self.env.filters[f_name] | 447 filter_func = self.env.filters[f_name] |
427 try: | 448 try: |
428 eval_context_filter = filter_func.evalcontextfilter | 449 eval_context_filter = filter_func.evalcontextfilter |
429 except AttributeError: | 450 except AttributeError: |
431 | 452 |
432 if eval_context_filter: | 453 if eval_context_filter: |
433 value = filter_func(ctx.eval_ctx, value, **kwargs) | 454 value = filter_func(ctx.eval_ctx, value, **kwargs) |
434 else: | 455 else: |
435 value = filter_func(value, **kwargs) | 456 value = filter_func(value, **kwargs) |
436 template = filter_.get(u'template') | 457 template = filter_.get(u"template") |
437 if template: | 458 if template: |
438 # format will return a string, so we need to check first | 459 # format will return a string, so we need to check first |
439 # if the value is safe or not, and re-mark it after formatting | 460 # if the value is safe or not, and re-mark it after formatting |
440 is_safe = isinstance(value, safe) | 461 is_safe = isinstance(value, safe) |
441 value = template.format(value=value) | 462 value = template.format(value=value) |
453 None to return value unchanged | 474 None to return value unchanged |
454 @return (unicode): formatted value | 475 @return (unicode): formatted value |
455 """ | 476 """ |
456 if template is None: | 477 if template is None: |
457 return value | 478 return value |
458 # jinja use string when no special char is used, so we have to convert to unicode | 479 # jinja use string when no special char is used, so we have to convert to unicode |
459 return unicode(template).format(value=value, **kwargs) | 480 return unicode(template).format(value=value, **kwargs) |
460 | 481 |
461 def _dict_ext(self, source_dict, extra_dict, key=None): | 482 def _dict_ext(self, source_dict, extra_dict, key=None): |
462 """extend source_dict with extra dict and return the result | 483 """extend source_dict with extra dict and return the result |
463 | 484 |
509 | 530 |
510 ## template methods ## | 531 ## template methods ## |
511 | 532 |
512 def _icon_defs(self, *names): | 533 def _icon_defs(self, *names): |
513 """Define svg icons which will be used in the template, and use their name as id""" | 534 """Define svg icons which will be used in the template, and use their name as id""" |
514 svg_elt = etree.Element('svg', nsmap={None: 'http://www.w3.org/2000/svg'}, | 535 svg_elt = etree.Element( |
515 width='0', height='0', style='display: block' | 536 "svg", |
516 ) | 537 nsmap={None: "http://www.w3.org/2000/svg"}, |
517 defs_elt = etree.SubElement(svg_elt, 'defs') | 538 width="0", |
539 height="0", | |
540 style="display: block", | |
541 ) | |
542 defs_elt = etree.SubElement(svg_elt, "defs") | |
518 for name in names: | 543 for name in names: |
519 path = os.path.join(self.icons_path, name + u'.svg') | 544 path = os.path.join(self.icons_path, name + u".svg") |
520 icon_svg_elt = etree.parse(path).getroot() | 545 icon_svg_elt = etree.parse(path).getroot() |
521 # we use icon name as id, so we can retrieve them easily | 546 # we use icon name as id, so we can retrieve them easily |
522 icon_svg_elt.set('id', name) | 547 icon_svg_elt.set("id", name) |
523 if not icon_svg_elt.tag == '{http://www.w3.org/2000/svg}svg': | 548 if not icon_svg_elt.tag == "{http://www.w3.org/2000/svg}svg": |
524 raise exceptions.DataError(u'invalid SVG element') | 549 raise exceptions.DataError(u"invalid SVG element") |
525 defs_elt.append(icon_svg_elt) | 550 defs_elt.append(icon_svg_elt) |
526 return safe(etree.tostring(svg_elt, encoding='unicode')) | 551 return safe(etree.tostring(svg_elt, encoding="unicode")) |
527 | 552 |
528 def _icon_use(self, name, cls=''): | 553 def _icon_use(self, name, cls=""): |
529 return safe(u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | 554 return safe( |
555 u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
530 <use href="#{name}"/> | 556 <use href="#{name}"/> |
531 </svg> | 557 </svg> |
532 """.format( | 558 """.format( |
533 name=name, | 559 name=name, cls=(" " + cls) if cls else "" |
534 cls=(' ' + cls) if cls else '')) | 560 ) |
535 | 561 ) |
536 def render(self, template, theme=None, locale=C.DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs): | 562 |
563 def render( | |
564 self, | |
565 template, | |
566 theme=None, | |
567 locale=C.DEFAULT_LOCALE, | |
568 root_path=u"", | |
569 media_path=u"", | |
570 css_files=None, | |
571 css_inline=False, | |
572 **kwargs | |
573 ): | |
537 """render a template | 574 """render a template |
538 . | 575 . |
539 @param template(unicode): template to render (e.g. blog/articles.html) | 576 @param template(unicode): template to render (e.g. blog/articles.html) |
540 @param theme(unicode): template theme | 577 @param theme(unicode): template theme |
541 @param root_path(unicode): prefix of the path/URL to use for template root | 578 @param root_path(unicode): prefix of the path/URL to use for template root |
551 """ | 588 """ |
552 if not template: | 589 if not template: |
553 raise ValueError(u"template can't be empty") | 590 raise ValueError(u"template can't be empty") |
554 if theme is not None: | 591 if theme is not None: |
555 # use want to set a theme, we add it to the template path | 592 # use want to set a theme, we add it to the template path |
556 if template[0] == u'(': | 593 if template[0] == u"(": |
557 raise ValueError(u"you can't specify theme in template path and in argument at the same time") | 594 raise ValueError( |
558 elif template[0] == u'/': | 595 u"you can't specify theme in template path and in argument at the same time" |
596 ) | |
597 elif template[0] == u"/": | |
559 raise ValueError(u"you can't specify theme with absolute paths") | 598 raise ValueError(u"you can't specify theme with absolute paths") |
560 template= u'(' + theme + u')' + template | 599 template = u"(" + theme + u")" + template |
561 else: | 600 else: |
562 theme, dummy = self.env.loader.parse_template(template) | 601 theme, dummy = self.env.loader.parse_template(template) |
563 | 602 |
564 template_source = self.env.get_template(template) | 603 template_source = self.env.get_template(template) |
565 template_root_dir = os.path.normpath(self.base_dir) # FIXME: should be modified if we handle use extra dirs | 604 template_root_dir = os.path.normpath( |
566 # XXX: template_path may have a different theme as first element than theme if a default page is used | 605 self.base_dir |
567 template_path = template_source.filename[len(template_root_dir)+1:] | 606 ) # FIXME: should be modified if we handle use extra dirs |
607 # XXX: template_path may have a different theme as first element than theme if a default page is used | |
608 template_path = template_source.filename[len(template_root_dir) + 1 :] | |
568 | 609 |
569 if css_files is None: | 610 if css_files is None: |
570 css_files = self.getCSSFiles(template_path, template_root_dir) | 611 css_files = self.getCSSFiles(template_path, template_root_dir) |
571 | 612 |
572 kwargs['icon_defs'] = self._icon_defs | 613 kwargs["icon_defs"] = self._icon_defs |
573 kwargs['icon'] = self._icon_use | 614 kwargs["icon"] = self._icon_use |
574 | 615 |
575 if css_inline: | 616 if css_inline: |
576 css_contents = [] | 617 css_contents = [] |
577 for css_file in css_files: | 618 for css_file in css_files: |
578 css_file_path = os.path.join(template_root_dir, css_file) | 619 css_file_path = os.path.join(template_root_dir, css_file) |
579 with open(css_file_path) as f: | 620 with open(css_file_path) as f: |
580 css_contents.append(f.read()) | 621 css_contents.append(f.read()) |
581 if css_contents: | 622 if css_contents: |
582 kwargs['css_content'] = '\n'.join(css_contents) | 623 kwargs["css_content"] = "\n".join(css_contents) |
583 | 624 |
584 scripts_handler = ScriptsHandler(self, template_path, template_root_dir, root_path) | 625 scripts_handler = ScriptsHandler( |
626 self, template_path, template_root_dir, root_path | |
627 ) | |
585 self.setLocale(locale) | 628 self.setLocale(locale) |
586 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme | 629 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme |
587 # if the template doesn't exist in the requested theme. | 630 # if the template doesn't exist in the requested theme. |
588 return template_source.render(theme=theme, root_path=root_path, media_path=media_path, | 631 return template_source.render( |
589 css_files=css_files, locale=self._locale, | 632 theme=theme, |
590 gidx=Indexer(), script=scripts_handler, | 633 root_path=root_path, |
591 **kwargs) | 634 media_path=media_path, |
635 css_files=css_files, | |
636 locale=self._locale, | |
637 gidx=Indexer(), | |
638 script=scripts_handler, | |
639 **kwargs | |
640 ) |