Projet

Général

Profil

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

Josué Kouka, 06 novembre 2017 16:16

Télécharger (8,46 ko)

Voir les différences:

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

 .../migrations/0002_bookingcalendar_chunk_size.py  | 19 ----------
 combo/apps/calendar/static/css/calendar.css        |  5 +++
 .../templates/calendar/booking_calendar_cell.html  |  7 +++-
 combo/apps/calendar/utils.py                       | 41 ++++++++++++++++++----
 tests/test_calendar.py                             | 23 +++++++++---
 5 files changed, 64 insertions(+), 31 deletions(-)
 delete mode 100644 combo/apps/calendar/migrations/0002_bookingcalendar_chunk_size.py
combo/apps/calendar/migrations/0002_bookingcalendar_chunk_size.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('calendar', '0001_initial'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='bookingcalendar',
16
            name='chunk_size',
17
            field=models.PositiveSmallIntegerField(default=7, verbose_name='Number of days to display'),
18
        ),
19
    ]
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>
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(list(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)
107 110

  
108 111
    for event in events:
109 112
        event_datetime = parse_datetime(event['datetime'])
......
134 137
class DaySlot(object):
135 138

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

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

  
144 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
145 153
    def label(self):
146 154
        return '%s' % self.date_time.isoformat()
147 155

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

  
183
    def get_first_available_slot(self, offset, min_duration):
184
        step = min_duration.seconds / offset.seconds
185
        for idx in range(len(self.slots)):
186
            tmp = self.slots[idx:idx + step]
187
            if (tmp[-1].date_time - tmp[0].date_time) != ((step - 1) * offset):
188
                continue
189
            if not all(map(lambda x: x.available, tmp)):
190
                continue
191
            return tmp[0]
192
        return None
193

  
175 194

  
176 195
class Calendar(object):
177 196

  
178
    def __init__(self, offset, days_displayed):
197
    def __init__(self, offset, days_displayed, min_duration):
179 198
        self.offset = offset
180 199
        self.days_displayed = days_displayed
181 200
        self.days = []
201
        self.min_duration = min_duration
182 202

  
183 203
    def __repr__(self):
184 204
        return '<Calendar>'
185 205

  
206
    def get_info(self):
207
        for day in self.days:
208
            first_available_slot = day.get_first_available_slot(self.offset, self.min_duration)
209
            if first_available_slot:
210
                return _('(Next available slot: %s)') % first_available_slot.info
211
        return _('(No slot available)')
212

  
186 213
    def get_slots(self):
187 214
        start = self.get_minimum_slot()
188 215
        end = self.get_maximum_slot()
tests/test_calendar.py
5 5
import pytest
6 6
import mock
7 7

  
8
from django.utils.dateparse import parse_time
9 8
from django.utils.timezone import now
10 9
from django.contrib.auth.models import User
11 10

  
......
222 221
    assert parsed.path == '/test/'
223 222
    qs = urlparse.parse_qs(parsed.query)
224 223
    assert qs['session_var_booking_agenda_slug'] == ['test']
225
    assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00']
226
    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']
227 226

  
228 227

  
229 228
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
230 229
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get)
231 230
def test_calendar(mocked_get, cell):
232
    cal = get_calendar('default:whatever', cell.slot_duration, 7)
231
    cal = get_calendar('default:whatever', cell.slot_duration, 7, cell.minimal_booking_duration)
233 232
    assert len(cal.days) == 3
234 233
    for day in cal.get_days():
235 234
        assert day in [
......
244 243
    assert cal.get_minimum_slot() == min_slot.time()
245 244
    assert cal.get_maximum_slot() == max_slot.time()
246 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
247
-