Projet

Général

Profil

0001-booking-calendar-cell-display-first-available-slot-i.patch

Josué Kouka, 07 novembre 2017 16:29

Télécharger (7,85 ko)

Voir les différences:

Subject: [PATCH] booking calendar cell: display first available slot if any
 (#19460)

 combo/apps/calendar/static/css/calendar.css        |  5 +++
 .../templates/calendar/booking_calendar_cell.html  | 11 +++++-
 combo/apps/calendar/utils.py                       | 27 +++++++++----
 tests/test_calendar.py                             | 45 ++++++++++++++++++++--
 4 files changed, 77 insertions(+), 11 deletions(-)
combo/apps/calendar/static/css/calendar.css
1 1
div.cell.bookingcalendar a {
2 2
    cursor: pointer;
3 3
}
4

  
5
div.cell.bookingcalendar h2 > span.calinfo {
6
    font-style: italic;
7
    font-size: 80%;
8
}
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">
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
18 18
import math
19 19
import urllib
20 20

  
21

  
22 21
from django.conf import settings
23 22
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
24 23
from django.utils.dateparse import parse_datetime
25
from django.utils.timezone import now
24
from django.utils.timezone import localtime, make_aware, now
26 25

  
27 26

  
28 27
from combo.utils import requests
......
83 82
    request = context['request']
84 83
    cell = context['cell']
85 84
    page = request.GET.get('chunk_%s' % cell.pk, 1)
86
    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)
87 87
    paginator = Paginator(calendar.get_computed_days(), cell.days_displayed)
88 88
    try:
89 89
        cal_page = paginator.page(page)
......
97 97
    return context
98 98

  
99 99

  
100
def get_calendar(agenda_reference, offset, days_displayed):
100
def get_calendar(agenda_reference, offset, days_displayed, min_duration):
101 101
    if not agenda_reference:
102 102
        return []
103 103
    events = get_chrono_events(agenda_reference)
104
    calendar = Calendar(offset, days_displayed)
104
    calendar = Calendar(offset, days_displayed, min_duration)
105

  
105 106
    for event in events:
106 107
        event_datetime = parse_datetime(event['datetime'])
107 108
        if not calendar.has_day(event_datetime.date()):
......
131 132
class DaySlot(object):
132 133

  
133 134
    def __init__(self, date_time, available, exist=True):
134
        self.date_time = date_time
135
        self.date_time = localtime(make_aware(date_time))
135 136
        self.available = available
136 137
        self.exist = exist
137 138

  
......
172 173

  
173 174
class Calendar(object):
174 175

  
175
    def __init__(self, offset, days_displayed):
176
    def __init__(self, offset, days_displayed, min_duration):
176 177
        self.offset = offset
177 178
        self.days_displayed = days_displayed
178 179
        self.days = []
180
        self.min_duration = min_duration
179 181

  
180 182
    def __repr__(self):
181 183
        return '<Calendar>'
182 184

  
185
    def get_first_available_slot(self):
186
        """Return the first available slot
187
        """
188
        required_contiguous_slots = self.min_duration.seconds / self.offset.seconds
189
        for day in self.days:
190
            slots = day.slots
191
            for idx in range(len(slots) - required_contiguous_slots):
192
                if all([x.available for x in slots[idx:idx+required_contiguous_slots]]):
193
                    return slots[idx]
194
        return None
195

  
183 196
    def get_slots(self):
184 197
        start = self.get_minimum_slot()
185 198
        end = self.get_maximum_slot()
tests/test_calendar.py
221 221
    assert parsed.path == '/test/'
222 222
    qs = urlparse.parse_qs(parsed.query)
223 223
    assert qs['session_var_booking_agenda_slug'] == ['test']
224
    assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00']
225
    assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00']
224
    assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00+00:00']
225
    assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00+00:00']
226 226

  
227 227

  
228 228
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
229 229
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get)
230 230
def test_calendar(mocked_get, cell):
231
    cal = get_calendar('default:whatever', cell.slot_duration, 7)
231
    cal = get_calendar('default:whatever', cell.slot_duration, 7, cell.minimal_booking_duration)
232 232
    assert len(cal.days) == 3
233 233
    for day in cal.get_computed_days():
234 234
        assert day in [
......
276 276
    previous_page_link = links[0]
277 277
    assert previous_page_link.text == 'previous'
278 278
    assert previous_page_link.attrs['data-content-url'] == '/ajax/calendar/content/%d/?chunk_%d=1' % (cell.pk, cell.pk)
279

  
280

  
281
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
282
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get)
283
def test_cell_rendering_cal_info(mocked_get, client, cell):
284
    page = client.get('/booking/')
285
    title_info = page.html.h2.find('span', {'class': 'calinfo'})
286
    assert title_info.text.strip() == '(Next available slot: June 13, 2017, 8 a.m.)'
287

  
288

  
289
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
290
def test_cell_rendering_cal_info_when_available_slots_next_day(client, cell):
291
    with mock.patch('combo.utils.requests.get') as request_get:
292
        events = CHRONO_EVENTS['data'][::]
293
        for idx in range(2):
294
            events[idx]['disabled'] = True
295

  
296
        def side_effect(*args, **kwargs):
297
            if 'chrono' in kwargs['remote_service']['url']:
298
                return MockedRequestResponse(content=json.dumps({"data": events}))
299
            return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS))
300

  
301
        request_get.side_effect = side_effect
302
        page = client.get('/booking/')
303
        title_info = page.html.h2.find('span', {'class': 'calinfo'})
304
        assert title_info.text.strip() == '(Next available slot: June 14, 2017, 9:30 a.m.)'
305

  
306

  
307
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
308
def test_cell_rendering_cal_info_when_no_available_slots(client, cell):
309
    with mock.patch('combo.utils.requests.get') as request_get:
310
        def side_effect(*args, **kwargs):
311
            if 'chrono' in kwargs['remote_service']['url']:
312
                return MockedRequestResponse(content=json.dumps({"data": []}))
313
            return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS))
314

  
315
        request_get.side_effect = side_effect
316
        page = client.get('/booking/')
317
        assert '(No available slots.)' in page.content
279
-