From 87fed301a8e3defd679cac3a93ca99369edf318f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 9 Jun 2022 16:35:13 +0200
Subject: [PATCH 3/5] api: use check_types from lingo (#66015)

---
 chrono/api/serializers.py                     |  22 +-
 .../datetimes/test_events_multiple_agendas.py |   9 +-
 tests/api/test_booking.py                     | 200 ++++++------------
 3 files changed, 74 insertions(+), 157 deletions(-)

diff --git a/chrono/api/serializers.py b/chrono/api/serializers.py
index 8a47578e..3abefae9 100644
--- a/chrono/api/serializers.py
+++ b/chrono/api/serializers.py
@@ -13,7 +13,6 @@ from chrono.agendas.models import (
     Booking,
     BookingColor,
     Category,
-    CheckType,
     Event,
     EventsType,
     Person,
@@ -23,6 +22,7 @@ from chrono.agendas.models import (
     Subscription,
     TimePeriodExceptionGroup,
 )
+from chrono.utils.lingo import get_agenda_check_types
 
 
 def get_objects_from_slugs(slugs, qs):
@@ -201,19 +201,13 @@ class BookingSerializer(serializers.ModelSerializer):
             'presence': _('unknown presence reason'),
         }
 
-        if not self.instance.event.agenda.check_type_group:
-            raise serializers.ValidationError(error_messages[kind])
-
-        check_types_qs = self.instance.event.agenda.check_type_group.check_types.filter(
-            kind=kind, disabled=False
-        )
-        try:
-            return check_types_qs.get(slug=value)
-        except CheckType.DoesNotExist:
-            try:
-                return check_types_qs.get(label=value)
-            except (CheckType.DoesNotExist, CheckType.MultipleObjectsReturned):
-                raise serializers.ValidationError(error_messages[kind])
+        check_types = get_agenda_check_types(self.instance.event.agenda)
+        for check_type in check_types:
+            if check_type.kind != kind:
+                continue
+            if value in [check_type.slug, check_type.label]:
+                return check_type
+        raise serializers.ValidationError(error_messages[kind])
 
     def validate_user_absence_reason(self, value):
         return self._validate_check_type('absence', value)
diff --git a/tests/api/datetimes/test_events_multiple_agendas.py b/tests/api/datetimes/test_events_multiple_agendas.py
index bccf6fb3..2237b8a8 100644
--- a/tests/api/datetimes/test_events_multiple_agendas.py
+++ b/tests/api/datetimes/test_events_multiple_agendas.py
@@ -9,8 +9,6 @@ from chrono.agendas.models import (
     Agenda,
     Booking,
     Category,
-    CheckType,
-    CheckTypeGroup,
     Desk,
     Event,
     EventsType,
@@ -1200,9 +1198,6 @@ def test_datetimes_multiple_agendas_shared_custody_holiday_rules(app):
 
 
 def test_datetimes_multiple_agendas_with_status(app):
-    group = CheckTypeGroup.objects.create(label='Foo bar')
-    check_type_absence = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
-    check_type_presence = CheckType.objects.create(label='Foo reason', group=group, kind='presence')
     agenda = Agenda.objects.create(label='agenda', kind='events')
     Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
     event_booked = Event.objects.create(
@@ -1242,7 +1237,7 @@ def test_datetimes_multiple_agendas_with_status(app):
         event=event_absence_with_reason,
         user_external_id='xxx',
         user_was_present=False,
-        user_check_type_slug=check_type_absence.slug,
+        user_check_type_slug='foo-reason',
     )
     event_presence = Event.objects.create(
         slug='event-presence',
@@ -1261,7 +1256,7 @@ def test_datetimes_multiple_agendas_with_status(app):
         event=event_presence_with_reason,
         user_external_id='xxx',
         user_was_present=True,
-        user_check_type_slug=check_type_presence.slug,
+        user_check_type_slug='foo-reason',
     )
     event_booked_future = Event.objects.create(
         slug='event-booked-future',
diff --git a/tests/api/test_booking.py b/tests/api/test_booking.py
index c3d7f1dc..a96b9b5a 100644
--- a/tests/api/test_booking.py
+++ b/tests/api/test_booking.py
@@ -6,17 +6,8 @@ from django.db import connection
 from django.test.utils import CaptureQueriesContext
 from django.utils.timezone import make_aware, now
 
-from chrono.agendas.models import (
-    Agenda,
-    Booking,
-    BookingColor,
-    Category,
-    CheckType,
-    CheckTypeGroup,
-    Desk,
-    Event,
-    MeetingType,
-)
+from chrono.agendas.models import Agenda, Booking, BookingColor, Category, Desk, Event, MeetingType
+from chrono.utils.lingo import CheckType
 
 pytestmark = pytest.mark.django_db
 
@@ -318,9 +309,6 @@ def test_bookings_api_filter_user_was_present(app, user):
 
 
 def test_bookings_api_filter_user_absence_reason(app, user):
-    group = CheckTypeGroup.objects.create(label='Foo')
-    check_type_absence = CheckType.objects.create(group=group, label='Foo bar', kind='absence')
-    check_type_presence = CheckType.objects.create(group=group, label='Foo bar', kind='presence')
     agenda = Agenda.objects.create(label='Foo bar')
     event = Event.objects.create(
         agenda=agenda, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 0, 0)), places=10
@@ -330,48 +318,37 @@ def test_bookings_api_filter_user_absence_reason(app, user):
         event=event,
         user_external_id='42',
         user_was_present=False,
-        user_check_type_slug=check_type_absence.slug,
-        user_check_type_label=check_type_absence.label,
+        user_check_type_slug='foo-bar',
+        user_check_type_label='Foo bar',
     )
     Booking.objects.create(
         event=event,
         user_external_id='42',
         user_was_present=True,
-        user_check_type_slug=check_type_presence.slug,
-        user_check_type_label=check_type_presence.label,
+        user_check_type_slug='foo-bar-2',
+        user_check_type_label='Foo bar 2',
     )
 
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': check_type_absence.slug}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo-bar'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == [booking2.pk]
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': check_type_absence.label}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'Foo bar'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == [booking2.pk]
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': check_type_presence.slug}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo-bar-2'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
     Booking.objects.update(user_was_present=True)
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': check_type_absence.label}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'Foo bar 2'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
 
 
 def test_bookings_api_filter_user_presence_reason(app, user):
-    group = CheckTypeGroup.objects.create(label='Foo')
-    check_type_absence = CheckType.objects.create(group=group, label='Foo bar', kind='absence')
-    check_type_presence = CheckType.objects.create(group=group, label='Foo bar', kind='presence')
     agenda = Agenda.objects.create(label='Foo bar')
     event = Event.objects.create(
         agenda=agenda, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 0, 0)), places=10
@@ -381,40 +358,32 @@ def test_bookings_api_filter_user_presence_reason(app, user):
         event=event,
         user_external_id='42',
         user_was_present=True,
-        user_check_type_slug=check_type_presence.slug,
-        user_check_type_label=check_type_presence.label,
+        user_check_type_slug='foo-bar',
+        user_check_type_label='Foo bar',
     )
     Booking.objects.create(
         event=event,
         user_external_id='42',
         user_was_present=False,
-        user_check_type_slug=check_type_absence.slug,
-        user_check_type_label=check_type_absence.label,
+        user_check_type_slug='foo-bar-2',
+        user_check_type_label='Foo bar 2',
     )
 
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': check_type_presence.slug}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo-bar'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == [booking2.pk]
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': check_type_presence.label}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'Foo bar'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == [booking2.pk]
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': check_type_absence.slug}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo-bar-2'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
     Booking.objects.update(user_was_present=False)
-    resp = app.get(
-        '/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': check_type_presence.label}
-    )
+    resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'Foo bar 2'})
     assert resp.json['err'] == 0
     assert [b['id'] for b in resp.json['data']] == []
 
@@ -487,13 +456,9 @@ def test_bookings_api_filter_event(app, user):
 
 @pytest.mark.parametrize('flag', [True, False, None])
 def test_booking_api_present(app, user, flag):
-    group = CheckTypeGroup.objects.create(label='Foo')
-    check_type = CheckType.objects.create(
-        group=group, label='Foo bar', kind='presence' if flag else 'absence'
-    )
     agenda = Agenda.objects.create(label='Foo bar', kind='events')
     event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
-    booking = Booking.objects.create(event=event, user_was_present=flag, user_check_type_slug=check_type.slug)
+    booking = Booking.objects.create(event=event, user_was_present=flag, user_check_type_slug='foo-bar')
 
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.get('/api/booking/%s/' % booking.pk)
@@ -628,7 +593,9 @@ def test_booking_patch_api_present(app, user, flag):
     assert resp.json['err'] == 0
 
 
-def test_booking_patch_api_absence_reason(app, user):
+@mock.patch('chrono.api.serializers.get_agenda_check_types')
+def test_booking_patch_api_absence_reason(check_types, app, user):
+    check_types.return_value = []
     agenda = Agenda.objects.create(kind='events')
     event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
     booking = Booking.objects.create(event=event, user_was_present=False)
@@ -642,44 +609,27 @@ def test_booking_patch_api_absence_reason(app, user):
     assert resp.json['err'] == 4
     assert resp.json['err_desc'] == 'invalid payload'
 
-    group = CheckTypeGroup.objects.create(label='Foo')
-    check_type_absence = CheckType.objects.create(group=group, label='Foo bar', kind='absence')
-    check_type_presence = CheckType.objects.create(group=group, label='Foo baz', kind='presence')
-    check_type_absence_disabled = CheckType.objects.create(
-        label='disabled', group=group, kind='absence', disabled=True
-    )
-
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'}, status=400
     )
     assert resp.json['err'] == 4
     assert resp.json['err_desc'] == 'invalid payload'
 
-    # wrong kind
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'Foo baz'}, status=400
-    )
-    assert resp.json['err'] == 4
-    assert resp.json['err_desc'] == 'invalid payload'
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_presence.slug}, status=400
-    )
-    assert resp.json['err'] == 4
-    assert resp.json['err_desc'] == 'invalid payload'
-
     # set check_type
-    agenda.check_type_group = group
-    agenda.save()
+    check_types.return_value = [
+        CheckType(slug='foo-bar', label='Foo bar', kind='absence'),
+        CheckType(slug='foo-baz', label='Foo baz', kind='presence'),
+    ]
     # it works with label
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'Foo bar'})
     booking.refresh_from_db()
-    assert booking.user_check_type_slug == check_type_absence.slug
-    assert booking.user_check_type_label == check_type_absence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
 
-    # disabled
+    # unknown
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk,
-        params={'user_absence_reason': check_type_absence_disabled.slug},
+        params={'user_presence_reason': 'unknown'},
         status=400,
     )
     assert resp.json['err'] == 4
@@ -702,14 +652,14 @@ def test_booking_patch_api_absence_reason(app, user):
     other_booking = Booking.objects.create(event=event)
 
     # it works also with slug
-    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_absence.slug})
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foo-bar'})
     booking.refresh_from_db()
-    assert booking.user_check_type_slug == check_type_absence.slug
-    assert booking.user_check_type_label == check_type_absence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
     # all secondary bookings are updated
     assert list(booking.secondary_booking_set.values_list('user_check_type_slug', flat=True)) == [
-        check_type_absence.slug,
-        check_type_absence.slug,
+        'foo-bar',
+        'foo-bar',
     ]
     other_booking.refresh_from_db()
     assert other_booking.user_check_type_slug is None  # not changed
@@ -718,7 +668,7 @@ def test_booking_patch_api_absence_reason(app, user):
     # user_was_present is True, can not set user_absence_reason
     Booking.objects.update(user_was_present=True)
     resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_absence.slug}, status=400
+        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foo-bar'}, status=400
     )
     assert resp.json['err'] == 6
     assert resp.json['err_desc'] == 'user is marked as present, can not set absence reason'
@@ -726,27 +676,25 @@ def test_booking_patch_api_absence_reason(app, user):
     # but it's ok if user_was_present is set to False
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk,
-        params={'user_absence_reason': check_type_absence.slug, 'user_was_present': False},
+        params={'user_absence_reason': 'foo-bar', 'user_was_present': False},
     )
     assert resp.json['err'] == 0
     booking.refresh_from_db()
     assert booking.user_was_present is False
-    assert booking.user_check_type_slug == check_type_absence.slug
-    assert booking.user_check_type_label == check_type_absence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
 
     # mark the event as checked
     event.checked = True
     event.save()
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_absence.slug}
-    )
+    resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foo-bar'})
     assert resp.json['err'] == 0
 
     # now disable check update
     agenda.disable_check_update = True
     agenda.save()
     resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_absence.slug}, status=400
+        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foo-bar'}, status=400
     )
     assert resp.json['err'] == 5
     assert resp.json['err_desc'] == 'event is marked as checked'
@@ -754,7 +702,9 @@ def test_booking_patch_api_absence_reason(app, user):
     assert resp.json['err'] == 0
 
 
-def test_booking_patch_api_presence_reason(app, user):
+@mock.patch('chrono.api.serializers.get_agenda_check_types')
+def test_booking_patch_api_presence_reason(check_types, app, user):
+    check_types.return_value = []
     agenda = Agenda.objects.create(kind='events')
     event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
     booking = Booking.objects.create(event=event, user_was_present=True)
@@ -768,44 +718,27 @@ def test_booking_patch_api_presence_reason(app, user):
     assert resp.json['err'] == 4
     assert resp.json['err_desc'] == 'invalid payload'
 
-    group = CheckTypeGroup.objects.create(label='Foo')
-    check_type_presence = CheckType.objects.create(group=group, label='Foo bar', kind='presence')
-    check_type_absence = CheckType.objects.create(group=group, label='Foo baz', kind='absence')
-    check_type_presence_disabled = CheckType.objects.create(
-        label='disabled', group=group, kind='presence', disabled=True
-    )
-
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foobar'}, status=400
     )
     assert resp.json['err'] == 4
     assert resp.json['err_desc'] == 'invalid payload'
 
-    # wrong kind
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'Foo baz'}, status=400
-    )
-    assert resp.json['err'] == 4
-    assert resp.json['err_desc'] == 'invalid payload'
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_absence.slug}, status=400
-    )
-    assert resp.json['err'] == 4
-    assert resp.json['err_desc'] == 'invalid payload'
-
     # set check_type
-    agenda.check_type_group = group
-    agenda.save()
+    check_types.return_value = [
+        CheckType(slug='foo-bar', label='Foo bar', kind='presence'),
+        CheckType(slug='foo-baz', label='Foo baz', kind='absence'),
+    ]
     # it works with label
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'Foo bar'})
     booking.refresh_from_db()
-    assert booking.user_check_type_slug == check_type_presence.slug
-    assert booking.user_check_type_label == check_type_presence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
 
-    # disabled
+    # unknown
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk,
-        params={'user_presence_reason': check_type_presence_disabled.slug},
+        params={'user_presence_reason': 'unknown'},
         status=400,
     )
     assert resp.json['err'] == 4
@@ -828,14 +761,14 @@ def test_booking_patch_api_presence_reason(app, user):
     other_booking = Booking.objects.create(event=event)
 
     # it works also with slug
-    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug})
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foo-bar'})
     booking.refresh_from_db()
-    assert booking.user_check_type_slug == check_type_presence.slug
-    assert booking.user_check_type_label == check_type_presence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
     # all secondary bookings are updated
     assert list(booking.secondary_booking_set.values_list('user_check_type_slug', flat=True)) == [
-        check_type_presence.slug,
-        check_type_presence.slug,
+        'foo-bar',
+        'foo-bar',
     ]
     other_booking.refresh_from_db()
     assert other_booking.user_check_type_slug is None  # not changed
@@ -844,7 +777,7 @@ def test_booking_patch_api_presence_reason(app, user):
     # user_was_present is False, can not set user_presence_reason
     Booking.objects.update(user_was_present=False)
     resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}, status=400
+        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foo-bar'}, status=400
     )
     assert resp.json['err'] == 6
     assert resp.json['err_desc'] == 'user is marked as absent, can not set presence reason'
@@ -852,27 +785,25 @@ def test_booking_patch_api_presence_reason(app, user):
     # but it's ok if user_was_present is set to True
     resp = app.patch_json(
         '/api/booking/%s/' % booking.pk,
-        params={'user_presence_reason': check_type_presence.slug, 'user_was_present': True},
+        params={'user_presence_reason': 'foo-bar', 'user_was_present': True},
     )
     assert resp.json['err'] == 0
     booking.refresh_from_db()
     assert booking.user_was_present is True
-    assert booking.user_check_type_slug == check_type_presence.slug
-    assert booking.user_check_type_label == check_type_presence.label
+    assert booking.user_check_type_slug == 'foo-bar'
+    assert booking.user_check_type_label == 'Foo bar'
 
     # mark the event as checked
     event.checked = True
     event.save()
-    resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}
-    )
+    resp = app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foo-bar'})
     assert resp.json['err'] == 0
 
     # now disable check update
     agenda.disable_check_update = True
     agenda.save()
     resp = app.patch_json(
-        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}, status=400
+        '/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foo-bar'}, status=400
     )
     assert resp.json['err'] == 5
     assert resp.json['err_desc'] == 'event is marked as checked'
@@ -881,10 +812,7 @@ def test_booking_patch_api_presence_reason(app, user):
 
 
 def test_booking_patch_api_both_reasons(app, user):
-    group = CheckTypeGroup.objects.create(label='Foo')
-    CheckType.objects.create(group=group, label='Foo bar', kind='presence')
-    CheckType.objects.create(group=group, label='Foo baz', kind='absence')
-    agenda = Agenda.objects.create(kind='events', check_type_group=group)
+    agenda = Agenda.objects.create(kind='events')
     event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
     booking = Booking.objects.create(event=event)
 
-- 
2.35.1

