Mercurial > libervia-web
comparison libervia/pages/_browser/template.py @ 1306:c07112ef01cd
browser (template): adapted filters/global/extensions to manage SàT templates:
`template` module has been update so most SàT template can be run from browser:
- `profile` and `csrf_token` are set as globals
- an implementation of `xmlattr` filter has been added
- `date_fmt` filter has been implemented using `moment.js`
- i18n method `_` has been added to globals, and `{% trans %}` statement has been
implemented using an extension. For now they are not actually translating but just
returning the unmodified string.
- new `get_args` helper method to handle `nunjucks` convention for arguments.
- fixed `get_elt` to only return the first child element (avoiding any text child) + added
a defaut value for `context`
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 16 Jul 2020 09:08:50 +0200 |
parents | 8aba2a2078ca |
children | 3411ec23c389 |
comparison
equal
deleted
inserted
replaced
1305:db9ea167c409 | 1306:c07112ef01cd |
---|---|
13 'trimBlocks': True, | 13 'trimBlocks': True, |
14 'lstripBlocks': True, | 14 'lstripBlocks': True, |
15 }) | 15 }) |
16 | 16 |
17 nunjucks.installJinjaCompat() | 17 nunjucks.installJinjaCompat() |
18 env.addGlobal("profile", window.profile) | |
19 env.addGlobal("csrf_token", window.csrf_token) | |
20 # FIXME: integrate gettext or equivalent here | |
21 env.addGlobal("_", lambda txt: txt) | |
18 | 22 |
19 | 23 |
20 class Indexer: | 24 class Indexer: |
21 """Index global to a page""" | 25 """Index global to a page""" |
22 | 26 |
36 | 40 |
37 gidx = Indexer() | 41 gidx = Indexer() |
38 # suffix use to avoid collision with IDs generated in static page | 42 # suffix use to avoid collision with IDs generated in static page |
39 SCRIPT_SUFF = "__script__" | 43 SCRIPT_SUFF = "__script__" |
40 | 44 |
45 def escape_html(txt): | |
46 return ( | |
47 txt | |
48 .replace('&', '&') | |
49 .replace('<', '<') | |
50 .replace('>', '>') | |
51 .replace('"', '"') | |
52 ) | |
53 | |
54 | |
55 def get_args(n_args, *sig_args, **sig_kwargs): | |
56 """Retrieve function args when they are transmitted using nunjucks convention | |
57 | |
58 cf. https://mozilla.github.io/nunjucks/templating.html#keyword-arguments | |
59 @param n_args: argument from nunjucks call | |
60 @param sig_args: expected positional arguments | |
61 @param sig_kwargs: expected keyword arguments | |
62 @return: all expected arguments, with default value if not specified in nunjucks | |
63 """ | |
64 # nunjucks set kwargs in last argument | |
65 given_args = list(n_args) | |
66 try: | |
67 given_kwargs = given_args.pop().to_dict() | |
68 except (AttributeError, IndexError): | |
69 # we don't have a dict as last argument | |
70 # that happens when there is no keyword argument | |
71 given_args = list(n_args) | |
72 given_kwargs = {} | |
73 ret = given_args[:len(sig_args)] | |
74 # we check if we have remaining positional arguments | |
75 # in which case they may be specified in keyword arguments | |
76 for name in sig_args[len(given_args):]: | |
77 try: | |
78 value = given_kwargs.pop(name) | |
79 except KeyError: | |
80 raise ValueError(f"missing positional arguments {name!r}") | |
81 ret.append(value) | |
82 | |
83 extra_pos_args = given_args[len(sig_args):] | |
84 # and now the keyword arguments | |
85 for name, default in sig_kwargs.items(): | |
86 if extra_pos_args: | |
87 # kw args has been specified with a positional argument | |
88 ret.append(extra_pos_args.pop(0)) | |
89 continue | |
90 value = given_kwargs.get(name, default) | |
91 ret.append(value) | |
92 | |
93 return ret | |
94 | |
41 | 95 |
42 def _next_gidx(value): | 96 def _next_gidx(value): |
43 """Use next current global index as suffix""" | 97 """Use next current global index as suffix""" |
44 next_ = gidx.next(value) | 98 next_ = gidx.next(value) |
45 return f"{value}{SCRIPT_SUFF}" if next_ == 0 else f"{value}_{SCRIPT_SUFF}{next_}" | 99 return f"{value}{SCRIPT_SUFF}" if next_ == 0 else f"{value}_{SCRIPT_SUFF}{next_}" |
53 return f"{value}{SCRIPT_SUFF}" if not current else f"{value}_{SCRIPT_SUFF}{current}" | 107 return f"{value}{SCRIPT_SUFF}" if not current else f"{value}_{SCRIPT_SUFF}{current}" |
54 | 108 |
55 env.addFilter("cur_gidx", _cur_gidx) | 109 env.addFilter("cur_gidx", _cur_gidx) |
56 | 110 |
57 | 111 |
112 def _xmlattr(d, autospace=True): | |
113 if not d: | |
114 return | |
115 d = d.to_dict() | |
116 ret = [''] if autospace else [] | |
117 for key, value in d.items(): | |
118 if value is not None: | |
119 ret.append(f'{escape_html(key)}="{escape_html(str(value))}"') | |
120 | |
121 return safe(' '.join(ret)) | |
122 | |
123 env.addFilter("xmlattr", _xmlattr) | |
124 | |
125 | |
58 def _tojson(value): | 126 def _tojson(value): |
59 return safe( | 127 return safe(escape_html(window.JSON.stringify(value))) |
60 window.JSON.stringify(value) | |
61 .replace('&', '&') | |
62 .replace('<', '<') | |
63 .replace('>', '>') | |
64 .replace('"', '"') | |
65 ) | |
66 | 128 |
67 env.addFilter("tojson", _tojson) | 129 env.addFilter("tojson", _tojson) |
68 | 130 |
69 | 131 |
70 def _icon_use(name, cls=""): | 132 def _icon_use(name, cls=""): |
78 ) | 140 ) |
79 | 141 |
80 env.addGlobal("icon", _icon_use) | 142 env.addGlobal("icon", _icon_use) |
81 | 143 |
82 | 144 |
145 def _date_fmt( | |
146 timestamp, *args | |
147 ): | |
148 """Date formatting | |
149 | |
150 cf. sat.tools.common.date_utils for arguments details | |
151 """ | |
152 fmt, date_only, auto_limit, auto_old_fmt, auto_new_fmt = get_args( | |
153 args, fmt="short", date_only=False, auto_limit=7, auto_old_fmt="short", | |
154 auto_new_fmt="relative", | |
155 ) | |
156 from js_modules.moment import moment | |
157 date = moment.unix(timestamp) | |
158 | |
159 if fmt == "auto_day": | |
160 fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm" | |
161 if fmt == "auto": | |
162 limit = moment().startOf('day').subtract(auto_limit, 'days') | |
163 m_fmt = auto_old_fmt if date < limit else auto_new_fmt | |
164 | |
165 if fmt == "short": | |
166 m_fmt = "DD/MM/YY" if date_only else "DD/MM/YY HH:mm" | |
167 elif fmt == "medium": | |
168 m_fmt = "ll" if date_only else "lll" | |
169 elif fmt == "long": | |
170 m_fmt = "LL" if date_only else "LLL" | |
171 elif fmt == "full": | |
172 m_fmt = "dddd, LL" if date_only else "LLLL" | |
173 elif fmt == "relative": | |
174 return date.fromNow() | |
175 elif fmt == "iso": | |
176 if date_only: | |
177 m_fmt == "YYYY-MM-DD" | |
178 else: | |
179 return date.toISOString() | |
180 else: | |
181 raise NotImplementedError("free format is not implemented yet") | |
182 | |
183 return date.format(m_fmt) | |
184 | |
185 env.addFilter("date_fmt", _date_fmt) | |
186 | |
187 | |
188 class I18nExtension: | |
189 """Extension to handle the {% trans %}{% endtrans %} statement""" | |
190 # FIXME: for now there is no translation, this extension only returns the string | |
191 # unmodified | |
192 tags = ['trans'] | |
193 | |
194 def parse(self, parser, nodes, lexer): | |
195 tok = parser.nextToken() | |
196 args = parser.parseSignature(None, True) | |
197 parser.advanceAfterBlockEnd(tok.value) | |
198 body = parser.parseUntilBlocks('endtrans') | |
199 parser.advanceAfterBlockEnd() | |
200 return nodes.CallExtension.new(self._js_ext, 'run', args, [body]) | |
201 | |
202 def run(self, context, *args): | |
203 body = args[-1] | |
204 return body() | |
205 | |
206 @classmethod | |
207 def install(cls, env): | |
208 ext = cls() | |
209 ext_dict = { | |
210 "tags": ext.tags, | |
211 "parse": ext.parse, | |
212 "run": ext.run | |
213 } | |
214 ext._js_ext = javascript.pyobj2jsobj(ext_dict) | |
215 env.addExtension(cls.__name__, ext._js_ext) | |
216 | |
217 I18nExtension.install(env) | |
218 | |
219 | |
83 class Template: | 220 class Template: |
84 | 221 |
85 def __init__(self, tpl_name): | 222 def __init__(self, tpl_name): |
86 self._tpl = env.getTemplate(tpl_name, True) | 223 self._tpl = env.getTemplate(tpl_name, True) |
87 | 224 |
88 def render(self, context): | 225 def render(self, context): |
89 return self._tpl.render(context) | 226 return self._tpl.render(context) |
90 | 227 |
91 def get_elt(self, context): | 228 def get_elt(self, context=None): |
229 if context is None: | |
230 context = {} | |
92 raw_html = self.render(context) | 231 raw_html = self.render(context) |
93 template_elt = document.createElement('template') | 232 template_elt = document.createElement('template') |
94 template_elt.innerHTML = raw_html | 233 template_elt.innerHTML = raw_html |
95 return template_elt.content.firstChild | 234 return template_elt.content.firstElementChild |