From d73f36cbbd66ec5026cfafed5b3d728bd95ecea8 Mon Sep 17 00:00:00 2001 From: Josue Kouka Date: Tue, 17 Oct 2017 16:13:45 +0200 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 diff --git a/combo/apps/calendar/migrations/0002_bookingcalendar_chunk_size.py b/combo/apps/calendar/migrations/0002_bookingcalendar_chunk_size.py deleted file mode 100644 index 1dcee83..0000000 --- a/combo/apps/calendar/migrations/0002_bookingcalendar_chunk_size.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('calendar', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='bookingcalendar', - name='chunk_size', - field=models.PositiveSmallIntegerField(default=7, verbose_name='Number of days to display'), - ), - ] diff --git a/combo/apps/calendar/static/css/calendar.css b/combo/apps/calendar/static/css/calendar.css index 287f89f..a519555 100644 --- a/combo/apps/calendar/static/css/calendar.css +++ b/combo/apps/calendar/static/css/calendar.css @@ -1,3 +1,8 @@ div.cell.bookingcalendar a { cursor: pointer; } + +div.cell.bookingcalendar h2 > span.calinfo { + font-style: italic; + font-size: 13px; +} diff --git a/combo/apps/calendar/templates/calendar/booking_calendar_cell.html b/combo/apps/calendar/templates/calendar/booking_calendar_cell.html index 6ace233..2324a10 100644 --- a/combo/apps/calendar/templates/calendar/booking_calendar_cell.html +++ b/combo/apps/calendar/templates/calendar/booking_calendar_cell.html @@ -1,7 +1,12 @@ {% load i18n calendar %} {% if cell.title %} -

{{cell.title}}

+

+ {{cell.title}} + {% if calendar %} + {{ calendar.get_info }} + {% endif %} +

{% endif %}
{% include 'calendar/booking_calendar_content.html' %} diff --git a/combo/apps/calendar/utils.py b/combo/apps/calendar/utils.py index e107676..492e024 100644 --- a/combo/apps/calendar/utils.py +++ b/combo/apps/calendar/utils.py @@ -18,11 +18,13 @@ import datetime import math import urllib - from django.conf import settings from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.utils.dateformat import DateFormat from django.utils.dateparse import parse_datetime -from django.utils.timezone import now +from django.utils.formats import get_format +from django.utils.timezone import localtime, make_aware, now +from django.utils.translation import ugettext_lazy as _ from combo.utils import requests @@ -85,7 +87,8 @@ def get_paginated_calendar(context): request = context['request'] cell = context['cell'] page = request.GET.get('chunk_%s' % cell.pk, 1) - calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed) + calendar = get_calendar(cell.agenda_reference, cell.slot_duration, cell.days_displayed, + cell.minimal_booking_duration) paginator = Paginator(list(calendar.get_days()), cell.days_displayed) try: cal_page = paginator.page(page) @@ -99,11 +102,11 @@ def get_paginated_calendar(context): return context -def get_calendar(agenda_reference, offset, days_displayed): +def get_calendar(agenda_reference, offset, days_displayed, min_duration): if not agenda_reference: return [] events = get_chrono_events(agenda_reference) - calendar = Calendar(offset, days_displayed) + calendar = Calendar(offset, days_displayed, min_duration) for event in events: event_datetime = parse_datetime(event['datetime']) @@ -134,7 +137,7 @@ def get_form_url_with_params(cell, data): class DaySlot(object): def __init__(self, date_time, available, exist=True): - self.date_time = date_time + self.date_time = localtime(make_aware(date_time)) self.available = available self.exist = exist @@ -142,6 +145,11 @@ class DaySlot(object): return '' % (self.date_time.isoformat(), self.available) @property + def info(self): + date = DateFormat(self.date_time).format(get_format('DATE_FORMAT')) + return _('%s at %s') % (date, self.date_time.strftime('%H:%M')) + + @property def label(self): return '%s' % self.date_time.isoformat() @@ -172,17 +180,36 @@ class WeekDay(object): def get_maximum_slot(self): return max(self.slots, key=lambda x: x.date_time.time()) + def get_first_available_slot(self, offset, min_duration): + step = min_duration.seconds / offset.seconds + for idx in range(len(self.slots)): + tmp = self.slots[idx:idx + step] + if (tmp[-1].date_time - tmp[0].date_time) != ((step - 1) * offset): + continue + if not all(map(lambda x: x.available, tmp)): + continue + return tmp[0] + return None + class Calendar(object): - def __init__(self, offset, days_displayed): + def __init__(self, offset, days_displayed, min_duration): self.offset = offset self.days_displayed = days_displayed self.days = [] + self.min_duration = min_duration def __repr__(self): return '' + def get_info(self): + for day in self.days: + first_available_slot = day.get_first_available_slot(self.offset, self.min_duration) + if first_available_slot: + return _('(Next available slot: %s)') % first_available_slot.info + return _('(No slot available)') + def get_slots(self): start = self.get_minimum_slot() end = self.get_maximum_slot() diff --git a/tests/test_calendar.py b/tests/test_calendar.py index 37f168d..ffdb8d2 100644 --- a/tests/test_calendar.py +++ b/tests/test_calendar.py @@ -5,7 +5,6 @@ import datetime import pytest import mock -from django.utils.dateparse import parse_time from django.utils.timezone import now from django.contrib.auth.models import User @@ -222,14 +221,14 @@ def test_cell_rendering(mocked_get, client, cell): assert parsed.path == '/test/' qs = urlparse.parse_qs(parsed.query) assert qs['session_var_booking_agenda_slug'] == ['test'] - assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00'] - assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00'] + assert qs['session_var_booking_start'] == ['2017-06-13T08:00:00+00:00'] + assert qs['session_var_booking_end'] == ['2017-06-13T09:30:00+00:00'] @mock.patch('combo.apps.calendar.utils.now', mocked_now) @mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get) def test_calendar(mocked_get, cell): - cal = get_calendar('default:whatever', cell.slot_duration, 7) + cal = get_calendar('default:whatever', cell.slot_duration, 7, cell.minimal_booking_duration) assert len(cal.days) == 3 for day in cal.get_days(): assert day in [ @@ -244,3 +243,19 @@ def test_calendar(mocked_get, cell): assert cal.get_minimum_slot() == min_slot.time() assert cal.get_maximum_slot() == max_slot.time() assert cal.get_day(max_slot.date()).slots[-1].available is False + + +@mock.patch('combo.apps.calendar.utils.requests.get', side_effect=mocked_requests_get) +def test_cell_rendering_cal_info(mocked_get, client, cell): + page = client.get('/booking/') + assert '(Next available slot: June 13' in page.content + # test when no slot is available + with mock.patch('combo.utils.requests.get') as request_get: + def side_effect(*args, **kwargs): + if 'chrono' in kwargs['remote_service']['url']: + return MockedRequestResponse(content=json.dumps({"data": []})) + return MockedRequestResponse(content=json.dumps(WCS_FORMDEFS)) + + request_get.side_effect = side_effect + page = client.get('/booking/') + assert '(No slot available)' in page.content -- 2.11.0