diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py index f3abff79d..75b97fd08 100644 --- a/ietf/meeting/forms.py +++ b/ietf/meeting/forms.py @@ -18,7 +18,7 @@ from ietf.meeting.helpers import get_next_interim_number, make_materials_directo from ietf.meeting.helpers import is_meeting_approved, get_next_agenda_name from ietf.message.models import Message from ietf.person.models import Person -from ietf.utils.fields import DatepickerDateField, DurationField +from ietf.utils.fields import DatepickerDateField, DurationField, MultiEmailField from ietf.utils.validators import ( validate_file_size, validate_mime_type, validate_file_extension, validate_no_html_frame) @@ -345,4 +345,8 @@ class FileUploadForm(forms.Form): validate_no_html_frame(file) return file - +class RequestMinutesForm(forms.Form): + to = MultiEmailField() + cc = MultiEmailField(required=False) + subject = forms.CharField() + body = forms.CharField(widget=forms.Textarea,strip=False) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index af5963772..416c79759 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -24,12 +24,12 @@ from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_r from ietf.meeting.helpers import send_interim_approval_request from ietf.meeting.helpers import send_interim_cancellation_notice from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates -from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule +from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting from ietf.meeting.utils import finalize from ietf.name.models import SessionStatusName from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent -from ietf.utils.mail import outbox +from ietf.utils.mail import outbox, empty_outbox from ietf.utils.text import xslugify from ietf.person.factories import PersonFactory @@ -1997,3 +1997,24 @@ class SessionTests(TestCase): self.assertEqual(r.status_code,200) self.assertTrue(requested_session.group.acronym in unicontent(r)) self.assertTrue(not_meeting.group.acronym in unicontent(r)) + + def test_request_minutes(self): + meeting = MeetingFactory(type_id='ietf') + area = GroupFactory(type_id='area') + has_minutes = SessionFactory(meeting=meeting,group__parent=area) + has_no_minutes = SessionFactory(meeting=meeting,group__parent=area) + SessionPresentation.objects.create(session=has_minutes,document=DocumentFactory(type_id='minutes')) + + empty_outbox() + url = urlreverse('ietf.meeting.views.request_minutes',kwargs={'num':meeting.number}) + login_testing_unauthorized(self,"secretary",url) + r = self.client.get(url) + self.assertNotIn(has_minutes.group.acronym, unicontent(r).lower()) + self.assertIn(has_no_minutes.group.acronym, unicontent(r).lower()) + r = self.client.post(url,{'to':'wgchairs@ietf.org', + 'cc': 'irsg@irtf.org', + 'subject': 'I changed the subject', + 'body': 'corpus', + }) + self.assertEqual(r.status_code,302) + self.assertEqual(len(outbox),1) diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index a8323c4b0..69a2a3885 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -83,6 +83,7 @@ type_ietf_only_patterns_id_optional = [ url(r'^week-view(?:.html)?/?$', views.week_view), url(r'^room-view(?:.html)?/?$', views.room_view), url(r'^materials(?:.html)?/?$', views.materials), + url(r'^request_minutes/?$', views.request_minutes), url(r'^materials/%(document)s((?P\.[a-z0-9]+)|/)?$' % settings.URL_REGEXPS, views.materials_document), url(r'^session/?$', views.materials_editable_groups), url(r'^proceedings(?:.html)?/?$', views.proceedings), diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 56ebac980..66e49a03b 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -63,14 +63,14 @@ from ietf.secr.proceedings.utils import handle_upload_file from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files, import_youtube_video_urls, create_recording) from ietf.utils.decorators import require_api_key -from ietf.utils.mail import send_mail_message +from ietf.utils.mail import send_mail_message, send_mail_text from ietf.utils.pipe import pipe from ietf.utils.pdf import pdf_pages from ietf.utils.text import xslugify from ietf.utils.validators import get_mime_type from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm, - InterimCancelForm, InterimSessionInlineFormSet, FileUploadForm) + InterimCancelForm, InterimSessionInlineFormSet, FileUploadForm, RequestMinutesForm,) def get_menu_entries(request): @@ -139,7 +139,7 @@ def materials(request, num=None): if date_list: setattr(event, 'last_update', sorted(date_list, reverse=True)[0]) return render(request, "meeting/materials.html", { - 'meeting_num': meeting.number, + 'meeting': meeting, 'plenaries': plenaries, 'ietf': ietf, 'training': training, @@ -2284,3 +2284,39 @@ def edit_timeslot_type(request, num, slot_id): return render(request, 'meeting/edit_timeslot_type.html', {'timeslot':timeslot,'form':form,'sessions':sessions}) +@role_required('Secretariat') +def request_minutes(request, num=None): + meeting = get_ietf_meeting(num) + if request.method=='POST': + form = RequestMinutesForm(data=request.POST) + if form.is_valid(): + send_mail_text(request, + to=form.cleaned_data.get('to'), + frm=request.user.person.email_address(), + subject=form.cleaned_data.get('subject'), + txt=form.cleaned_data.get('body'), + cc=form.cleaned_data.get('cc'), + ) + return HttpResponseRedirect(reverse('ietf.meeting.views.materials',kwargs={'num':num})) + else: + needs_minutes = set() + for a in meeting.agenda.assignments.filter(session__group__type_id__in=('wg','rg')): + if not a.session.all_meeting_minutes(): + group = a.session.group + if group.parent and group.parent.type_id in ('area','irtf'): + needs_minutes.add(a.session.group) + needs_minutes = list(needs_minutes) + needs_minutes.sort(key=lambda g: ('zzz' if g.parent.acronym == 'irtf' else g.parent.acronym)+":"+g.acronym) + body_context = {'meeting':meeting, + 'needs_minutes':needs_minutes, + 'settings':settings, + } + body = render_to_string('meeting/request_minutes.txt', body_context) + initial = {'to': 'wgchairs@ietf.org', + 'cc': 'irsg@irtf.org', + 'subject': 'Request for IETF WG and Bof Session Minutes', + 'body': body, + } + form = RequestMinutesForm(initial=initial) + context = {'meeting':meeting, 'form': form} + return render(request, 'meeting/request_minutes.html', context) diff --git a/ietf/templates/meeting/materials.html b/ietf/templates/meeting/materials.html index 4c30f244f..37eff253d 100644 --- a/ietf/templates/meeting/materials.html +++ b/ietf/templates/meeting/materials.html @@ -10,14 +10,14 @@ {% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %} -{% block title %}IETF {{ meeting_num }} preliminary & interim materials{% endblock %} +{% block title %}IETF {{ meeting.number }} preliminary & interim materials{% endblock %} {% block content %} {% origin %}
-

IETF {{ meeting_num }} meeting materials

+

IETF {{ meeting.number }} meeting materials

{% if submission_started %}

@@ -29,8 +29,11 @@

{% if user|has_role:"Secretariat" %} Secretariat proceedings functions + {% if meeting.end_date.today > meeting.end_date %} + Send request for minutes + {% endif %} {% endif %} - Meeting requests/conflicts + Meeting requests/conflicts

{% with "True" as show_agenda %} diff --git a/ietf/templates/meeting/request_minutes.html b/ietf/templates/meeting/request_minutes.html new file mode 100644 index 000000000..194412f34 --- /dev/null +++ b/ietf/templates/meeting/request_minutes.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2018, All Rights Reserved #} +{% load origin %} + +{% load ietf_filters staticfiles bootstrap3 %} + +{% block morecss %} +#id_body {height:700px;} +{% endblock %} + +{% block title %}IETF {{ meeting.number }}: Request Minutes{% endblock %} + +{% block content %} + {% origin %} +
+
+ +

IETF {{ meeting.number }}: Request Minutes
+ {{meeting.city}}, {{meeting.country}} -- {{meeting.venue_name}} +

+
+
+
+
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
+
+ +{% endblock %} + diff --git a/ietf/templates/meeting/request_minutes.txt b/ietf/templates/meeting/request_minutes.txt new file mode 100644 index 000000000..c579fa955 --- /dev/null +++ b/ietf/templates/meeting/request_minutes.txt @@ -0,0 +1,21 @@ +{% autoescape off %} Dear WG Chairs and BOF Chairs, + +The secretariat is in the process of compiling the proceedings for {{meeting }} +and we are still missing meeting minutes from various sessions. + +The cutoff for submissions is {{ meeting.get_submission_cut_off_date }}, and the correction submissions +cutoff is {{ meeting.get_submission_correction_date }}. + +Please upload meeting minutes, as well as any presentations from your +sessions, at your earliest convenience using the Meeting Materials Manager +found here: {{ settings.IDTRACKER_BASE_URL }}{% url 'ietf.meeting.views.materials' num=meeting.number %} + +Alternatively, you are welcome to send them to iesg-secretary@ietf.org for +manual posting. + +Groups that are missing minutes:{% for group in needs_minutes %}{% ifchanged group.parent %} + +{{group.parent.name}}:{% endifchanged %} + {{ group.acronym | upper }}{% if group.state_id == 'bof' %} (BoF){% endif %}{% endfor %} + +{% endautoescape %}