From caa2ccb124aaae9522ce6a54e35aa3a77e629ad4 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 14:53:13 +0200
Subject: [PATCH 2/5] agendas: Booking.user_check_type becomes 2 fields (slug,
 label) (#66015)

---
 .../migrations/0128_remove_check_type.py      |  21 ++++
 .../migrations/0129_remove_check_type.py      |  46 ++++++++
 .../migrations/0130_remove_check_type.py      |  15 +++
 chrono/agendas/models.py                      |  21 ++--
 chrono/api/serializers.py                     |  17 ++-
 chrono/api/views.py                           |   9 +-
 chrono/manager/forms.py                       |  18 ++--
 .../manager_event_check_booking_fragment.html |   4 +-
 chrono/manager/views.py                       |  70 ++++++------
 .../datetimes/test_events_multiple_agendas.py |   4 +-
 tests/api/test_booking.py                     |  74 +++++++++----
 tests/manager/test_check_type.py              |  17 +--
 tests/manager/test_event.py                   | 101 +++++++++++-------
 13 files changed, 277 insertions(+), 140 deletions(-)
 create mode 100644 chrono/agendas/migrations/0128_remove_check_type.py
 create mode 100644 chrono/agendas/migrations/0129_remove_check_type.py
 create mode 100644 chrono/agendas/migrations/0130_remove_check_type.py

diff --git a/chrono/agendas/migrations/0128_remove_check_type.py b/chrono/agendas/migrations/0128_remove_check_type.py
new file mode 100644
index 00000000..24e49d88
--- /dev/null
+++ b/chrono/agendas/migrations/0128_remove_check_type.py
@@ -0,0 +1,21 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0127_remove_pricing_models'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='booking',
+            name='user_check_type_label',
+            field=models.CharField(blank=True, null=True, max_length=150),
+        ),
+        migrations.AddField(
+            model_name='booking',
+            name='user_check_type_slug',
+            field=models.CharField(blank=True, null=True, max_length=160),
+        ),
+    ]
diff --git a/chrono/agendas/migrations/0129_remove_check_type.py b/chrono/agendas/migrations/0129_remove_check_type.py
new file mode 100644
index 00000000..a08872e3
--- /dev/null
+++ b/chrono/agendas/migrations/0129_remove_check_type.py
@@ -0,0 +1,46 @@
+from collections import defaultdict
+
+from django.db import migrations
+
+
+def forwards(apps, schema_editor):
+    Booking = apps.get_model('agendas', 'Booking')
+    for booking in Booking.objects.filter(user_check_type__isnull=False):
+        booking.user_check_type_slug = booking.user_check_type.slug
+        booking.user_check_type_label = booking.user_check_type.label
+        booking.save()
+
+
+def backwards(apps, schema_editor):
+    Booking = apps.get_model('agendas', 'Booking')
+    Agenda = apps.get_model('agendas', 'Agenda')
+    check_types_by_agenda_id = defaultdict(list)
+    for agenda in Agenda.objects.filter(kind='events'):
+        if not agenda.check_type_group:
+            continue
+        for check_type in agenda.check_type_group.check_types.all():
+            check_types_by_agenda_id[agenda.pk].append(check_type)
+    for booking in Booking.objects.filter(user_check_type_slug__isnull=False, user_was_present__isnull=False):
+        if booking.event.agenda_id not in check_types_by_agenda_id:
+            # no check_types for this agenda
+            continue
+        for check_type in check_types_by_agenda_id[booking.event.agenda_id]:
+            if check_type.kind == 'absence' and booking.user_was_present is True:
+                continue
+            if check_type.kind == 'presence' and booking.user_was_present is False:
+                continue
+            if check_type.slug == booking.user_check_type_slug:
+                booking.user_check_type = check_type
+                booking.save()
+                break
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0128_remove_check_type'),
+    ]
+
+    operations = [
+        migrations.RunPython(forwards, reverse_code=backwards),
+    ]
diff --git a/chrono/agendas/migrations/0130_remove_check_type.py b/chrono/agendas/migrations/0130_remove_check_type.py
new file mode 100644
index 00000000..b3f9037d
--- /dev/null
+++ b/chrono/agendas/migrations/0130_remove_check_type.py
@@ -0,0 +1,15 @@
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0129_remove_check_type'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='booking',
+            name='user_check_type',
+        ),
+    ]
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 53f1b008..3391d108 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -1943,7 +1943,8 @@ class Booking(models.Model):
     user_email = models.EmailField(blank=True)
     user_phone_number = models.CharField(max_length=16, blank=True)
     user_was_present = models.NullBooleanField()
-    user_check_type = models.ForeignKey('agendas.CheckType', on_delete=models.PROTECT, blank=True, null=True)
+    user_check_type_slug = models.CharField(max_length=160, blank=True, null=True)
+    user_check_type_label = models.CharField(max_length=150, blank=True, null=True)
     out_of_min_delay = models.BooleanField(default=False)
 
     extra_emails = ArrayField(models.EmailField(), default=list)
@@ -1994,22 +1995,24 @@ class Booking(models.Model):
             self.secondary_booking_set.update(in_waiting_list=True)
             self.save()
 
-    def mark_user_absence(self, check_type=None):
-        check_type = check_type or None
-        self.user_check_type = check_type
+    def mark_user_absence(self, check_type_slug=None, check_type_label=None):
+        self.user_check_type_slug = check_type_slug
+        self.user_check_type_label = check_type_label
         self.user_was_present = False
         with transaction.atomic():
-            self.secondary_booking_set.update(user_check_type=check_type)
+            self.secondary_booking_set.update(user_check_type_slug=check_type_slug)
+            self.secondary_booking_set.update(user_check_type_label=check_type_label)
             self.secondary_booking_set.update(user_was_present=False)
             self.save()
             self.event.set_is_checked()
 
-    def mark_user_presence(self, check_type=None):
-        check_type = check_type or None
-        self.user_check_type = check_type
+    def mark_user_presence(self, check_type_slug=None, check_type_label=None):
+        self.user_check_type_slug = check_type_slug
+        self.user_check_type_label = check_type_label
         self.user_was_present = True
         with transaction.atomic():
-            self.secondary_booking_set.update(user_check_type=check_type)
+            self.secondary_booking_set.update(user_check_type_slug=check_type_slug)
+            self.secondary_booking_set.update(user_check_type_label=check_type_label)
             self.secondary_booking_set.update(user_was_present=True)
             self.save()
             self.event.set_is_checked()
diff --git a/chrono/api/serializers.py b/chrono/api/serializers.py
index f6241357..8a47578e 100644
--- a/chrono/api/serializers.py
+++ b/chrono/api/serializers.py
@@ -184,9 +184,12 @@ class BookingSerializer(serializers.ModelSerializer):
 
     def to_representation(self, instance):
         ret = super().to_representation(instance)
-        check_type_slug = self.instance.user_check_type.slug if self.instance.user_check_type else ''
-        ret['user_absence_reason'] = check_type_slug if self.instance.user_was_present is False else ''
-        ret['user_presence_reason'] = check_type_slug if self.instance.user_was_present is True else ''
+        ret['user_absence_reason'] = (
+            self.instance.user_check_type_slug if self.instance.user_was_present is False else ''
+        )
+        ret['user_presence_reason'] = (
+            self.instance.user_check_type_slug if self.instance.user_was_present is True else ''
+        )
         return ret
 
     def _validate_check_type(self, kind, value):
@@ -225,10 +228,14 @@ class BookingSerializer(serializers.ModelSerializer):
                 {'user_absence_reason': _('can not set user_absence_reason and user_presence_reason')}
             )
         if 'user_absence_reason' in attrs:
-            attrs['user_check_type'] = attrs['user_absence_reason']
+            check_type = attrs['user_absence_reason']
+            attrs['user_check_type_slug'] = check_type.slug if check_type else None
+            attrs['user_check_type_label'] = check_type.label if check_type else None
             del attrs['user_absence_reason']
         elif 'user_presence_reason' in attrs:
-            attrs['user_check_type'] = attrs['user_presence_reason']
+            check_type = attrs['user_presence_reason']
+            attrs['user_check_type_slug'] = check_type.slug if check_type else None
+            attrs['user_check_type_label'] = check_type.label if check_type else None
             del attrs['user_presence_reason']
         return attrs
 
diff --git a/chrono/api/views.py b/chrono/api/views.py
index f20b9581..1657fa3d 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -2296,15 +2296,13 @@ class BookingFilter(filters.FilterSet):
 
     def filter_user_absence_reason(self, queryset, name, value):
         return queryset.filter(
-            Q(user_check_type__slug=value) | Q(user_check_type__label=value),
-            user_check_type__kind='absence',
+            Q(user_check_type_slug=value) | Q(user_check_type_label=value),
             user_was_present=False,
         )
 
     def filter_user_presence_reason(self, queryset, name, value):
         return queryset.filter(
-            Q(user_check_type__slug=value) | Q(user_check_type__label=value),
-            user_check_type__kind='presence',
+            Q(user_check_type_slug=value) | Q(user_check_type_label=value),
             user_was_present=True,
         )
 
@@ -2432,7 +2430,8 @@ class BookingAPI(APIView):
             if key in request.data:
                 secondary_bookings_update[key] = getattr(self.booking, key)
         if 'user_absence_reason' in request.data or 'user_presence_reason' in request.data:
-            secondary_bookings_update['user_check_type'] = self.booking.user_check_type
+            secondary_bookings_update['user_check_type_slug'] = self.booking.user_check_type_slug
+            secondary_bookings_update['user_check_type_label'] = self.booking.user_check_type_label
         if extra_data:
             secondary_bookings_update['extra_data'] = self.booking.extra_data
         if secondary_bookings_update:
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 25068a8b..988f0fde 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -445,14 +445,14 @@ class BookingCheckFilterSet(django_filters.FilterSet):
         ]
         if self.agenda.check_type_group:
             status_choices += [
-                ('presence-%s' % r.pk, _('Presence (%s)') % r.label)
+                ('presence::%s' % r.slug, _('Presence (%s)') % r.label)
                 for r in self.agenda.check_type_group.check_types.all()
                 if r.kind == 'presence' and not r.disabled
             ]
         status_choices += [('absence', _('Absence'))]
         if self.agenda.check_type_group:
             status_choices += [
-                ('absence-%s' % r.pk, _('Absence (%s)') % r.label)
+                ('absence::%s' % r.slug, _('Absence (%s)') % r.label)
                 for r in self.agenda.check_type_group.check_types.all()
                 if r.kind == 'absence' and not r.disabled
             ]
@@ -479,10 +479,10 @@ class BookingCheckFilterSet(django_filters.FilterSet):
             return queryset.filter(user_was_present=True)
         if value == 'absence':
             return queryset.filter(user_was_present=False)
-        if value.startswith('absence-'):
-            return queryset.filter(user_was_present=False, user_check_type=value.split('-')[1])
-        if value.startswith('presence-'):
-            return queryset.filter(user_was_present=True, user_check_type=value.split('-')[1])
+        if value.startswith('absence::'):
+            return queryset.filter(user_was_present=False, user_check_type_slug=value.split('::')[1])
+        if value.startswith('presence::'):
+            return queryset.filter(user_was_present=True, user_check_type_slug=value.split('::')[1])
         return queryset
 
     def do_nothing(self, queryset, name, value):
@@ -508,7 +508,7 @@ class BookingCheckAbsenceForm(forms.Form):
         super().__init__(*args, **kwargs)
         if agenda.check_type_group:
             self.fields['check_type'].choices = [('', '---------')] + [
-                (r.pk, r.label)
+                (r.slug, r.label)
                 for r in agenda.check_type_group.check_types.all()
                 if r.kind == 'absence' and not r.disabled
             ]
@@ -522,7 +522,7 @@ class BookingCheckPresenceForm(forms.Form):
         super().__init__(*args, **kwargs)
         if agenda.check_type_group:
             self.fields['check_type'].choices = [('', '---------')] + [
-                (r.pk, r.label)
+                (r.slug, r.label)
                 for r in agenda.check_type_group.check_types.all()
                 if r.kind == 'presence' and not r.disabled
             ]
@@ -1304,7 +1304,7 @@ class AgendaBookingCheckSettingsForm(forms.ModelForm):
         super().__init__(*args, **kwargs)
         if not CheckTypeGroup.objects.exists():
             del self.fields['check_type_group']
-        elif Booking.objects.filter(event__agenda=self.instance, user_check_type__isnull=False).exists():
+        elif Booking.objects.filter(event__agenda=self.instance, user_check_type_slug__isnull=False).exists():
             # not possible to update check_type_group if bookings with non null check_type exist
             del self.fields['check_type_group']
 
diff --git a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
index b973db57..0b75a49d 100644
--- a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
+++ b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html
@@ -3,8 +3,8 @@
 <td class="booking-username main-list">{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}</td>
 <td class="booking-status {% if booking.user_was_present is None %}without-status{% endif %}">
   {{ booking.user_was_present|yesno:_('Present,Absent,-') }}
-  {% if booking.user_was_present is not None and booking.user_check_type %}
-    ({{ booking.user_check_type }})
+  {% if booking.user_was_present is not None and booking.user_check_type_label %}
+    ({{ booking.user_check_type_label }})
   {% endif %}
 </td>
 {% if not event.checked or not agenda.disable_check_update %}
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index e4ef7518..fee5693b 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -714,19 +714,6 @@ class CheckTypeGroupDeleteView(DeleteView):
             raise PermissionDenied()
         return super().dispatch(request, *args, **kwargs)
 
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context['cannot_delete'] = Booking.objects.filter(user_check_type__group=self.get_object()).exists()
-        context['cannot_delete_msg'] = _(
-            'Can not delete this check type group: a check type of this group is set on some existing bookings.'
-        )
-        return context
-
-    def delete(self, request, *args, **kwargs):
-        if Booking.objects.filter(user_check_type__group=self.get_object()).exists():
-            raise Http404
-        return super().delete(request, *args, **kwargs)
-
     def get_success_url(self):
         return reverse('chrono-manager-check-type-list')
 
@@ -797,7 +784,7 @@ class CheckTypeEditView(UpdateView):
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context['is_used'] = Booking.objects.filter(user_check_type=self.get_object()).exists()
+        context['is_used'] = Booking.objects.filter(user_check_type_slug=self.get_object().slug).exists()
         return context
 
     def get_success_url(self):
@@ -822,14 +809,16 @@ class CheckTypeDeleteView(DeleteView):
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context['cannot_delete'] = Booking.objects.filter(user_check_type=self.get_object()).exists()
+        context['cannot_delete'] = Booking.objects.filter(
+            user_check_type_slug=self.get_object().slug
+        ).exists()
         context['cannot_delete_msg'] = _(
             'Can not delete this check type: it is set on some existing bookings.'
         )
         return context
 
     def delete(self, request, *args, **kwargs):
-        if Booking.objects.filter(user_check_type=self.get_object()).exists():
+        if Booking.objects.filter(user_check_type_slug=self.get_object().slug).exists():
             raise Http404
         return super().delete(request, *args, **kwargs)
 
@@ -2345,7 +2334,7 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
         booking_qs_kwargs = {}
         if not self.agenda.subscriptions.exists():
             booking_qs_kwargs = {'cancellation_datetime__isnull': True}
-        booking_qs = event.booking_set.prefetch_related('user_check_type')
+        booking_qs = event.booking_set
         booked_qs = booking_qs.filter(
             in_waiting_list=False, primary_booking__isnull=True, **booking_qs_kwargs
         )
@@ -2391,11 +2380,11 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
                 booked_without_status = True
             booking.absence_form = BookingCheckAbsenceForm(
                 agenda=self.agenda,
-                initial={'check_type': booking.user_check_type_id},
+                initial={'check_type': booking.user_check_type_slug},
             )
             booking.presence_form = BookingCheckPresenceForm(
                 agenda=self.agenda,
-                initial={'check_type': booking.user_check_type_id},
+                initial={'check_type': booking.user_check_type_slug},
             )
             booking.kind = 'booking'
             results.append(booking)
@@ -2453,6 +2442,15 @@ class EventCheckMixin:
             user_was_present__isnull=True,
         )
 
+    def get_check_type(self, kind):
+        form = self.get_form()
+        check_type = None
+        if form.is_valid() and form.cleaned_data['check_type']:
+            check_type = CheckType.objects.filter(
+                slug=form.cleaned_data['check_type'], kind=kind, group=self.agenda.check_type_group
+            ).first()
+        return check_type
+
     def response(self, request):
         return HttpResponseRedirect(
             reverse(
@@ -2471,10 +2469,10 @@ class EventPresenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
         return kwargs
 
     def post(self, request, *args, **kwargs):
-        form = self.get_form()
         qs_kwargs = {}
-        if form.is_valid():
-            qs_kwargs['user_check_type'] = form.cleaned_data['check_type']
+        check_type = self.get_check_type(kind='presence')
+        qs_kwargs['user_check_type_slug'] = check_type.slug if check_type else None
+        qs_kwargs['user_check_type_label'] = check_type.label if check_type else None
         bookings = self.get_bookings()
         bookings.update(user_was_present=True, **qs_kwargs)
         self.event.set_is_checked()
@@ -2493,10 +2491,10 @@ class EventAbsenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
         return kwargs
 
     def post(self, request, *args, **kwargs):
-        form = self.get_form()
         qs_kwargs = {}
-        if form.is_valid():
-            qs_kwargs['user_check_type'] = form.cleaned_data['check_type']
+        check_type = self.get_check_type(kind='absence')
+        qs_kwargs['user_check_type_slug'] = check_type.slug if check_type else None
+        qs_kwargs['user_check_type_label'] = check_type.label if check_type else None
         bookings = self.get_bookings()
         bookings.update(user_was_present=False, **qs_kwargs)
         self.event.set_is_checked()
@@ -3190,20 +3188,22 @@ class BookingCheckMixin:
             primary_booking__isnull=True,
         )
 
-    def get_check_type(self):
+    def get_check_type(self, kind):
         form = self.get_form()
         check_type = None
         if form.is_valid() and form.cleaned_data['check_type']:
-            check_type = CheckType.objects.get(pk=form.cleaned_data['check_type'])
+            check_type = CheckType.objects.filter(
+                slug=form.cleaned_data['check_type'], kind=kind, group=self.agenda.check_type_group
+            ).first()
         return check_type
 
     def response(self, request, booking):
         if request.is_ajax():
             booking.absence_form = BookingCheckAbsenceForm(
-                agenda=self.agenda, initial={'check_type': booking.user_check_type_id}
+                agenda=self.agenda, initial={'check_type': booking.user_check_type_slug}
             )
             booking.presence_form = BookingCheckPresenceForm(
-                agenda=self.agenda, initial={'check_type': booking.user_check_type_id}
+                agenda=self.agenda, initial={'check_type': booking.user_check_type_slug}
             )
             return render(
                 request,
@@ -3228,7 +3228,11 @@ class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
 
     def post(self, request, *args, **kwargs):
         booking = self.get_booking(**kwargs)
-        booking.mark_user_presence(check_type=self.get_check_type())
+        check_type = self.get_check_type(kind='presence')
+        booking.mark_user_presence(
+            check_type_slug=check_type.slug if check_type else None,
+            check_type_label=check_type.label if check_type else None,
+        )
         return self.response(request, booking)
 
 
@@ -3245,7 +3249,11 @@ class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
 
     def post(self, request, *args, **kwargs):
         booking = self.get_booking(**kwargs)
-        booking.mark_user_absence(check_type=self.get_check_type())
+        check_type = self.get_check_type(kind='absence')
+        booking.mark_user_absence(
+            check_type_slug=check_type.slug if check_type else None,
+            check_type_label=check_type.label if check_type else None,
+        )
         return self.response(request, booking)
 
 
diff --git a/tests/api/datetimes/test_events_multiple_agendas.py b/tests/api/datetimes/test_events_multiple_agendas.py
index 6c12e50f..bccf6fb3 100644
--- a/tests/api/datetimes/test_events_multiple_agendas.py
+++ b/tests/api/datetimes/test_events_multiple_agendas.py
@@ -1242,7 +1242,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=check_type_absence,
+        user_check_type_slug=check_type_absence.slug,
     )
     event_presence = Event.objects.create(
         slug='event-presence',
@@ -1261,7 +1261,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=check_type_presence,
+        user_check_type_slug=check_type_presence.slug,
     )
     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 c65e6104..c3d7f1dc 100644
--- a/tests/api/test_booking.py
+++ b/tests/api/test_booking.py
@@ -327,10 +327,18 @@ def test_bookings_api_filter_user_absence_reason(app, user):
     )
     Booking.objects.create(event=event, user_external_id='42', user_was_present=False)
     booking2 = Booking.objects.create(
-        event=event, user_external_id='42', user_was_present=False, user_check_type=check_type_absence
+        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,
     )
     Booking.objects.create(
-        event=event, user_external_id='42', user_was_present=False, user_check_type=check_type_presence
+        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,
     )
 
     app.authorization = ('Basic', ('john.doe', 'password'))
@@ -370,10 +378,18 @@ def test_bookings_api_filter_user_presence_reason(app, user):
     )
     Booking.objects.create(event=event, user_external_id='42', user_was_present=True)
     booking2 = Booking.objects.create(
-        event=event, user_external_id='42', user_was_present=True, user_check_type=check_type_presence
+        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,
     )
     Booking.objects.create(
-        event=event, user_external_id='42', user_was_present=True, user_check_type=check_type_absence
+        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,
     )
 
     app.authorization = ('Basic', ('john.doe', 'password'))
@@ -477,7 +493,7 @@ def test_booking_api_present(app, user, flag):
     )
     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=check_type)
+    booking = Booking.objects.create(event=event, user_was_present=flag, user_check_type_slug=check_type.slug)
 
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.get('/api/booking/%s/' % booking.pk)
@@ -657,7 +673,8 @@ def test_booking_patch_api_absence_reason(app, user):
     # 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 == check_type_absence
+    assert booking.user_check_type_slug == check_type_absence.slug
+    assert booking.user_check_type_label == check_type_absence.label
 
     # disabled
     resp = app.patch_json(
@@ -671,10 +688,12 @@ def test_booking_patch_api_absence_reason(app, user):
     # reset
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': ''})
     booking.refresh_from_db()
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': None})
     booking.refresh_from_db()
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
 
     # make secondary bookings
     Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
@@ -685,14 +704,16 @@ def test_booking_patch_api_absence_reason(app, user):
     # it works also with slug
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': check_type_absence.slug})
     booking.refresh_from_db()
-    assert booking.user_check_type == check_type_absence
+    assert booking.user_check_type_slug == check_type_absence.slug
+    assert booking.user_check_type_label == check_type_absence.label
     # all secondary bookings are updated
-    assert list(booking.secondary_booking_set.values_list('user_check_type', flat=True)) == [
-        check_type_absence.pk,
-        check_type_absence.pk,
+    assert list(booking.secondary_booking_set.values_list('user_check_type_slug', flat=True)) == [
+        check_type_absence.slug,
+        check_type_absence.slug,
     ]
     other_booking.refresh_from_db()
-    assert other_booking.user_check_type is None  # not changed
+    assert other_booking.user_check_type_slug is None  # not changed
+    assert other_booking.user_check_type_label is None  # not changed
 
     # user_was_present is True, can not set user_absence_reason
     Booking.objects.update(user_was_present=True)
@@ -710,7 +731,8 @@ def test_booking_patch_api_absence_reason(app, user):
     assert resp.json['err'] == 0
     booking.refresh_from_db()
     assert booking.user_was_present is False
-    assert booking.user_check_type == check_type_absence
+    assert booking.user_check_type_slug == check_type_absence.slug
+    assert booking.user_check_type_label == check_type_absence.label
 
     # mark the event as checked
     event.checked = True
@@ -777,7 +799,8 @@ def test_booking_patch_api_presence_reason(app, user):
     # 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 == check_type_presence
+    assert booking.user_check_type_slug == check_type_presence.slug
+    assert booking.user_check_type_label == check_type_presence.label
 
     # disabled
     resp = app.patch_json(
@@ -791,10 +814,12 @@ def test_booking_patch_api_presence_reason(app, user):
     # reset
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': ''})
     booking.refresh_from_db()
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': None})
     booking.refresh_from_db()
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
 
     # make secondary bookings
     Booking.objects.create(event=event, primary_booking=booking, user_was_present=True)
@@ -805,14 +830,16 @@ def test_booking_patch_api_presence_reason(app, user):
     # it works also with slug
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug})
     booking.refresh_from_db()
-    assert booking.user_check_type == check_type_presence
+    assert booking.user_check_type_slug == check_type_presence.slug
+    assert booking.user_check_type_label == check_type_presence.label
     # all secondary bookings are updated
-    assert list(booking.secondary_booking_set.values_list('user_check_type', flat=True)) == [
-        check_type_presence.pk,
-        check_type_presence.pk,
+    assert list(booking.secondary_booking_set.values_list('user_check_type_slug', flat=True)) == [
+        check_type_presence.slug,
+        check_type_presence.slug,
     ]
     other_booking.refresh_from_db()
-    assert other_booking.user_check_type is None  # not changed
+    assert other_booking.user_check_type_slug is None  # not changed
+    assert other_booking.user_check_type_label is None  # not changed
 
     # user_was_present is False, can not set user_presence_reason
     Booking.objects.update(user_was_present=False)
@@ -830,7 +857,8 @@ def test_booking_patch_api_presence_reason(app, user):
     assert resp.json['err'] == 0
     booking.refresh_from_db()
     assert booking.user_was_present is True
-    assert booking.user_check_type == check_type_presence
+    assert booking.user_check_type_slug == check_type_presence.slug
+    assert booking.user_check_type_label == check_type_presence.label
 
     # mark the event as checked
     event.checked = True
diff --git a/tests/manager/test_check_type.py b/tests/manager/test_check_type.py
index 5bb0b34c..08923471 100644
--- a/tests/manager/test_check_type.py
+++ b/tests/manager/test_check_type.py
@@ -83,17 +83,6 @@ def test_delete_group(app, admin_user):
     assert CheckTypeGroup.objects.exists() is False
     assert CheckType.objects.exists() is False
 
-    # check_type is used
-    group = CheckTypeGroup.objects.create(label='Foo bar')
-    check_type = CheckType.objects.create(label='Foo reason', group=group)
-    Booking.objects.update(user_check_type=check_type)
-    resp = app.get('/manage/check-type/group/%s/delete/' % group.pk)
-    assert (
-        'Can not delete this check type group: a check type of this group is set on some existing bookings.'
-        in resp
-    )
-    resp.form.submit(status=404)
-
 
 def test_delete_group_as_manager(app, manager_user, agenda_with_restrictions):
     group = CheckTypeGroup.objects.create(label='Foo bar')
@@ -169,7 +158,7 @@ def test_edit_check_type(app, admin_user):
     assert check_type.disabled is True
 
     # check_type is used
-    Booking.objects.update(user_check_type=check_type)
+    Booking.objects.update(user_check_type_slug=check_type.slug)
     resp = app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk))
     assert 'This check type is set on some existing bookings, modify it with caution.' in resp
 
@@ -201,7 +190,7 @@ def test_delete_check_type(app, admin_user):
 
     # check_type is used
     check_type = CheckType.objects.create(label='Foo reason', group=group)
-    Booking.objects.update(user_check_type=check_type)
+    Booking.objects.update(user_check_type_slug=check_type.slug)
     resp = app.get('/manage/check-type/group/%s/%s/delete/' % (group.pk, check_type.pk))
     assert 'Can not delete this check type: it is set on some existing bookings.' in resp
     resp.form.submit(status=404)
@@ -259,7 +248,7 @@ def test_agenda_group(app, admin_user):
     assert 'Check type group: Foo bar' in resp
 
     # cannot change check_type group booking with non null check_type exists
-    booking.user_check_type = check_type
+    booking.user_check_type_slug = check_type.slug
     booking.save()
     resp = app.get('/manage/agendas/%s/check-options' % agenda.pk)
     assert 'check_type_group' not in resp.context['form'].fields
diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py
index 754d0801..846f6036 100644
--- a/tests/manager/test_event.py
+++ b/tests/manager/test_event.py
@@ -1678,7 +1678,7 @@ def test_event_check_filters(app, admin_user):
         user_last_name='foo-none bar-val2 reason-foo',
         extra_data={'bar': 'val2'},
         user_was_present=False,
-        user_check_type=check_type_absence,
+        user_check_type_slug=check_type_absence.slug,
     )
     Booking.objects.create(
         event=event,
@@ -1687,7 +1687,7 @@ def test_event_check_filters(app, admin_user):
         user_last_name='foo-none bar-val2 reason-bar',
         extra_data={'bar': 'val2'},
         user_was_present=True,
-        user_check_type=check_type_presence,
+        user_check_type_slug=check_type_presence.slug,
     )
     Booking.objects.create(
         event=event,
@@ -1696,7 +1696,7 @@ def test_event_check_filters(app, admin_user):
         user_last_name='foo-none bar-val2 cancelled-absence',
         extra_data={'bar': 'val2'},
         user_was_present=False,
-        user_check_type=check_type_absence,
+        user_check_type_slug=check_type_absence.slug,
         cancellation_datetime=now(),
     )
     Booking.objects.create(
@@ -1706,7 +1706,7 @@ def test_event_check_filters(app, admin_user):
         user_last_name='foo-none bar-val2 cancelled-presence',
         extra_data={'bar': 'val2'},
         user_was_present=True,
-        user_check_type=check_type_presence,
+        user_check_type_slug=check_type_presence.slug,
         cancellation_datetime=now(),
     )
 
@@ -1787,14 +1787,14 @@ def test_event_check_filters(app, admin_user):
         assert 'Subscription foo-val2 bar-val1' in resp
         assert 'Subscription foo-val1 bar-val2' in resp
         assert 'Subscription foo-none bar-val2' in resp
-        assert len(resp.pyquery.find('input[value=absence-%s]' % check_type_absence_disabled.pk)) == 0
-        assert len(resp.pyquery.find('input[value=absence-%s]' % check_type_presence_disabled.pk)) == 0
+        assert len(resp.pyquery.find('input[value=absence-%s]' % check_type_absence_disabled.slug)) == 0
+        assert len(resp.pyquery.find('input[value=absence-%s]' % check_type_presence_disabled.slug)) == 0
 
     with CaptureQueriesContext(connection) as ctx:
         resp = app.get(
             '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'extra-data-foo': 'val1'}
         )
-        assert len(ctx.captured_queries) == 13
+        assert len(ctx.captured_queries) == 12
     assert 'User none' not in resp
     assert 'User empty' not in resp
     assert 'User foo-val1 bar-none presence' in resp
@@ -1967,7 +1967,7 @@ def test_event_check_filters(app, admin_user):
 
     resp = app.get(
         '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk),
-        params={'booking-status': 'absence-%s' % check_type_absence.pk},
+        params={'booking-status': 'absence::%s' % check_type_absence.slug},
     )
     assert 'User none' not in resp
     assert 'User empty' not in resp
@@ -1987,7 +1987,7 @@ def test_event_check_filters(app, admin_user):
 
     resp = app.get(
         '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk),
-        params={'booking-status': 'presence-%s' % check_type_presence.pk},
+        params={'booking-status': 'presence::%s' % check_type_presence.slug},
     )
     assert 'User none' not in resp
     assert 'User empty' not in resp
@@ -2078,10 +2078,12 @@ def test_event_check_booking(app, admin_user):
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
     booking.refresh_from_db()
     assert booking.user_was_present is True
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is True
-    assert secondary_booking.user_check_type is None
+    assert secondary_booking.user_check_type_slug is None
+    assert secondary_booking.user_check_type_label is None
     event.refresh_from_db()
     assert event.checked is False
 
@@ -2098,10 +2100,12 @@ def test_event_check_booking(app, admin_user):
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Absence'
     booking.refresh_from_db()
     assert booking.user_was_present is False
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is False
-    assert secondary_booking.user_check_type is None
+    assert secondary_booking.user_check_type_slug is None
+    assert secondary_booking.user_check_type_label is None
     event.refresh_from_db()
     assert event.checked is True
 
@@ -2121,15 +2125,17 @@ def test_event_check_booking(app, admin_user):
     # set as absent with check_type
     resp = app.post(
         '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.slug},
     ).follow()
     assert 'Foo reason' in resp
     booking.refresh_from_db()
     assert booking.user_was_present is False
-    assert booking.user_check_type == check_type_absence
+    assert booking.user_check_type_slug == check_type_absence.slug
+    assert booking.user_check_type_label == check_type_absence.label
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is False
-    assert secondary_booking.user_check_type == check_type_absence
+    assert secondary_booking.user_check_type_slug == check_type_absence.slug
+    assert secondary_booking.user_check_type_label == check_type_absence.label
 
     # set as present without check_type
     resp = app.post(
@@ -2141,10 +2147,12 @@ def test_event_check_booking(app, admin_user):
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
     booking.refresh_from_db()
     assert booking.user_was_present is True
-    assert booking.user_check_type is None
+    assert booking.user_check_type_slug is None
+    assert booking.user_check_type_label is None
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is True
-    assert secondary_booking.user_check_type is None
+    assert secondary_booking.user_check_type_slug is None
+    assert secondary_booking.user_check_type_label is None
     event.refresh_from_db()
     assert event.checked is True
 
@@ -2162,15 +2170,17 @@ def test_event_check_booking(app, admin_user):
     # set as present with check_type
     resp = app.post(
         '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.slug},
     ).follow()
     assert 'Bar reason' in resp
     booking.refresh_from_db()
     assert booking.user_was_present is True
-    assert booking.user_check_type == check_type_presence
+    assert booking.user_check_type_slug == check_type_presence.slug
+    assert booking.user_check_type_label == check_type_presence.label
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is True
-    assert secondary_booking.user_check_type == check_type_presence
+    assert secondary_booking.user_check_type_slug == check_type_presence.slug
+    assert secondary_booking.user_check_type_label == check_type_presence.label
 
     # mark the event as checked
     event.checked = True
@@ -2228,26 +2238,26 @@ def test_event_check_booking_ajax(app, admin_user):
     # set as present
     resp = app.post(
         '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.slug},
         extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
     )
     assert '<tr>' not in resp  # because this is a fragment
     assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n  \n    (Bar reason)'
     assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
-    assert '<option value="%s" selected>Bar reason</option>' % check_type_presence.pk in resp
+    assert '<option value="%s" selected>Bar reason</option>' % check_type_presence.slug in resp
 
     # set as absent
     resp = app.post(
         '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.slug},
         extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
     )
     assert '<tr>' not in resp  # because this is a fragment
     assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n  \n    (Foo reason)'
     assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
     assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence')
-    assert '<option value="%s" selected>Foo reason</option>' % check_type_absence.pk in resp
+    assert '<option value="%s" selected>Foo reason</option>' % check_type_absence.slug in resp
 
 
 def test_event_check_all_bookings(app, admin_user):
@@ -2277,7 +2287,8 @@ def test_event_check_all_bookings(app, admin_user):
     )
     booking1.refresh_from_db()
     assert booking1.user_was_present is False
-    assert booking1.user_check_type is None
+    assert booking1.user_check_type_slug is None
+    assert booking1.user_check_type_label is None
     event.refresh_from_db()
     assert event.checked is False
 
@@ -2303,13 +2314,16 @@ def test_event_check_all_bookings(app, admin_user):
     )
     booking1.refresh_from_db()
     assert booking1.user_was_present is False
-    assert booking1.user_check_type is None
+    assert booking1.user_check_type_slug is None
+    assert booking1.user_check_type_label is None
     booking2.refresh_from_db()
     assert booking2.user_was_present is True
-    assert booking2.user_check_type is None
+    assert booking2.user_check_type_slug is None
+    assert booking2.user_check_type_label is None
     secondary_booking.refresh_from_db()
     assert secondary_booking.user_was_present is True
-    assert secondary_booking.user_check_type is None
+    assert secondary_booking.user_check_type_slug is None
+    assert secondary_booking.user_check_type_label is None
     event.refresh_from_db()
     assert event.checked is True
 
@@ -2319,37 +2333,44 @@ def test_event_check_all_bookings(app, admin_user):
     assert 'Mark all bookings without status' in resp
     app.post(
         '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.slug},
     )
     booking1.refresh_from_db()
     assert booking1.user_was_present is False
-    assert booking1.user_check_type is None
+    assert booking1.user_check_type_slug is None
+    assert booking1.user_check_type_label is None
     booking2.refresh_from_db()
     assert booking2.user_was_present is True
-    assert booking2.user_check_type is None
+    assert booking2.user_check_type_slug is None
+    assert booking2.user_check_type_label is None
     booking3.refresh_from_db()
     assert booking3.user_was_present is False
-    assert booking3.user_check_type == check_type_absence
+    assert booking3.user_check_type_slug == check_type_absence.slug
+    assert booking3.user_check_type_label == check_type_absence.label
 
     booking4 = Booking.objects.create(event=event, user_first_name='User', user_last_name='52')
     resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
     assert 'Mark all bookings without status' in resp
     app.post(
         '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.slug},
     )
     booking1.refresh_from_db()
     assert booking1.user_was_present is False
-    assert booking1.user_check_type is None
+    assert booking1.user_check_type_slug is None
+    assert booking1.user_check_type_label is None
     booking2.refresh_from_db()
     assert booking2.user_was_present is True
-    assert booking2.user_check_type is None
+    assert booking2.user_check_type_slug is None
+    assert booking2.user_check_type_label is None
     booking3.refresh_from_db()
     assert booking3.user_was_present is False
-    assert booking3.user_check_type == check_type_absence
+    assert booking3.user_check_type_slug == check_type_absence.slug
+    assert booking3.user_check_type_label == check_type_absence.label
     booking4.refresh_from_db()
     assert booking4.user_was_present is True
-    assert booking4.user_check_type == check_type_presence
+    assert booking4.user_check_type_slug == check_type_presence.slug
+    assert booking4.user_check_type_label == check_type_presence.label
 
     # now disable check update
     agenda.disable_check_update = True
@@ -2359,7 +2380,7 @@ def test_event_check_all_bookings(app, admin_user):
     assert 'Mark all bookings without status' not in resp
     app.post(
         '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_absence.slug},
         status=404,
     )
     resp = app.post(
@@ -2369,7 +2390,7 @@ def test_event_check_all_bookings(app, admin_user):
     )
     app.post(
         '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk),
-        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.pk},
+        params={'csrfmiddlewaretoken': token, 'check_type': check_type_presence.slug},
         status=404,
     )
     resp = app.post(
-- 
2.35.1

