From db9160ea639743730ee94e3e291ba28cdca58f2d Mon Sep 17 00:00:00 2001 From: Agate Berriot Date: Tue, 9 Aug 2022 12:17:34 +0200 Subject: [PATCH 2/2] Fixed Django 3.2 related test failures (#68025) --- chrono/agendas/models.py | 45 ++++++++++++------- .../datetimes/test_events_multiple_agendas.py | 16 ++++--- tests/api/test_event.py | 2 +- tests/conftest.py | 20 +++++++++ tests/manager/test_all.py | 31 +++++++------ tests/manager/test_event.py | 14 +++--- tests/manager/test_exception.py | 6 +-- tests/manager/test_resource.py | 12 ++--- 8 files changed, 93 insertions(+), 53 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index c7435ad..a26935e 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -25,6 +25,7 @@ import sys import uuid from contextlib import contextmanager +import django import requests import vobject from dateutil.relativedelta import SU, relativedelta @@ -853,7 +854,7 @@ class Agenda(models.Model): guardian__user_external_id=guardian_external_id, agenda=agenda, ) - .annotate(day=Func(F('days'), function='unnest')) + .annotate(day=Func(F('days'), function='unnest', output_field=models.IntegerField())) .annotate(week_day=(F('day') + 1) % 7 + 1) # convert ISO day number to db lookup day number .values('week_day') ) @@ -1551,7 +1552,7 @@ class Event(models.Model): F('start_datetime') + datetime.timedelta(minutes=1) * F('duration'), output_field=models.DateTimeField(), ), - computed_slug=Concat('agenda__slug', Value('@'), 'slug'), + computed_slug=Concat('agenda__slug', Value('@'), 'slug', output_field=models.CharField()), ) overlapping_events = qs.filter( @@ -1576,7 +1577,7 @@ class Event(models.Model): output_field=models.DateTimeField(), ), end_hour=Cast('computed_end_datetime', models.TimeField()), - computed_slug=Concat('agenda__slug', Value('@'), 'slug'), + computed_slug=Concat('agenda__slug', Value('@'), 'slug', output_field=models.CharField()), ) overlapping_events = qs.filter( @@ -1585,19 +1586,27 @@ class Event(models.Model): recurrence_days__overlap=F('recurrence_days'), ).exclude(pk=OuterRef('pk')) - json_object = Func( - Value('slug'), - 'computed_slug', - Value('days'), - 'recurrence_days', - function='jsonb_build_object', - output_field=JSONField(), - ) # use django.db.models.functions.JSONObject in Django>=3.2 + if django.VERSION >= (3, 2): + from django.db.models.functions import JSONObject + + json_object = JSONObject( + slug=F('computed_slug'), + days=F('recurrence_days'), + ) + else: + json_object = Func( + Value('slug'), + 'computed_slug', + Value('days'), + 'recurrence_days', + function='jsonb_build_object', + output_field=JSONField(), + ) return qs.annotate( overlaps=ArraySubquery( overlapping_events.values(json=json_object), - output_field=ArrayField(models.CharField()), + output_field=ArrayField(JSONField()), ) ) @@ -3170,9 +3179,15 @@ class SharedCustodyAgenda(models.Model): def is_complete(self): day_counts = self.rules.aggregate( - all_week=Coalesce(SumCardinality('days', filter=Q(weeks='')), 0), - even_week=Coalesce(SumCardinality('days', filter=Q(weeks='even')), 0), - odd_week=Coalesce(SumCardinality('days', filter=Q(weeks='odd')), 0), + all_week=Coalesce( + SumCardinality('days', filter=Q(weeks='')), 0, output_field=models.IntegerField() + ), + even_week=Coalesce( + SumCardinality('days', filter=Q(weeks='even')), 0, output_field=models.IntegerField() + ), + odd_week=Coalesce( + SumCardinality('days', filter=Q(weeks='odd')), 0, output_field=models.IntegerField() + ), ) even_week_day_count = day_counts['all_week'] + day_counts['even_week'] odd_week_day_count = day_counts['all_week'] + day_counts['odd_week'] diff --git a/tests/api/datetimes/test_events_multiple_agendas.py b/tests/api/datetimes/test_events_multiple_agendas.py index c66f5dc..0e5a615 100644 --- a/tests/api/datetimes/test_events_multiple_agendas.py +++ b/tests/api/datetimes/test_events_multiple_agendas.py @@ -1567,20 +1567,22 @@ def test_datetimes_multiple_agendas_overlapping_events(app): ) resp = app.get('/api/agendas/datetimes/', params={'agendas': 'foo-bar,foo-bar-2', 'check_overlaps': True}) - assert [(x['id'], x['overlaps']) for x in resp.json['data']] == [ + + expected = [ ( 'foo-bar@event-containing-all-events', - ['foo-bar@event-12-14', 'foo-bar-2@event-13-15', 'foo-bar-2@event-14-16'], + {'foo-bar@event-12-14', 'foo-bar-2@event-13-15', 'foo-bar-2@event-14-16'}, ), - ('foo-bar@event-12-14', ['foo-bar@event-containing-all-events', 'foo-bar-2@event-13-15']), + ('foo-bar@event-12-14', {'foo-bar@event-containing-all-events', 'foo-bar-2@event-13-15'}), ( 'foo-bar-2@event-13-15', - ['foo-bar@event-containing-all-events', 'foo-bar@event-12-14', 'foo-bar-2@event-14-16'], + {'foo-bar@event-containing-all-events', 'foo-bar@event-12-14', 'foo-bar-2@event-14-16'}, ), - ('foo-bar-2@event-no-duration', []), - ('foo-bar-2@event-14-16', ['foo-bar@event-containing-all-events', 'foo-bar-2@event-13-15']), - ('foo-bar-2@event-other-day', []), + ('foo-bar-2@event-no-duration', set()), + ('foo-bar-2@event-14-16', {'foo-bar@event-containing-all-events', 'foo-bar-2@event-13-15'}), + ('foo-bar-2@event-other-day', set()), ] + assert [(x['id'], set(x['overlaps'])) for x in resp.json['data']] == expected resp = app.get('/api/agendas/datetimes/', params={'agendas': 'foo-bar,foo-bar-2'}) assert ['overlaps' not in x for x in resp.json['data']] diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 82e3a6a..f2ee722 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -965,7 +965,7 @@ def test_events(app, user): ] }, ) - assert len(ctx.captured_queries) == 6 + assert len(ctx.captured_queries) in (5, 6) assert [(d['agenda'], d['slug']) for d in resp.json['data']] == [ ('foo', 'recurring-event-slug--2022-07-01-1600'), ('bar', 'event-slug'), diff --git a/tests/conftest.py b/tests/conftest.py index 5caa6c0..5521e40 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import django import django_webtest import pytest @@ -23,3 +24,22 @@ def nocache(settings): 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } } + + +@pytest.fixture +def get_proper_html_str(): + """ + There are some subtle differences in the HTML generated by django 2 and 3 + making it harder to write tests compatible with both versions. + + XXX: remove when django 2 compat isn't necessary. + """ + + def inner(s): + if django.VERSION[0] == 2: + return s.replace(''', ''') + if django.VERSION[0] == 3: + return s.replace(''', ''') + return s + + return inner diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py index 4cc5e8f..895b42a 100644 --- a/tests/manager/test_all.py +++ b/tests/manager/test_all.py @@ -797,7 +797,7 @@ def test_add_meetings_agenda(app, admin_user): assert agenda.kind == 'meetings' -def test_agenda_day_view(app, admin_user, manager_user, api_user): +def test_agenda_day_view(app, admin_user, manager_user, api_user, get_proper_html_str): agenda = Agenda.objects.create(label='New Example', kind='meetings') desk = Desk.objects.create(agenda=agenda, label='New Desk') desk.save() @@ -847,7 +847,7 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user): assert resp.text.count('div class="booking') == 2 assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's" - assert 'foo - bar's' in resp + assert get_proper_html_str('foo - bar's') in resp assert 'hourspan-2' in resp.text # table CSS class assert 'height: 50%; top: 0%;' in resp.text # booking cells @@ -856,7 +856,7 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user): resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day)) assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == ' Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Foo Bar" - assert '<b>bar's</b> Foo Bar' in resp + assert get_proper_html_str('<b>bar's</b> Foo Bar') in resp # create a shorter meeting type, this will change the table CSS class # (and visually this will give more room for events) @@ -1351,7 +1351,7 @@ def test_agenda_open_events_view(app, admin_user, manager_user): app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=404) -def test_agenda_month_view(app, admin_user, manager_user, api_user): +def test_agenda_month_view(app, admin_user, manager_user, api_user, get_proper_html_str): agenda = Agenda.objects.create(label='Passeports', kind='meetings') desk = Desk.objects.create(agenda=agenda, label='Desk A') today = datetime.date.today() @@ -1406,7 +1406,7 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user): assert resp.text.count('
Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Foo Bar" - assert '<b>bar's</b> Foo Bar' in resp + assert get_proper_html_str('<b>bar's</b> Foo Bar') in resp desk = Desk.objects.create(agenda=agenda, label='Desk B') resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month)) @@ -1854,7 +1854,7 @@ def test_virtual_agenda_add(app, admin_user): assert agenda.maximal_booking_delay is None -def test_virtual_agenda_day_view(app, admin_user, manager_user): +def test_virtual_agenda_day_view(app, admin_user, manager_user, get_proper_html_str): agenda = Agenda.objects.create(label='Virtual', kind='virtual') real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings') real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings') @@ -1915,7 +1915,7 @@ def test_virtual_agenda_day_view(app, admin_user, manager_user): assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's" assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked' assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's" - assert 'foo - bar's' in resp + assert get_proper_html_str('foo - bar's') in resp assert 'hourspan-2' in resp.text # table CSS class assert 'height: 50%; top: 0%;' in resp.text # booking cells @@ -1928,7 +1928,7 @@ def test_virtual_agenda_day_view(app, admin_user, manager_user): assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Bar Foo" assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == ' Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "bar's Bar Foo" - assert '<b>bar's</b> Bar Foo' in resp + assert get_proper_html_str('<b>bar's</b> Bar Foo') in resp # create a shorter meeting type, this will change the table CSS class # (and visually this will give more room for events) @@ -1997,7 +1997,7 @@ def test_virtual_agenda_day_view(app, admin_user, manager_user): assert 'exceptions-hours' not in resp.text -def test_virtual_agenda_month_view(app, admin_user): +def test_virtual_agenda_month_view(app, admin_user, get_proper_html_str): agenda = Agenda.objects.create(label='Virtual', kind='virtual') real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings') real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings') @@ -2067,7 +2067,7 @@ def test_virtual_agenda_month_view(app, admin_user): assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's" assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked' assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's" - assert 'foo - bar's' in resp + assert get_proper_html_str('foo - bar's') in resp real_agenda_1.booking_user_block_template = '{{ booking.user_name }} Foo Bar' real_agenda_1.save() @@ -2078,7 +2078,7 @@ def test_virtual_agenda_month_view(app, admin_user): assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Bar Foo" assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == ' Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "bar's Bar Foo" - assert '<b>bar's</b> Bar Foo' in resp + assert get_proper_html_str('<b>bar's</b> Bar Foo') in resp # cancel a booking booking = Booking.objects.first() @@ -2388,7 +2388,7 @@ def test_cant_modify_meetingtype_used_by_virtual_agenda(app, admin_user): assert mt.label == 'MTT' -def test_cant_add_meetingtype_if_virtual_agenda(app, admin_user): +def test_cant_add_meetingtype_if_virtual_agenda(app, admin_user, get_proper_html_str): agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual') meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings') MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10) @@ -2412,7 +2412,10 @@ def test_cant_add_meetingtype_if_virtual_agenda(app, admin_user): resp.form['duration'].value = '12' resp.form['label'].value = 'Oho' resp = resp.form.submit() - assert 'Can't add a meetingtype to an agenda that is included in a virtual agenda.' in resp.text + assert ( + get_proper_html_str('Can't add a meetingtype to an agenda that is included in a virtual agenda.') + in resp.text + ) assert MeetingType.objects.filter(agenda=meeting_agenda_1).count() == 1 diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py index 3fd7d88..25c87cb 100644 --- a/tests/manager/test_event.py +++ b/tests/manager/test_event.py @@ -1136,7 +1136,7 @@ def test_import_events_wrong_kind(app, admin_user): @pytest.mark.freeze_time('2022-05-24') -def test_event_detail(app, admin_user): +def test_event_detail(app, admin_user, get_proper_html_str): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', @@ -1151,7 +1151,7 @@ def test_event_detail(app, admin_user): login(app) resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (1/10)' in resp.text - assert 'User's 1, May 24, 2022, 2 a.m.' in resp.text + assert get_proper_html_str('User's 1, May 24, 2022, 2 a.m.') in resp.text assert 'Waiting List (1/2): 1 remaining place' in resp.text assert 'User 2, May 24, 2022, 2 a.m.' in resp.text @@ -1159,7 +1159,7 @@ def test_event_detail(app, admin_user): agenda.save() resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (1/10)' in resp.text - assert '<b>User's 1</b> Foo Bar, May 24, 2022, 2 a.m.' in resp.text + assert get_proper_html_str('<b>User's 1</b> Foo Bar, May 24, 2022, 2 a.m.') in resp.text assert 'Waiting List (1/2): 1 remaining place' in resp.text assert '<b>User 2</b> Foo Bar, May 24, 2022, 2 a.m.' in resp.text @@ -1402,7 +1402,7 @@ def test_booking_cancellation_events_agenda(app, admin_user): assert secondary.cancellation_datetime -def test_event_check(app, admin_user): +def test_event_check(app, admin_user, get_proper_html_str): agenda = Agenda.objects.create(label='Events', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') agenda2 = Agenda.objects.create(label='Events', kind='events') @@ -1476,7 +1476,7 @@ def test_event_check(app, admin_user): resp = resp.click('Check') assert ( resp.text.index('Bookings (6/10)') - < resp.text.index("User's 01") + < resp.text.index(get_proper_html_str("User's 01")) < resp.text.index('User 05') < resp.text.index('User 17') < resp.text.index('User 35') @@ -1538,7 +1538,7 @@ def test_event_check(app, admin_user): resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert ( resp.text.index('Bookings (6/10)') - < resp.text.index("User's 01") + < resp.text.index(get_proper_html_str("User's 01")) < resp.text.index('User 05') < resp.text.index('User 12 Cancelled') < resp.text.index('Subscription 14') @@ -1559,7 +1559,7 @@ def test_event_check(app, admin_user): agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar' agenda.save() resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) - assert '<b>User's 01</b> Foo Bar' in resp + assert get_proper_html_str('<b>User's 01</b> Foo Bar') in resp assert '<b>Subscription 14</b> Foo Bar' in resp assert '<b>User Waiting</b> Foo Bar' in resp diff --git a/tests/manager/test_exception.py b/tests/manager/test_exception.py index 697be6a..616af59 100644 --- a/tests/manager/test_exception.py +++ b/tests/manager/test_exception.py @@ -562,7 +562,7 @@ def test_exception_list(app, admin_user): assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk not in resp.text -def test_agenda_import_time_period_exception_from_ics(app, admin_user): +def test_agenda_import_time_period_exception_from_ics(app, admin_user, get_proper_html_str): agenda = Agenda.objects.create(label='Example', kind='meetings') desk = Desk.objects.create(agenda=agenda, label='Test Desk') desk.duplicate() @@ -588,7 +588,7 @@ END:VEVENT END:VCALENDAR""" resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_start_date, 'text/calendar') resp = resp.form.submit(status=200) - assert 'Event "New Year's Eve" has no start date.' in resp.text + assert get_proper_html_str('Event "New Year's Eve" has no start date.') in resp.text assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0 ics_with_no_events = b"""BEGIN:VCALENDAR VERSION:2.0 @@ -596,7 +596,7 @@ PRODID:-//foo.bar//EN END:VCALENDAR""" resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_events, 'text/calendar') resp = resp.form.submit(status=200) - assert "The file doesn't contain any events." in resp.text + assert get_proper_html_str("The file doesn't contain any events.") in resp.text assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0 ics_with_exceptions = b"""BEGIN:VCALENDAR diff --git a/tests/manager/test_resource.py b/tests/manager/test_resource.py index feb02f5..c634815 100644 --- a/tests/manager/test_resource.py +++ b/tests/manager/test_resource.py @@ -66,7 +66,7 @@ def test_view_resource_as_manager(app, manager_user): app.get('/manage/resource/%s/' % resource.pk, status=403) -def test_resource_day_view(app, admin_user): +def test_resource_day_view(app, admin_user, get_proper_html_str): today = datetime.date.today() resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') @@ -113,7 +113,7 @@ def test_resource_day_view(app, admin_user): assert resp.text.count('div class="booking') == 2 assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's" - assert 'foo - bar's' in resp + assert get_proper_html_str('foo - bar's') in resp assert 'hourspan-2' in resp.text # table CSS class assert 'height: 50%; top: 0%;' in resp.text # booking cells assert 'height: 50%; top: 50%;' in resp.text # booking cells @@ -123,7 +123,7 @@ def test_resource_day_view(app, admin_user): resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == ' Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Foo Bar" - assert '<b>bar's</b> Foo Bar' in resp + assert get_proper_html_str('<b>bar's</b> Foo Bar') in resp # create a shorter meeting type, this will change the table CSS class # (and visually this will give more room for events) @@ -221,7 +221,7 @@ def test_day_view_resource_as_manager(app, manager_user): @freezegun.freeze_time('2020-06-15') -def test_resource_month_view(app, admin_user): +def test_resource_month_view(app, admin_user, get_proper_html_str): resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') agenda.resources.add(resource) @@ -265,14 +265,14 @@ def test_resource_month_view(app, admin_user): assert resp.text.count('
Foo Bar' assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "bar's Foo Bar" - assert '<b>bar's</b> Foo Bar' in resp + assert get_proper_html_str('<b>bar's</b> Foo Bar') in resp # cancel booking booking = Booking.objects.first() -- 2.36.1