From c546547815ad211f16ade149b0fd5fbeaf131ae5 Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Tue, 18 Feb 2020 12:14:36 +0100
Subject: [PATCH 2/2] manager: add views for virtual agendas (#37123)

---
 chrono/agendas/models.py                      |  24 ++-
 chrono/manager/forms.py                       |  16 ++
 ...manager_confirm_virtual_member_delete.html |  19 +++
 .../manager_virtual_agenda_settings.html      |  64 ++++++++
 .../chrono/manager_virtual_agenda_view.html   |  26 ++++
 .../chrono/manager_virtual_member_form.html   |  28 ++++
 chrono/manager/urls.py                        |  10 ++
 chrono/manager/views.py                       |  84 ++++++++++
 tests/test_manager.py                         | 144 ++++++++++++++++++
 9 files changed, 414 insertions(+), 1 deletion(-)
 create mode 100644 chrono/manager/templates/chrono/manager_confirm_virtual_member_delete.html
 create mode 100644 chrono/manager/templates/chrono/manager_virtual_agenda_settings.html
 create mode 100644 chrono/manager/templates/chrono/manager_virtual_agenda_view.html
 create mode 100644 chrono/manager/templates/chrono/manager_virtual_member_form.html

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index fe89fef..59663fa 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -107,6 +107,9 @@ class Agenda(models.Model):
     class Meta:
         ordering = ['label']
 
+    def __str__(self):
+        return self.label
+
     def save(self, *args, **kwargs):
         if not self.slug:
             self.slug = generate_slug(self)
@@ -176,6 +179,9 @@ class Agenda(models.Model):
             return MeetingType.objects.get(id=id_, agenda=self)
         return MeetingType.objects.get(slug=slug, agenda=self)
 
+    def get_virtual_members(self):
+        return VirtualMember.objects.filter(virtual_agenda=self)
+
     def get_base_meeting_duration(self):
         durations = [x.duration for x in self.iter_meetingtypes()]
         if not durations:
@@ -245,7 +251,23 @@ class Agenda(models.Model):
 
 class VirtualMember(models.Model):
     virtual_agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='real_members')
-    real_agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='virtual_members')
+    real_agenda = models.ForeignKey(
+        Agenda, on_delete=models.CASCADE, related_name='virtual_members', verbose_name='Agenda'
+    )
+
+    def clean(self):
+        for meetingtype in self.virtual_agenda.iter_meetingtypes():
+            try:
+                MeetingType.objects.get(
+                    agenda=self.real_agenda,
+                    label=meetingtype.label,
+                    slug=meetingtype.slug,
+                    duration=meetingtype.duration,
+                )
+            except MeetingType.DoesNotExist:
+                raise ValidationError(
+                    _('This agenda does not the meetingtypes provided by the virtualagenda.')
+                )
 
 
 WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 03d2bf6..52d01c1 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -34,6 +34,7 @@ from chrono.agendas.models import (
     Desk,
     TimePeriodException,
     TimePeriodExceptionSource,
+    VirtualMember,
     WEEKDAYS_LIST,
 )
 
@@ -161,6 +162,21 @@ class TimePeriodExceptionForm(forms.ModelForm):
         return self.cleaned_data['end_datetime']
 
 
+class VirtualMemberForm(forms.ModelForm):
+    class Meta:
+        model = VirtualMember
+        fields = ['virtual_agenda', 'real_agenda']
+        widgets = {
+            'virtual_agenda': forms.HiddenInput(),
+        }
+
+    def __init__(self, *args, **kwargs):
+        super(VirtualMemberForm, self).__init__(*args, **kwargs)
+        self.fields['real_agenda'].queryset = Agenda.objects.filter(kind='meetings').exclude(
+            virtual_agendas__pk__in=[kwargs['initial']['agenda']]
+        )
+
+
 class ImportEventsForm(forms.Form):
     events_csv_file = forms.FileField(
         label=_('Events File'),
diff --git a/chrono/manager/templates/chrono/manager_confirm_virtual_member_delete.html b/chrono/manager/templates/chrono/manager_confirm_virtual_member_delete.html
new file mode 100644
index 0000000..9ba14c8
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_confirm_virtual_member_delete.html
@@ -0,0 +1,19 @@
+{% extends "chrono/manager_home.html" %}
+{% load i18n %}
+
+{% block appbar %}
+<h2>{% trans "Exclude Agenda" %}</h2>
+{% endblock %}
+
+{% block content %}
+<form method="post">
+  {% csrf_token %}
+  <p>
+  {% blocktrans %}Are you sure you want to exclude this agenda from the virtual agenda ?{% endblocktrans %}
+  </p>
+  <div class="buttons">
+    <button class="delete-button">{% trans 'Exclude' %}</button>
+    <a class="cancel" href="{% url 'chrono-manager-agenda-settings' agenda.pk %}">{% trans 'Cancel' %}</a>
+  </div>
+</form>
+{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html b/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html
new file mode 100644
index 0000000..71ad303
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html
@@ -0,0 +1,64 @@
+{% extends "chrono/manager_agenda_settings.html" %}
+{% load i18n %}
+
+{% block agenda-extra-management-actions %}
+  <a rel="popup" href="{% url 'chrono-manager-agenda-add-virtual-member' pk=object.id %}">{% trans 'Include Agenda' %}</a>
+{% endblock %}
+
+{% block agenda-settings %}
+
+<div class="section">
+<h3>{% trans 'Included Agendas' %}</h3>
+<div>
+{% if virtual_members.count %}
+  <ul class="objects-list single-links">
+    {% for virtual_member in virtual_members %}
+    <li><a href="{% url 'chrono-manager-agenda-settings' pk=virtual_member.real_agenda.id %}">
+        {{real_agenda.label}}
+        <span class="identifier">[{% trans "identifier:" %} {{virtual_member.real_agenda.slug}}]</span>
+        </a>
+        <a rel="popup" class="delete" href="{% url 'chrono-manager-virtual-member-delete' pk=virtual_member.pk %}">{% trans "remove" %}</a>
+    </li>
+    {% endfor %}
+  </ul>
+{% else %}
+<div class="big-msg-info">
+  {% blocktrans %}
+  This virtual agenda doesn't include any agenda yet. Click on the "Include Agenda" button in
+  the top right of the page to include a first one.
+  {% endblocktrans %}
+</div>
+{% endif %}
+</div>
+</div>
+
+{% if virtual_members.count %}
+<div class="section">
+<h3>{% trans 'Meeting Types' %}</h3>
+<div>
+{% if meeting_types %}
+  <ul class="objects-list single-links">
+    {% for meeting_type in meeting_types %}
+    <li><a rel="popup" href="">
+        {{meeting_type.label}}
+        <span class="duration">({{meeting_type.duration}} {% trans "minutes" %})</span>
+        <span class="identifier">[{% trans "identifier:" %} {{meeting_type.slug}}]</span>
+        </a>
+    </li>
+    {% endfor %}
+  </ul>
+{% else %}
+<div class="errornotice">
+  {% blocktrans %}
+  This virtual agenda doesn't have any meeting type.
+  It is probably because its included agendas have incompatible meeting types
+  and it makes this virtual agenda unusable.
+  {% endblocktrans %}
+</div>
+{% endif %}
+</div>
+</div>
+{% endif %}
+
+
+{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_virtual_agenda_view.html b/chrono/manager/templates/chrono/manager_virtual_agenda_view.html
new file mode 100644
index 0000000..6629dfe
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_virtual_agenda_view.html
@@ -0,0 +1,26 @@
+{% extends "chrono/manager_agenda_view.html" %}
+{% load i18n %}
+
+{% block content %}
+<div class="section">
+<h3>{% trans 'Included Agendas' %}</h3>
+<div>
+{% if agenda.real_agendas.count %}
+  <ul class="objects-list single-links">
+    {% for real_agenda in agenda.real_agendas.all %}
+    <li><a href="{% url 'chrono-manager-agenda-view' pk=real_agenda.pk %}">
+        {{real_agenda.label}}
+        <span class="identifier">[{% trans "identifier:" %} {{real_agenda.slug}}]</span>
+        </a>
+    {% endfor %}
+  </ul>
+{% else %}
+<div class="big-msg-info">
+  {% blocktrans %}
+  This virtual agenda is empty.
+  {% endblocktrans %}
+</div>
+{% endif %}
+</div>
+</div>
+{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_virtual_member_form.html b/chrono/manager/templates/chrono/manager_virtual_member_form.html
new file mode 100644
index 0000000..f905b96
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_virtual_member_form.html
@@ -0,0 +1,28 @@
+{% extends "chrono/manager_agenda_view.html" %}
+{% load i18n %}
+
+{% block extrascripts %}
+{{ block.super }}
+{{ form.media }}
+{% endblock %}
+
+{% block breadcrumb %}
+{{ block.super }}
+<a href="">{% trans "Include Agenda" %}</a>
+{% endblock %}
+
+{% block appbar %}
+<h2>{% trans "Include Agenda" %}</h2>
+{% endblock %}
+
+{% block content %}
+
+<form method="post" enctype="multipart/form-data">
+  {% csrf_token %}
+  {{ form.as_p }}
+  <div class="buttons">
+    <button class="submit-button">{% trans "Save" %}</button>
+    <a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a>
+  </div>
+</form>
+{% endblock %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 1d4f926..382a5c1 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -93,6 +93,16 @@ urlpatterns = [
         views.desk_import_time_period_exceptions,
         name='chrono-manager-desk-add-import-time-period-exceptions',
     ),
+    url(
+        r'^agendas/(?P<pk>\d+)/add-virtual-member$',
+        views.agenda_add_virtual_member,
+        name='chrono-manager-agenda-add-virtual-member',
+    ),
+    url(
+        r'^virtual-members/(?P<pk>\d+)/delete$',
+        views.virtual_member_delete,
+        name='chrono-manager-virtual-member-delete',
+    ),
     url(
         r'^time-period-exceptions/(?P<pk>\d+)/edit$',
         views.time_period_exception_edit,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index ae6e514..2f9acce 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -23,6 +23,7 @@ from django.db.models import Q
 from django.forms import ValidationError
 from django.http import Http404, HttpResponse, HttpResponseRedirect
 from django.shortcuts import get_object_or_404
+from django.template.response import TemplateResponse
 from django.urls import reverse, reverse_lazy
 from django.utils.dates import MONTHS
 from django.utils.timezone import now, make_aware, make_naive
@@ -53,6 +54,7 @@ from chrono.agendas.models import (
     ICSError,
     AgendaImportError,
     TimePeriodExceptionSource,
+    VirtualMember,
 )
 
 from .forms import (
@@ -71,6 +73,7 @@ from .forms import (
     AgendasImportForm,
     TimePeriodAddForm,
     TimePeriodExceptionSourceReplaceForm,
+    VirtualMemberForm,
 )
 from .utils import import_site
 
@@ -241,6 +244,17 @@ agenda_delete = AgendaDeleteView.as_view()
 class AgendaView(ViewableAgendaMixin, View):
     def get(self, request, *args, **kwargs):
         today = datetime.date.today()
+        if self.agenda.kind == 'virtual':
+            return TemplateResponse(
+                request=request,
+                template='chrono/manager_virtual_agenda_view.html',
+                context={
+                    'agenda': self.agenda,
+                    'object': self.agenda,
+                    'user_can_manage': self.agenda.can_be_managed(self.request.user),
+                },
+            )
+
         if self.agenda.kind == 'meetings':
             # redirect to today view
             return HttpResponseRedirect(
@@ -633,6 +647,13 @@ class ManagedDeskSubobjectMixin(object):
 class AgendaSettings(ManagedAgendaMixin, DetailView):
     model = Agenda
 
+    def get_context_data(self, **kwargs):
+        context = super(AgendaSettings, self).get_context_data(**kwargs)
+        if self.agenda.kind == 'virtual':
+            context['virtual_members'] = self.object.get_virtual_members()
+            context['meeting_types'] = self.object.iter_meetingtypes()
+        return context
+
     def get_events(self):
         return Event.annotate_queryset(Event.objects.filter(agenda=self.agenda).select_related('agenda'))
 
@@ -794,6 +815,33 @@ class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
     template_name = 'chrono/manager_confirm_delete.html'
     model = MeetingType
 
+    def get_context_data(self, **kwargs):
+        context = super(MeetingTypeDeleteView, self).get_context_data(**kwargs)
+        cannot_delete = False
+        meeting_type = self.get_object()
+        for virtual_agenda in self.get_object().agenda.virtual_agendas.all():
+            for mt in virtual_agenda.iter_meetingtypes():
+                if (
+                    meeting_type.slug == mt.slug
+                    and meeting_type.label == mt.label
+                    and meeting_type.duration == mt.duration
+                ):
+                    cannot_delete = True
+                    context['cannot_delete_msg'] = _(
+                        'This cannot be removed as it used by a virtual agenda: %(agenda)s'
+                        % {'agenda': virtual_agenda}
+                    )
+                    break
+        context['cannot_delete'] = cannot_delete
+        return context
+
+    def delete(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        context = self.get_context_data()
+        if context['cannot_delete']:
+            raise PermissionDenied()
+        return super(MeetingTypeDeleteView, self).delete(request, *args, **kwargs)
+
 
 meeting_type_delete = MeetingTypeDeleteView.as_view()
 
@@ -875,6 +923,42 @@ class DeskDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
 desk_delete = DeskDeleteView.as_view()
 
 
+class VirtualMemberAddView(ManagedAgendaMixin, CreateView):
+    template_name = 'chrono/manager_virtual_member_form.html'
+    form_class = VirtualMemberForm
+    model = VirtualMember
+
+    def get_form_kwargs(self):
+        kwargs = super(VirtualMemberAddView, self).get_form_kwargs()
+        kwargs['initial']['virtual_agenda'] = kwargs['initial']['agenda']
+        return kwargs
+
+
+agenda_add_virtual_member = VirtualMemberAddView.as_view()
+
+
+class VirtualMemberDeleteView(DeleteView):
+    template_name = 'chrono/manager_confirm_virtual_member_delete.html'
+    model = VirtualMember
+
+    def dispatch(self, request, *args, **kwargs):
+        self.agenda = self.get_object().virtual_agenda
+        if not self.agenda.can_be_managed(request.user):
+            raise PermissionDenied()
+        return super(VirtualMemberDeleteView, self).dispatch(request, *args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        context = super(VirtualMemberDeleteView, self).get_context_data(**kwargs)
+        context['agenda'] = self.agenda
+        return context
+
+    def get_success_url(self):
+        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.pk})
+
+
+virtual_member_delete = VirtualMemberDeleteView.as_view()
+
+
 class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
     template_name = 'chrono/manager_time_period_exception_form.html'
     model = TimePeriodException
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 50e3748..af2f5e3 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -24,6 +24,7 @@ from chrono.agendas.models import (
     TimePeriod,
     TimePeriodException,
     TimePeriodExceptionSource,
+    VirtualMember,
 )
 
 pytestmark = pytest.mark.django_db
@@ -2114,3 +2115,146 @@ def test_import_agenda(app, admin_user):
     resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
     resp = resp.form.submit()
     assert u'Missing &quot;gé1&quot; role' in resp.text
+
+
+def test_virtual_agenda_add(app, admin_user):
+    app = login(app)
+    resp = app.get('/manage/', status=200)
+    resp = resp.click('New')
+    resp.form['label'] = 'Virtual agenda'
+    resp.form['kind'] = 'virtual'
+    resp = resp.form.submit()
+    agenda = Agenda.objects.get(label='Virtual agenda')
+    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
+
+
+def test_virtual_agenda_baseview_empty(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    app = login(app)
+    resp = app.get(agenda.get_absolute_url())
+    assert 'Settings' in resp.text
+    assert 'My Virtual agenda' in resp.text
+    assert 'Included Agendas' in resp.text
+    assert 'This virtual agenda is empty.' in resp.text
+    assert '/manage/agendas/%s/settings' % agenda.pk in resp.text
+
+
+def test_virtual_agenda_baseview(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
+    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
+
+    app = login(app)
+    resp = app.get(agenda.get_absolute_url())
+    assert 'Settings' in resp.text
+    assert 'My Virtual agenda' in resp.text
+    assert 'Included Agendas' in resp.text
+    assert 'This virtual agenda is empty.' not in resp.text
+    for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
+        assert real_agenda.label in resp.text
+        assert real_agenda.slug in resp.text
+        assert real_agenda.get_absolute_url() in resp.text
+
+
+def test_virtual_agenda_settings_empty(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    assert 'Include Agenda' in resp.text
+    assert 'Options' in resp.text
+    assert 'Export' in resp.text
+    assert 'Delete' in resp.text
+    assert 'Included Agendas' in resp.text
+    assert "This virtual agenda doesn't include any agenda yet" in resp.text
+    # No meeting types yet
+    assert 'Meeting Types' not in resp.text
+
+
+def test_virtual_agenda_settings(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
+    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
+    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
+    mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
+
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    assert "This virtual agenda doesn't include any agenda yet" not in resp.text
+    for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
+        assert real_agenda.slug in resp.text
+        assert '/manage/agendas/%s/settings' % real_agenda.pk in resp.text
+
+    assert 'Meeting Types' in resp.text
+    assert 'MT' in resp.text
+    assert 'mt' in resp.text
+    assert '10' in resp.text
+
+    # Error message when incompatible meeting types
+    mt2.delete()
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    assert "This virtual agenda doesn't have any meeting type." in resp.text
+
+
+def test_virtual_agenda_settings_include(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    Agenda.objects.create(label='Event agenda', kind='events')
+    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
+    Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
+
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    resp = resp.click('Include Agenda')
+    # Only meetings agenda are proposed (2) + 1 empty choice = 3
+    assert len(resp.form['real_agenda'].options) == 3
+    # Include a real agenda
+    resp.form['real_agenda'].value = meeting_agenda_1.pk
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
+    assert VirtualMember.objects.get(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
+
+    resp = resp.follow()
+    resp = resp.click('Include Agenda')
+    # The previously include agenda is not proposed any more
+    assert len(resp.form['real_agenda'].options) == 2
+
+
+def test_virtual_agenda_settings_include_incompatible_agenda(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
+    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
+
+    # This one is incompatible because of its meeting type
+    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
+    MeetingType.objects.create(agenda=meeting_agenda_2, label='AA', slug='aa', duration=30)
+
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    resp = resp.click('Include Agenda')
+    resp.form['real_agenda'].value = meeting_agenda_2.pk
+    resp = resp.form.submit()
+    assert 'This agenda does not the meetingtypes provided by the virtualagenda.' in resp.text
+    assert meeting_agenda_2.virtual_agendas.count() == 0
+
+
+def test_cant_delete_meetingtype_used_by_virtual_agenda(app, admin_user):
+    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
+    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
+    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
+    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
+    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
+    mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
+
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_2.pk)
+    resp = resp.click('MT')
+    resp = resp.click('Delete')
+    assert 'This cannot be removed as it used by a virtual agenda' in resp.text
+    assert 'disabled' in resp.text
+
+    resp = app.post('/manage/meetingtypes/%s/delete' % mt.pk, status=403)
-- 
2.20.1

