templates: add filters for working days (#31851)

This commit is contained in:
Lauréline Guérin 2020-06-19 14:48:31 +02:00 committed by Frédéric Péters
parent cedb3d2cf0
commit 2f030fd345
7 changed files with 357 additions and 5 deletions

1
debian/control vendored
View File

@ -33,6 +33,7 @@ Recommends: libreoffice-writer-nogui | libreoffice-writer,
python3-langdetect,
python3-magic,
python3-qrcode,
python3-workalendar,
python3-xlwt
Suggests: python3-libxml2
Description: web application to design and set up online forms

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
import os
import pytest
import string
@ -11,7 +12,8 @@ except ImportError:
from django.test import override_settings
from django.utils.timezone import now
from quixote import cleanup
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.substitution import CompatibilityNamesDict
from wcs.qommon.template import Template, TemplateError
from wcs.variables import LazyFormData
@ -21,11 +23,15 @@ from wcs import fields
from utilities import create_temporary_pub, clean_temporary_pub
def setup_module(module):
cleanup()
global pub
@pytest.fixture
def pub():
pub = create_temporary_pub()
pub.substitutions.feed(pub)
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
pub.set_app_dir(req)
pub.site_options.set('options', 'working_day_calendar', '')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
return pub
def teardown_module(module):
@ -73,7 +79,7 @@ def test_template():
assert tmpl.render({'foo': 'bar'}) == '[if-any foo][foo][endif]'
def test_now_and_today_variables():
def test_now_and_today_variables(pub):
# create a today string, verify it contains the year, at least
today = Template('{{d}}').render({'d': datetime.date.today()})
assert datetime.date.today().strftime('%Y') in today
@ -689,3 +695,239 @@ def test_language_detect():
def test_datetime_in_past(value, expected):
t = Template('{{ value|datetime_in_past }}')
assert t.render({'value': value}) == str(expected)
def test_is_working_day_settings(settings, pub):
settings.WORKING_DAY_CALENDAR = None
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'False'
settings.WORKING_DAY_CALENDAR = ''
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'False'
settings.WORKING_DAY_CALENDAR = 'foobar'
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'False'
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'True'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'True'
pub.site_options.set('options', 'working_day_calendar', 'foobar')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'False'
settings.WORKING_DAY_CALENDAR = 'foobar'
pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-07-15'}) == 'True'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-07-15'}) == 'True'
@pytest.mark.parametrize('value, expected', [
(None, False),
('', False),
('foobar', False),
(42, False),
('2020-07-14T12:01:03', False),
('2020-07-15T12:01:03', True),
('2020-07-14 02:03', False),
('2020-07-15 02:03', True),
('14/07/2020 02h03', False),
('15/07/2020 02h03', True),
('2020-07-14', False),
('2020-07-15', True),
('14/07/2020', False),
('15/07/2020', True),
(datetime.datetime(2020, 7, 14, 12, 1, 3), False),
(datetime.datetime(2020, 7, 15, 12, 1, 3), True),
(datetime.date(2020, 7, 14), False),
(datetime.date(2020, 7, 15), True),
])
def test_is_working_day(settings, value, expected):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|is_working_day }}')
assert t.render({'value': value}) == str(expected)
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': value}) == str(expected)
def test_is_working_day_weekend(settings):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
# check saturday
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-06-20'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-06-20'}) == 'True'
# check sunday
t = Template('{{ value|is_working_day }}')
assert t.render({'value': '2020-06-21'}) == 'False'
t = Template('{{ value|is_working_day_with_saturday }}')
assert t.render({'value': '2020-06-21'}) == 'False'
def test_add_working_days_settings(settings, pub):
settings.WORKING_DAY_CALENDAR = None
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = ''
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'foobar'
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-15'
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-15'
pub.site_options.set('options', 'working_day_calendar', 'foobar')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'foobar'
pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-15'
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-15'
def test_add_working_days_arg(settings):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|add_working_days:"foobar" }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days_with_saturday:"foobar" }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|add_working_days:2 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-16'
t = Template('{{ value|add_working_days_with_saturday:2 }}')
assert t.render({'value': '2020-07-13'}) == '2020-07-16'
@pytest.mark.parametrize('value, expected', [
(None, ''),
('', ''),
('foobar', ''),
(42, ''),
('2020-07-13T12:01:03', '2020-07-15'),
('2020-07-13 02:03', '2020-07-15'),
('13/07/2020 02h03', '2020-07-15'),
('2020-07-13', '2020-07-15'),
('13/07/2020', '2020-07-15'),
(datetime.datetime(2020, 7, 13, 12, 1, 3), '2020-07-15'),
(datetime.date(2020, 7, 13), '2020-07-15'),
])
def test_add_working_days(settings, value, expected):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': value}) == str(expected)
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': value}) == str(expected)
def test_add_working_days_weekend(settings):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|add_working_days:1 }}')
assert t.render({'value': '2020-06-19'}) == '2020-06-22'
t = Template('{{ value|add_working_days_with_saturday:1 }}')
assert t.render({'value': '2020-06-19'}) == '2020-06-20'
def test_adjust_to_working_day_settings(settings, pub):
settings.WORKING_DAY_CALENDAR = None
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = ''
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'foobar'
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-14'}) == '2020-07-15'
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-14'}) == '2020-07-15'
pub.site_options.set('options', 'working_day_calendar', 'foobar')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-13'}) == ''
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-13'}) == ''
settings.WORKING_DAY_CALENDAR = 'foobar'
pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-07-14'}) == '2020-07-15'
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-07-14'}) == '2020-07-15'
@pytest.mark.parametrize('value, expected', [
(None, ''),
('', ''),
('foobar', ''),
(42, ''),
('2020-07-14T12:01:03', '2020-07-15'),
('2020-07-14 02:03', '2020-07-15'),
('14/07/2020 02h03', '2020-07-15'),
('2020-07-14', '2020-07-15'),
('14/07/2020', '2020-07-15'),
(datetime.datetime(2020, 7, 14, 12, 1, 3), '2020-07-15'),
(datetime.date(2020, 7, 14), '2020-07-15'),
(datetime.date(2020, 7, 15), '2020-07-15'),
])
def test_adjust_to_working_day(settings, value, expected):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': value}) == str(expected)
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': value}) == str(expected)
def test_adjust_to_working_day_weekend(settings):
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
t = Template('{{ value|adjust_to_working_day }}')
assert t.render({'value': '2020-06-20'}) == '2020-06-22'
t = Template('{{ value|adjust_to_working_day_with_saturday }}')
assert t.render({'value': '2020-06-20'}) == '2020-06-20'

View File

@ -30,6 +30,7 @@ deps =
vobject
qrcode
Pillow<7.2
workalendar
python-magic
docutils
langdetect

41
wcs/qommon/calendar.py Normal file
View File

@ -0,0 +1,41 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2020 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from django.utils.module_loading import import_string
from quixote import get_publisher
try:
from workalendar.core import SUN
except ImportError:
SUN = None
def get_calendar(saturday_is_a_working_day=False):
# get calendar from settings
try:
calendar_class = import_string(get_publisher().get_working_day_calendar())
except (AttributeError, ImportError):
return
# saturday is not a working day, return this calendar
if not saturday_is_a_working_day:
return calendar_class()
# saturday is a working day, build a custom calendar
class CalendarWithSaturday(calendar_class):
WEEKEND_DAYS = (SUN,)
return CalendarWithSaturday()

View File

@ -892,6 +892,9 @@ class QommonPublisher(Publisher, object):
url += '?key=%s' % key
return url
def get_working_day_calendar(self):
return self.get_site_option('working_day_calendar') or settings.WORKING_DAY_CALENDAR
def get_supported_authentication_contexts(self):
contexts = collections.OrderedDict()
labels = {

View File

@ -39,6 +39,7 @@ from django.utils import six
from django.utils.encoding import force_bytes, force_text
from django.utils.safestring import mark_safe
from django.utils.timezone import is_naive, make_aware
from wcs.qommon import calendar
from wcs.qommon import evalutils
from wcs.qommon import tokens
from wcs.qommon.admin.texts import TextsDirectory
@ -281,6 +282,66 @@ def datetime_in_past(value):
return value <= date_now
@register.filter(expects_localtime=True)
def is_working_day(value, saturday_is_a_working_day=False):
value = parse_date(value)
if not value:
return False
cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
if not cal:
return False
return cal.is_working_day(value)
@register.filter(expects_localtime=True)
def is_working_day_with_saturday(value):
return is_working_day(value, saturday_is_a_working_day=True)
@register.filter(expects_localtime=True)
def add_working_days(value, arg, saturday_is_a_working_day=False):
value = parse_date(value)
if not value:
return ''
cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
if not cal:
return ''
try:
return cal.add_working_days(value, int(arg))
except ValueError:
return ''
@register.filter(expects_localtime=True)
def add_working_days_with_saturday(value, arg):
return add_working_days(value, arg, saturday_is_a_working_day=True)
@register.filter(expects_localtime=True)
def adjust_to_working_day(value, saturday_is_a_working_day=False):
value = parse_date(value)
if not value:
return ''
cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
if not cal:
return ''
if cal.is_working_day(value):
return value
# return next working day
return cal.add_working_days(value, 1)
@register.filter(expects_localtime=True)
def adjust_to_working_day_with_saturday(value):
return adjust_to_working_day(value, saturday_is_a_working_day=True)
@register.simple_tag
def standard_text(text_id):
return mark_safe(TextsDirectory.get_html_text(str(text_id)))

View File

@ -188,6 +188,9 @@ DISABLE_CRON_JOBS = False
# w.c.s. can have very large forms, in backoffice and frontoffice
DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000 # Django default is 1000
# workalendar config
WORKING_DAY_CALENDAR = 'workalendar.europe.France'
local_settings_file = os.environ.get('WCS_SETTINGS_FILE',
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
if os.path.exists(local_settings_file):