From 10903610be587e9c3068ce350d38349422cb7d69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 18 May 2020 16:45:35 +0200
Subject: [PATCH 4/8] agendas: add resource to agenda (#38942)

---
 chrono/manager/forms.py                       |  8 +++
 .../chrono/manager_agenda_resource_form.html  | 23 ++++++++
 .../manager_meetings_agenda_settings.html     | 31 ++++++++++
 .../chrono/manager_resource_detail.html       | 29 ++++++++++
 chrono/manager/urls.py                        | 10 ++++
 chrono/manager/views.py                       | 36 ++++++++++++
 tests/test_manager.py                         | 58 +++++++++++++++++++
 7 files changed, 195 insertions(+)
 create mode 100644 chrono/manager/templates/chrono/manager_agenda_resource_form.html

diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 5ac66f4..a2934af 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -102,6 +102,14 @@ class EventForm(forms.ModelForm):
         exclude = ['full', 'meeting_type', 'desk', 'resources']
 
 
+class AgendaResourceForm(forms.Form):
+    resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['resource'].queryset = Resource.objects.exclude(agenda=self.initial['agenda'])
+
+
 class NewMeetingTypeForm(forms.ModelForm):
     class Meta:
         model = MeetingType
diff --git a/chrono/manager/templates/chrono/manager_agenda_resource_form.html b/chrono/manager/templates/chrono/manager_agenda_resource_form.html
new file mode 100644
index 0000000..4947bb9
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_agenda_resource_form.html
@@ -0,0 +1,23 @@
+{% extends "chrono/manager_agenda_view.html" %}
+{% load i18n %}
+
+{% block breadcrumb %}
+{{ block.super }}
+<a href="">{% trans "Add resource" %}</a>
+{% endblock %}
+
+{% block appbar %}
+<h2>{% trans "Add resource" %}</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.pk %}">{% trans 'Cancel' %}</a>
+  </div>
+</form>
+{% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html b/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
index 84cbf81..a6b87a8 100644
--- a/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
+++ b/chrono/manager/templates/chrono/manager_meetings_agenda_settings.html
@@ -13,6 +13,7 @@
 {% endblock %}
 
 {% block agenda-extra-management-actions %}
+  {% if has_resources %}<a rel="popup" href="{% url 'chrono-manager-agenda-add-resource' pk=object.pk %}">{% trans 'Add resource' %}</a>{% endif %}
   <a rel="popup" href="{% url 'chrono-manager-agenda-add-meeting-type' pk=object.id %}">{% trans 'New Meeting Type' %}</a>
   <a rel="popup" href="{% url 'chrono-manager-agenda-add-desk' pk=object.id %}">{% trans 'New Desk' %}</a>
 {% endblock %}
@@ -97,4 +98,34 @@
 </div>
 </div>
 
+{% with object.resources.all as agenda_resources %}
+{% if has_resources %}
+<div class="section">
+<h3>{% trans 'Resources' %}</h3>
+<div>
+{% if agenda_resources %}
+  <ul class="objects-list single-links">
+    {% for resource in agenda_resources %}
+    <li>
+        <a href="{% url 'chrono-manager-resource-view' pk=resource.pk %}">
+            {{ resource.label }}
+            <span class="identifier">[{% trans "identifier:" %} {{ resource.slug }}]</span>
+        </a>
+        <a rel="popup" class="delete" href="{% url 'chrono-manager-agenda-delete-resource' pk=object.pk resource_pk=resource.pk %}">{% trans "remove" %}</a>
+    </li>
+    {% endfor %}
+  </ul>
+{% else %}
+<div class="big-msg-info">
+  {% blocktrans %}
+  This agenda doesn't have any resource yet. Click on the "Add resource" button in
+  the top right of the page to add a first one.
+  {% endblocktrans %}
+</div>
+{% endif %}
+</div>
+</div>
+{% endif %}
+{% endwith %}
+
 {% endblock %}
diff --git a/chrono/manager/templates/chrono/manager_resource_detail.html b/chrono/manager/templates/chrono/manager_resource_detail.html
index 4287718..78b54bf 100644
--- a/chrono/manager/templates/chrono/manager_resource_detail.html
+++ b/chrono/manager/templates/chrono/manager_resource_detail.html
@@ -17,3 +17,32 @@
 <a rel="popup" href="{% url 'chrono-manager-resource-delete' pk=object.pk %}">{% trans 'Delete' %}</a>
 </span>
 {% endblock %}
+
+{% block content %}
+
+<div class="section">
+<h3>{% trans 'Used in meetings agendas' %}</h3>
+<div>
+{% with object.agenda_set.all as agendas %}
+{% if agendas %}
+  <ul class="objects-list single-links">
+    {% for agenda in agendas %}
+    <li>
+        <a href="{% url 'chrono-manager-agenda-settings' pk=agenda.pk %}">
+            {{ agenda.label }}
+        </a>
+    </li>
+    {% endfor %}
+  </ul>
+{% else %}
+<div class="big-msg-info">
+  {% blocktrans %}
+  This resource is not used yet.
+  {% endblocktrans %}
+</div>
+{% endif %}
+{% endwith %}
+</div>
+</div>
+
+{% endblock %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 55d5112..810223f 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -63,6 +63,16 @@ urlpatterns = [
         views.event_delete,
         name='chrono-manager-event-delete',
     ),
+    url(
+        r'^agendas/(?P<pk>\d+)/add-resource/$',
+        views.agenda_add_resource,
+        name='chrono-manager-agenda-add-resource',
+    ),
+    url(
+        r'^agendas/(?P<pk>\d+)/resource/(?P<resource_pk>\d+)/delete/$',
+        views.agenda_delete_resource,
+        name='chrono-manager-agenda-delete-resource',
+    ),
     url(
         r'^agendas/(?P<pk>\d+)/add-meeting-type$',
         views.agenda_add_meeting_type,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 1e71457..a45a90e 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -79,6 +79,7 @@ from .forms import (
     VirtualMemberForm,
     ResourceAddForm,
     ResourceEditForm,
+    AgendaResourceForm,
 )
 from .utils import import_site
 
@@ -787,6 +788,8 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
                 for virtual_member in self.object.get_virtual_members()
             ]
             context['meeting_types'] = self.object.iter_meetingtypes()
+        if self.agenda.kind == 'meetings':
+            context['has_resources'] = Resource.objects.exists()
         return context
 
     def get_events(self):
@@ -933,6 +936,39 @@ class EventDeleteView(ManagedAgendaMixin, DeleteView):
 event_delete = EventDeleteView.as_view()
 
 
+class AgendaAddResourceView(ManagedAgendaMixin, FormView):
+    template_name = 'chrono/manager_agenda_resource_form.html'
+    model = Event
+    form_class = AgendaResourceForm
+
+    def set_agenda(self, **kwargs):
+        self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'), kind='meetings')
+
+    def form_valid(self, form):
+        self.agenda.resources.add(form.cleaned_data['resource'])
+        return super().form_valid(form)
+
+
+agenda_add_resource = AgendaAddResourceView.as_view()
+
+
+class AgendaResourceDeleteView(ManagedAgendaMixin, DeleteView):
+    template_name = 'chrono/manager_confirm_delete.html'
+    model = Resource
+    pk_url_kwarg = 'resource_pk'
+
+    def set_agenda(self, **kwargs):
+        self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'), kind='meetings')
+
+    def delete(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        self.agenda.resources.remove(self.object)
+        return HttpResponseRedirect(self.get_success_url())
+
+
+agenda_delete_resource = AgendaResourceDeleteView.as_view()
+
+
 class AgendaAddMeetingTypeView(ManagedAgendaMixin, CreateView):
     template_name = 'chrono/manager_meeting_type_form.html'
     model = Event
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 2ff70bd..a489836 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -193,6 +193,19 @@ def test_add_resource(app, admin_user):
     assert resource.slug == 'foo-bar'
 
 
+def test_view_resource(app, admin_user):
+    agenda = Agenda.objects.create(label=u'Foo Bar', kind='meetings')
+    resource = Resource.objects.create(label='Resource 1')
+
+    app = login(app)
+    resp = app.get('/manage/resource/%s/' % resource.pk, status=200)
+    assert '/manage/agendas/%s/settings' % agenda.pk not in resp.text
+
+    agenda.resources.add(resource)
+    resp = app.get('/manage/resource/%s/' % resource.pk, status=200)
+    assert '/manage/agendas/%s/settings' % agenda.pk in resp.text
+
+
 def test_edit_resource(app, admin_user):
     resource = Resource.objects.create(label='Foo bar')
 
@@ -259,6 +272,7 @@ def test_options_agenda(app, admin_user):
     resp = resp.form.submit()
     assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
     resp = resp.follow()
+    assert 'has_resources' not in resp.context
     assert 'Foo baz' in resp.text
     assert '<h2>Settings' in resp.text
 
@@ -322,6 +336,50 @@ def test_options_agenda_as_manager(app, manager_user):
     assert '<h2>Settings' in resp.text
 
 
+def test_agenda_resources(app, admin_user):
+    agenda = Agenda.objects.create(label=u'Foo bar', kind='events')
+    resource = Resource.objects.create(label='Resource 1')
+    app = login(app)
+    # not for events agenda
+    app.get('/manage/agendas/%s/add-resource/' % agenda.pk, status=404)
+    app.get('/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk), status=404)
+
+
+def test_meetings_agenda_resources(app, admin_user):
+    agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
+    app = login(app)
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    assert 'has_resources' in resp.context
+    assert resp.context['has_resources'] is False
+    assert 'Add resource' not in resp.text
+
+    resource = Resource.objects.create(label='Resource 1')
+    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
+    assert 'has_resources' in resp.context
+    assert resp.context['has_resources'] is True
+    assert '/manage/resource/%s/' % resource.pk not in resp.text
+    assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) not in resp.text
+    resp = resp.click('Add resource')
+    assert list(resp.context['form'].fields['resource'].queryset) == [resource]
+    resp.form['resource'] = resource.pk
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert list(agenda.resources.all()) == [resource]
+    resp = resp.follow()
+    assert '/manage/resource/%s/' % resource.pk in resp.text
+    assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) in resp.text
+    resp = resp.click('Add resource')
+    assert list(resp.context['form'].fields['resource'].queryset) == []
+
+    resp = app.get('/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk))
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
+    assert list(agenda.resources.all()) == []
+    resp = resp.follow()
+    assert '/manage/resource/%s/' % resource.pk not in resp.text
+    assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) not in resp.text
+
+
 def test_delete_agenda(app, admin_user):
     agenda = Agenda(label=u'Foo bar')
     agenda.save()
-- 
2.20.1

