0001-booking-calendar-cell-display-first-available-slot-i.patch
combo/apps/calendar/templates/calendar/booking_calendar_cell.html | ||
---|---|---|
1 | 1 |
{% load i18n calendar %} |
2 | 2 | |
3 | 3 |
{% if cell.title %} |
4 |
<h2>{{cell.title}}</h2> |
|
4 |
<h2> |
|
5 |
<span>{{cell.title}}</span> |
|
6 |
{% if calendar %} |
|
7 |
<span class="calinfo" style="font-style: italic; font-size: 80%;"> |
|
8 |
{% with calendar.get_first_available_slot as slot %} |
|
9 |
({% if slot %}{% trans "Next available slot:" %} {{ slot.date_time|date:"DATETIME_FORMAT"}}{% else %}{% trans "No available slots." %}{% endif %}) |
|
10 |
{% endwith %} |
|
11 |
</span> |
|
12 |
{% endif %} |
|
13 |
</h2> |
|
5 | 14 |
{% endif %} |
6 | 15 |
<div class="calcontent"> |
7 | 16 |
{% include 'calendar/booking_calendar_content.html' %} |
combo/apps/calendar/utils.py | ||
---|---|---|
22 | 22 |
from django.conf import settings |
23 | 23 |
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
24 | 24 |
from django.utils.dateparse import parse_datetime |
25 |
from django.utils.timezone import localtime, make_aware |
|
25 | 26 | |
26 | 27 |
from combo.utils import requests |
27 | 28 | |
... | ... | |
81 | 82 |
request = context['request'] |
82 | 83 |
cell = context['cell'] |
83 | 84 |
page = request.GET.get('chunk_%s' % cell.pk, 1) |
84 |
calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed) |
|
85 |
calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed, |
|
86 |
cell.minimal_booking_duration) |
|
85 | 87 |
paginator = Paginator(calendar.get_computed_days(), cell.days_displayed) |
86 | 88 |
try: |
87 | 89 |
cal_page = paginator.page(page) |
... | ... | |
95 | 97 |
return context |
96 | 98 | |
97 | 99 | |
98 |
def get_calendar(agenda_reference, offset, days_displayed): |
|
100 |
def get_calendar(agenda_reference, offset, days_displayed, min_duration):
|
|
99 | 101 |
if not agenda_reference: |
100 | 102 |
return [] |
101 | 103 |
events = get_chrono_events(agenda_reference) |
102 |
calendar = Calendar(offset, days_displayed) |
|
104 |
calendar = Calendar(offset, days_displayed, min_duration) |
|
105 | ||
103 | 106 |
for event in events: |
104 | 107 |
event_datetime = parse_datetime(event['datetime']) |
105 | 108 |
if not calendar.has_day(event_datetime.date()): |
... | ... | |
129 | 132 |
class DaySlot(object): |
130 | 133 | |
131 | 134 |
def __init__(self, date_time, available, exist=True): |
132 |
self.date_time = date_time
|
|
135 |
self.date_time = localtime(make_aware(date_time))
|
|
133 | 136 |
self.available = available |
134 | 137 |
self.exist = exist |
135 | 138 | |
... | ... | |
170 | 173 | |
171 | 174 |
class Calendar(object): |
172 | 175 | |
173 |
def __init__(self, offset, days_displayed): |
|
176 |
def __init__(self, offset, days_displayed, min_duration):
|
|
174 | 177 |
self.offset = offset |
175 | 178 |
self.days_displayed = days_displayed |
176 | 179 |
self.days = [] |
180 |
self.min_duration = min_duration |
|
177 | 181 | |
178 | 182 |
def __repr__(self): |
179 | 183 |
return '<Calendar>' |
180 | 184 | |
185 |
def get_first_available_slot(self): |
|
186 |
"""return the first available slot that has enough |
|
187 |
consecutive available slots to be allowed for booking |
|
188 |
""" |
|
189 |
required_contiguous_slots = self.min_duration.seconds / self.offset.seconds |
|
190 |
for day in self.days: |
|
191 |
slots = day.slots |
|
192 |
for idx in range(len(slots) - required_contiguous_slots): |
|
193 |
if all([x.available for x in slots[idx:idx+required_contiguous_slots]]): |
|
194 |
return slots[idx] |
|
195 |
return None |
|
196 | ||
181 | 197 |
def get_slots(self): |
182 | 198 |
start = self.get_minimum_slot() |
183 | 199 |
end = self.get_maximum_slot() |
tests/test_calendar.py | ||
---|---|---|
215 | 215 |
assert parsed.path == '/test/' |
216 | 216 |
qs = urlparse.parse_qs(parsed.query) |
217 | 217 |
assert qs['session_var_booking_agenda_slug'] == ['test'] |
218 |
assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00'] |
|
219 |
assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00'] |
|
218 |
assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00+00:00']
|
|
219 |
assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00+00:00']
|
|
220 | 220 | |
221 | 221 | |
222 | 222 |
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get) |
223 | 223 |
def test_calendar(mocked_get, cell): |
224 |
cal = get_calendar('default:whatever', cell.slot_duration, 7) |
|
224 |
cal = get_calendar('default:whatever', cell.slot_duration, 7, cell.minimal_booking_duration)
|
|
225 | 225 |
assert len(cal.days) == 3 |
226 | 226 |
for day in cal.get_computed_days(): |
227 | 227 |
assert day in [ |
... | ... | |
286 | 286 |
previous_page_link = links[0] |
287 | 287 |
assert previous_page_link.text == 'previous' |
288 | 288 |
assert previous_page_link.attrs['data-content-url'] == '/ajax/calendar/content/%d/?chunk_%d=1' % (cell.pk, cell.pk) |
289 | ||
290 | ||
291 |
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get) |
|
292 |
def test_cell_rendering_cal_info(mocked_get, client, cell): |
|
293 |
page = client.get('/booking/') |
|
294 |
title_info = page.html.h2.find('span', {'class': 'calinfo'}) |
|
295 |
assert title_info.text.strip() == '(Next available slot: June 13, 2017, 8 a.m.)' |
|
296 | ||
297 | ||
298 |
def test_cell_rendering_cal_info_when_available_slots_next_day(client, cell): |
|
299 |
with mock.patch('combo.utils.requests.get') as request_get: |
|
300 |
events = CHRONO_EVENTS['data'][::] |
|
301 |
for idx in range(2): |
|
302 |
events[idx]['disabled'] = True |
|
303 | ||
304 |
def side_effect(*args, **kwargs): |
|
305 |
if 'chrono' in kwargs['remote_service']['url']: |
|
306 |
return MockedRequestResponse(content=json.dumps({"data": events})) |
|
307 |
return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS)) |
|
308 | ||
309 |
request_get.side_effect = side_effect |
|
310 |
page = client.get('/booking/') |
|
311 |
title_info = page.html.h2.find('span', {'class': 'calinfo'}) |
|
312 |
assert title_info.text.strip() == '(Next available slot: June 14, 2017, 9:30 a.m.)' |
|
313 | ||
314 | ||
315 |
def test_cell_rendering_cal_info_when_no_available_slots(client, cell): |
|
316 |
with mock.patch('combo.utils.requests.get') as request_get: |
|
317 |
def side_effect(*args, **kwargs): |
|
318 |
if 'chrono' in kwargs['remote_service']['url']: |
|
319 |
return MockedRequestResponse(content=json.dumps({"data": []})) |
|
320 |
return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS)) |
|
321 | ||
322 |
request_get.side_effect = side_effect |
|
323 |
page = client.get('/booking/') |
|
324 |
title_info = page.html.h2.find('span', {'class': 'calinfo'}) |
|
325 |
assert title_info.text.strip() == '(No available slots.)' |
|
289 |
- |