Projet

Général

Profil

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

Josué Kouka, 06 novembre 2017 21:05

Télécharger (7,73 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  |  7 +++-
 combo/apps/calendar/utils.py                       | 48 ++++++++++++++++++----
 tests/test_calendar.py                             | 22 ++++++++--
 4 files changed, 71 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: 13px;
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">{{ calendar.get_info }}</span>
8
    {% endif %}
9
</h2>
5 10
{% endif %}
6 11
<div class="calcontent">
7 12
{% 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
23
from django.utils.dateformat import DateFormat
24 24
from django.utils.dateparse import parse_datetime
25
from django.utils.timezone import now
25
from django.utils.formats import get_format
26
from django.utils.timezone import localtime, make_aware, now
27
from django.utils.translation import ugettext_lazy as _
26 28

  
27 29

  
28 30
from combo.utils import requests
......
85 87
    request = context['request']
86 88
    cell = context['cell']
87 89
    page = request.GET.get('chunk_%s' % cell.pk, 1)
88
    calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed)
90
    calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed,
91
                            cell.minimal_booking_duration)
89 92
    paginator = Paginator(calendar.get_days(), cell.days_displayed)
90 93
    try:
91 94
        cal_page = paginator.page(page)
......
99 102
    return context
100 103

  
101 104

  
102
def get_calendar(agenda_reference, offset, days_displayed):
105
def get_calendar(agenda_reference, offset, days_displayed, min_duration):
103 106
    if not agenda_reference:
104 107
        return []
105 108
    events = get_chrono_events(agenda_reference)
106
    calendar = Calendar(offset, days_displayed)
109
    calendar = Calendar(offset, days_displayed, min_duration)
110

  
107 111
    for event in events:
108 112
        event_datetime = parse_datetime(event['datetime'])
109 113
        if not calendar.has_day(event_datetime.date()):
......
133 137
class DaySlot(object):
134 138

  
135 139
    def __init__(self, date_time, available, exist=True):
136
        self.date_time = date_time
140
        self.date_time = localtime(make_aware(date_time))
137 141
        self.available = available
138 142
        self.exist = exist
139 143

  
......
141 145
        return '<DaySlot date_time=%s - available=%s>' % (self.date_time.isoformat(), self.available)
142 146

  
143 147
    @property
148
    def info(self):
149
        date = DateFormat(self.date_time).format(get_format('DATE_FORMAT'))
150
        return _('%s at %s') % (date, self.date_time.strftime('%H:%M'))
151

  
152
    @property
144 153
    def label(self):
145 154
        return '%s' % self.date_time.isoformat()
146 155

  
......
171 180
    def get_maximum_slot(self):
172 181
        return max(self.slots, key=lambda x: x.date_time.time())
173 182

  
183
    def get_first_available_slot(self, offset, min_duration):
184
        """Search the first available slots
185
        but more importantly contiguous ones
186
        """
187
        step = min_duration.seconds / offset.seconds
188
        for idx in range(len(self.slots)):
189
            slots_slice = self.slots[idx:idx + step]
190
            end, start = slots_slice[-1], slots_slice[0]
191
            # slice is valid if diff of end - start = min_duration
192
            # and all slots within the slice are marked available
193
            if (end.date_time - start.date_time) != ((step - 1) * offset):
194
                continue
195
            if not all(map(lambda x: x.available, slots_slice)):
196
                continue
197
            return start
198
        return None
199

  
174 200

  
175 201
class Calendar(object):
176 202

  
177
    def __init__(self, offset, days_displayed):
203
    def __init__(self, offset, days_displayed, min_duration):
178 204
        self.offset = offset
179 205
        self.days_displayed = days_displayed
180 206
        self.days = []
207
        self.min_duration = min_duration
181 208

  
182 209
    def __repr__(self):
183 210
        return '<Calendar>'
184 211

  
212
    def get_info(self):
213
        for day in self.days:
214
            first_available_slot = day.get_first_available_slot(self.offset, self.min_duration)
215
            if first_available_slot:
216
                return _('(Next available slot: %s)') % first_available_slot.info
217
        return _('(No slot available)')
218

  
185 219
    def get_slots(self):
186 220
        start = self.get_minimum_slot()
187 221
        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_days():
234 234
        assert day in [
......
243 243
    assert cal.get_minimum_slot() == min_slot.time()
244 244
    assert cal.get_maximum_slot() == max_slot.time()
245 245
    assert cal.get_day(max_slot.date()).slots[-1].available is False
246

  
247

  
248
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get)
249
def test_cell_rendering_cal_info(mocked_get, client, cell):
250
    page = client.get('/booking/')
251
    assert '(Next available slot: June 13' in page.content
252
    # test when no slot is available
253
    with mock.patch('combo.utils.requests.get') as request_get:
254
        def side_effect(*args, **kwargs):
255
            if 'chrono' in kwargs['remote_service']['url']:
256
                return MockedRequestResponse(content=json.dumps({"data": []}))
257
            return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS))
258

  
259
        request_get.side_effect = side_effect
260
        page = client.get('/booking/')
261
        assert '(No slot available)' in page.content
246
-