refactor and correct geojson parsing in time-table template tags (#25582)

This commit is contained in:
Elias Showk 2018-08-17 10:18:48 +02:00
parent de2cfa6fbd
commit 768b713d62
3 changed files with 150 additions and 92 deletions

View File

@ -87,12 +87,13 @@ def get_open_close_from_specification(specification, valid_from, base_datetime):
return (opening_time, closing_time, day_number)
def openinghours_to_datetime(codename, hour, minute, default=None):
def openinghours_to_datetime(codename, hour, minute):
"""
return the next date and time after now()
"""
try:
weekday = EN_ABBREV_WEEKDAYS.get(codename, None)
# if default is None, return the next date and time after now()
# a default datetime instance is used to replace absent parameters for datetime
datetime_obj = dateutil_parse('%s %d:%d:00' % (weekday, hour, minute), default=default)
datetime_obj = dateutil_parse('%s %d:%d:00' % (weekday, hour, minute))
if is_naive(datetime_obj):
datetime_obj = make_aware(datetime_obj)
return datetime_obj
@ -154,6 +155,29 @@ def get_slots_from_mdr_format(data, today):
return (slots, known_format)
def parse_opening_hours_data(mairie_data):
"""Parse every known openinghours data formats
"""
for openinghours in mairie_data.get('openinghours', []):
# format is comma-separated days and/or intervals, or only one day
groups = re.match('(\w\w(?:(?:,|\-)?\w\w)*) (\d\d?):(\d\d?)-(\d\d?):(\d\d?)', openinghours).groups()
for day in groups[0].split(','):
if '-' in day:
# interval
parts = re.match('(\w\w)-(\w\w)', day).groups() + groups[1:]
time_table = dict(zip(('start_day', 'end_day', 'start_hour', 'start_minute', 'end_hour', 'end_minute'), parts))
days_list = EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(time_table['start_day'].lower()):
EN_ABBREV_WEEKDAYS_LIST.index(time_table['end_day'].lower()) + 1]
else:
# one day
time_table = dict(zip(('start_day', 'start_hour', 'start_minute', 'end_hour', 'end_minute'), (day,) + groups[1:]))
days_list = [EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(time_table['start_day'].lower())]]
yield (days_list, time_table)
def get_slots_from_mairie_format(data, base_datetime):
"""Process mairie json and return slots the opening hours in chronological order beginning today
"""
@ -183,17 +207,11 @@ def get_slots_from_mairie_format(data, base_datetime):
# case when exclusions are defined
exclusion_slots.append(TimeSlot(valid_from, valid_through))
for openinghours in data.get('openinghours', []):
try:
parts = re.match('(\w\w)-(\w\w) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
except AttributeError:
continue
for weekday in EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(parts[0].lower()):
EN_ABBREV_WEEKDAYS_LIST.index(parts[1].lower())+1]:
for days_list, time_table in parse_opening_hours_data(data):
for weekday in days_list:
timeslot = TimeSlot(
openinghours_to_datetime(weekday, int(parts[2]), int(parts[3]), default=base_datetime),
openinghours_to_datetime(weekday, int(parts[4]), int(parts[5]), default=base_datetime)
openinghours_to_datetime(weekday, int(time_table['start_hour']), int(time_table['start_minute'])),
openinghours_to_datetime(weekday, int(time_table['end_hour']), int(time_table['end_minute']))
)
# add to slots the opening hours in chronological order beginning from today
slots.append(timeslot)
@ -218,46 +236,20 @@ def get_mairie_opening_hours(mairie_data):
if 'properties' in mairie_data:
mairie_data = mairie_data['properties']
end_day = None
base_datetime = make_aware(datetime.datetime.now())
days_list = []
opening_hours_dict = OrderedDict(zip(EN_ABBREV_WEEKDAYS_LIST, [{
'am': None, 'pm': None
} for i in range(7)]))
known_format = False
for openinghours in mairie_data.get('openinghours', []):
for days_list, time_table in parse_opening_hours_data(mairie_data):
known_format = True
try:
# days interval
parts = re.match('(\w\w)-(\w\w) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
(start_day, end_day, start_hour, start_minute, end_hour, end_minute) = parts
days_list = EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(start_day.lower()):
EN_ABBREV_WEEKDAYS_LIST.index(end_day.lower()) + 1]
except AttributeError:
try:
# one day
parts = re.match('(\w\w) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
(start_day, start_hour, start_minute, end_hour, end_minute) = parts
days_list = [EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(start_day.lower())]]
except AttributeError:
try:
# comma-separated days list
parts = re.match('(\w\w(?:,\w\w)+) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
start_day = parts[0].split(',')
(start_hour, start_minute, end_hour, end_minute) = parts[1:]
days_list = [EN_ABBREV_WEEKDAYS_LIST[
EN_ABBREV_WEEKDAYS_LIST.index(day.lower())] for day in start_day]
except AttributeError:
continue
for weekday in days_list:
(period, all_day_hours) = get_period_from_data(weekday,
open_close_time_string=(start_hour, start_minute, end_hour, end_minute))
open_close_time_string=(time_table['start_hour'], time_table['start_minute'], time_table['end_hour'], time_table['end_minute']))
if all_day_hours and period == 'am':
opening_hours_dict[weekday]['pm'] = '' # empty string to avoid displaying fermé
opening_hours_dict[weekday][period] = "%sh%s-%sh%s" % (start_hour, start_minute, end_hour, end_minute)
opening_hours_dict[weekday][period] = "%sh%s-%sh%s" % (time_table['start_hour'], time_table['start_minute'], time_table['end_hour'], time_table['end_minute'])
if not known_format:
# some mairie only have openinghoursspecification (e.g. Jonage)
for specification in mairie_data.get('openinghoursspecification', []):
@ -290,17 +282,13 @@ def get_mairie_opening_hours(mairie_data):
@register.filter
def as_opening_hours_badge(data, base_datetime=None):
def as_opening_hours_badge(data):
if not data:
return ''
if base_datetime is None:
base_datetime = make_aware(datetime.datetime.now())
# defaults
base_datetime = make_aware(datetime.datetime.now())
exclusion_slots = []
today = base_datetime.date()
(slots, known_format) = get_slots_from_mdr_format(data, base_datetime)
if not known_format:
(slots, exclusion_slots, known_format) = get_slots_from_mairie_format(data, base_datetime)

View File

@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
from dateutil.parser import parse
import pytest
import json
import os
from django.utils.safestring import mark_safe
from django.utils.timezone import is_naive, make_aware
from combo_plugin_gnm.templatetags.gnm import as_opening_hours_badge
@ -17,10 +16,10 @@ MDR_GEOJSON = json.load(open(os.path.join(
TZOFFSETS = {"Europe/Paris": 3600}
def test_every_mairie_closed_at_midnight():
@pytest.mark.freeze_time("2018-03-04 23:00:00",)
def test_every_mairie_closed():
"""every mairie is closed at mignight"""
midnight = parse("2018-03-05T00:00:00+01:00")
opening_hours = [as_opening_hours_badge(x, base_datetime=midnight) for x in GEOJSON]
opening_hours = [as_opening_hours_badge(x) for x in GEOJSON]
assert len([x for x in opening_hours if 'open' in x]) == 0
@ -31,45 +30,100 @@ def test_all_mairie_data_parsed_correct():
assert opening_hours.count('') == 0
def test_mairie_nodata_as_opening_hours_templatetag():
""""no data return the right empty html"""
test_datetime = parse("2018-03-05T15:59:00+01:00")
test_html = as_opening_hours_badge(GEOJSON[0]['properties'], base_datetime=test_datetime)
@pytest.mark.freeze_time("2018-03-05 14:59:00")
def test_empty_data():
""""no data return the empty html"""
test_html = as_opening_hours_badge(GEOJSON[0]['properties'])
assert test_html == ''
def test_jonage():
test_datetime = parse("2018-03-05T15:59:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
@pytest.mark.freeze_time("2018-08-13 14:59:00")
def test_mairie_bron_monday():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1326'][0]
klass, label = 'open', u"Ouvert jusqu'à 17h15"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-08-12 14:59:00")
def test_mairie_bron_sunday():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1326'][0]
klass, label = 'closed', u"Réouvre demain à 8h00"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-08-09 13:30:01")
def test_mairie_sathonay_thursday_afternoon():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1415'][0]
klass, label = 'open', u"Ouvert jusqu'à 17h00"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
test_datetime = parse("2018-03-10T16:30:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
klass, label = 'soon-to-be-closed', u"Ouvert jusqu'à 17h00"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
test_datetime = parse("2018-03-10T17:30:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
@pytest.mark.freeze_time("2018-08-11 11:30:01")
def test_mairie_saint_priest_closed():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1406'][0]
klass, label = 'closed', u"Réouvre lundi à 8h15"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-08-22 12:13:01")
def test_mairie_saint_priest_open():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1406'][0]
klass, label = 'soon-to-be-closed', u"Ouvert jusqu'à 12h15"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-08-11 14:59:00")
def test_mairie_bron_saturday():
"""S1326"""
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1326'][0]
klass, label = 'closed', u"Réouvre lundi à 8h00"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-03-05 09:59:13")
def test_jonage_open():
"Jonage is defined only by openinghoursspecification data"
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
klass, label = 'open', u"Ouvert jusqu'à 12h30"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-03-05 10:32:00")
def test_jonage_soon_to_be_closed():
"Jonage is defined only by openinghoursspecification data"
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
klass, label = 'soon-to-be-closed', u"Ouvert jusqu'à 12h30"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
@pytest.mark.freeze_time("2018-03-10 17:30:00")
def test_jonage_closed():
"Jonage is defined only by openinghoursspecification data"
test_html = [as_opening_hours_badge(x) for x in GEOJSON if x['properties']['nom'] == 'Mairie de Jonage'][0]
klass, label = 'closed', u"Réouvre lundi à 8h30"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
def test_mairie_open_as_opening_hours_templatetag():
"""mairie as_opening_hours with a fixed datetime"""
@pytest.mark.freeze_time("2018-03-05 15:55:00")
def test_data_input_compatibility():
"""as_opening_hours with a fixed datetime"""
klass = 'open'
label = u"Ouvert jusqu'à 17h00"
test_datetime = parse("2018-03-05T15:55:00+01:00")
test_html = as_opening_hours_badge(GEOJSON[1]['properties'], base_datetime=test_datetime)
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
test_html = as_opening_hours_badge(GEOJSON[1], base_datetime=test_datetime)
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
test_html = as_opening_hours_badge(GEOJSON[1]['properties'])
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
test_html = as_opening_hours_badge(GEOJSON[1])
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
def test_every_mdr_closed_at_midnight():
@pytest.mark.freeze_time("2018-03-05 00:00:00")
def test_all_mdr_closed():
"""every mdr is closed at mignight"""
midnight = parse("2018-03-05T00:00:00+01:00")
opening_hours = [as_opening_hours_badge(x, base_datetime=midnight) for x in MDR_GEOJSON]
opening_hours = [as_opening_hours_badge(x) for x in MDR_GEOJSON]
assert len([x for x in opening_hours if 'open' in x]) == 0
@ -78,18 +132,23 @@ def test_all_mdr_data_parsed_correct():
opening_hours = [as_opening_hours_badge(x) for x in MDR_GEOJSON]
assert opening_hours.count('') == 0
def test_mdr_closing_time():
test_datetime = parse("2018-03-05T14:59:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in MDR_GEOJSON if x['properties']['nom'] == u'Maison de la Métropole Bramet'][0]
@pytest.mark.freeze_time("2018-03-05 14:59:00")
def test_mdr_open():
test_html = as_opening_hours_badge(MDR_GEOJSON[0])
klass, label = 'open', u"Ouvert jusqu'à 16h45"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
test_datetime = parse("2018-03-05T15:50:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in MDR_GEOJSON if x['properties']['nom'] == u'Maison de la Métropole Bramet'][0]
@pytest.mark.freeze_time("2018-03-05 15:46:00")
def test_mdr_soon_to_be_closed():
test_html = as_opening_hours_badge(MDR_GEOJSON[0])
klass, label = 'soon-to-be-closed', u"Ouvert jusqu'à 16h45"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))
test_datetime = parse("2018-03-10T17:30:00+01:00")
test_html = [as_opening_hours_badge(x, base_datetime=test_datetime) for x in MDR_GEOJSON if x['properties']['nom'] == u'Maison de la Métropole Bramet'][0]
@pytest.mark.freeze_time("2018-03-10 17:30:00")
def test_mdr_just_closed():
test_html = as_opening_hours_badge(MDR_GEOJSON[0])
klass, label = 'closed', u"Réouvre lundi à 8h30"
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>'% (klass, label))
assert test_html == mark_safe(u'<div class="badge %s"><span>%s</span></div>' % (klass, label))

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import pytest
import json
import os
@ -14,7 +15,7 @@ TZOFFSETS = {"Europe/Paris": 3600}
def test_mairie_hours_parsing():
"""everything got parsed correctly"""
"""everything is parsed correctly"""
opening_hours = [get_mairie_opening_hours(x) for x in GEOJSON]
assert len(opening_hours) == len(GEOJSON)
@ -59,4 +60,14 @@ def test_mairie_openinghoursspecification_period_invalid():
for x in GEOJSON:
if x['properties']['nom'] == 'Mairie de Jonage':
assert get_mairie_opening_hours(x) == [('lundi', {'am': None, 'pm': None}), ('mardi', {'am': None, 'pm': None}), ('mercredi', {'am': None, 'pm': None}), ('jeudi', {'am': None, 'pm': None}), ('vendredi', {'am': None, 'pm': None})]
return
return
def test_mairie_sathonay_timetable():
"""Sathonay-Village S1415"""
test_time_table = [get_mairie_opening_hours(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1415'][0]
assert test_time_table == [('lundi', {'am': u'08h30-12h00', 'pm': u'14h00-17h00'}), ('mardi', {'am': u'08h30-12h00', 'pm': u'13h30-17h00'}), ('mercredi', {'am': u'08h30-12h00', 'pm': u'14h00-17h00'}), ('jeudi', {'am': u'08h30-12h00', 'pm': u'14h00-17h00'}), ('vendredi', {'am': u'08h30-12h00', 'pm': u'14h00-17h00'}), ('samedi', {'am': u'08h30-12h00', 'pm': None})]
def test_mairie_saint_priest():
"S1406"
test_time_table = [get_mairie_opening_hours(x) for x in GEOJSON if x['properties']['identifiant'] == 'S1406'][0]
assert test_time_table == [('lundi', {'am': u'08h15-12h15', 'pm': u'13h30-17h30'}), ('mardi', {'am': u'08h15-12h15', 'pm': u'13h30-17h30'}), ('mercredi', {'am': u'08h15-12h15', 'pm': u'13h30-17h30'}), ('jeudi', {'am': u'08h15-11h15', 'pm': u'13h30-17h30'}), ('vendredi', {'am': u'08h15-12h15', 'pm': u'13h30-17h30'}), ('samedi', {'am': u'09h00-11h30', 'pm': None})]