From 5fba5a4d3cd22992b64de1e1cdba7bc782f44fef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 24 May 2022 14:38:29 +0200
Subject: [PATCH 2/2] manager: use user_block also in meetings agenda views
 (#63915)

---
 chrono/agendas/models.py                      | 23 +++---
 chrono/manager/forms.py                       | 12 +++-
 .../manager_meetings_agenda_day_view.html     |  2 +-
 .../manager_meetings_agenda_month_view.html   |  2 +-
 .../manager_meetings_agenda_settings.html     | 14 ++++
 .../chrono/manager_resource_day_view.html     |  2 +-
 .../chrono/manager_resource_month_view.html   |  2 +-
 chrono/manager/views.py                       | 12 ++--
 tests/manager/test_all.py                     | 72 +++++++++++++++++--
 tests/manager/test_resource.py                | 30 +++++++-
 10 files changed, 140 insertions(+), 31 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index c2770973..8ba64e14 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -797,10 +797,17 @@ class Agenda(models.Model):
         return [x.strip() for x in self.booking_check_filters.split(',')]
 
     def get_booking_user_block_template(self):
-        return (
-            self.booking_user_block_template
-            or '{{ booking.user_name|default:booking.label|default:"%s" }}' % _('Anonymous')
-        )
+        if self.kind == 'events':
+            default = '{{ booking.user_name|default:booking.label|default:"%s" }}' % _('Anonymous')
+        else:
+            default = """{%% if booking.label and booking.user_name %%}
+{{ booking.label }} - {{ booking.user_name }}
+{%% else %%}
+{{ booking.user_name|default:booking.label|default:"%s" }}
+{%% endif %%}""" % _(
+                'booked'
+            )
+        return self.booking_user_block_template or default
 
     def get_recurrence_exceptions(self, min_start, max_start):
         return TimePeriodException.objects.filter(
@@ -2089,14 +2096,6 @@ class Booking(models.Model):
         name = self.user_name or self.label or _('Anonymous')
         return '%s, %s' % (name, date_format(localtime(self.creation_datetime), 'DATETIME_FORMAT'))
 
-    def meetings_display(self):
-        if self.label and self.user_name:
-            return '%s - %s' % (self.label, self.user_name)
-        elif self.label or self.user_name:
-            return self.label or self.user_name
-        else:
-            return ugettext('booked')
-
     def get_form_url(self):
         return translate_from_publik_url(self.form_url)
 
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index cc6bc86e..33471f0b 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -1314,9 +1314,15 @@ class AgendaDisplaySettingsForm(forms.ModelForm):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.fields['booking_user_block_template'].help_text = (
-            _('Displayed for each booking in event page and check page'),
-        )
+        if kwargs['instance'].kind == 'events':
+            self.fields['booking_user_block_template'].help_text = (
+                _('Displayed for each booking in event page and check page'),
+            )
+        else:
+            self.fields['booking_user_block_template'].help_text = (
+                _('Displayed for each booking in agenda view pages'),
+            )
+            del self.fields['event_display_template']
 
 
 class AgendaBookingCheckSettingsForm(forms.ModelForm):
diff --git a/chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html b/chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html
index 8bcca32f..f71fa46a 100644
--- a/chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html
+++ b/chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html
@@ -46,7 +46,7 @@
           <div class="booking{% if booking.color %} booking-color-{{ booking.color.index }}{% endif %}"
               style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
             ><span class="start-time">{{booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
-            <a {% if booking.get_backoffice_url %}href="{{booking.get_backoffice_url}}"{% endif %}>{{ booking.meetings_display }}</a>
+            <a {% if booking.get_backoffice_url %}href="{{booking.get_backoffice_url}}"{% endif %}>{{ booking.get_user_block }}</a>
             <a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=booking.event.agenda_id booking_pk=booking.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
             {% if booking.color %}<span class="booking-color-label booking-bg-color-{{ booking.color.index }}">{{ booking.color }}</span>{% endif %}
           </div>
diff --git a/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html b/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html
index ec15861d..b856d672 100644
--- a/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html
+++ b/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html
@@ -36,7 +36,7 @@
       {% for slot in day.infos.booked_slots %}
       <div class="booking{% if slot.booking.color %} booking-color-{{ slot.booking.color.index }}{% endif %}" style="left:{{ slot.css_left|stringformat:".1f" }}%;height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;">
         <span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
-        <a {% if slot.booking.get_backoffice_url %}href="{{slot.booking.get_backoffice_url}}"{% endif %}>{{ slot.booking.meetings_display }}</a>
+        <a {% if slot.booking.get_backoffice_url %}href="{{slot.booking.get_backoffice_url}}"{% endif %}>{{ slot.booking.get_user_block }}</a>
           <a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=slot.booking.event.agenda_id booking_pk=slot.booking.id %}?next={{ request.path }}">{% trans "Cancel" %}</a>
         {% if not single_desk %}<span class="desk">{{ slot.desk }}</span>{% endif %}
         {% if slot.booking.color %}<span class="booking-color-label booking-bg-color-{{ slot.booking.color.index }}">{{ slot.booking.color }}</span>{% endif %}
diff --git a/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html b/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
index 7499a69c..d0bf1a77 100644
--- a/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
+++ b/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
@@ -153,4 +153,18 @@
 {% endif %}
 {% endwith %}
 
+<div class="section">
+<h3>{% trans "Display options" %}
+    <a rel="popup" class="button" href="{% url 'chrono-manager-agenda-display-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
+</h3>
+<div>
+    <ul>
+        <li>
+            {% trans "Booking display template:" %}
+            <pre>{{ agenda.get_booking_user_block_template }}</pre>
+        </li>
+    </ul>
+</div>
+</div>
+
 {% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_resource_day_view.html b/chrono/manager/templates/chrono/manager_resource_day_view.html
index d32f1223..f277ccaa 100644
--- a/chrono/manager/templates/chrono/manager_resource_day_view.html
+++ b/chrono/manager/templates/chrono/manager_resource_day_view.html
@@ -40,7 +40,7 @@
                       <div class="booking"
                           style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
                         ><span class="start-time">{{ booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
-                        <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
+                        <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.get_user_block }}</a>
                       </div>
                     {% endfor %}
                 </td>
diff --git a/chrono/manager/templates/chrono/manager_resource_month_view.html b/chrono/manager/templates/chrono/manager_resource_month_view.html
index 5b2bf2de..ad318b59 100644
--- a/chrono/manager/templates/chrono/manager_resource_month_view.html
+++ b/chrono/manager/templates/chrono/manager_resource_month_view.html
@@ -49,7 +49,7 @@
                 {% for slot in day.infos.booked_slots %}
                 <div class="booking" style="height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%">
                     <span class="start-time">{{ slot.booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
-                    <a {% if slot.booking.get_backoffice_url %}href="{{ slot.booking.get_backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
+                    <a {% if slot.booking.get_backoffice_url %}href="{{ slot.booking.get_backoffice_url }}"{% endif %}>{{ slot.booking.get_user_block }}</a>
                 </div>
                 {% endfor %}
                 {% endif %}
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 5344ef41..064bdfff 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -287,7 +287,9 @@ class ResourceDayView(DateMixin, DayArchiveView):
 
     def get_queryset(self):
         queryset = (
-            self.resource.event_set.all().select_related('meeting_type').prefetch_related('booking_set')
+            self.resource.event_set.all()
+            .select_related('meeting_type', 'agenda')
+            .prefetch_related('booking_set')
         )
         return queryset
 
@@ -402,7 +404,9 @@ class ResourceMonthView(DateMixin, MonthArchiveView):
 
     def get_queryset(self):
         queryset = (
-            self.resource.event_set.all().select_related('meeting_type').prefetch_related('booking_set')
+            self.resource.event_set.all()
+            .select_related('meeting_type', 'agenda')
+            .prefetch_related('booking_set')
         )
         return queryset
 
@@ -1247,7 +1251,7 @@ class AgendaDisplaySettingsView(AgendaEditView):
     title = _("Configure display options")
 
     def set_agenda(self, **kwargs):
-        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
+        self.agenda = get_object_or_404(Agenda.objects.exclude(kind='virtual'), pk=kwargs.get('pk'))
 
     def get_initial(self):
         return {'booking_user_block_template': self.agenda.get_booking_user_block_template()}
@@ -1419,7 +1423,7 @@ class AgendaDateView(DateMixin, ViewableAgendaMixin):
             else:
                 queryset = (
                     Event.objects.filter(agenda__virtual_agendas=self.agenda)
-                    .select_related('meeting_type')
+                    .select_related('meeting_type', 'agenda')
                     .prefetch_related('booking_set')
                 )
         return queryset
diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py
index d87fa424..36352dfa 100644
--- a/tests/manager/test_all.py
+++ b/tests/manager/test_all.py
@@ -575,10 +575,14 @@ def test_options_agenda_booking_display_options(app, admin_user):
         resp = resp.form.submit()
         assert 'syntax error' in resp.text
 
-    # check kind
+    # and for meetings agenda
     agenda.kind = 'meetings'
     agenda.save()
-    app.get(url, status=404)
+    resp = app.get(url)
+    assert 'event_display_template' not in resp.form.fields
+    assert 'booking_user_block_template' in resp.form.fields
+
+    # check kind
     agenda.kind = 'virtual'
     agenda.save()
     app.get(url, status=404)
@@ -858,16 +862,28 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
     booking_url = resp.json['data'][0]['api']['fillslot_url']
     booking_url2 = resp.json['data'][2]['api']['fillslot_url']
     resp = app.post(booking_url)
-    resp = app.post_json(booking_url2, params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
+    resp = app.post_json(
+        booking_url2, params={'label': 'foo', 'user_last_name': "bar's", 'url': 'http://baz/'}
+    )
 
     app.reset()
     login(app)
     date = Booking.objects.all()[0].event.start_datetime
     resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
     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 'hourspan-2' in resp.text  # table CSS class
     assert 'height: 50%; top: 0%;' in resp.text  # booking cells
 
+    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    agenda.save()
+    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
+
     # create a shorter meeting type, this will change the table CSS class
     # (and visually this will give more room for events)
     meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
@@ -1405,15 +1421,27 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user):
     booking_url = resp.json['data'][0]['api']['fillslot_url']
     booking_url2 = resp.json['data'][2]['api']['fillslot_url']
     booking = app.post(booking_url)
-    booking_2 = app.post_json(booking_url2, params={'label': 'foo book', 'user': 'bar', 'url': 'http://baz/'})
+    booking_2 = app.post_json(
+        booking_url2, params={'label': 'foo book', 'user_last_name': "bar's", 'url': 'http://baz/'}
+    )
 
     app.reset()
     login(app)
     date = Booking.objects.all()[0].event.start_datetime
     resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
     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 len(resp.pyquery.find('span.desk')) == 0
 
+    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    agenda.save()
+    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
+
     desk = Desk.objects.create(agenda=agenda, label='Desk B')
     resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
     assert len(resp.pyquery.find('span.desk')) == 2
@@ -1904,14 +1932,30 @@ def test_virtual_agenda_day_view(app, admin_user, manager_user):
             meeting_type=meetingtype2,
             start_datetime=now().replace(hour=hour, minute=minute),
         )
-        Booking.objects.create(event=event)
+        Booking.objects.create(event=event, label='foo', user_last_name="bar's")
 
     date = Booking.objects.all()[0].event.start_datetime
     resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
     assert resp.text.count('div class="booking') == 4
+    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 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 'hourspan-2' in resp.text  # table CSS class
     assert 'height: 50%; top: 0%;' in resp.text  # booking cells
 
+    real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    real_agenda_1.save()
+    real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
+    real_agenda_2.save()
+    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> 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
+
     # create a shorter meeting type, this will change the table CSS class
     # (and visually this will give more room for events)
     MeetingType.objects.create(agenda=real_agenda_1, label='Baz', duration=15)
@@ -2037,7 +2081,7 @@ def test_virtual_agenda_month_view(app, admin_user):
             meeting_type=meetingtype2,
             start_datetime=now().replace(hour=hour, minute=minute),
         )
-        Booking.objects.create(event=event)
+        Booking.objects.create(event=event, label='foo', user_last_name="bar's")
 
     date = Booking.objects.all()[0].event.start_datetime
     resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
@@ -2045,6 +2089,22 @@ def test_virtual_agenda_month_view(app, admin_user):
     assert (
         resp.text.count('<div class="booking" style="left:50.0%;height:50.0%;min-height:50.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 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
+
+    real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    real_agenda_1.save()
+    real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
+    real_agenda_2.save()
+    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> 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
 
     # cancel a booking
     booking = Booking.objects.first()
diff --git a/tests/manager/test_resource.py b/tests/manager/test_resource.py
index 54a4edba..26a78165 100644
--- a/tests/manager/test_resource.py
+++ b/tests/manager/test_resource.py
@@ -102,16 +102,29 @@ def test_resource_day_view(app, admin_user):
             start_datetime=now().replace(hour=hour, minute=minute),
         )
         event.resources.add(resource)
-        Booking.objects.create(event=event)
+        if hour == 10:
+            Booking.objects.create(event=event)
+        else:
+            Booking.objects.create(event=event, label='foo', user_last_name="bar's")
 
     with CaptureQueriesContext(connection) as ctx:
         resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
         assert len(ctx.captured_queries) == 7
     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 '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
 
+    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
+    agenda.save()
+    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
+
     # create a shorter meeting type, this will change the table CSS class
     # (and visually this will give more room for events)
     meetingtype = MeetingType.objects.create(agenda=agenda, label='Baz', duration=15)
@@ -240,13 +253,26 @@ def test_resource_month_view(app, admin_user):
             start_datetime=now().replace(hour=hour, minute=minute),
         )
         event.resources.add(resource)
-        Booking.objects.create(event=event)
+        if hour == 10:
+            Booking.objects.create(event=event)
+        else:
+            Booking.objects.create(event=event, label='foo', user_last_name="bar's")
 
     today = datetime.date.today()
     with CaptureQueriesContext(connection) as ctx:
         resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
         assert len(ctx.captured_queries) == 8
     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
+
+    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
 
     # cancel booking
     booking = Booking.objects.first()
-- 
2.35.1

