chrono/tests/manager/test_partial_bookings.py

1264 lines
51 KiB
Python

import datetime
import json
from unittest import mock
import pytest
from chrono.agendas.models import Agenda, Booking, BookingCheck, Event, Subscription
from chrono.utils.lingo import CheckType
from chrono.utils.timezone import make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db
def test_manager_partial_bookings_add_agenda(app, admin_user, settings):
app = login(app)
resp = app.get('/manage/agendas/add/')
assert 'partial-bookings' not in resp.text
settings.PARTIAL_BOOKINGS_ENABLED = True
resp = app.get('/manage/agendas/add/')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'partial-bookings'
resp = resp.form.submit().follow()
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.kind == 'events'
assert agenda.partial_bookings is True
assert agenda.default_view == 'day'
assert agenda.enable_check_for_future_events is True
resp = resp.click('Options')
assert resp.form['default_view'].options == [
('', False, '---------'),
('day', True, 'Day view'),
('month', False, 'Month view'),
]
def test_options_partial_bookings_invoicing_settings(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
assert agenda.invoicing_unit == 'hour'
assert agenda.invoicing_tolerance == 0
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
booking = Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=event,
)
booking.mark_user_presence(start_time=datetime.time(10, 55), end_time=datetime.time(14, 4))
agenda.refresh_booking_computed_times()
booking.refresh_from_db()
assert booking.user_check.computed_start_time == datetime.time(10, 0)
assert booking.user_check.computed_end_time == datetime.time(15, 0)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click(href='/manage/agendas/%s/invoicing-options' % agenda.pk)
assert resp.form['invoicing_unit'].options == [
('hour', True, 'Per hour'),
('half_hour', False, 'Per half hour'),
('quarter', False, 'Per quarter-hour'),
('minute', False, 'Per minute'),
]
resp.form['invoicing_unit'] = 'half_hour'
resp.form['invoicing_tolerance'] = 10
resp = resp.form.submit().follow()
agenda.refresh_from_db()
assert agenda.invoicing_unit == 'half_hour'
assert agenda.invoicing_tolerance == 10
booking.refresh_from_db()
assert booking.user_check.computed_start_time == datetime.time(11, 0)
assert booking.user_check.computed_end_time == datetime.time(14, 0)
# check kind
agenda.partial_bookings = False
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/invoicing-options' % agenda.pk not in resp
def test_options_partial_bookings_simpler_settings(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Management notifications' not in resp.text
assert 'Booking reminders' not in resp.text
assert 'Booking display template' not in resp.text
assert 'Extra user block template' not in resp.text
assert 'Enable the check of bookings when event has not passed' not in resp.text
resp = app.get('/manage/agendas/%s/display-options' % agenda.pk)
assert 'booking_user_block_template' not in resp.form.fields
resp = app.get('/manage/agendas/%s/check-options' % agenda.pk)
assert 'enable_check_for_future_events' not in resp.form.fields
assert 'booking_extra_user_block_template' not in resp.form.fields
def test_manager_partial_bookings_add_event(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Event')
assert 'duration' not in resp.form.fields
assert 'recurrence_week_interval' not in resp.form.fields
resp.form['start_datetime_0'] = '2023-02-15'
resp.form['start_datetime_1'] = '08:00'
resp.form['end_time'] = '18:00'
resp.form['places'] = 10
resp = resp.form.submit().follow()
event = Event.objects.get()
assert event.end_time == datetime.time(18, 00)
assert event.duration is None
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk))
assert 'duration' not in resp.form.fields
assert resp.form['end_time'].value == '18:00'
resp.form['end_time'] = '17:00'
resp = resp.form.submit().follow()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Event')
resp.form['start_datetime_0'] = '2023-02-15'
resp.form['start_datetime_1'] = '10:00'
resp.form['end_time'] = '12:00'
resp.form['places'] = 10
resp = resp.form.submit()
assert 'There can only be one event per day.' in resp.text
def test_manager_partial_bookings_day_view(app, admin_user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=event,
)
Booking.objects.create(
user_external_id='yyy',
user_first_name='Jon',
user_last_name='Doe',
start_time=datetime.time(8, 00),
end_time=datetime.time(10, 00),
event=event,
)
Booking.objects.create(
user_external_id='zzz',
user_first_name='Bruce',
user_last_name='Doe',
start_time=datetime.time(12, 00),
end_time=datetime.time(14, 00),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'Week' not in resp.text
# time range from one hour before event start to one hour after end
assert resp.pyquery('.partial-booking--hour')[0].text == '07\u202fh'
assert resp.pyquery('.partial-booking--hour')[-1].text == '19\u202fh'
assert [int(x.text.replace('\u202fh', '')) for x in resp.pyquery('.partial-booking--hour')] == list(
range(7, 20)
)
assert len(resp.pyquery('.partial-booking--registrant')) == 3
assert resp.pyquery('.registrant--name-label')[0].text_content() == 'Bruce Doe'
assert resp.pyquery('.registrant--name-label')[1].text_content() == 'Jane Doe'
assert resp.pyquery('.registrant--name-label')[2].text_content() == 'Jon Doe'
assert resp.pyquery('.registrant--bar')[0].findall('time')[0].text == '12:00'
assert resp.pyquery('.registrant--bar')[0].findall('time')[1].text == '14:00'
assert resp.pyquery('.registrant--bar')[0].attrib['style'] == 'left: 38.46%; width: 15.38%;'
assert resp.pyquery('.registrant--bar')[1].findall('time')[0].text == '11:00'
assert resp.pyquery('.registrant--bar')[1].findall('time')[1].text == '13:30'
assert resp.pyquery('.registrant--bar')[1].attrib['style'] == 'left: 30.77%; width: 19.23%;'
assert resp.pyquery('.registrant--bar')[2].findall('time')[0].text == '08:00'
assert resp.pyquery('.registrant--bar')[2].findall('time')[1].text == '10:00'
assert resp.pyquery('.registrant--bar')[2].attrib['style'] == 'left: 7.69%; width: 15.38%;'
resp = resp.click('Next day')
assert 'No opening hours this day.' in resp.text
Event.objects.all().delete()
event = Event.objects.create(
label='Other Event',
start_datetime=start_datetime,
end_time=datetime.time(18, 00),
places=10,
agenda=agenda,
recurrence_days=[1, 2, 3, 4, 5, 6, 7],
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
)
event.create_all_recurrences()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.pyquery('.partial-booking--hour')[0].text == '07\u202fh'
assert resp.pyquery('.partial-booking--hour')[-1].text == '19\u202fh'
def test_manager_partial_bookings_day_view_24_hours(app, admin_user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 0, 0))
Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(23, 59), places=10, agenda=agenda
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
# from 00h to 23h
assert [int(x.text.replace('\u202fh', '')) for x in resp.pyquery('.partial-booking--hour')] == list(
range(0, 24)
)
@pytest.mark.freeze_time('2023-03-10 08:00')
def test_manager_partial_bookings_day_view_hour_indicator(app, admin_user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
Event.objects.create(
label='Event', start_datetime=now(), end_time=datetime.time(18, 00), places=10, agenda=agenda
)
app = login(app)
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, now().year, now().month, now().day)
resp = app.get(url)
assert resp.pyquery('.partial-booking--hour-indicator')
assert resp.pyquery('.partial-booking').attr('data-start-datetime') == '2023-03-10T08:00:00'
assert resp.pyquery('.partial-booking').attr('data-end-datetime') == '2023-03-10T19:00:00'
freezer.move_to('2023-03-11 08:00')
resp = app.get(url)
assert not resp.pyquery('.partial-booking--hour-indicator')
def test_manager_partial_bookings_day_view_multiple_bookings(app, admin_user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
first_booking = Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(9, 00),
end_time=datetime.time(12, 00),
from_recurring_fillslots=True,
event=event,
)
second_booking = Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(15, 00),
end_time=datetime.time(18, 00),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert len(resp.pyquery('.partial-booking--registrant')) == 1
assert len(resp.pyquery('.registrant--bar')) == 2
assert resp.pyquery('.registrant--bar')[0].findall('time')[0].text == '09:00'
assert resp.pyquery('.registrant--bar')[0].findall('time')[1].text == '12:00'
assert 'occasional' not in resp.pyquery('.registrant--bar')[0].text_content()
assert len(resp.pyquery('.registrant--bar')[0].findall('span.occasional')) == 0
assert resp.pyquery('.registrant--bar')[1].findall('time')[0].text == '15:00'
assert resp.pyquery('.registrant--bar')[1].findall('time')[1].text == '18:00'
assert 'occasional' in resp.pyquery('.registrant--bar')[1].text_content()
# check first booking
resp = resp.click('Check')
assert len(resp.pyquery('.pk-tabs')) == 1
assert resp.pyquery('#tab-%s' % first_booking.pk).text() == '09:00 - 12:00'
assert resp.pyquery('#tab-%s' % second_booking.pk).text() == '15:00 - 18:00'
first_prefix = 'booking-%s-' % first_booking.pk
second_prefix = 'booking-%s-' % second_booking.pk
# first booking inital check value is "Present", second booking is "Not checked"
assert resp.form[first_prefix + 'presence'].value == 'True'
assert resp.form[second_prefix + 'presence'].value == ''
resp.form[first_prefix + 'start_time'] = '09:30'
resp.form[first_prefix + 'end_time'] = '12:00'
resp.form[first_prefix + 'presence'] = 'True'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.partial-booking--registrant')) == 1
assert len(resp.pyquery('.registrant--bar')) == 4
assert len(resp.pyquery('.registrant--bar.check')) == 1
assert len(resp.pyquery('.registrant--bar.computed')) == 1
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
# check second booking
resp = resp.click('Check')
resp.form[second_prefix + 'start_time'] = '15:00'
resp.form[second_prefix + 'end_time'] = '17:00'
resp.form[second_prefix + 'presence'] = 'True'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.partial-booking--registrant')) == 1
assert len(resp.pyquery('.registrant--bar')) == 6
assert len(resp.pyquery('.registrant--bar.check')) == 2
assert len(resp.pyquery('.registrant--bar.computed')) == 2
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[0].text == '15:00'
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[1].text == '17:00'
# check display filtering
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
params={'display': ['booked', 'checked', 'computed']},
)
assert len(resp.pyquery('.registrant--bar.booking')) == 2
assert len(resp.pyquery('.registrant--bar.check')) == 2
assert len(resp.pyquery('.registrant--bar.computed')) == 2
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
params={'display': ['booked']},
)
assert len(resp.pyquery('.registrant--bar.booking')) == 2
assert len(resp.pyquery('.registrant--bar.check')) == 0
assert len(resp.pyquery('.registrant--bar.computed')) == 0
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
params={'display': ['checked']},
)
assert len(resp.pyquery('.registrant--bar.booking')) == 0
assert len(resp.pyquery('.registrant--bar.check')) == 2
assert len(resp.pyquery('.registrant--bar.computed')) == 0
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day),
params={'display': ['computed']},
)
assert len(resp.pyquery('.registrant--bar.booking')) == 0
assert len(resp.pyquery('.registrant--bar.check')) == 0
assert len(resp.pyquery('.registrant--bar.computed')) == 2
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_manager_partial_bookings_check(check_types, app, admin_user):
check_types.return_value = []
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
booking = Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert len(resp.pyquery('.registrant--bar')) == 1
assert resp.pyquery('.registrant--bar time')[0].text == '11:00'
assert resp.pyquery('.registrant--bar time')[1].text == '13:30'
assert resp.pyquery('.registrant--bar')[0].attrib['style'] == 'left: 30.77%; width: 19.23%;'
resp = resp.click('Check')
assert 'presence_check_type' not in resp.form.fields
assert 'absence_check_type' not in resp.form.fields
assert len(resp.pyquery('.pk-tabs')) == 0
assert resp.pyquery('.booking-check-forms').attr('data-fill-start_time') == '11:00'
assert resp.pyquery('.booking-check-forms').attr('data-fill-end_time') == '13:30'
assert resp.pyquery('.time-widget-button')[0].text == 'Fill with booking start time'
assert resp.pyquery('.time-widget-button')[1].text == 'Fill with booking end time'
# submitting empty form works
resp.form['presence'] = ''
resp = resp.form.submit().follow()
resp = resp.click('Check')
resp.form['start_time'] = '11:01'
resp.form['end_time'] = '11:00'
resp.form['presence'] = 'True'
resp = resp.form.submit()
assert 'Arrival must be before departure.' in resp.text
resp.form['start_time'] = '07:59'
resp.form['end_time'] = '18:00'
resp.form['presence'] = 'True'
resp = resp.form.submit()
assert 'Arrival must be after opening time.' in resp.text
resp.form['start_time'] = '08:00'
resp.form['end_time'] = '18:01'
resp.form['presence'] = 'True'
resp = resp.form.submit()
assert 'Departure must be before closing time.' in resp.text
resp.form['start_time'] = '11:01'
resp.form['end_time'] = '13:15'
resp.form['presence'] = 'True'
resp = resp.form.submit().follow()
booking.refresh_from_db()
assert booking.user_check.start_time == datetime.time(11, 1)
assert booking.user_check.end_time == datetime.time(13, 15)
assert booking.user_check.computed_start_time == datetime.time(11, 0)
assert booking.user_check.computed_end_time == datetime.time(14, 0)
assert len(resp.pyquery('.registrant--bar')) == 3
assert len(resp.pyquery('.registrant--bar.booking')) == 1
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert resp.pyquery('.registrant--bar.check time')[0].text == '11:01'
assert resp.pyquery('.registrant--bar.check time')[1].text == '13:15'
assert resp.pyquery('.registrant--bar.check')[0].attrib['style'] == 'left: 30.9%; width: 17.18%;'
assert resp.pyquery('.registrant--bar.check span').text() == ''
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
assert resp.pyquery('.registrant--bar.computed time')[0].text == '11:00'
assert resp.pyquery('.registrant--bar.computed time')[1].text == '14:00'
assert resp.pyquery('.registrant--bar.computed')[0].attrib['style'] == 'left: 30.77%; width: 23.08%;'
agenda.invoicing_unit = 'half_hour'
agenda.invoicing_tolerance = 10
agenda.save()
agenda.refresh_booking_computed_times()
booking.refresh_from_db()
assert booking.user_check.start_time == datetime.time(11, 1)
assert booking.user_check.end_time == datetime.time(13, 15)
assert booking.user_check.computed_start_time == datetime.time(11, 0)
assert booking.user_check.computed_end_time == datetime.time(13, 30)
check_types.return_value = [
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
CheckType(slug='baz-reason', label='Baz reason', kind='presence'),
]
resp = resp.click('Check')
assert resp.form['presence_check_type'].options == [
('', True, '---------'),
('bar-reason', False, 'Bar reason'),
('baz-reason', False, 'Baz reason'),
]
assert resp.form['absence_check_type'].options == [
('', True, '---------'),
('foo-reason', False, 'Foo reason'),
]
resp.form['presence_check_type'] = 'bar-reason'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 3
assert len(resp.pyquery('.registrant--bar.booking')) == 1
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert resp.pyquery('.registrant--bar.check time')[0].text == '11:01'
assert resp.pyquery('.registrant--bar.check time')[1].text == '13:15'
assert resp.pyquery('.registrant--bar.check')[0].attrib['style'] == 'left: 30.9%; width: 17.18%;'
assert resp.pyquery('.registrant--bar.check span').text() == 'Bar reason'
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
assert resp.pyquery('.registrant--bar.computed time')[0].text == '11:00'
assert resp.pyquery('.registrant--bar.computed time')[1].text == '13:30'
assert resp.pyquery('.registrant--bar.computed')[0].attrib['style'] == 'left: 30.77%; width: 19.23%;'
resp = resp.click('Check')
assert resp.form['presence_check_type'].value == 'bar-reason'
resp.form['presence'] = 'False'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 3
assert len(resp.pyquery('.registrant--bar.booking')) == 1
assert len(resp.pyquery('.registrant--bar.check.absent')) == 1
assert len(resp.pyquery('.registrant--bar.computed.absent')) == 1
assert resp.pyquery('.registrant--bar.check span').text() == ''
resp = resp.click('Check')
resp.form['presence'] = ''
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 1
assert len(resp.pyquery('.registrant--bar.booking')) == 1
assert resp.pyquery('.registrant--bar.check span').text() == ''
# event is checked
event.checked = True
event.save()
assert agenda.disable_check_update is False
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) in resp
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=200)
# event check is locked
event.check_locked = True
event.save()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) not in resp
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=404)
# now disable check update
event.check_locked = False
event.save()
agenda.disable_check_update = True
agenda.save()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk) not in resp
app.get('/manage/agendas/%s/events/%s/check-bookings/xxx' % (agenda.pk, event.pk), status=404)
def test_manager_partial_bookings_multiple_checks(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(9, 00),
end_time=datetime.time(18, 00),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Check')
# main check inital value is "Present", second check is "Not checked"
assert resp.form['presence'].value == 'True'
assert resp.form['check-2-presence'].value == ''
resp.form['start_time'] = '09:30'
resp.form['end_time'] = '12:00'
resp.form['presence'] = 'True'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 3
assert len(resp.pyquery('.registrant--bar.check')) == 1
assert len(resp.pyquery('.registrant--bar.computed')) == 1
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
resp = resp.click('Check')
assert 'gadjo-folded' in resp.text
resp.form['check-2-start_time'] = '12:30'
resp.form['check-2-end_time'] = '17:30'
resp.form['check-2-presence'] = 'False'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 5
assert len(resp.pyquery('.registrant--bar.check')) == 2
assert len(resp.pyquery('.registrant--bar.computed')) == 2
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[0].text == '12:30'
assert resp.pyquery('.registrant--bar.check')[1].findall('time')[1].text == '17:30'
resp = resp.click('Check')
assert 'gadjo-folded' not in resp.text
resp.form['check-2-start_time'] = '11:30'
resp.form['check-2-end_time'] = ''
resp = resp.form.submit()
assert 'Booking check hours are overlapping.' in resp.text
resp.form['check-2-end_time'] = '17:30'
resp = resp.form.submit()
assert 'Booking check hours are overlapping.' in resp.text
resp.form['check-2-start_time'] = '12:30'
resp.form['check-2-presence'] = ''
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 3
assert len(resp.pyquery('.registrant--bar.check')) == 1
assert len(resp.pyquery('.registrant--bar.computed')) == 1
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[0].text == '09:30'
assert resp.pyquery('.registrant--bar.check')[0].findall('time')[1].text == '12:00'
resp = resp.click('Check')
assert 'gadjo-folded' in resp.text
resp.form['check-2-presence'] = 'False'
resp.form['check-2-start_time'] = '11:30'
resp.form['check-2-end_time'] = '11:29'
resp = resp.form.submit()
assert 'Arrival must be before departure.' in resp.text
assert 'gadjo-folded' not in resp.text
# overlap is detected when 2 checks are created at the same time
BookingCheck.objects.all().delete()
resp.form['presence'] = 'True'
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '11:00'
resp.form['check-2-presence'] = 'False'
resp.form['check-2-start_time'] = '10:30'
resp.form['check-2-end_time'] = '11:30'
resp = resp.form.submit()
assert 'Booking check hours are overlapping.' in resp.text
# two check cannot have the same presence value
resp.form['presence'] = 'True'
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '11:00'
resp.form['check-2-presence'] = 'True'
resp.form['check-2-start_time'] = '12:00'
resp.form['check-2-end_time'] = '13:00'
resp = resp.form.submit()
assert 'Both booking checks cannot have the same status.' in resp.text
resp.form['presence'] = 'False'
resp.form['check-2-presence'] = 'False'
resp = resp.form.submit()
assert 'Both booking checks cannot have the same status.' in resp.text
@pytest.mark.parametrize('subscription_only', (True, False))
def test_manager_partial_bookings_incomplete_check(subscription_only, app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
if subscription_only:
Subscription.objects.create(
agenda=agenda,
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=2),
)
else:
booking = Booking.objects.create(
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Check')
resp = resp.form.submit()
assert 'Both arrival and departure cannot not be empty.' in resp.text
resp.form['start_time'] = '11:01'
resp.form['presence'] = 'True'
resp = resp.form.submit().follow()
booking = Booking.objects.get()
assert booking.user_check.start_time == datetime.time(11, 1)
assert booking.user_check.end_time is None
assert booking.user_check.computed_start_time == datetime.time(11, 0)
assert booking.user_check.computed_end_time is None
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert resp.pyquery('.registrant--bar.check.present')[0].attrib['style'] == 'left: 30.9%;'
assert resp.pyquery('.registrant--bar.check time').text() == '11:01'
resp = resp.click('Check')
resp.form['start_time'] = ''
resp.form['end_time'] = '13:15'
resp = resp.form.submit().follow()
booking.refresh_from_db()
assert booking.user_check.start_time is None
assert booking.user_check.end_time == datetime.time(13, 15)
assert booking.user_check.computed_start_time is None
assert booking.user_check.computed_end_time == datetime.time(14, 0)
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert resp.pyquery('.registrant--bar.check.present')[0].attrib['style'] == 'left: 0%; width: 48.08%;'
assert resp.pyquery('.registrant--bar.check time').text() == '13:15'
resp = resp.click('Check')
resp.form['start_time'] = ''
resp.form['end_time'] = ''
resp = resp.form.submit()
assert 'Both arrival and departure cannot not be empty.' in resp.text
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_manager_partial_bookings_check_subscription(check_types, app, admin_user):
check_types.return_value = []
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event',
start_datetime=start_datetime,
end_time=datetime.time(18, 00),
places=10,
agenda=agenda,
recurrence_days=list(range(1, 8)),
recurrence_end_date=start_datetime + datetime.timedelta(days=30),
)
event.create_all_recurrences()
Subscription.objects.create(
agenda=agenda,
user_external_id='xxx',
user_first_name='Jane',
user_last_name='Doe',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=2),
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert len(resp.pyquery('.registrant--bar')) == 0
assert len(resp.pyquery('.registrant--name a')) == 1
resp = resp.click('Check')
assert 'Fill with booking start time' not in resp.text
assert 'absence_check_type' not in resp.form.fields
assert resp.form['presence'].options == [
('', False, None),
('True', True, None),
] # no 'False' option
assert 'check-2-presence' not in resp.form.fields
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '16:00'
resp.form['presence'] = 'True'
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--name-label a')) == 0
assert len(resp.pyquery('.registrant--bar')) == 2
assert len(resp.pyquery('.registrant--bar.check.present')) == 1
assert len(resp.pyquery('.registrant--bar.computed.present')) == 1
assert resp.pyquery('.registrant--bar.check time')[0].text == '10:00'
assert resp.pyquery('.registrant--bar.check time')[1].text == '16:00'
booking = Booking.objects.get()
assert booking.user_external_id == 'xxx'
assert booking.user_first_name == 'Jane'
assert booking.user_last_name == 'Doe'
resp = resp.click('Check')
assert 'Fill with booking start time' not in resp.text
assert 'absence_check_type' not in resp.form.fields
assert resp.form['presence'].options == [
('', False, None),
('True', True, None),
] # no 'False' option
resp.form['presence'] = ''
resp = resp.form.submit().follow()
assert len(resp.pyquery('.registrant--bar')) == 0
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_manager_partial_bookings_check_filters(check_types, app, admin_user):
check_types.return_value = [
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
]
agenda = Agenda.objects.create(
label='Foo bar',
kind='events',
partial_bookings=True,
booking_check_filters='menu',
)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
Booking.objects.create(
user_external_id='user:1',
user_first_name='User',
user_last_name='Not Checked',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=event,
)
booking = Booking.objects.create(
user_external_id='user:2',
user_first_name='User',
user_last_name='Present Vegan',
start_time=datetime.time(8, 00),
end_time=datetime.time(10, 00),
event=event,
extra_data={'menu': 'vegan'},
)
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
booking = Booking.objects.create(
user_external_id='user:3',
user_first_name='User',
user_last_name='Absent Meat Foo Reason',
start_time=datetime.time(12, 00),
end_time=datetime.time(14, 00),
event=event,
extra_data={'menu': 'meat'},
)
booking.mark_user_absence(
check_type_slug='foo-reason', start_time=datetime.time(12, 30), end_time=datetime.time(14, 30)
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='Present Vegan',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=1),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:4',
user_first_name='Subscription',
user_last_name='Not Booked',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=1),
)
app = login(app)
today = start_datetime.date()
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day)
resp = app.get(url)
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
'User Absent Meat Foo Reason',
'Subscription Not Booked',
'User Not Checked',
'User Present Vegan',
]
# one registrant has not booked, no bar is shown
assert len(resp.pyquery('.registrant--bar.booking')) == 3
resp = app.get(url, params={'booking-status': 'booked'})
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
'User Absent Meat Foo Reason',
'User Not Checked',
'User Present Vegan',
]
resp = app.get(url, params={'booking-status': 'presence'})
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == ['User Present Vegan']
resp = app.get(url, params={'booking-status': 'presence', 'extra-data-menu': 'meat'})
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == []
resp = app.get(url, params={'extra-data-menu': 'meat'})
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
'User Absent Meat Foo Reason'
]
resp = app.get(url, params={'booking-status': 'absence::foo-reason'})
assert [x.text_content() for x in resp.pyquery('.registrant--name-label')] == [
'User Absent Meat Foo Reason'
]
def test_manager_partial_bookings_event_checked(app, admin_user):
agenda = Agenda.objects.create(
label='Foo bar',
kind='events',
partial_bookings=True,
)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
login(app)
today = start_datetime.date()
url = '/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day)
resp = app.get(url)
assert 'Mark the event as checked' not in resp
for i in range(8):
booking = Booking.objects.create(
event=event,
user_external_id='xxx',
start_time=datetime.time(8, 00),
end_time=datetime.time(10, 00),
presence_callback_url='https://example.invalid/presence/%s' % i,
absence_callback_url='https://example.invalid/absence/%s' % i,
)
if i == 0:
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(9, 00))
BookingCheck.objects.create(
booking=booking,
start_time=datetime.time(9, 00),
end_time=datetime.time(10, 00),
presence=False,
)
elif i < 3:
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
elif i < 7:
booking.mark_user_absence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
resp = app.get(url)
assert 'Mark the event as checked' in resp
assert event.checked is False
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'checked tag' not in resp
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '<span class="checked tag">Checked</span>' not in resp
assert 'check-locked' not in resp
assert 'invoiced' not in resp
token = resp.context['csrf_token']
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
mock_response = mock.Mock(status_code=200)
mock_send.return_value = mock_response
resp = app.post(
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
params={'csrfmiddlewaretoken': token},
)
event.refresh_from_db()
assert event.checked is True
resp = resp.follow()
assert 'Mark the event as checked' not in resp
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'checked tag' in resp
for booking in Booking.objects.filter(user_checks__isnull=True):
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(10, 00))
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '<span class="checked tag">Checked</span>' in resp
assert 'check-locked' not in resp
assert 'invoiced' not in resp
assert {x[0][0].url for x in mock_send.call_args_list} == {
'https://example.invalid/presence/0',
'https://example.invalid/presence/1',
'https://example.invalid/presence/2',
'https://example.invalid/absence/0',
'https://example.invalid/absence/3',
'https://example.invalid/absence/4',
'https://example.invalid/absence/5',
'https://example.invalid/absence/6',
}
assert set(json.loads(mock_send.call_args_list[0][0][0].body).keys()) == {
'user_check_type_label',
'user_check_type_slug',
'user_was_present',
'start_time',
'end_time',
'computed_start_time',
'computed_end_time',
}
# event not in past
agenda.disable_check_update = False
agenda.save()
assert agenda.enable_check_for_future_events is False
today = now().date()
event.start_datetime = make_aware(
datetime.datetime(today.year, today.month, today.day, 8, 0)
) + datetime.timedelta(days=1)
event.save()
app.post(
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
# not in past, but check for future events is enabled
agenda.enable_check_for_future_events = True
agenda.save()
app.post(
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=302,
)
# event check is locked
event.checked = False
event.check_locked = True
event.save()
today = event.start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert '<span class="check-locked tag">Check locked</span>' in resp
assert 'invoiced' not in resp
app.post(
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
# event check is locked and envent is invoiced
event.invoiced = True
event.save()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'check-locked' not in resp
assert '<span class="invoiced tag">Invoiced</span>' in resp
def test_manager_partial_bookings_month_view(app, admin_user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
)
event2 = Event.objects.create(
label='Event 2',
start_datetime=start_datetime + datetime.timedelta(days=2),
end_time=datetime.time(18, 00),
places=10,
agenda=agenda,
)
for e in (event, event2):
Booking.objects.create(
user_external_id='user:1',
user_first_name='User',
user_last_name='Not Checked',
start_time=datetime.time(11, 00),
end_time=datetime.time(13, 30),
event=e,
)
booking = Booking.objects.create(
user_external_id='user:2',
user_first_name='User',
user_last_name='Present',
start_time=datetime.time(8, 00),
end_time=datetime.time(10, 00),
event=event,
)
booking.mark_user_presence(start_time=datetime.time(8, 00), end_time=datetime.time(9, 00))
BookingCheck.objects.create(
booking=booking,
start_time=datetime.time(9, 00),
end_time=datetime.time(10, 00),
presence=False, # second 'absence' check will be ignored
)
booking = Booking.objects.create(
user_external_id='user:3',
user_first_name='User',
user_last_name='Absent',
start_time=datetime.time(12, 00),
end_time=datetime.time(14, 00),
event=event,
)
booking.mark_user_absence(start_time=datetime.time(12, 30), end_time=datetime.time(14, 30))
booking = Booking.objects.create(
user_external_id='user:7',
user_first_name='User',
user_last_name='Present (incomplete)',
start_time=datetime.time(12, 00),
end_time=datetime.time(14, 00),
event=event,
)
booking.mark_user_presence(start_time=datetime.time(12, 30))
booking = Booking.objects.create(
user_external_id='user:8',
user_first_name='User',
user_last_name='Present (complete) / Absent (incomplete)',
start_time=datetime.time(12, 00),
end_time=datetime.time(14, 00),
event=event,
)
booking.mark_user_presence(start_time=datetime.time(12, 30), end_time=datetime.time(14, 30))
BookingCheck.objects.create(
booking=booking,
start_time=datetime.time(9, 00),
presence=False,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='Present',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=1),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:4',
user_first_name='Subscription',
user_last_name='Not Booked',
date_start=event.start_datetime,
date_end=event.start_datetime + datetime.timedelta(days=1),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:5',
user_first_name='Subscription',
user_last_name='Next Month',
date_start=datetime.date(2023, 6, 1),
date_end=datetime.date(2023, 6, 10),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:6',
user_first_name='Subscription',
user_last_name='Previous Month',
date_start=datetime.date(2023, 4, 20),
date_end=datetime.date(2023, 4, 30),
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 32))
assert [x.text for x in resp.pyquery('tbody tr th')] == [
'User Absent',
'Subscription Not Booked',
'User Not Checked',
'User Present',
'User Present (complete) / Absent (incomplete)',
'User Present (incomplete)',
]
user_absent_row = resp.pyquery('tbody tr')[0]
assert len(resp.pyquery(user_absent_row)('td')) == 31
assert len(resp.pyquery(user_absent_row)('td span.booking')) == 1
assert len(resp.pyquery(user_absent_row)('td span.booking.absent')) == 1
assert resp.pyquery(user_absent_row)('td span.booking.absent').text() == 'Absent'
subscription_not_booked_row = resp.pyquery('tbody tr')[1]
assert len(resp.pyquery(subscription_not_booked_row)('td')) == 31
assert len(resp.pyquery(subscription_not_booked_row)('td span.booking')) == 0
user_not_checked_row = resp.pyquery('tbody tr')[2]
assert len(resp.pyquery(user_not_checked_row)('td')) == 31
assert len(resp.pyquery(user_not_checked_row)('td span.booking')) == 2
assert resp.pyquery(user_not_checked_row)('td span.booking').text() == 'Not checked Not checked'
user_present_row = resp.pyquery('tbody tr')[3]
assert len(resp.pyquery(user_present_row)('td')) == 31
assert len(resp.pyquery(user_present_row)('td span.booking')) == 1
assert len(resp.pyquery(user_present_row)('td span.booking.present')) == 1
assert resp.pyquery(user_present_row)('td span.booking.present').text() == 'Present'
user_present_mixed_row = resp.pyquery('tbody tr')[4]
assert len(resp.pyquery(user_present_mixed_row)('td')) == 31
assert len(resp.pyquery(user_present_mixed_row)('td span.booking')) == 1
assert len(resp.pyquery(user_present_mixed_row)('td span.booking.present')) == 0
user_present_incomplete_row = resp.pyquery('tbody tr')[4]
assert len(resp.pyquery(user_present_incomplete_row)('td')) == 31
assert len(resp.pyquery(user_present_incomplete_row)('td span.booking')) == 1
assert len(resp.pyquery(user_present_incomplete_row)('td span.booking.present')) == 0
resp = resp.click('Next month')
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 31))
assert [x.text for x in resp.pyquery('tbody tr th')] == ['Subscription Next Month']
assert len(resp.pyquery('tbody tr td')) == 30
freezer.move_to('2023-05-10 14:00')
resp = app.get(resp.request.url)
assert len(resp.pyquery('th.today')) == 0
assert len(resp.pyquery('col.today')) == 0
freezer.move_to('2023-06-10 14:00')
resp = app.get(resp.request.url)
assert resp.pyquery('th.today').text() == '10'
assert len(resp.pyquery('col.today')) == 1
def test_manager_partial_bookings_occupation_rates(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 10, 0))
event = Event.objects.create(
label='Event', start_datetime=start_datetime, end_time=datetime.time(17, 00), places=3, agenda=agenda
)
Booking.objects.create(
user_external_id='1',
user_first_name='User',
user_last_name='10h - 17h',
start_time=datetime.time(10, 00),
end_time=datetime.time(17, 00),
event=event,
)
Booking.objects.create(
user_external_id='2',
user_first_name='User',
user_last_name='10h10 - 12h30',
start_time=datetime.time(10, 10),
end_time=datetime.time(12, 30),
event=event,
)
Booking.objects.create(
user_external_id='3',
user_first_name='User',
user_last_name='11h30 - 15h45',
start_time=datetime.time(11, 30),
end_time=datetime.time(15, 45),
event=event,
)
Booking.objects.create(
user_external_id='4',
user_first_name='User',
user_last_name='11h15 - 12h10',
start_time=datetime.time(11, 15),
end_time=datetime.time(12, 10),
event=event,
)
app = login(app)
today = start_datetime.date()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert len(resp.pyquery('.partial-booking--registrant')) == 4
hours = [x.text.replace('\u202f', ' ') for x in resp.pyquery('.partial-booking--hour')]
labels = [resp.pyquery(x).text() for x in resp.pyquery('.occupation-rate--info')]
styles = [x.attrib['style'] for x in resp.pyquery('.occupation-rate')]
overbooked = [
' overbooked' if 'overbooked' in x.attrib['class'] else '' for x in resp.pyquery('.occupation-rate')
]
assert ['%s: %s / %s%s' % x for x in zip(hours, labels, styles, overbooked)] == [
'09 h: 0%\n(0/3) / --rate-percent: 0;',
'10 h: 66%\n(2/3) / --rate-percent: 66;',
'11 h: 133%\n(4/3) / --rate-percent: 100; overbooked',
'12 h: 133%\n(4/3) / --rate-percent: 100; overbooked',
'13 h: 66%\n(2/3) / --rate-percent: 66;',
'14 h: 66%\n(2/3) / --rate-percent: 66;',
'15 h: 66%\n(2/3) / --rate-percent: 66;',
'16 h: 33%\n(1/3) / --rate-percent: 33;',
'17 h: 0%\n(0/3) / --rate-percent: 0;',
'18 h: 0%\n(0/3) / --rate-percent: 0;',
]