template: improved date formatter:

the date filter is now named "date_fmt" and can be use to specify several useful formats (see docstring), using the current locale.
The "relative" format return the whole human readable relative date.

The new in_the_past test allow to check if a date is passed.
This commit is contained in:
Goffi 2017-10-27 18:20:10 +02:00
commit ecb3ba897e

View file

@ -31,6 +31,7 @@ import re
from babel import support
from babel import Locale
from babel.core import UnknownLocaleError
from babel import dates
try:
import sat_templates
except ImportError:
@ -44,9 +45,10 @@ except:
raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2')
from jinja2 import Markup as safe
from jinja2 import is_undefined
HTML_EXT = ('html', 'xhtml')
DEFAULT_LOCALE = u'en'
DEFAULT_LOCALE = u'en_GB'
RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]')
# TODO: handle external path (an additional search path for templates should be settable by user
# TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason
@ -200,11 +202,13 @@ class Renderer(object):
# custom filters
self.env.filters['next_gidx'] = self._next_gidx
self.env.filters['cur_gidx'] = self._cur_gidx
self.env.filters['date_days'] = self._date_days
self.env.filters['date_fmt'] = self._date_fmt
self.env.filters['xmlui_class'] = self._xmlui_class
self.env.filters['attr_escape'] = self.attr_escape
self.env.filters['item_filter'] = self._item_filter
self.env.filters['adv_format'] = self._adv_format
# custom tests
self.env.tests['in_the_past'] = self._in_the_past
def installTranslations(self):
i18n_dir = os.path.join(self.base_dir, 'i18n')
@ -226,8 +230,16 @@ class Renderer(object):
self.env.install_null_translations(True)
def setLocale(self, locale_str):
"""set current locale
change current translation locale and self self._locale and self._locale_str
"""
if locale_str == self._locale_str:
return
if locale_str == 'en':
# we default to GB English when it's not specified
# one of the main reason is to avoid the nonsense U.S. short date format
locale_str = 'en_GB'
try:
locale = Locale.parse(locale_str)
except ValueError as e:
@ -324,6 +336,9 @@ class Renderer(object):
return css_files
## custom filters ##
@jinja2.contextfilter
def _next_gidx(self, ctx, value):
"""Use next current global index as suffix"""
@ -336,8 +351,54 @@ class Renderer(object):
current = ctx['gidx'].current(value)
return value if not current else u"{}_{}".format(value, current)
def _date_days(self, timestamp):
return int(time.time() - int(timestamp))/(3600*24)
def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None):
"""format date according to locale
@param timestamp(basestring, int): unix time
@param fmt(str): one of:
- short: e.g. u'31/12/17'
- medium: e.g. u'Apr 1, 2007'
- long: e.g. u'April 1, 2007'
- full: e.g. u'Sunday, April 1, 2007'
- relative: format in relative time
e.g.: 3 hours
note that this format is not precise
- iso: ISO 8601 format
e.g.: u'2007-04-01T19:53:23Z'
- auto_limit (int, None): limit in days before using auto_old_fmt
None: use default(7 days)
- auto_old_fmt(unicode, None): format to use when date is olded than limit
None: use default(short)
or a free value which is passed to babel.dates.format_datetime
@param date_only(bool): if True, only display date (not datetime)
"""
if is_undefined(fmt):
fmt = u'short'
if (auto_limit is not None or auto_old_fmt is not None) and fmt != 'auto':
raise ValueError(u'auto argument can only be used with auto fmt')
if fmt == 'auto':
days_delta = (time.time() - int(timestamp)) / 3600
if days_delta > (auto_limit or 7):
fmt = auto_old_fmt or 'short'
else:
fmt = 'relative'
if fmt == 'relative':
delta = int(timestamp) - time.time()
return dates.format_timedelta(delta, granularity="minute", add_direction=True, locale=self._locale_str)
elif fmt in ('short', 'long'):
formatter = dates.format_date if date_only else dates.format_datetime
return formatter(int(timestamp), format=fmt, locale=self._locale_str)
elif fmt == 'iso':
if date_only:
fmt = 'yyyy-MM-dd'
else:
fmt = "yyyy-MM-ddTHH:mm:ss'Z'"
return dates.format_datetime(int(timestamp), format=fmt)
else:
return dates.format_datetime(int(timestamp), format=fmt, locale=self._locale_str)
def attr_escape(self, text):
"""escape a text to a value usable as an attribute
@ -409,6 +470,16 @@ class Renderer(object):
# jinja use string when no special char is used, so we have to convert to unicode
return unicode(template).format(value=value, **kwargs)
## custom tests ##
def _in_the_past(self, timestamp):
"""check if a date is in the past
@param timestamp(unicode, int): unix time
@return (bool): True if date is in the past
"""
return time.time() > int(timestamp)
def render(self, template, theme=None, locale=DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs):
"""render a template