Projet

Général

Profil

0001-general-exhaustively-list-available-meeting-datetime.patch

Frédéric Péters, 20 novembre 2017 09:55

Télécharger (11,5 ko)

Voir les différences:

Subject: [PATCH] general: exhaustively list available meeting datetimes
 (#19150)

 chrono/agendas/models.py   | 20 ++++++++++++----
 tests/test_agendas.py      | 21 ++++++++++++++++-
 tests/test_api.py          | 57 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/test_time_periods.py | 16 ++++++++-----
 4 files changed, 103 insertions(+), 11 deletions(-)
chrono/agendas/models.py
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18
import datetime
19
import fractions
19 20
import requests
20 21
import vobject
21 22

  
......
95 96
        group_ids = [x.id for x in user.groups.all()]
96 97
        return bool(self.view_role_id in group_ids)
97 98

  
99
    def get_base_meeting_duration(self):
100
        durations = [x.duration for x in MeetingType.objects.filter(agenda=self)]
101
        if not durations:
102
            raise ValueError()
103
        gcd = durations[0]
104
        for duration in durations[1:]:
105
            gcd = fractions.gcd(duration, gcd)
106
        return gcd
107

  
98 108
    def export_json(self):
99 109
        agenda = {
100 110
            'label': self.label,
......
187 197
        }
188 198

  
189 199
    def get_time_slots(self, min_datetime, max_datetime, meeting_type):
190
        duration = datetime.timedelta(minutes=meeting_type.duration)
200
        meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
201
        duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())
191 202
        min_datetime = make_naive(min_datetime)
192 203
        max_datetime = make_naive(max_datetime)
193 204

  
......
199 210
        event_datetime = real_min_datetime.replace(hour=self.start_time.hour,
200 211
                minute=self.start_time.minute, second=0, microsecond=0)
201 212
        while event_datetime < max_datetime:
202
            end_time = event_datetime + duration
213
            end_time = event_datetime + meeting_duration
214
            next_time = event_datetime + duration
203 215
            if end_time.time() > self.end_time:
204 216
                # back to morning
205 217
                event_datetime = event_datetime.replace(hour=self.start_time.hour, minute=self.start_time.minute)
206 218
                # but next week
207 219
                event_datetime += datetime.timedelta(days=7)
208
                end_time = event_datetime + duration
220
                next_time = event_datetime + duration
209 221

  
210 222
            if event_datetime > max_datetime:
211 223
                break
212 224

  
213 225
            yield TimeSlot(start_datetime=make_aware(event_datetime), meeting_type=meeting_type, desk=self.desk)
214
            event_datetime = end_time
226
            event_datetime = next_time
215 227

  
216 228

  
217 229
class MeetingType(models.Model):
tests/test_agendas.py
10 10
from django.core.management.base import CommandError
11 11

  
12 12
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
13
                        Desk, TimePeriodException, ICSError)
13
                        Desk, TimePeriod, TimePeriodException, ICSError)
14 14

  
15 15
pytestmark = pytest.mark.django_db
16 16

  
......
330 330
    mocked_get.return_value = mocked_response
331 331
    call_command('sync_desks_timeperiod_exceptions')
332 332
    assert not TimePeriodException.objects.filter(desk=desk).exists()
333

  
334
def test_base_meeting_duration():
335
    agenda = Agenda(label='Meeting', kind='meetings')
336
    agenda.save()
337

  
338
    with pytest.raises(ValueError):
339
        agenda.get_base_meeting_duration()
340

  
341
    meeting_type = MeetingType(agenda=agenda, label='Foo', duration=30)
342
    meeting_type.save()
343
    assert agenda.get_base_meeting_duration() == 30
344

  
345
    meeting_type = MeetingType(agenda=agenda, label='Bar', duration=60)
346
    meeting_type.save()
347
    assert agenda.get_base_meeting_duration() == 30
348

  
349
    meeting_type = MeetingType(agenda=agenda, label='Bar', duration=45)
350
    meeting_type.save()
351
    assert agenda.get_base_meeting_duration() == 15
tests/test_api.py
1023 1023
    resp = app.post(booking_url2)
1024 1024
    assert resp.json['desk']['label'] == desk.label
1025 1025
    assert resp.json['desk']['slug'] == desk.slug
1026

  
1027

  
1028
def test_agenda_meeting_gcd_durations(app, meetings_agenda, user):
1029
    meetings_agenda.maximal_booking_delay = 8
1030
    meetings_agenda.save()
1031

  
1032
    time_period = TimePeriod.objects.get(end_time=datetime.time(12, 0))
1033
    time_period.end_time = datetime.time(13, 0)
1034
    time_period.save()
1035

  
1036
    meeting_type_30 = MeetingType.objects.get(duration=30)
1037
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1038
    assert len(resp.json['data']) == 20
1039

  
1040
    meeting_type_20 = MeetingType(agenda=meetings_agenda, label='Lorem', duration=20)
1041
    meeting_type_20.save()
1042

  
1043
    assert meetings_agenda.get_base_meeting_duration() == 10
1044
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1045
    assert len(resp.json['data']) == 56
1046
    # 16:30 is time period end time (17:00) minus meeting type duration
1047
    assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:30:00'
1048

  
1049
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
1050
    assert len(resp.json['data']) == 58
1051
    assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:40:00'
1052

  
1053
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1054
    event_id = resp.json['data'][0]['id']
1055
    app.authorization = ('Basic', ('john.doe', 'password'))
1056
    app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
1057
    assert Booking.objects.count() == 1
1058

  
1059
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
1060
    assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 55
1061
    event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
1062
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
1063
    assert resp.json['datetime'].startswith('2017-05-22T10:30:00')
1064
    assert Booking.objects.count() == 2
1065

  
1066
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1067
    event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
1068
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
1069
    assert resp.json['datetime'].startswith('2017-05-22T10:50:00')
1070
    assert Booking.objects.count() == 3
1071

  
1072
    # create a gap
1073
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1074
    event_id = [x for x in resp.json['data'] if not x.get('disabled')][1]['id']
1075
    resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
1076
    assert resp.json['datetime'].startswith('2017-05-22T11:30:00')
1077
    assert Booking.objects.count() == 4
1078

  
1079
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
1080
    assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00')
1081
    resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
1082
    assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00')
tests/test_time_periods.py
12 12
    agenda = Agenda(label=u'Foo bar', slug='bar')
13 13
    agenda.save()
14 14
    desk = Desk.objects.create(label='Desk 1', agenda=agenda)
15
    meeting_type = MeetingType(duration=60, agenda=agenda)
16
    meeting_type.save()
15 17
    timeperiod = TimePeriod(desk=desk, weekday=0,
16 18
            start_time=datetime.time(9, 0),
17 19
            end_time=datetime.time(12, 0))
18 20
    events = timeperiod.get_time_slots(
19 21
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
20 22
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
21
            meeting_type=MeetingType(duration=60))
23
            meeting_type=meeting_type)
22 24
    events = list(sorted(events, key=lambda x: x.start_datetime))
23 25
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 9, 0)
24 26
    assert events[1].start_datetime.timetuple()[:5] == (2016, 9, 5, 10, 0)
......
35 37
    events = timeperiod.get_time_slots(
36 38
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
37 39
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
38
            meeting_type=MeetingType(duration=60))
40
            meeting_type=meeting_type)
39 41
    events = list(sorted(events, key=lambda x: x.start_datetime))
40 42
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 6, 9, 0)
41 43
    assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 27, 11, 0)
......
48 50
    events = timeperiod.get_time_slots(
49 51
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
50 52
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
51
            meeting_type=MeetingType(duration=60))
53
            meeting_type=meeting_type)
52 54
    events = list(sorted(events, key=lambda x: x.start_datetime))
53 55
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 1, 9, 0)
54 56
    assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 29, 11, 0)
......
61 63
    events = timeperiod.get_time_slots(
62 64
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
63 65
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
64
            meeting_type=MeetingType(duration=60))
66
            meeting_type=meeting_type)
65 67
    events = list(sorted(events, key=lambda x: x.start_datetime))
66 68
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 2, 9, 0)
67 69
    assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 30, 11, 0)
......
74 76
    events = timeperiod.get_time_slots(
75 77
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
76 78
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
77
            meeting_type=MeetingType(duration=60))
79
            meeting_type=meeting_type)
78 80
    events = list(sorted(events, key=lambda x: x.start_datetime))
79 81
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
80 82
    assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 0)
81 83
    assert len(events) == 12
82 84

  
83 85
    # shorter duration -> double the events
86
    meeting_type.duration = 30
87
    meeting_type.save()
84 88
    timeperiod = TimePeriod(desk=desk, weekday=5,
85 89
            start_time=datetime.time(9, 0),
86 90
            end_time=datetime.time(12, 0))
87 91
    events = timeperiod.get_time_slots(
88 92
            min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
89 93
            max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
90
            meeting_type=MeetingType(duration=30))
94
            meeting_type=meeting_type)
91 95
    events = list(sorted(events, key=lambda x: x.start_datetime))
92 96
    assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
93 97
    assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 30)
94
-