From 9949ec25bfbbb04a4f1ba44318ba9767eb11a592 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 15 Jan 2020 11:57:46 +0100 Subject: [PATCH] templatetags: add date filters (#36943) --- combo/public/templatetags/combo.py | 154 ++++++++++++++++- combo/utils/date.py | 59 +++++++ tests/test_public_templatetags.py | 264 ++++++++++++++++++++++++++++- 3 files changed, 471 insertions(+), 6 deletions(-) create mode 100644 combo/utils/date.py diff --git a/combo/public/templatetags/combo.py b/combo/public/templatetags/combo.py index b9f6ac6..cf491d6 100644 --- a/combo/public/templatetags/combo.py +++ b/combo/public/templatetags/combo.py @@ -26,13 +26,15 @@ from django.core import signing from django.core.exceptions import PermissionDenied from django.template import VariableDoesNotExist from django.template.base import TOKEN_BLOCK, TOKEN_VAR, TOKEN_COMMENT +from django.template import defaultfilters from django.template.defaultfilters import stringfilter -from django.utils import dateparse +from django.utils import dateparse, six from django.utils.encoding import force_text from combo.data.models import Page, Placeholder from combo.public.menu import get_menu_context from combo.utils import NothingInCacheException, flatten_context +from combo.utils.date import make_date, make_datetime from combo.apps.dashboard.models import DashboardCell, Tile register = template.Library() @@ -176,27 +178,68 @@ def strptime(date_string, date_format): @register.filter def parse_date(date_string): + try: + return make_date(date_string) + except ValueError: + pass + # fallback to Django function try: return dateparse.parse_date(date_string) except (ValueError, TypeError): return None +@register.filter(expects_localtime=True, is_safe=False) +def date(value, arg=None): + if arg is None: + return parse_date(value) or '' + if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)): + value = parse_datetime(value) or parse_date(value) + return defaultfilters.date(value, arg=arg) + @register.filter -def parse_datetime(date_string): - if isinstance(date_string, time.struct_time): - return datetime.datetime.fromtimestamp(time.mktime(date_string)) +def parse_datetime(datetime_string): try: - return dateparse.parse_datetime(date_string) + return make_datetime(datetime_string) + except ValueError: + pass + # fallback to Django function + try: + return dateparse.parse_datetime(datetime_string) except (ValueError, TypeError): return None +@register.filter(name='datetime', expects_localtime=True, is_safe=False) +def datetime_(value, arg=None): + if arg is None: + return parse_datetime(value) or '' + if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)): + value = parse_datetime(value) + return defaultfilters.date(value, arg=arg) + @register.filter def parse_time(time_string): + # if input is a datetime, extract its time + try: + dt = parse_datetime(time_string) + if dt: + return dt.time() + except (ValueError, TypeError): + pass + # fallback to Django function try: return dateparse.parse_time(time_string) except (ValueError, TypeError): return None +@register.filter(expects_localtime=True, is_safe=False) +def time(value, arg=None): + if arg is None: + parsed = parse_time(value) + return parsed if parsed is not None else '' # because bool(midnight) == False + if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)): + value = parse_time(value) + return defaultfilters.date(value, arg=arg) + @register.filter def shown_because_admin(cell, request): if not (request.user and request.user.is_superuser): @@ -269,3 +312,104 @@ def get_page(page_slug): @register.filter def startswith(string, substring): return string and force_text(string).startswith(force_text(substring)) + +def parse_float(value): + if isinstance(value, six.string_types): + # replace , by . for French users comfort + value = value.replace(',', '.') + try: + return float(value) + except (ValueError, TypeError): + return '' + +def get_as_datetime(s): + result = parse_datetime(s) + if not result: + result = parse_date(s) + if result: + result = datetime.datetime(year=result.year, month=result.month, day=result.day) + return result + +@register.filter(expects_localtime=True, is_safe=False) +def add_days(value, arg): + value = parse_date(value) # consider only date, not hours + if not value: + return '' + arg = parse_float(arg) + if not arg: + return value + result = value + datetime.timedelta(days=float(arg)) + return result + +@register.filter(expects_localtime=True, is_safe=False) +def add_hours(value, arg): + value = parse_datetime(value) + if not value: + return '' + arg = parse_float(arg) + if not arg: + return value + return value + datetime.timedelta(hours=float(arg)) + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_days(value, today=None): + value = parse_date(value) + if not value: + return '' + if today is not None: + today = parse_date(today) + if not today: + return '' + else: + today = datetime.date.today() + return (today - value).days + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_hours(value, now=None): + # consider value and now as datetimes (and not dates) + value = parse_datetime(value) + if not value: + return '' + if now is not None: + now = parse_datetime(now) + if not now: + return '' + else: + now = datetime.datetime.now() + return int((now - value).total_seconds() / 3600) + +def age_in_years_and_months(born, today=None): + '''Compute age since today as the number of years and months elapsed''' + born = make_date(born) + if not born: + return '' + if today is not None: + today = make_date(today) + if not today: + return '' + else: + today = datetime.date.today() + before = (today.month, today.day) < (born.month, born.day) + years = today.year - born.year + months = today.month - born.month + if before: + years -= 1 + months += 12 + if today.day < born.day: + months -= 1 + return years, months + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_years(value, today=None): + try: + return age_in_years_and_months(value, today)[0] + except ValueError: + return '' + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_months(value, today=None): + try: + years, months = age_in_years_and_months(value, today) + except ValueError: + return '' + return years * 12 + months diff --git a/combo/utils/date.py b/combo/utils/date.py new file mode 100644 index 0000000..8a0d538 --- /dev/null +++ b/combo/utils/date.py @@ -0,0 +1,59 @@ +import datetime +import time + +DATE_FORMATS = { + 'C': ['%Y-%m-%d', '%y-%m-%d'], + 'fr': ['%d/%m/%Y', '%d/%m/%y'], +} + +DATETIME_FORMATS = { + 'C': ['%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%SZ', + '%y-%m-%d %H:%M', '%y-%m-%d %H:%M:%S'], + 'fr': ['%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M:%S', '%d/%m/%Y %Hh%M', + '%d/%m/%y %H:%M', '%d/%m/%y %H:%M:%S', '%d/%m/%y %Hh%M'], +} + + +def get_as_datetime(s): + formats = [] + for value in DATETIME_FORMATS.values(): + formats.extend(value) + for value in DATE_FORMATS.values(): + formats.extend(value) + for format_string in formats: + try: + return datetime.datetime.strptime(s, format_string) + except ValueError: + pass + raise ValueError() + + +def make_date(date_var): + '''Extract a date from a datetime, a date, a struct_time or a string''' + if isinstance(date_var, datetime.datetime): + return date_var.date() + if isinstance(date_var, datetime.date): + return date_var + if isinstance(date_var, time.struct_time) or ( + isinstance(date_var, tuple) and len(date_var) == 9): + return datetime.date(*date_var[:3]) + try: + return get_as_datetime(str(date_var)).date() + except ValueError: + raise ValueError('invalid date value: %s' % date_var) + + +def make_datetime(datetime_var): + '''Extract a date from a datetime, a date, a struct_time or a string''' + if isinstance(datetime_var, datetime.datetime): + return datetime_var + if isinstance(datetime_var, datetime.date): + return datetime.datetime(year=datetime_var.year, month=datetime_var.month, + day=datetime_var.day) + if isinstance(datetime_var, time.struct_time) or ( + isinstance(datetime_var, tuple) and len(datetime_var) == 9): + return datetime.datetime(*datetime_var[:6]) + try: + return get_as_datetime(str(datetime_var)) + except ValueError: + raise ValueError('invalid datetime value: %s' % datetime_var) diff --git a/tests/test_public_templatetags.py b/tests/test_public_templatetags.py index d5efd57..1add319 100644 --- a/tests/test_public_templatetags.py +++ b/tests/test_public_templatetags.py @@ -1,3 +1,4 @@ +import datetime import os import shutil import time @@ -48,7 +49,7 @@ def test_parse_datetime(): t = Template('{{ someday|parse_date|date:"Y m d" }}') expected = '2015 04 15' assert t.render(Context({'someday': '2015-04-15'})) == expected - assert t.render(Context({'someday': '2015-04-15T13:11:12Z'})) == '' + assert t.render(Context({'someday': '2015-04-15T13:11:12Z'})) == expected assert t.render(Context({'someday': 'foobar'})) == '' assert t.render(Context({'someday': ''})) == '' assert t.render(Context({'someday': None})) == '' @@ -201,3 +202,264 @@ def test_startswith(): assert t.render(context) == '' context = Context({'foo': 'bar'}) assert t.render(context) == 'ok' + +def test_datetime_templatetags(): + tmpl = Template('{{ plop|datetime }}') + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == 'Dec. 21, 2017, 10:32 a.m.' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == 'Dec. 21, 2017, 10:32 a.m.' + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017, midnight' + assert tmpl.render(Context({'plop': '21/12/2017'})) == 'Dec. 21, 2017, midnight' + assert tmpl.render(Context({'plop': '10h32'})) == '' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{{ plop|datetime:"d i" }}') + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21 32' + assert tmpl.render(Context({'plop': '2017-12-21 10:32:42'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017'})) == '21 00' + assert tmpl.render(Context({'plop': '10h32'})) == '' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{% if d1|datetime > d2|datetime %}d1>d2{% else %}d1<=d2{% endif %}') + assert tmpl.render(Context({'d1': '2017-12-22', 'd2': '2017-12-21'})) == 'd1>d2' + assert tmpl.render(Context({'d1': '2017-12-21', 'd2': '2017-12-21'})) == 'd1<=d2' + assert tmpl.render(Context({'d1': '2017-12-21 10:30', 'd2': '2017-12-21 09:00'})) == 'd1>d2' + assert tmpl.render(Context({'d1': '2017-12-21 10:30', 'd2': '2017-12-21'})) == 'd1>d2' + assert tmpl.render(Context({'d1': '2017-12-22'})) == 'd1<=d2' + assert tmpl.render(Context({'d2': '2017-12-22'})) == 'd1<=d2' + + tmpl = Template('{{ plop|date }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '21/12/2017'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == 'Dec. 21, 2017' + assert tmpl.render(Context({'plop': '10:32'})) == '' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{{ plop|date:"d" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == '21' + assert tmpl.render(Context({'plop': '21/12/2017'})) == '21' + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21' + assert tmpl.render(Context({'plop': '10:32'})) == '' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{% if d1|date > d2|date %}d1>d2{% else %}d1<=d2{% endif %}') + assert tmpl.render(Context({'d1': '2017-12-22', 'd2': '2017-12-21'})) == 'd1>d2' + assert tmpl.render(Context({'d1': '2017-12-21', 'd2': '2017-12-21'})) == 'd1<=d2' + assert tmpl.render(Context({'d1': '2017-12-22'})) == 'd1<=d2' + assert tmpl.render(Context({'d2': '2017-12-22'})) == 'd1<=d2' + + tmpl = Template('{{ plop|time }}') + assert tmpl.render(Context({'plop': '10:32'})) == '10:32 a.m.' + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '10:32 a.m.' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '10:32 a.m.' + assert tmpl.render(Context({'plop': '21/12/2017'})) == 'midnight' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{{ plop|time:"H i" }}') + assert tmpl.render(Context({'plop': '10:32'})) == '10 32' + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '10 32' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '10 32' + assert tmpl.render(Context({'plop': '21/12/2017'})) == '00 00' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + # old fashion, with parse_* + tmpl = Template('{{ plop|parse_datetime|date:"d i" }}') + assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21 32' + assert tmpl.render(Context({'plop': '2017-12-21 10:32:42'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == '21 32' + assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '21 32' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{{ plop|parse_date|date:"d" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == '21' + assert tmpl.render(Context({'plop': '21/12/2017'})) == '21' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + tmpl = Template('{{ plop|parse_time|date:"H i" }}') + assert tmpl.render(Context({'plop': '10:32'})) == '10 32' + assert tmpl.render(Context({'plop': 'x'})) == '' + assert tmpl.render(Context({'plop': None})) == '' + assert tmpl.render(Context({'plop': 3})) == '' + assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == '' + assert tmpl.render(Context()) == '' + + +def test_date_maths(): + tmpl = Template('{{ plop|add_days:4 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 25, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 25, 2017' + tmpl = Template('{{ plop|add_days:"-1" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 20, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 20, 2017' + tmpl = Template('{{ plop|add_days:1.5 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017' + tmpl = Template('{{ plop|add_days:"1.5" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017' + + tmpl = Template('{{ plop|add_hours:24 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017, midnight' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6 p.m.' + tmpl = Template('{{ plop|add_hours:"12.5" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017, 12:30 p.m.' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6:30 a.m.' + + +def test_age_in(): + context = { + 'form_var_datefield': time.strptime('2018-07-31', '%Y-%m-%d'), + 'form_var_datefield2': time.strptime('2018-08-31', '%Y-%m-%d'), + 'form_var_datestring': '2018-07-31', + 'today': datetime.date.today(), + 'now': datetime.datetime.now(), + } + for condition_value in ( # hope date is > 2018 + # age_in_days + '"1970-01-01"|age_in_days > 0', + '"01/01/1970"|age_in_days > 0', + '"2500-01-01"|age_in_days < 0', + '"01/01/2500"|age_in_days < 0', + 'form_var_datefield|age_in_days > 50', + 'form_var_datefield|age_in_days:form_var_datestring == 0', + 'form_var_datefield|age_in_days:form_var_datefield2 == 31', + 'form_var_datefield2|age_in_days:form_var_datefield == -31', + 'form_var_datefield|age_in_days:form_var_datefield == 0', + 'form_var_datestring|age_in_days:form_var_datefield == 0', + 'form_var_datestring|age_in_days:form_var_datestring == 0', + 'today|add_days:-5|age_in_days == 5', + 'today|add_days:5|age_in_days == -5', + 'today|age_in_days == 0', + # with datetimes + '"1970-01-01 02:03"|age_in_days > 0', + '"01/01/1970 02h03"|age_in_days > 0', + '"2500-01-01 02:03"|age_in_days < 0', + '"01/01/2500 02h03"|age_in_days < 0', + 'now|age_in_days == 0', + 'now|add_hours:-24|age_in_days == 1', + 'now|add_hours:24|age_in_days == -1', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 13:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 12:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 14:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:13" == 1', + '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:15" == 1', + + # age_in_hours + 'now|add_hours:-5|age_in_hours == 5', + 'now|add_hours:25|age_in_hours == -24', + 'now|age_in_hours == 0', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 13:14" == 0', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 12:14" == -1', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 14:14" == 1', + '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:13" == 23', + '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:15" == 24', + '"1970-01-01 02:03"|age_in_hours > 0', + '"01/01/1970 02h03"|age_in_hours > 0', + '"2500-01-01 02:03"|age_in_hours < 0', + '"01/01/2500 02h03"|age_in_hours < 0', + # with dates + '"1970-01-01"|age_in_hours > 0', + '"01/01/1970"|age_in_hours > 0', + '"2500-01-01"|age_in_hours < 0', + '"01/01/2500"|age_in_hours < 0', + 'form_var_datefield|age_in_hours > 1200', + 'form_var_datefield|age_in_hours:form_var_datestring == 0', + 'form_var_datefield|age_in_hours:form_var_datefield2 == 744', # 31*24 + 'form_var_datefield2|age_in_hours:form_var_datefield == -744', + 'form_var_datefield|age_in_hours:form_var_datefield == 0', + 'form_var_datestring|age_in_hours:form_var_datefield == 0', + 'form_var_datestring|age_in_hours:form_var_datestring == 0', + 'today|add_days:-1|age_in_hours >= 24', + 'today|add_days:1|age_in_hours <= -0', + 'today|add_days:1|age_in_hours >= -24', + 'today|age_in_hours >= 0', + + # age_in_years + '"1970-01-01"|age_in_years > 0', + '"01/01/1970"|age_in_years > 0', + '"2500-01-01"|age_in_years < 0', + '"01/01/2500"|age_in_years < 0', + 'form_var_datefield|age_in_years:"2019-07-31" == 1', + 'form_var_datefield|age_in_years:"2019-09-20" == 1', + 'form_var_datefield|age_in_years:"2020-07-30" == 1', + 'form_var_datefield|age_in_years:"2020-07-31" == 2', + 'form_var_datestring|age_in_years:"2019-07-31" == 1', + 'today|age_in_years == 0', + 'today|add_days:-500|age_in_years == 1', + 'today|add_days:-300|age_in_years == 0', + 'today|add_days:300|age_in_years == -1', + 'now|age_in_years == 0', + 'now|add_days:-500|age_in_years == 1', + 'now|add_days:-300|age_in_years == 0', + 'now|add_days:300|age_in_years == -1', + '"1970-01-01 02:03"|age_in_years > 0', + '"2500-01-01 02:03"|age_in_years < 0', + + # age_in_months + 'form_var_datefield|age_in_months:form_var_datefield2 == 1', + 'form_var_datefield2|age_in_months:form_var_datefield == -1', + 'form_var_datefield|age_in_months:"2019-07-31" == 12', + 'form_var_datefield|age_in_months:"2019-08-20" == 12', + 'form_var_datefield|age_in_months:"2019-09-20" == 13', + 'form_var_datestring|age_in_months:"2019-09-20" == 13', + '"1970-01-01"|age_in_months > 0', + '"01/01/1970"|age_in_months > 0', + '"2500-01-01"|age_in_months < 0', + '"01/01/2500"|age_in_months < 0', + '"1970-01-01 02:03"|age_in_months > 0', + '"2500-01-01 02:03"|age_in_months < 0', + + # fail produce empty string + 'foobar|age_in_days == ""', + '"foobar"|age_in_days == ""', + '"1970-01-01"|age_in_days:"foobar" == ""', + 'foobar|age_in_hours == ""', + '"foobar"|age_in_hours == ""', + '"1970-01-01"|age_in_hours:"foobar" == ""', + 'foobar|age_in_years == ""', + '"foobar"|age_in_years == ""', + '"1970-01-01"|age_in_years:"foobar" == ""', + 'foobar|age_in_months == ""', + '"foobar"|age_in_months == ""', + '"1970-01-01"|age_in_months:"foobar" == ""', + ): + tmpl = Template('{%% if %s %%}Good{%% endif %%}' % condition_value) + assert tmpl.render(Context(context)) == 'Good' -- 2.20.1