Projet

Général

Profil

0001-templates-add-filters-for-working-days-31851.patch

Lauréline Guérin, 30 juin 2020 15:48

Télécharger (18,4 ko)

Voir les différences:

Subject: [PATCH] templates: add filters for working days (#31851)

 debian/control                    |   1 +
 tests/test_templates.py           | 252 +++++++++++++++++++++++++++++-
 tox.ini                           |   1 +
 wcs/qommon/calendar.py            |  41 +++++
 wcs/qommon/publisher.py           |   3 +
 wcs/qommon/templatetags/qommon.py |  61 ++++++++
 wcs/settings.py                   |   3 +
 7 files changed, 357 insertions(+), 5 deletions(-)
 create mode 100644 wcs/qommon/calendar.py
debian/control
33 33
    python3-langdetect,
34 34
    python3-magic,
35 35
    python3-qrcode,
36
    python3-workalendar,
36 37
    python3-xlwt
37 38
Suggests: python3-libxml2
38 39
Description: web application to design and set up online forms
tests/test_templates.py
1 1
# -*- coding: utf-8 -*-
2 2

  
3 3
import datetime
4
import os
4 5
import pytest
5 6
import string
6 7

  
......
11 12

  
12 13
from django.test import override_settings
13 14
from django.utils.timezone import now
14
from quixote import cleanup
15

  
16
from wcs.qommon.http_request import HTTPRequest
15 17
from wcs.qommon.substitution import CompatibilityNamesDict
16 18
from wcs.qommon.template import Template, TemplateError
17 19
from wcs.variables import LazyFormData
......
21 23
from utilities import create_temporary_pub, clean_temporary_pub
22 24

  
23 25

  
24
def setup_module(module):
25
    cleanup()
26
    global pub
26
@pytest.fixture
27
def pub():
27 28
    pub = create_temporary_pub()
28 29
    pub.substitutions.feed(pub)
30
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
31
    pub.set_app_dir(req)
32
    pub.site_options.set('options', 'working_day_calendar', '')
33
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
34
    return pub
29 35

  
30 36

  
31 37
def teardown_module(module):
......
73 79
    assert tmpl.render({'foo': 'bar'}) == '[if-any foo][foo][endif]'
74 80

  
75 81

  
76
def test_now_and_today_variables():
82
def test_now_and_today_variables(pub):
77 83
    # create a today string, verify it contains the year, at least
78 84
    today = Template('{{d}}').render({'d': datetime.date.today()})
79 85
    assert datetime.date.today().strftime('%Y') in today
......
689 695
def test_datetime_in_past(value, expected):
690 696
    t = Template('{{ value|datetime_in_past }}')
691 697
    assert t.render({'value': value}) == str(expected)
698

  
699

  
700
def test_is_working_day_settings(settings, pub):
701
    settings.WORKING_DAY_CALENDAR = None
702
    t = Template('{{ value|is_working_day }}')
703
    assert t.render({'value': '2020-07-15'}) == 'False'
704
    t = Template('{{ value|is_working_day_with_saturday }}')
705
    assert t.render({'value': '2020-07-15'}) == 'False'
706

  
707
    settings.WORKING_DAY_CALENDAR = ''
708
    t = Template('{{ value|is_working_day }}')
709
    assert t.render({'value': '2020-07-15'}) == 'False'
710
    t = Template('{{ value|is_working_day_with_saturday }}')
711
    assert t.render({'value': '2020-07-15'}) == 'False'
712

  
713
    settings.WORKING_DAY_CALENDAR = 'foobar'
714
    t = Template('{{ value|is_working_day }}')
715
    assert t.render({'value': '2020-07-15'}) == 'False'
716
    t = Template('{{ value|is_working_day_with_saturday }}')
717
    assert t.render({'value': '2020-07-15'}) == 'False'
718

  
719
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
720
    t = Template('{{ value|is_working_day }}')
721
    assert t.render({'value': '2020-07-15'}) == 'True'
722
    t = Template('{{ value|is_working_day_with_saturday }}')
723
    assert t.render({'value': '2020-07-15'}) == 'True'
724

  
725
    pub.site_options.set('options', 'working_day_calendar', 'foobar')
726
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
727
    t = Template('{{ value|is_working_day }}')
728
    assert t.render({'value': '2020-07-15'}) == 'False'
729
    t = Template('{{ value|is_working_day_with_saturday }}')
730
    assert t.render({'value': '2020-07-15'}) == 'False'
731

  
732
    settings.WORKING_DAY_CALENDAR = 'foobar'
733
    pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
734
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
735
    t = Template('{{ value|is_working_day }}')
736
    assert t.render({'value': '2020-07-15'}) == 'True'
737
    t = Template('{{ value|is_working_day_with_saturday }}')
738
    assert t.render({'value': '2020-07-15'}) == 'True'
739

  
740

  
741
@pytest.mark.parametrize('value, expected', [
742
    (None, False),
743
    ('', False),
744
    ('foobar', False),
745
    (42, False),
746
    ('2020-07-14T12:01:03', False),
747
    ('2020-07-15T12:01:03', True),
748
    ('2020-07-14 02:03', False),
749
    ('2020-07-15 02:03', True),
750
    ('14/07/2020 02h03', False),
751
    ('15/07/2020 02h03', True),
752
    ('2020-07-14', False),
753
    ('2020-07-15', True),
754
    ('14/07/2020', False),
755
    ('15/07/2020', True),
756
    (datetime.datetime(2020, 7, 14, 12, 1, 3), False),
757
    (datetime.datetime(2020, 7, 15, 12, 1, 3), True),
758
    (datetime.date(2020, 7, 14), False),
759
    (datetime.date(2020, 7, 15), True),
760
])
761
def test_is_working_day(settings, value, expected):
762
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
763
    t = Template('{{ value|is_working_day }}')
764
    assert t.render({'value': value}) == str(expected)
765
    t = Template('{{ value|is_working_day_with_saturday }}')
766
    assert t.render({'value': value}) == str(expected)
767

  
768

  
769
def test_is_working_day_weekend(settings):
770
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
771
    # check saturday
772
    t = Template('{{ value|is_working_day }}')
773
    assert t.render({'value': '2020-06-20'}) == 'False'
774
    t = Template('{{ value|is_working_day_with_saturday }}')
775
    assert t.render({'value': '2020-06-20'}) == 'True'
776
    # check sunday
777
    t = Template('{{ value|is_working_day }}')
778
    assert t.render({'value': '2020-06-21'}) == 'False'
779
    t = Template('{{ value|is_working_day_with_saturday }}')
780
    assert t.render({'value': '2020-06-21'}) == 'False'
781

  
782

  
783
def test_add_working_days_settings(settings, pub):
784
    settings.WORKING_DAY_CALENDAR = None
785
    t = Template('{{ value|add_working_days:1 }}')
786
    assert t.render({'value': '2020-07-13'}) == ''
787
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
788
    assert t.render({'value': '2020-07-13'}) == ''
789

  
790
    settings.WORKING_DAY_CALENDAR = ''
791
    t = Template('{{ value|add_working_days:1 }}')
792
    assert t.render({'value': '2020-07-13'}) == ''
793
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
794
    assert t.render({'value': '2020-07-13'}) == ''
795

  
796
    settings.WORKING_DAY_CALENDAR = 'foobar'
797
    t = Template('{{ value|add_working_days:1 }}')
798
    assert t.render({'value': '2020-07-13'}) == ''
799
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
800
    assert t.render({'value': '2020-07-13'}) == ''
801

  
802
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
803
    t = Template('{{ value|add_working_days:1 }}')
804
    assert t.render({'value': '2020-07-13'}) == '2020-07-15'
805
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
806
    assert t.render({'value': '2020-07-13'}) == '2020-07-15'
807

  
808
    pub.site_options.set('options', 'working_day_calendar', 'foobar')
809
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
810
    t = Template('{{ value|add_working_days:1 }}')
811
    assert t.render({'value': '2020-07-13'}) == ''
812
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
813
    assert t.render({'value': '2020-07-13'}) == ''
814

  
815
    settings.WORKING_DAY_CALENDAR = 'foobar'
816
    pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
817
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
818
    t = Template('{{ value|add_working_days:1 }}')
819
    assert t.render({'value': '2020-07-13'}) == '2020-07-15'
820
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
821
    assert t.render({'value': '2020-07-13'}) == '2020-07-15'
822

  
823

  
824
def test_add_working_days_arg(settings):
825
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
826
    t = Template('{{ value|add_working_days:"foobar" }}')
827
    assert t.render({'value': '2020-07-13'}) == ''
828
    t = Template('{{ value|add_working_days_with_saturday:"foobar" }}')
829
    assert t.render({'value': '2020-07-13'}) == ''
830
    t = Template('{{ value|add_working_days:2 }}')
831
    assert t.render({'value': '2020-07-13'}) == '2020-07-16'
832
    t = Template('{{ value|add_working_days_with_saturday:2 }}')
833
    assert t.render({'value': '2020-07-13'}) == '2020-07-16'
834

  
835

  
836
@pytest.mark.parametrize('value, expected', [
837
    (None, ''),
838
    ('', ''),
839
    ('foobar', ''),
840
    (42, ''),
841
    ('2020-07-13T12:01:03', '2020-07-15'),
842
    ('2020-07-13 02:03', '2020-07-15'),
843
    ('13/07/2020 02h03', '2020-07-15'),
844
    ('2020-07-13', '2020-07-15'),
845
    ('13/07/2020', '2020-07-15'),
846
    (datetime.datetime(2020, 7, 13, 12, 1, 3), '2020-07-15'),
847
    (datetime.date(2020, 7, 13), '2020-07-15'),
848
])
849
def test_add_working_days(settings, value, expected):
850
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
851
    t = Template('{{ value|add_working_days:1 }}')
852
    assert t.render({'value': value}) == str(expected)
853
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
854
    assert t.render({'value': value}) == str(expected)
855

  
856

  
857
def test_add_working_days_weekend(settings):
858
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
859
    t = Template('{{ value|add_working_days:1 }}')
860
    assert t.render({'value': '2020-06-19'}) == '2020-06-22'
861
    t = Template('{{ value|add_working_days_with_saturday:1 }}')
862
    assert t.render({'value': '2020-06-19'}) == '2020-06-20'
863

  
864

  
865
def test_adjust_to_working_day_settings(settings, pub):
866
    settings.WORKING_DAY_CALENDAR = None
867
    t = Template('{{ value|adjust_to_working_day }}')
868
    assert t.render({'value': '2020-07-13'}) == ''
869
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
870
    assert t.render({'value': '2020-07-13'}) == ''
871

  
872
    settings.WORKING_DAY_CALENDAR = ''
873
    t = Template('{{ value|adjust_to_working_day }}')
874
    assert t.render({'value': '2020-07-13'}) == ''
875
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
876
    assert t.render({'value': '2020-07-13'}) == ''
877

  
878
    settings.WORKING_DAY_CALENDAR = 'foobar'
879
    t = Template('{{ value|adjust_to_working_day }}')
880
    assert t.render({'value': '2020-07-13'}) == ''
881
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
882
    assert t.render({'value': '2020-07-13'}) == ''
883

  
884
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
885
    t = Template('{{ value|adjust_to_working_day }}')
886
    assert t.render({'value': '2020-07-14'}) == '2020-07-15'
887
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
888
    assert t.render({'value': '2020-07-14'}) == '2020-07-15'
889

  
890
    pub.site_options.set('options', 'working_day_calendar', 'foobar')
891
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
892
    t = Template('{{ value|adjust_to_working_day }}')
893
    assert t.render({'value': '2020-07-13'}) == ''
894
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
895
    assert t.render({'value': '2020-07-13'}) == ''
896

  
897
    settings.WORKING_DAY_CALENDAR = 'foobar'
898
    pub.site_options.set('options', 'working_day_calendar', 'workalendar.europe.France')
899
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
900
    t = Template('{{ value|adjust_to_working_day }}')
901
    assert t.render({'value': '2020-07-14'}) == '2020-07-15'
902
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
903
    assert t.render({'value': '2020-07-14'}) == '2020-07-15'
904

  
905

  
906
@pytest.mark.parametrize('value, expected', [
907
    (None, ''),
908
    ('', ''),
909
    ('foobar', ''),
910
    (42, ''),
911
    ('2020-07-14T12:01:03', '2020-07-15'),
912
    ('2020-07-14 02:03', '2020-07-15'),
913
    ('14/07/2020 02h03', '2020-07-15'),
914
    ('2020-07-14', '2020-07-15'),
915
    ('14/07/2020', '2020-07-15'),
916
    (datetime.datetime(2020, 7, 14, 12, 1, 3), '2020-07-15'),
917
    (datetime.date(2020, 7, 14), '2020-07-15'),
918
    (datetime.date(2020, 7, 15), '2020-07-15'),
919
])
920
def test_adjust_to_working_day(settings, value, expected):
921
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
922
    t = Template('{{ value|adjust_to_working_day }}')
923
    assert t.render({'value': value}) == str(expected)
924
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
925
    assert t.render({'value': value}) == str(expected)
926

  
927

  
928
def test_adjust_to_working_day_weekend(settings):
929
    settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
930
    t = Template('{{ value|adjust_to_working_day }}')
931
    assert t.render({'value': '2020-06-20'}) == '2020-06-22'
932
    t = Template('{{ value|adjust_to_working_day_with_saturday }}')
933
    assert t.render({'value': '2020-06-20'}) == '2020-06-20'
tox.ini
30 30
    vobject
31 31
    qrcode
32 32
    Pillow<7.2
33
    workalendar
33 34
    python-magic
34 35
    docutils
35 36
    langdetect
wcs/qommon/calendar.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2020  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.utils.module_loading import import_string
18
from quixote import get_publisher
19

  
20
try:
21
    from workalendar.core import SUN
22
except ImportError:
23
    SUN = None
24

  
25

  
26
def get_calendar(saturday_is_a_working_day=False):
27
    # get calendar from settings
28
    try:
29
        calendar_class = import_string(get_publisher().get_working_day_calendar())
30
    except (AttributeError, ImportError):
31
        return
32

  
33
    # saturday is not a working day, return this calendar
34
    if not saturday_is_a_working_day:
35
        return calendar_class()
36

  
37
    # saturday is a working day, build a custom calendar
38
    class CalendarWithSaturday(calendar_class):
39
        WEEKEND_DAYS = (SUN,)
40

  
41
    return CalendarWithSaturday()
wcs/qommon/publisher.py
897 897
            url += '?key=%s' % key
898 898
        return url
899 899

  
900
    def get_working_day_calendar(self):
901
        return self.get_site_option('working_day_calendar') or settings.WORKING_DAY_CALENDAR
902

  
900 903
    def get_supported_authentication_contexts(self):
901 904
        contexts = collections.OrderedDict()
902 905
        labels = {
wcs/qommon/templatetags/qommon.py
39 39
from django.utils.encoding import force_bytes, force_text
40 40
from django.utils.safestring import mark_safe
41 41
from django.utils.timezone import is_naive, make_aware
42
from wcs.qommon import calendar
42 43
from wcs.qommon import evalutils
43 44
from wcs.qommon import tokens
44 45
from wcs.qommon.admin.texts import TextsDirectory
......
281 282
    return value <= date_now
282 283

  
283 284

  
285
@register.filter(expects_localtime=True)
286
def is_working_day(value, saturday_is_a_working_day=False):
287
    value = parse_date(value)
288
    if not value:
289
        return False
290

  
291
    cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
292
    if not cal:
293
        return False
294

  
295
    return cal.is_working_day(value)
296

  
297

  
298
@register.filter(expects_localtime=True)
299
def is_working_day_with_saturday(value):
300
    return is_working_day(value, saturday_is_a_working_day=True)
301

  
302

  
303
@register.filter(expects_localtime=True)
304
def add_working_days(value, arg, saturday_is_a_working_day=False):
305
    value = parse_date(value)
306
    if not value:
307
        return ''
308

  
309
    cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
310
    if not cal:
311
        return ''
312

  
313
    try:
314
        return cal.add_working_days(value, int(arg))
315
    except ValueError:
316
        return ''
317

  
318

  
319
@register.filter(expects_localtime=True)
320
def add_working_days_with_saturday(value, arg):
321
    return add_working_days(value, arg, saturday_is_a_working_day=True)
322

  
323

  
324
@register.filter(expects_localtime=True)
325
def adjust_to_working_day(value, saturday_is_a_working_day=False):
326
    value = parse_date(value)
327
    if not value:
328
        return ''
329

  
330
    cal = calendar.get_calendar(saturday_is_a_working_day=saturday_is_a_working_day)
331
    if not cal:
332
        return ''
333

  
334
    if cal.is_working_day(value):
335
        return value
336
    # return next working day
337
    return cal.add_working_days(value, 1)
338

  
339

  
340
@register.filter(expects_localtime=True)
341
def adjust_to_working_day_with_saturday(value):
342
    return adjust_to_working_day(value, saturday_is_a_working_day=True)
343

  
344

  
284 345
@register.simple_tag
285 346
def standard_text(text_id):
286 347
    return mark_safe(TextsDirectory.get_html_text(str(text_id)))
wcs/settings.py
188 188
# w.c.s. can have very large forms, in backoffice and frontoffice
189 189
DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000  # Django default is 1000
190 190

  
191
# workalendar config
192
WORKING_DAY_CALENDAR = 'workalendar.europe.France'
193

  
191 194
local_settings_file = os.environ.get('WCS_SETTINGS_FILE',
192 195
        os.path.join(os.path.dirname(__file__), 'local_settings.py'))
193 196
if os.path.exists(local_settings_file):
194
-