Projet

Général

Profil

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

Josué Kouka, 03 novembre 2017 11:34

Télécharger (7,33 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                       | 41 ++++++++++++++++++----
 tests/test_calendar.py                             | 22 ++++++++++--
 4 files changed, 64 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>
7 12
{% include 'calendar/booking_calendar_content.html' %}
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 21
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
22
from django.utils.dateformat import DateFormat
22 23
from django.utils.dateparse import parse_datetime
23
from django.utils.timezone import now
24
from django.utils.formats import get_format
25
from django.utils.timezone import localtime, make_aware, now
26
from django.utils.translation import ugettext_lazy as _
24 27

  
25 28

  
26 29
from combo.utils import requests
......
83 86
    request = context['request']
84 87
    cell = context['cell']
85 88
    page = request.GET.get('chunk_%s' % cell.pk, 1)
86
    calendar = get_calendar(cell.agenda_reference, cell.slot_duration)
89
    calendar = get_calendar(cell.agenda_reference, cell.slot_duration,
90
                            cell.minimal_booking_duration)
87 91
    paginator = Paginator(list(calendar.get_days()), 7)
88 92
    try:
89 93
        cal_page = paginator.page(page)
......
97 101
    return context
98 102

  
99 103

  
100
def get_calendar(agenda_reference, offset):
104
def get_calendar(agenda_reference, offset, min_duration):
101 105
    if not agenda_reference:
102 106
        return []
103 107
    events = get_chrono_events(agenda_reference)
104
    calendar = Calendar(offset)
108
    calendar = Calendar(offset, min_duration)
105 109
    for event in events:
106 110
        event_datetime = parse_datetime(event['datetime'])
107 111
        if not calendar.has_day(event_datetime.date()):
......
131 135
class DaySlot(object):
132 136

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

  
138 142
    def __repr__(self):
139 143
        return '<DaySlot date_time=%s - available=%s>' % (self.date_time.isoformat(), self.available)
140 144

  
145
    def __str__(self):
146
        date = DateFormat(self.date_time).format(get_format('DATE_FORMAT'))
147
        return _('%s at %s') % (date, self.date_time.strftime('%H:%M'))
148

  
141 149
    @property
142 150
    def label(self):
143 151
        return '%s' % self.date_time.isoformat()
......
169 177
    def get_maximum_slot(self):
170 178
        return max(self.slots, key=lambda x: x.date_time.time())
171 179

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

  
172 191

  
173 192
class Calendar(object):
174 193

  
175
    def __init__(self, offset):
194
    def __init__(self, offset, min_duration):
176 195
        self.offset = offset
177 196
        self.days = []
197
        self.min_duration = min_duration
178 198

  
179 199
    def __repr__(self):
180 200
        return '<Calendar>'
181 201

  
202
    def get_info(self):
203
        for day in self.days:
204
            first_available_slot = day.get_first_available_slot(self.offset, self.min_duration)
205
            if first_available_slot:
206
                return _('(Next available slot: %s)') % first_available_slot
207
        return _('(No slot available)')
208

  
182 209
    def get_slots(self):
183 210
        start = self.get_minimum_slot()
184 211
        end = self.get_maximum_slot()
tests/test_calendar.py
222 222
    assert parsed.path == '/test/'
223 223
    qs = urlparse.parse_qs(parsed.query)
224 224
    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']
225
    assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00+00:00']
226
    assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00+00:00']
227 227

  
228 228

  
229 229
@mock.patch('combo.apps.calendar.utils.now', mocked_now)
230 230
@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get)
231 231
def test_calendar(mocked_get, cell):
232
    cal = get_calendar('default:whatever', cell.slot_duration)
232
    cal = get_calendar('default:whatever', cell.slot_duration, cell.minimal_booking_duration)
233 233
    assert len(cal.days) == 3
234 234
    for day in cal.get_days():
235 235
        assert day in [
......
244 244
    assert cal.get_minimum_slot() == min_slot.time()
245 245
    assert cal.get_maximum_slot() == max_slot.time()
246 246
    assert cal.get_day(max_slot.date()).slots[-1].available is False
247

  
248

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

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