fix Django 3.2 related test failures (#68025)

This commit is contained in:
Agate 2022-08-09 12:17:34 +02:00
parent 7c4ea5104a
commit 963871ce43
8 changed files with 95 additions and 53 deletions

View File

@ -25,6 +25,7 @@ import sys
import uuid
from contextlib import contextmanager
import django
import requests
import vobject
from dateutil.relativedelta import SU, relativedelta
@ -858,7 +859,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')
)
@ -1556,7 +1557,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(
@ -1581,7 +1582,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(
@ -1590,19 +1591,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()),
)
)
@ -3175,9 +3184,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']

View File

@ -1596,20 +1596,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']]

View File

@ -965,7 +965,9 @@ def test_events(app, user):
]
},
)
assert len(ctx.captured_queries) == 6
# there's one less query on django 3.2, the ORM has probably
# been optimized there
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'),

View File

@ -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

View File

@ -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() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; 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('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
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 book - bar's"
assert 'foo book - bar&#39;s' in resp
assert get_proper_html_str('foo book - bar&#39;s') in resp
assert len(resp.pyquery.find('span.desk')) == 0
agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
@ -1414,7 +1414,7 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user):
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; 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&#39;s' in resp
assert get_proper_html_str('foo - bar&#39;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() == "<b>bar's</b> Bar Foo"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Bar Foo' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; 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&#39;s' in resp
assert get_proper_html_str('foo - bar&#39;s') in resp
real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> 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() == "<b>bar's</b> Bar Foo"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Bar Foo' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; 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&#39;t add a meetingtype to an agenda that is included in a virtual agenda.' in resp.text
assert (
get_proper_html_str('Can&#39;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

View File

@ -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&#39;s 1, May 24, 2022, 2 a.m.' in resp.text
assert get_proper_html_str('User&#39;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 '&lt;b&gt;User&#39;s 1&lt;/b&gt; Foo Bar, May 24, 2022, 2 a.m.' in resp.text
assert get_proper_html_str('&lt;b&gt;User&#39;s 1&lt;/b&gt; Foo Bar, May 24, 2022, 2 a.m.') in resp.text
assert 'Waiting List (1/2): 1 remaining place' in resp.text
assert '&lt;b&gt;User 2&lt;/b&gt; 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&#39;s 01")
< resp.text.index(get_proper_html_str("User&#39;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&#39;s 01")
< resp.text.index(get_proper_html_str("User&#39;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 = '<b>{{ booking.user_name }}</b> Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert '&lt;b&gt;User&#39;s 01&lt;/b&gt; Foo Bar' in resp
assert get_proper_html_str('&lt;b&gt;User&#39;s 01&lt;/b&gt; Foo Bar') in resp
assert '&lt;b&gt;Subscription 14&lt;/b&gt; Foo Bar' in resp
assert '&lt;b&gt;User Waiting&lt;/b&gt; Foo Bar' in resp

View File

@ -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 &quot;New Year&#39;s Eve&quot; has no start date.' in resp.text
assert get_proper_html_str('Event &quot;New Year&#39;s Eve&quot; 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&#39;t contain any events." in resp.text
assert get_proper_html_str("The file doesn&#39;t contain any events.") in resp.text
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
ics_with_exceptions = b"""BEGIN:VCALENDAR

View File

@ -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&#39;s' in resp
assert get_proper_html_str('foo - bar&#39;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() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; 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('<div class="booking" style="height:33.0%;') == 2 # booking cells
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&#39;s' in resp
assert get_proper_html_str('foo - bar&#39;s') in resp
agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
agenda.save()
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar' in resp
assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar') in resp
# cancel booking
booking = Booking.objects.first()