Projet

Général

Profil

0006-manager-display-date-time-period-in-calendar-views-7.patch

Valentin Deniaud, 31 octobre 2022 14:43

Télécharger (15,2 ko)

Voir les différences:

Subject: [PATCH 6/7] manager: display date time period in calendar views
 (#70185)

 chrono/agendas/models.py   |  14 ++--
 chrono/manager/views.py    |  20 ++++-
 tests/manager/test_all.py  | 149 +++++++++++++++++++++++++++++++++++++
 tests/test_agendas.py      |   2 +-
 tests/test_time_periods.py |  71 ++++++++++++++++++
 5 files changed, 245 insertions(+), 11 deletions(-)
chrono/agendas/models.py
795 795
            end_datetime__gt=min_start,
796 796
        )
797 797

  
798
    def prefetch_desks_and_exceptions(self, with_sources=False, min_date=None):
798
    def prefetch_desks_and_exceptions(self, min_date, max_date=None, with_sources=False):
799 799
        if self.kind == 'meetings':
800 800
            desks = self.desk_set.all()
801 801
        elif self.kind == 'virtual':
......
807 807
        else:
808 808
            raise ValueError('does not work with kind %r' % self.kind)
809 809

  
810
        if min_date:
811
            past_date_time_periods = TimePeriod.objects.filter(desk=OuterRef('pk'), date__lt=min_date)
812
            desks = desks.annotate(has_past_date_time_periods=Exists(past_date_time_periods))
810
        past_date_time_periods = TimePeriod.objects.filter(desk=OuterRef('pk'), date__lt=min_date)
811
        desks = desks.annotate(has_past_date_time_periods=Exists(past_date_time_periods))
813 812

  
814
            time_period_queryset = TimePeriod.objects.filter(Q(date__isnull=True) | Q(date__gte=min_date))
813
        time_period_queryset = TimePeriod.objects.filter(Q(date__isnull=True) | Q(date__gte=min_date))
814
        if max_date:
815
            time_period_queryset = time_period_queryset.filter(Q(date__isnull=True) | Q(date__lte=max_date))
815 816

  
816 817
        self.prefetched_desks = desks.prefetch_related(
817 818
            'unavailability_calendars', Prefetch('timeperiod_set', queryset=time_period_queryset)
......
2276 2277
    def get_opening_hours(self, date):
2277 2278
        openslots = IntervalSet()
2278 2279
        weekday_index = get_weekday_index(date)
2280
        real_date = date.date() if isinstance(date, datetime.datetime) else date
2279 2281
        for timeperiod in self.timeperiod_set.all():
2280 2282
            if timeperiod.weekday_indexes and weekday_index not in timeperiod.weekday_indexes:
2281 2283
                continue
2282 2284
            # timeperiod_set.all() are prefetched, do not filter in queryset
2283
            if timeperiod.weekday != date.weekday():
2285
            if timeperiod.date != real_date and timeperiod.weekday != date.weekday():
2284 2286
                continue
2285 2287
            start_datetime = make_aware(datetime.datetime.combine(date, timeperiod.start_time))
2286 2288
            end_datetime = make_aware(datetime.datetime.combine(date, timeperiod.end_time))
chrono/manager/views.py
1264 1264
        if self.agenda.kind == 'events':
1265 1265
            queryset = self.agenda.event_set.filter(recurrence_days__isnull=True)
1266 1266
        else:
1267
            self.agenda.prefetch_desks_and_exceptions()
1267
            self.agenda.prefetch_desks_and_exceptions(min_date=self.date, max_date=self.get_max_date())
1268 1268
            if self.agenda.kind == 'meetings':
1269 1269
                queryset = self.agenda.event_set.select_related('meeting_type').prefetch_related(
1270 1270
                    'booking_set'
......
1324 1324
            },
1325 1325
        )
1326 1326

  
1327
    def get_max_date(self):
1328
        return self.date.date() + datetime.timedelta(days=1)
1329

  
1327 1330
    def get_timetable_infos(self):
1328 1331
        timeperiods = itertools.chain(*(d.timeperiod_set.all() for d in self.agenda.prefetched_desks))
1329 1332
        timeperiods = [
1330 1333
            t
1331 1334
            for t in timeperiods
1332
            if t.weekday == self.date.weekday()
1335
            if t.date == self.date.date()
1336
            or t.weekday == self.date.weekday()
1333 1337
            and (not t.weekday_indexes or get_weekday_index(self.date) in t.weekday_indexes)
1334 1338
        ]
1335 1339

  
......
1457 1461
        if timeperiods:
1458 1462
            min_timeperiod = min(x.start_time for x in timeperiods)
1459 1463
            max_timeperiod = max(x.end_time for x in timeperiods)
1460
            hide_sunday_timeperiod = not any([e.weekday == 6 for e in timeperiods])
1464
            hide_sunday_timeperiod = not any(
1465
                [e.weekday == 6 or (e.date and e.date.weekday() == 6) for e in timeperiods]
1466
            )
1461 1467
            hide_weekend_timeperiod = hide_sunday_timeperiod and not any(
1462
                [e.weekday == 5 for e in timeperiods]
1468
                [e.weekday == 5 or (e.date and e.date.weekday() == 5) for e in timeperiods]
1463 1469
            )
1464 1470
        active_events = [
1465 1471
            x for x in self.object_list if any([y.cancellation_datetime is None for y in x.booking_set.all()])
......
1633 1639
        date = datetime.datetime.strptime('%s-W%s-1' % (self.get_year(), self.get_week()), "%Y-W%W-%w")
1634 1640
        return date.day
1635 1641

  
1642
    def get_max_date(self):
1643
        return self.get_next_week(self.date.date())
1644

  
1636 1645

  
1637 1646
agenda_weekly_view = AgendaWeekView.as_view()
1638 1647

  
......
1665 1674
    def get_day(self):
1666 1675
        return '1'
1667 1676

  
1677
    def get_max_date(self):
1678
        return self.get_next_month(self.date.date())
1679

  
1668 1680

  
1669 1681
agenda_monthly_view = AgendaMonthView.as_view()
1670 1682

  
tests/manager/test_all.py
3716 3716
    assert resp.text.count('height:400.0%') == 1
3717 3717
    assert resp.text.count('height:700.0%') == 1
3718 3718
    assert resp.text.count('height:300.0%') == 1
3719

  
3720

  
3721
@freezegun.freeze_time('2022-11-15 14:00')
3722
def test_agenda_calendar_views_date_time_period(app, admin_user):
3723
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
3724
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3725
    MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
3726
    today = datetime.date.today()
3727
    TimePeriod.objects.create(
3728
        desk=desk,
3729
        date=today,
3730
        start_time=datetime.time(10, 0),
3731
        end_time=datetime.time(14, 0),
3732
    )
3733
    login(app)
3734

  
3735
    # check day view
3736
    resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
3737
    assert resp.text.count('<tr') == 5  # 10->14
3738
    assert 'style="height: 400%; top: 0%;"' in resp.text
3739

  
3740
    resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 7))
3741
    assert 'No opening hours this day.' in resp.text
3742

  
3743
    # check week view
3744
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3745
    assert resp.text.count('height:400.0%') == 1
3746

  
3747
    # check month view
3748
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3749
    assert resp.text.count('height:400.0%') == 1
3750

  
3751
    # check month boundaries
3752
    TimePeriod.objects.create(
3753
        desk=desk,
3754
        date=today.replace(day=1),
3755
        start_time=datetime.time(10, 0),
3756
        end_time=datetime.time(14, 0),
3757
    )
3758
    TimePeriod.objects.create(
3759
        desk=desk,
3760
        date=today.replace(day=30),
3761
        start_time=datetime.time(10, 0),
3762
        end_time=datetime.time(14, 0),
3763
    )
3764
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3765
    assert resp.text.count('height:400.0%') == 3
3766

  
3767
    TimePeriod.objects.create(
3768
        desk=desk,
3769
        date=today.replace(day=31, month=10),
3770
        start_time=datetime.time(10, 0),
3771
        end_time=datetime.time(14, 0),
3772
    )
3773
    TimePeriod.objects.create(
3774
        desk=desk,
3775
        date=today.replace(day=1, month=12),
3776
        start_time=datetime.time(10, 0),
3777
        end_time=datetime.time(14, 0),
3778
    )
3779
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3780
    assert resp.text.count('height:400.0%') == 3
3781

  
3782
    # check week boundaries
3783
    TimePeriod.objects.create(
3784
        desk=desk,
3785
        date=today.replace(day=14),
3786
        start_time=datetime.time(10, 0),
3787
        end_time=datetime.time(14, 0),
3788
    )
3789
    TimePeriod.objects.create(
3790
        desk=desk,
3791
        date=today.replace(day=20),
3792
        start_time=datetime.time(10, 0),
3793
        end_time=datetime.time(14, 0),
3794
    )
3795
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3796
    assert resp.text.count('height:400.0%') == 3
3797

  
3798
    TimePeriod.objects.create(
3799
        desk=desk,
3800
        date=today.replace(day=13),
3801
        start_time=datetime.time(10, 0),
3802
        end_time=datetime.time(14, 0),
3803
    )
3804
    TimePeriod.objects.create(
3805
        desk=desk,
3806
        date=today.replace(day=21),
3807
        start_time=datetime.time(10, 0),
3808
        end_time=datetime.time(14, 0),
3809
    )
3810
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3811
    assert resp.text.count('height:400.0%') == 3
3812

  
3813

  
3814
@freezegun.freeze_time('2022-11-15 14:00')
3815
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
3816
def test_agenda_date_time_period_hide_weekend(app, admin_user, kind):
3817
    today = datetime.date.today()  # Tuesday
3818
    if kind == 'meetings':
3819
        agenda = Agenda.objects.create(label='Passeports', kind='meetings')
3820
        desk = Desk.objects.create(agenda=agenda, label='Desk A')
3821
    else:
3822
        agenda = Agenda.objects.create(label='Virtual', kind='virtual')
3823
        real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
3824
        VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
3825
        desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
3826
    TimePeriod.objects.create(
3827
        desk=desk, date=today, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
3828
    )
3829

  
3830
    login(app)
3831
    # check month view
3832
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3833
    assert 'Sunday' not in resp.text
3834
    assert 'Saturday' not in resp.text
3835

  
3836
    # check week view
3837
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3838
    assert 'Sunday' not in resp.text
3839
    assert 'Saturday' not in resp.text
3840

  
3841
    TimePeriod.objects.create(
3842
        desk=desk, date=today.replace(day=19), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
3843
    )  # Saturday
3844

  
3845
    # check month view
3846
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3847
    assert 'Sunday' not in resp.text
3848
    assert 'Saturday' in resp.text
3849

  
3850
    # check week view
3851
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3852
    assert 'Sunday' not in resp.text
3853
    assert 'Saturday' in resp.text
3854

  
3855
    TimePeriod.objects.create(
3856
        desk=desk, date=today.replace(day=20), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
3857
    )  # Sunday
3858

  
3859
    # check month view
3860
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
3861
    assert 'Sunday' in resp.text
3862
    assert 'Saturday' in resp.text
3863

  
3864
    # check week view
3865
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.isocalendar().week))
3866
    assert 'Sunday' in resp.text
3867
    assert 'Saturday' in resp.text
tests/test_agendas.py
276 276
    def check_is_available(result, use_prefetch=True):
277 277
        agenda = Agenda.objects.get()
278 278
        if with_prefetch and use_prefetch:
279
            agenda.prefetch_desks_and_exceptions(with_sources=True)
279
            agenda.prefetch_desks_and_exceptions(with_sources=True, min_date=now())
280 280
        assert agenda.is_available_for_simple_management() == result
281 281

  
282 282
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
tests/test_time_periods.py
299 299
    assert len(hours) == 0
300 300

  
301 301

  
302
def test_desk_opening_hours_date_time_period():
303
    def set_prefetched_exceptions(desk):
304
        desk.prefetched_exceptions = TimePeriodException.objects.filter(
305
            Q(desk=desk) | Q(unavailability_calendar__desks=desk)
306
        )
307

  
308
    agenda = Agenda.objects.create(label='Foo bar', slug='bar')
309
    desk = Desk.objects.create(label='Desk 1', agenda=agenda)
310

  
311
    # morning
312
    TimePeriod.objects.create(
313
        desk=desk,
314
        date=datetime.date(2022, 10, 24),
315
        start_time=datetime.time(9, 0),
316
        end_time=datetime.time(12, 0),
317
    )
318
    set_prefetched_exceptions(desk)
319
    hours = desk.get_opening_hours(datetime.date(2022, 10, 24))
320
    assert len(hours) == 1
321
    assert hours[0].begin.time() == datetime.time(9, 0)
322
    assert hours[0].end.time() == datetime.time(12, 0)
323

  
324
    # and afternoon
325
    TimePeriod.objects.create(
326
        desk=desk,
327
        date=datetime.date(2022, 10, 24),
328
        start_time=datetime.time(14, 0),
329
        end_time=datetime.time(17, 0),
330
    )
331
    set_prefetched_exceptions(desk)
332
    previous_hours = hours
333
    hours = desk.get_opening_hours(datetime.date(2022, 10, 24))
334
    assert len(hours) == 2
335
    assert hours[0] == previous_hours[0]
336

  
337
    assert hours[1].begin.time() == datetime.time(14, 0)
338
    assert hours[1].end.time() == datetime.time(17, 0)
339

  
340
    # mix with repeating period
341
    TimePeriod.objects.create(
342
        desk=desk,
343
        weekday=0,
344
        start_time=datetime.time(19, 0),
345
        end_time=datetime.time(20, 0),
346
    )
347
    previous_hours = hours
348
    hours = desk.get_opening_hours(datetime.date(2022, 10, 24))
349
    assert len(hours) == 3
350
    assert hours[:2] == previous_hours[:2]
351

  
352
    assert hours[2].begin.time() == datetime.time(19, 0)
353
    assert hours[2].end.time() == datetime.time(20, 0)
354

  
355
    # full day exception
356
    TimePeriodException.objects.create(
357
        desk=desk,
358
        start_datetime=make_aware(datetime.datetime(2022, 10, 24)),
359
        end_datetime=make_aware(datetime.datetime(2022, 10, 25)),
360
    )
361

  
362
    set_prefetched_exceptions(desk)
363
    hours = desk.get_opening_hours(datetime.date(2022, 10, 24))
364
    assert len(hours) == 0
365

  
366
    # next week
367
    hours = desk.get_opening_hours(datetime.date(2022, 10, 31))
368
    assert len(hours) == 1
369
    assert hours[0].begin.time() == datetime.time(19, 0)
370
    assert hours[0].end.time() == datetime.time(20, 0)
371

  
372

  
302 373
def test_timeperiod_midnight_overlap_time_slots():
303 374
    # https://dev.entrouvert.org/issues/29142
304 375
    agenda = Agenda(label='Foo bar', slug='bar')
305
-