comparison sat/tools/common/template.py @ 2599:5b26033c49a8

tools (common): moved date_fmt function from template filters to new date_utils module, so it can be used everywhere.
author Goffi <goffi@goffi.org>
date Fri, 01 Jun 2018 12:04:06 +0200
parents 26edcf3a30eb
children 56f94936df1e
comparison
equal deleted inserted replaced
2598:0b6adc2672d9 2599:5b26033c49a8
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.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.core.log import getLogger 26 from sat.core.log import getLogger
26 log = getLogger(__name__) 27 log = getLogger(__name__)
27 import os.path 28 import os.path
28 from xml.sax.saxutils import quoteattr 29 from xml.sax.saxutils import quoteattr
29 import datetime
30 import time 30 import time
31 import re 31 import re
32 from babel import support 32 from babel import support
33 from babel import Locale 33 from babel import Locale
34 from babel.core import UnknownLocaleError 34 from babel.core import UnknownLocaleError
35 from babel import dates
36 import pygments 35 import pygments
37 from pygments import lexers 36 from pygments import lexers
38 from pygments import formatters 37 from pygments import formatters
39 try: 38 try:
40 import sat_templates 39 import sat_templates
51 from jinja2 import Markup as safe 50 from jinja2 import Markup as safe
52 from jinja2 import is_undefined 51 from jinja2 import is_undefined
53 from lxml import etree 52 from lxml import etree
54 53
55 HTML_EXT = ('html', 'xhtml') 54 HTML_EXT = ('html', 'xhtml')
56 DEFAULT_LOCALE = u'en_GB'
57 RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]') 55 RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]')
58 # TODO: handle external path (an additional search path for templates should be settable by user 56 # TODO: handle external path (an additional search path for templates should be settable by user
59 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason 57 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason
60 58
61 59
203 autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']), 201 autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']),
204 trim_blocks=True, 202 trim_blocks=True,
205 lstrip_blocks=True, 203 lstrip_blocks=True,
206 extensions=['jinja2.ext.i18n'], 204 extensions=['jinja2.ext.i18n'],
207 ) 205 )
208 self._locale_str = DEFAULT_LOCALE 206 self._locale_str = C.DEFAULT_LOCALE
209 self._locale = Locale.parse(self._locale_str) 207 self._locale = Locale.parse(self._locale_str)
210 self.installTranslations() 208 self.installTranslations()
211 # we want to have access to SàT constants in templates 209 # we want to have access to SàT constants in templates
212 self.env.globals[u'C'] = C 210 self.env.globals[u'C'] = C
213 # custom filters 211 # custom filters
256 locale_str = 'en_GB' 254 locale_str = 'en_GB'
257 try: 255 try:
258 locale = Locale.parse(locale_str) 256 locale = Locale.parse(locale_str)
259 except ValueError as e: 257 except ValueError as e:
260 log.warning(_(u"invalid locale value: {msg}").format(msg=e)) 258 log.warning(_(u"invalid locale value: {msg}").format(msg=e))
261 locale_str = self._locale_str = DEFAULT_LOCALE 259 locale_str = self._locale_str = C.DEFAULT_LOCALE
262 locale = Locale.parse(locale_str) 260 locale = Locale.parse(locale_str)
263 261
264 locale_str = unicode(locale) 262 locale_str = unicode(locale)
265 if locale_str != DEFAULT_LOCALE: 263 if locale_str != C.DEFAULT_LOCALE:
266 try: 264 try:
267 translations = self.translations[locale] 265 translations = self.translations[locale]
268 except KeyError: 266 except KeyError:
269 log.warning(_(u"Can't find locale {locale}".format(locale=locale))) 267 log.warning(_(u"Can't find locale {locale}".format(locale=locale)))
270 locale_str = DEFAULT_LOCALE 268 locale_str = C.DEFAULT_LOCALE
271 locale = Locale.parse(self._locale_str) 269 locale = Locale.parse(self._locale_str)
272 else: 270 else:
273 self.env.install_gettext_translations(translations, True) 271 self.env.install_gettext_translations(translations, True)
274 log.debug(_(u'Switched to {lang}').format(lang=locale.english_name)) 272 log.debug(_(u'Switched to {lang}').format(lang=locale.english_name))
275 273
276 if locale_str == DEFAULT_LOCALE: 274 if locale_str == C.DEFAULT_LOCALE:
277 self.env.install_null_translations(True) 275 self.env.install_null_translations(True)
278 276
279 self._locale = locale 277 self._locale = locale
280 self._locale_str = locale_str 278 self._locale_str = locale_str
281 279
364 """Use current current global index as suffix""" 362 """Use current current global index as suffix"""
365 current = ctx['gidx'].current(value) 363 current = ctx['gidx'].current(value)
366 return value if not current else u"{}_{}".format(value, current) 364 return value if not current else u"{}_{}".format(value, current)
367 365
368 def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None): 366 def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None):
367 if is_undefined(fmt):
368 fmt = u'short'
369
369 try: 370 try:
370 return self.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt) 371 return date_utils.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt)
371 except Exception as e: 372 except Exception as e:
372 log.warning(_(u"Can't parse date: {msg}").format(msg=e)) 373 log.warning(_(u"Can't parse date: {msg}").format(msg=e))
373 return timestamp 374 return timestamp
374
375 def date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=7, auto_old_fmt='short', auto_new_fmt='relative'):
376 """format date according to locale
377
378 @param timestamp(basestring, int): unix time
379 @param fmt(str): one of:
380 - short: e.g. u'31/12/17'
381 - medium: e.g. u'Apr 1, 2007'
382 - long: e.g. u'April 1, 2007'
383 - full: e.g. u'Sunday, April 1, 2007'
384 - relative: format in relative time
385 e.g.: 3 hours
386 note that this format is not precise
387 - iso: ISO 8601 format
388 e.g.: u'2007-04-01T19:53:23Z'
389 - auto: use auto_old_fmt if date is older than auto_limit
390 else use auto_new_fmt
391 - auto_day: shorcut to set auto format with change on day
392 old format will be short, and new format will be time only
393 or a free value which is passed to babel.dates.format_datetime
394 @param date_only(bool): if True, only display date (not datetime)
395 @param auto_limit (int): limit in days before using auto_old_fmt
396 use 0 to have a limit at last midnight (day change)
397 @param auto_old_fmt(unicode): format to use when date is older than limit
398 @param auto_new_fmt(unicode): format to use when date is equal to or more recent
399 than limit
400
401 """
402 if is_undefined(fmt):
403 fmt = u'short'
404
405 if (auto_limit is not None or auto_old_fmt is not None) and fmt != 'auto':
406 raise ValueError(u'auto argument can only be used with auto fmt')
407 if fmt == 'auto_day':
408 fmt, auto_limit, auto_old_fmt, auto_new_fmt = 'auto', 0, 'short', 'HH:mm'
409 if fmt == 'auto':
410 if auto_limit == 0:
411 today = time.mktime(datetime.date.today().timetuple())
412 if int(timestamp) < today:
413 fmt = auto_old_fmt
414 else:
415 fmt = auto_new_fmt
416 else:
417 days_delta = (time.time() - int(timestamp)) / 3600
418 if days_delta > (auto_limit or 7):
419 fmt = auto_old_fmt
420 else:
421 fmt = auto_new_fmt
422
423 if fmt == 'relative':
424 delta = int(timestamp) - time.time()
425 return dates.format_timedelta(delta, granularity="minute", add_direction=True, locale=self._locale_str)
426 elif fmt in ('short', 'long'):
427 formatter = dates.format_date if date_only else dates.format_datetime
428 return formatter(int(timestamp), format=fmt, locale=self._locale_str)
429 elif fmt == 'iso':
430 if date_only:
431 fmt = 'yyyy-MM-dd'
432 else:
433 fmt = "yyyy-MM-ddTHH:mm:ss'Z'"
434 return dates.format_datetime(int(timestamp), format=fmt)
435 else:
436 return dates.format_datetime(int(timestamp), format=fmt, locale=self._locale_str)
437 375
438 def attr_escape(self, text): 376 def attr_escape(self, text):
439 """escape a text to a value usable as an attribute 377 """escape a text to a value usable as an attribute
440 378
441 remove spaces, and put in lower case 379 remove spaces, and put in lower case
593 </svg> 531 </svg>
594 """.format( 532 """.format(
595 name=name, 533 name=name,
596 cls=(' ' + cls) if cls else '')) 534 cls=(' ' + cls) if cls else ''))
597 535
598 def render(self, template, theme=None, locale=DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs): 536 def render(self, template, theme=None, locale=C.DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs):
599 """render a template 537 """render a template
600 . 538 .
601 @param template(unicode): template to render (e.g. blog/articles.html) 539 @param template(unicode): template to render (e.g. blog/articles.html)
602 @param theme(unicode): template theme 540 @param theme(unicode): template theme
603 @param root_path(unicode): prefix of the path/URL to use for template root 541 @param root_path(unicode): prefix of the path/URL to use for template root