Projet

Général

Profil

0002-manager-add-views-for-virtual-agendas-37123.patch

Emmanuel Cazenave, 24 février 2020 16:48

Télécharger (23,7 ko)

Voir les différences:

Subject: [PATCH 2/2] manager: add views for virtual agendas (#37123)

 chrono/agendas/models.py                      |  20 +++
 chrono/manager/forms.py                       |  29 ++++
 ...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                         | 161 ++++++++++++++++++
 9 files changed, 441 insertions(+)
 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
chrono/agendas/models.py
107 107
    class Meta:
108 108
        ordering = ['label']
109 109

  
110
    def __str__(self):
111
        return self.label
112

  
110 113
    def save(self, *args, **kwargs):
111 114
        if not self.slug:
112 115
            self.slug = generate_slug(self)
......
176 179
            return MeetingType.objects.get(id=id_, agenda=self)
177 180
        return MeetingType.objects.get(slug=slug, agenda=self)
178 181

  
182
    def get_virtual_members(self):
183
        return VirtualMember.objects.filter(virtual_agenda=self)
184

  
179 185
    def get_base_meeting_duration(self):
180 186
        durations = [x.duration for x in self.iter_meetingtypes()]
181 187
        if not durations:
......
249 255
        Agenda, on_delete=models.CASCADE, related_name='virtual_members', verbose_name='Agenda'
250 256
    )
251 257

  
258
    def clean(self):
259
        for meetingtype in self.virtual_agenda.iter_meetingtypes():
260
            try:
261
                MeetingType.objects.get(
262
                    agenda=self.real_agenda,
263
                    label=meetingtype.label,
264
                    slug=meetingtype.slug,
265
                    duration=meetingtype.duration,
266
                )
267
            except MeetingType.DoesNotExist:
268
                raise ValidationError(
269
                    _('This agenda does not have the meetingtypes provided by the virtualagenda.')
270
                )
271

  
252 272

  
253 273
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
254 274

  
chrono/manager/forms.py
34 34
    Desk,
35 35
    TimePeriodException,
36 36
    TimePeriodExceptionSource,
37
    VirtualMember,
37 38
    WEEKDAYS_LIST,
38 39
)
39 40

  
......
97 98
        }
98 99
        exclude = []
99 100

  
101
    def clean(self):
102
        super().clean()
103
        for virtual_agenda in self.instance.agenda.virtual_agendas.all():
104
            for mt in virtual_agenda.iter_meetingtypes():
105
                if (
106
                    mt.label == self.instance.label
107
                    and mt.slug == self.instance.slug
108
                    and mt.duration == self.instance.duration
109
                ):
110
                    raise ValidationError(
111
                        _('This meetingtype is used by a virtual agenda: %s' % virtual_agenda)
112
                    )
113

  
100 114

  
101 115
class TimePeriodAddForm(forms.Form):
102 116
    weekdays = forms.MultipleChoiceField(
......
161 175
        return self.cleaned_data['end_datetime']
162 176

  
163 177

  
178
class VirtualMemberForm(forms.ModelForm):
179
    class Meta:
180
        model = VirtualMember
181
        fields = ['virtual_agenda', 'real_agenda']
182
        widgets = {
183
            'virtual_agenda': forms.HiddenInput(),
184
        }
185

  
186
    def __init__(self, *args, **kwargs):
187
        super(VirtualMemberForm, self).__init__(*args, **kwargs)
188
        self.fields['real_agenda'].queryset = Agenda.objects.filter(kind='meetings').exclude(
189
            virtual_agendas__pk__in=[kwargs['initial']['agenda']]
190
        )
191

  
192

  
164 193
class ImportEventsForm(forms.Form):
165 194
    events_csv_file = forms.FileField(
166 195
        label=_('Events File'),
chrono/manager/templates/chrono/manager_confirm_virtual_member_delete.html
1
{% extends "chrono/manager_home.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
<h2>{% trans "Exclude Agenda" %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post">
10
  {% csrf_token %}
11
  <p>
12
  {% blocktrans %}Are you sure you want to exclude this agenda from the virtual agenda ?{% endblocktrans %}
13
  </p>
14
  <div class="buttons">
15
    <button class="delete-button">{% trans 'Exclude' %}</button>
16
    <a class="cancel" href="{% url 'chrono-manager-agenda-settings' agenda.pk %}">{% trans 'Cancel' %}</a>
17
  </div>
18
</form>
19
{% endblock %}
chrono/manager/templates/chrono/manager_virtual_agenda_settings.html
1
{% extends "chrono/manager_agenda_settings.html" %}
2
{% load i18n %}
3

  
4
{% block agenda-extra-management-actions %}
5
  <a rel="popup" href="{% url 'chrono-manager-agenda-add-virtual-member' pk=object.id %}">{% trans 'Include Agenda' %}</a>
6
{% endblock %}
7

  
8
{% block agenda-settings %}
9

  
10
<div class="section">
11
<h3>{% trans 'Included Agendas' %}</h3>
12
<div>
13
{% if virtual_members.count %}
14
  <ul class="objects-list single-links">
15
    {% for virtual_member in virtual_members %}
16
    <li><a href="{% url 'chrono-manager-agenda-settings' pk=virtual_member.real_agenda.id %}">
17
        {{real_agenda.label}}
18
        <span class="identifier">[{% trans "identifier:" %} {{virtual_member.real_agenda.slug}}]</span>
19
        </a>
20
        <a rel="popup" class="delete" href="{% url 'chrono-manager-virtual-member-delete' pk=virtual_member.pk %}">{% trans "remove" %}</a>
21
    </li>
22
    {% endfor %}
23
  </ul>
24
{% else %}
25
<div class="big-msg-info">
26
  {% blocktrans %}
27
  This virtual agenda doesn't include any agenda yet. Click on the "Include Agenda" button in
28
  the top right of the page to include a first one.
29
  {% endblocktrans %}
30
</div>
31
{% endif %}
32
</div>
33
</div>
34

  
35
{% if virtual_members.count %}
36
<div class="section">
37
<h3>{% trans 'Meeting Types' %}</h3>
38
<div>
39
{% if meeting_types %}
40
  <ul class="objects-list single-links">
41
    {% for meeting_type in meeting_types %}
42
    <li><a rel="popup" href="">
43
        {{meeting_type.label}}
44
        <span class="duration">({{meeting_type.duration}} {% trans "minutes" %})</span>
45
        <span class="identifier">[{% trans "identifier:" %} {{meeting_type.slug}}]</span>
46
        </a>
47
    </li>
48
    {% endfor %}
49
  </ul>
50
{% else %}
51
<div class="errornotice">
52
  {% blocktrans %}
53
  This virtual agenda doesn't have any meeting type.
54
  It is probably because its included agendas have incompatible meeting types
55
  and it makes this virtual agenda unusable.
56
  {% endblocktrans %}
57
</div>
58
{% endif %}
59
</div>
60
</div>
61
{% endif %}
62

  
63

  
64
{% endblock %}
chrono/manager/templates/chrono/manager_virtual_agenda_view.html
1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n %}
3

  
4
{% block content %}
5
<div class="section">
6
<h3>{% trans 'Included Agendas' %}</h3>
7
<div>
8
{% if agenda.real_agendas.count %}
9
  <ul class="objects-list single-links">
10
    {% for real_agenda in agenda.real_agendas.all %}
11
    <li><a href="{% url 'chrono-manager-agenda-view' pk=real_agenda.pk %}">
12
        {{real_agenda.label}}
13
        <span class="identifier">[{% trans "identifier:" %} {{real_agenda.slug}}]</span>
14
        </a>
15
    {% endfor %}
16
  </ul>
17
{% else %}
18
<div class="big-msg-info">
19
  {% blocktrans %}
20
  This virtual agenda is empty.
21
  {% endblocktrans %}
22
</div>
23
{% endif %}
24
</div>
25
</div>
26
{% endblock %}
chrono/manager/templates/chrono/manager_virtual_member_form.html
1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n %}
3

  
4
{% block extrascripts %}
5
{{ block.super }}
6
{{ form.media }}
7
{% endblock %}
8

  
9
{% block breadcrumb %}
10
{{ block.super }}
11
<a href="">{% trans "Include Agenda" %}</a>
12
{% endblock %}
13

  
14
{% block appbar %}
15
<h2>{% trans "Include Agenda" %}</h2>
16
{% endblock %}
17

  
18
{% block content %}
19

  
20
<form method="post" enctype="multipart/form-data">
21
  {% csrf_token %}
22
  {{ form.as_p }}
23
  <div class="buttons">
24
    <button class="submit-button">{% trans "Save" %}</button>
25
    <a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a>
26
  </div>
27
</form>
28
{% endblock %}
chrono/manager/urls.py
93 93
        views.desk_import_time_period_exceptions,
94 94
        name='chrono-manager-desk-add-import-time-period-exceptions',
95 95
    ),
96
    url(
97
        r'^agendas/(?P<pk>\d+)/add-virtual-member$',
98
        views.agenda_add_virtual_member,
99
        name='chrono-manager-agenda-add-virtual-member',
100
    ),
101
    url(
102
        r'^virtual-members/(?P<pk>\d+)/delete$',
103
        views.virtual_member_delete,
104
        name='chrono-manager-virtual-member-delete',
105
    ),
96 106
    url(
97 107
        r'^time-period-exceptions/(?P<pk>\d+)/edit$',
98 108
        views.time_period_exception_edit,
chrono/manager/views.py
23 23
from django.forms import ValidationError
24 24
from django.http import Http404, HttpResponse, HttpResponseRedirect
25 25
from django.shortcuts import get_object_or_404
26
from django.template.response import TemplateResponse
26 27
from django.urls import reverse, reverse_lazy
27 28
from django.utils.dates import MONTHS
28 29
from django.utils.timezone import now, make_aware, make_naive
......
53 54
    ICSError,
54 55
    AgendaImportError,
55 56
    TimePeriodExceptionSource,
57
    VirtualMember,
56 58
)
57 59

  
58 60
from .forms import (
......
71 73
    AgendasImportForm,
72 74
    TimePeriodAddForm,
73 75
    TimePeriodExceptionSourceReplaceForm,
76
    VirtualMemberForm,
74 77
)
75 78
from .utils import import_site
76 79

  
......
241 244
class AgendaView(ViewableAgendaMixin, View):
242 245
    def get(self, request, *args, **kwargs):
243 246
        today = datetime.date.today()
247
        if self.agenda.kind == 'virtual':
248
            return TemplateResponse(
249
                request=request,
250
                template='chrono/manager_virtual_agenda_view.html',
251
                context={
252
                    'agenda': self.agenda,
253
                    'object': self.agenda,
254
                    'user_can_manage': self.agenda.can_be_managed(self.request.user),
255
                },
256
            )
257

  
244 258
        if self.agenda.kind == 'meetings':
245 259
            # redirect to today view
246 260
            return HttpResponseRedirect(
......
633 647
class AgendaSettings(ManagedAgendaMixin, DetailView):
634 648
    model = Agenda
635 649

  
650
    def get_context_data(self, **kwargs):
651
        context = super(AgendaSettings, self).get_context_data(**kwargs)
652
        if self.agenda.kind == 'virtual':
653
            context['virtual_members'] = self.object.get_virtual_members()
654
            context['meeting_types'] = self.object.iter_meetingtypes()
655
        return context
656

  
636 657
    def get_events(self):
637 658
        return Event.annotate_queryset(Event.objects.filter(agenda=self.agenda).select_related('agenda'))
638 659

  
......
794 815
    template_name = 'chrono/manager_confirm_delete.html'
795 816
    model = MeetingType
796 817

  
818
    def get_context_data(self, **kwargs):
819
        context = super(MeetingTypeDeleteView, self).get_context_data(**kwargs)
820
        cannot_delete = False
821
        meeting_type = self.get_object()
822
        for virtual_agenda in self.get_object().agenda.virtual_agendas.all():
823
            for mt in virtual_agenda.iter_meetingtypes():
824
                if (
825
                    meeting_type.slug == mt.slug
826
                    and meeting_type.label == mt.label
827
                    and meeting_type.duration == mt.duration
828
                ):
829
                    cannot_delete = True
830
                    context['cannot_delete_msg'] = _(
831
                        'This cannot be removed as it used by a virtual agenda: %(agenda)s'
832
                        % {'agenda': virtual_agenda}
833
                    )
834
                    break
835
        context['cannot_delete'] = cannot_delete
836
        return context
837

  
838
    def delete(self, request, *args, **kwargs):
839
        self.object = self.get_object()
840
        context = self.get_context_data()
841
        if context['cannot_delete']:
842
            raise PermissionDenied()
843
        return super(MeetingTypeDeleteView, self).delete(request, *args, **kwargs)
844

  
797 845

  
798 846
meeting_type_delete = MeetingTypeDeleteView.as_view()
799 847

  
......
875 923
desk_delete = DeskDeleteView.as_view()
876 924

  
877 925

  
926
class VirtualMemberAddView(ManagedAgendaMixin, CreateView):
927
    template_name = 'chrono/manager_virtual_member_form.html'
928
    form_class = VirtualMemberForm
929
    model = VirtualMember
930

  
931
    def get_form_kwargs(self):
932
        kwargs = super(VirtualMemberAddView, self).get_form_kwargs()
933
        kwargs['initial']['virtual_agenda'] = kwargs['initial']['agenda']
934
        return kwargs
935

  
936

  
937
agenda_add_virtual_member = VirtualMemberAddView.as_view()
938

  
939

  
940
class VirtualMemberDeleteView(DeleteView):
941
    template_name = 'chrono/manager_confirm_virtual_member_delete.html'
942
    model = VirtualMember
943

  
944
    def dispatch(self, request, *args, **kwargs):
945
        self.agenda = self.get_object().virtual_agenda
946
        if not self.agenda.can_be_managed(request.user):
947
            raise PermissionDenied()
948
        return super(VirtualMemberDeleteView, self).dispatch(request, *args, **kwargs)
949

  
950
    def get_context_data(self, **kwargs):
951
        context = super(VirtualMemberDeleteView, self).get_context_data(**kwargs)
952
        context['agenda'] = self.agenda
953
        return context
954

  
955
    def get_success_url(self):
956
        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.pk})
957

  
958

  
959
virtual_member_delete = VirtualMemberDeleteView.as_view()
960

  
961

  
878 962
class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
879 963
    template_name = 'chrono/manager_time_period_exception_form.html'
880 964
    model = TimePeriodException
tests/test_manager.py
24 24
    TimePeriod,
25 25
    TimePeriodException,
26 26
    TimePeriodExceptionSource,
27
    VirtualMember,
27 28
)
28 29

  
29 30
pytestmark = pytest.mark.django_db
......
2114 2115
    resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
2115 2116
    resp = resp.form.submit()
2116 2117
    assert u'Missing &quot;gé1&quot; role' in resp.text
2118

  
2119

  
2120
def test_virtual_agenda_add(app, admin_user):
2121
    app = login(app)
2122
    resp = app.get('/manage/', status=200)
2123
    resp = resp.click('New')
2124
    resp.form['label'] = 'Virtual agenda'
2125
    resp.form['kind'] = 'virtual'
2126
    resp = resp.form.submit()
2127
    agenda = Agenda.objects.get(label='Virtual agenda')
2128
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
2129

  
2130

  
2131
def test_virtual_agenda_baseview_empty(app, admin_user):
2132
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2133
    app = login(app)
2134
    resp = app.get(agenda.get_absolute_url())
2135
    assert 'Settings' in resp.text
2136
    assert 'My Virtual agenda' in resp.text
2137
    assert 'Included Agendas' in resp.text
2138
    assert 'This virtual agenda is empty.' in resp.text
2139
    assert '/manage/agendas/%s/settings' % agenda.pk in resp.text
2140

  
2141

  
2142
def test_virtual_agenda_baseview(app, admin_user):
2143
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2144
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2145
    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2146
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2147
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
2148

  
2149
    app = login(app)
2150
    resp = app.get(agenda.get_absolute_url())
2151
    assert 'Settings' in resp.text
2152
    assert 'My Virtual agenda' in resp.text
2153
    assert 'Included Agendas' in resp.text
2154
    assert 'This virtual agenda is empty.' not in resp.text
2155
    for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
2156
        assert real_agenda.label in resp.text
2157
        assert real_agenda.slug in resp.text
2158
        assert real_agenda.get_absolute_url() in resp.text
2159

  
2160

  
2161
def test_virtual_agenda_settings_empty(app, admin_user):
2162
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2163
    app = login(app)
2164
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2165
    assert 'Include Agenda' in resp.text
2166
    assert 'Options' in resp.text
2167
    assert 'Export' in resp.text
2168
    assert 'Delete' in resp.text
2169
    assert 'Included Agendas' in resp.text
2170
    assert "This virtual agenda doesn't include any agenda yet" in resp.text
2171
    # No meeting types yet
2172
    assert 'Meeting Types' not in resp.text
2173

  
2174

  
2175
def test_virtual_agenda_settings(app, admin_user):
2176
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2177
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2178
    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2179
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2180
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
2181
    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
2182
    mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
2183

  
2184
    app = login(app)
2185
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2186
    assert "This virtual agenda doesn't include any agenda yet" not in resp.text
2187
    for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
2188
        assert real_agenda.slug in resp.text
2189
        assert '/manage/agendas/%s/settings' % real_agenda.pk in resp.text
2190

  
2191
    assert 'Meeting Types' in resp.text
2192
    assert 'MT' in resp.text
2193
    assert 'mt' in resp.text
2194
    assert '10' in resp.text
2195

  
2196
    # Error message when incompatible meeting types
2197
    mt2.delete()
2198
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2199
    assert "This virtual agenda doesn't have any meeting type." in resp.text
2200

  
2201

  
2202
def test_virtual_agenda_settings_include(app, admin_user):
2203
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2204
    Agenda.objects.create(label='Event agenda', kind='events')
2205
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2206
    Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2207

  
2208
    app = login(app)
2209
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2210
    resp = resp.click('Include Agenda')
2211
    # Only meetings agenda are proposed (2) + 1 empty choice = 3
2212
    assert len(resp.form['real_agenda'].options) == 3
2213
    # Include a real agenda
2214
    resp.form['real_agenda'].value = meeting_agenda_1.pk
2215
    resp = resp.form.submit()
2216
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
2217
    assert VirtualMember.objects.get(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2218

  
2219
    resp = resp.follow()
2220
    resp = resp.click('Include Agenda')
2221
    # The previously include agenda is not proposed any more
2222
    assert len(resp.form['real_agenda'].options) == 2
2223

  
2224

  
2225
def test_virtual_agenda_settings_include_incompatible_agenda(app, admin_user):
2226
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2227
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2228
    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
2229
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2230

  
2231
    # This one is incompatible because of its meeting type
2232
    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2233
    MeetingType.objects.create(agenda=meeting_agenda_2, label='AA', slug='aa', duration=30)
2234

  
2235
    app = login(app)
2236
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2237
    resp = resp.click('Include Agenda')
2238
    resp.form['real_agenda'].value = meeting_agenda_2.pk
2239
    resp = resp.form.submit()
2240
    assert 'This agenda does not the meetingtypes provided by the virtualagenda.' in resp.text
2241
    assert meeting_agenda_2.virtual_agendas.count() == 0
2242

  
2243

  
2244
def test_cant_delete_meetingtype_used_by_virtual_agenda(app, admin_user):
2245
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2246
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2247
    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2248
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2249
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
2250
    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
2251
    mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
2252

  
2253
    app = login(app)
2254
    resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_2.pk)
2255
    resp = resp.click('MT')
2256
    resp = resp.click('Delete')
2257
    assert 'This cannot be removed as it used by a virtual agenda' in resp.text
2258
    assert 'disabled' in resp.text
2259

  
2260
    resp = app.post('/manage/meetingtypes/%s/delete' % mt.pk, status=403)
2261

  
2262

  
2263
def test_cant_modify_meetingtype_used_by_virtual_agenda(app, admin_user):
2264
    agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
2265
    meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
2266
    meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
2267
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
2268
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
2269
    MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
2270
    mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
2271

  
2272
    app = login(app)
2273
    resp = app.get('/manage/meetingtypes/%s/edit' % mt.pk)
2274
    resp.form['label'].value = 'Oho'
2275
    resp = resp.form.submit()
2276
    mt = MeetingType.objects.get(pk=mt.pk)
2277
    assert mt.label == 'MT'
2117
-