From 6fba6c859a690475652e67930b05aa774842875a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 4 Jan 2021 14:07:45 +0100
Subject: [PATCH] api: store the reason of an absence (#49179)

---
 .../migrations/0072_booking_absence_reason.py | 19 +++++++
 chrono/agendas/models.py                      |  1 +
 chrono/api/views.py                           | 15 +++++-
 tests/test_api.py                             | 49 ++++++++++++++++++-
 4 files changed, 81 insertions(+), 3 deletions(-)
 create mode 100644 chrono/agendas/migrations/0072_booking_absence_reason.py

diff --git a/chrono/agendas/migrations/0072_booking_absence_reason.py b/chrono/agendas/migrations/0072_booking_absence_reason.py
new file mode 100644
index 0000000..4b0c8bd
--- /dev/null
+++ b/chrono/agendas/migrations/0072_booking_absence_reason.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0071_booking_attendance'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='booking',
+            name='user_absence_reason',
+            field=models.CharField(blank=True, max_length=250),
+        ),
+    ]
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 22b0881..94edcf7 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -1099,6 +1099,7 @@ 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_absence_reason = models.CharField(max_length=250, blank=True)
 
     form_url = models.URLField(blank=True)
     backoffice_url = models.URLField(blank=True)
diff --git a/chrono/api/views.py b/chrono/api/views.py
index 7d9f624..f8d5dff 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -1163,9 +1163,16 @@ fillslot = Fillslot.as_view()
 
 
 class BookingSerializer(serializers.ModelSerializer):
+    user_absence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True)
+
     class Meta:
         model = Booking
-        fields = ['user_was_present']
+        fields = ['user_was_present', 'user_absence_reason']
+
+    def validate_user_absence_reason(self, value):
+        if value is None:
+            return ''
+        return value
 
 
 class BookingAPI(APIView):
@@ -1203,6 +1210,7 @@ class BookingAPI(APIView):
             'booking_id': self.booking.pk,
             'in_waiting_list': self.booking.in_waiting_list,
             'user_was_present': self.booking.user_was_present,
+            'user_absence_reason': self.booking.user_absence_reason,
         }
         return Response(response)
 
@@ -1225,7 +1233,10 @@ class BookingAPI(APIView):
             )
 
         serializer.save()
-        self.booking.secondary_booking_set.update(user_was_present=self.booking.user_was_present)
+        if 'user_was_present' in request.data:
+            self.booking.secondary_booking_set.update(user_was_present=self.booking.user_was_present)
+        if 'user_absence_reason' in request.data:
+            self.booking.secondary_booking_set.update(user_absence_reason=self.booking.user_absence_reason)
 
         response = {'err': 0, 'booking_id': self.booking.pk}
         return Response(response)
diff --git a/tests/test_api.py b/tests/test_api.py
index 294080a..fc05370 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1908,12 +1908,13 @@ def test_booking_api_with_cancel_booking(app, some_data, user):
 def test_booking_api_present(app, user, flag):
     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=flag)
+    booking = Booking.objects.create(event=event, user_was_present=flag, user_absence_reason='foobar')
 
     app.authorization = ('Basic', ('john.doe', 'password'))
     resp = app.get('/api/booking/%s/' % booking.pk)
     assert resp.json['booking_id'] == booking.pk
     assert resp.json['user_was_present'] == flag
+    assert resp.json['user_absence_reason'] == 'foobar'
 
 
 @pytest.mark.parametrize('flag', [True, False])
@@ -2015,6 +2016,52 @@ def test_booking_patch_api_present(app, user, flag):
     assert other_booking.user_was_present is None  # not changed
 
 
+def test_booking_patch_api_absence_reason(app, user):
+    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)
+
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    # set reason
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
+    booking.refresh_from_db()
+    assert booking.user_was_present is True  # not changed
+    assert booking.user_absence_reason == 'foobar'
+
+    # reset
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': ''})
+    booking.refresh_from_db()
+    assert booking.user_was_present is True  # not changed
+    assert booking.user_absence_reason == ''
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': None})
+    booking.refresh_from_db()
+    assert booking.user_was_present is True  # not changed
+    assert booking.user_absence_reason == ''
+
+    # make secondary bookings
+    Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
+    Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
+    # and other booking
+    other_booking = Booking.objects.create(event=event)
+
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
+    booking.refresh_from_db()
+    assert booking.user_was_present is True  # not changed
+    assert booking.user_absence_reason == 'foobar'
+    # all secondary bookings are upadted
+    assert list(booking.secondary_booking_set.values_list('user_absence_reason', flat=True)) == [
+        'foobar',
+        'foobar',
+    ]
+    assert list(booking.secondary_booking_set.values_list('user_was_present', flat=True)) == [
+        False,
+        False,
+    ]  # not changed
+    other_booking.refresh_from_db()
+    assert other_booking.user_absence_reason == ''  # not changed
+
+
 def test_booking_cancellation_api(app, some_data, user):
     agenda = Agenda.objects.filter(label=u'Foo bar')[0]
     event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
-- 
2.29.2

