0001-booking-calendar-cell-display-first-available-slot-i.patch
combo/apps/calendar/models.py | ||
---|---|---|
54 | 54 | |
55 | 55 |
def render(self, context): |
56 | 56 |
calendar = get_calendar(self.agenda_reference, self.slot_duration, |
57 |
self.displayed_days) |
|
57 |
self.displayed_days, self.minimal_booking_duration)
|
|
58 | 58 |
context['calendar'] = calendar |
59 | 59 |
return super(BookingCalendar, self).render(context) |
combo/apps/calendar/templates/calendar/booking_calendar_cell.html | ||
---|---|---|
2 | 2 | |
3 | 3 |
<div id="cal-{{cell.id}}"> |
4 | 4 |
{% if cell.title %} |
5 |
<h2>{{cell.title}}</h2> |
|
5 |
<h2> |
|
6 |
<span>{{cell.title}}</span> |
|
7 |
{% if calendar %} |
|
8 |
<span>{{ calendar.get_info }}</span> |
|
9 |
{% endif %} |
|
10 |
</h2> |
|
6 | 11 |
{% endif %} |
7 | 12 |
<div> |
8 | 13 |
{% if calendar.days %} |
combo/apps/calendar/utils.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import urllib |
|
18 | 17 |
import datetime |
18 |
import urllib |
|
19 | 19 | |
20 | 20 |
from django.conf import settings |
21 |
from django.utils.dateformat import DateFormat |
|
21 | 22 |
from django.utils.dateparse import parse_datetime |
22 |
from django.utils.timezone import now |
|
23 |
from django.utils.formats import get_format |
|
24 |
from django.utils.timezone import localtime, make_aware, now |
|
25 |
from django.utils.translation import ugettext_lazy as _ |
|
23 | 26 | |
24 | 27 |
from combo.utils import requests |
25 | 28 | |
... | ... | |
76 | 79 |
return result.get('data', []) |
77 | 80 | |
78 | 81 | |
79 |
def get_calendar(agenda_reference, offset, size): |
|
82 |
def get_calendar(agenda_reference, offset, size, min_duration):
|
|
80 | 83 |
if not agenda_reference: |
81 | 84 |
return [] |
82 | 85 |
events = get_chrono_events(agenda_reference) |
83 |
weekcal = Calendar(offset, size)
|
|
86 |
weekcal = Calendar(offset, min_duration)
|
|
84 | 87 |
for event in events: |
85 | 88 |
event_datetime = parse_datetime(event['datetime']) |
86 | 89 |
event_date = event_datetime.date() |
... | ... | |
110 | 113 |
class DaySlot(object): |
111 | 114 | |
112 | 115 |
def __init__(self, date_time, available, exist=True): |
113 |
self.date_time = date_time
|
|
116 |
self.date_time = localtime(make_aware(date_time))
|
|
114 | 117 |
self.available = available |
115 | 118 |
self.exist = exist |
116 | 119 | |
117 | 120 |
def __repr__(self): |
118 | 121 |
return '<DaySlot date_time=%s - available=%s>' % (self.date_time.isoformat(), self.available) |
119 | 122 | |
123 |
def __str__(self): |
|
124 |
date = DateFormat(self.date_time).format(get_format('DATE_FORMAT')) |
|
125 |
return _('%s at %s') % (date, self.date_time.strftime('%H:%M')) |
|
126 | ||
120 | 127 |
@property |
121 | 128 |
def label(self): |
122 | 129 |
return '%s' % self.date_time.isoformat() |
... | ... | |
148 | 155 |
def get_maximum_slot(self): |
149 | 156 |
return max(self.slots, key=lambda x: x.date_time.time()) |
150 | 157 | |
158 |
def get_first_available_slot(self, offset, min_duration): |
|
159 |
step = min_duration.seconds / offset.seconds |
|
160 |
for idx in range(len(self.slots)): |
|
161 |
tmp = self.slots[idx:idx + step] |
|
162 |
if (tmp[-1].date_time - tmp[0].date_time) != ((step - 1) * offset): |
|
163 |
continue |
|
164 |
if not all(map(lambda x: x.available, tmp)): |
|
165 |
continue |
|
166 |
return tmp[0] |
|
167 |
return None |
|
168 | ||
151 | 169 | |
152 | 170 |
class Calendar(object): |
153 | 171 | |
154 |
def __init__(self, offset, size): |
|
172 |
def __init__(self, offset, size, min_duration):
|
|
155 | 173 |
self.offset = offset |
156 | 174 |
self.days = [] |
157 | 175 |
self.size = size |
176 |
self.min_duration = min_duration |
|
158 | 177 | |
159 | 178 |
def __repr__(self): |
160 | 179 |
if self.days: |
161 | 180 |
return '<Calendar: %s -> %s >' % (self.days[0], self.days[-1]) |
162 | 181 |
return '<Calendar>' |
163 | 182 | |
183 |
def get_info(self): |
|
184 |
for day in self.days: |
|
185 |
first_available_slot = day.get_first_available_slot(self.offset, self.min_duration) |
|
186 |
if first_available_slot: |
|
187 |
return _('(Next available slot: %s)') % first_available_slot |
|
188 |
return _('(No slot available)') |
|
189 | ||
164 | 190 |
def get_slots(self): |
165 | 191 |
start = self.get_minimum_slot() |
166 | 192 |
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) |
|
224 |
cal = get_calendar('default:whatever', cell.slot_duration, cell.minimal_booking_duration)
|
|
225 | 225 |
assert len(cal.days) == 3 |
226 | 226 |
for day in cal.get_days(): |
227 | 227 |
assert day in [ |
... | ... | |
236 | 236 |
assert cal.get_minimum_slot() == min_slot.time() |
237 | 237 |
assert cal.get_maximum_slot() == max_slot.time() |
238 | 238 |
assert cal.get_day(max_slot.date()).slots[-1].available is False |
239 | ||
240 | ||
241 |
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get) |
|
242 |
def test_cell_rendering_cal_info(mocked_get, client, cell): |
|
243 |
page = client.get('/booking/') |
|
244 |
assert '(Next available slot: June 13' in page.content |
|
245 |
# test when no slot is available |
|
246 |
with mock.patch('combo.utils.requests.get') as request_get: |
|
247 |
def side_effect(*args, **kwargs): |
|
248 |
if 'chrono' in kwargs['remote_service']['url']: |
|
249 |
return MockedRequestResponse(content=json.dumps({"data": []})) |
|
250 |
return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS)) |
|
251 | ||
252 |
request_get.side_effect = side_effect |
|
253 |
page = client.get('/booking/') |
|
254 |
assert '(No slot available)' in page.content |
|
239 |
- |