Merged in [15125] from rjsparks@nostrum.com:

Added a view that generates the \'send minutes\' email for the secretariat. Fixes #2493.
 - Legacy-Id: 15134
Note: SVN reference [15125] has been migrated to Git commit 345bff8850
This commit is contained in:
Henrik Levkowetz 2018-05-07 12:03:14 +00:00
commit aeaf67af43
7 changed files with 130 additions and 10 deletions

View file

@ -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.meeting.helpers import is_meeting_approved, get_next_agenda_name
from ietf.message.models import Message from ietf.message.models import Message
from ietf.person.models import Person 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, from ietf.utils.validators import ( validate_file_size, validate_mime_type,
validate_file_extension, validate_no_html_frame) validate_file_extension, validate_no_html_frame)
@ -345,4 +345,8 @@ class FileUploadForm(forms.Form):
validate_no_html_frame(file) validate_no_html_frame(file)
return file return file
class RequestMinutesForm(forms.Form):
to = MultiEmailField()
cc = MultiEmailField(required=False)
subject = forms.CharField()
body = forms.CharField(widget=forms.Textarea,strip=False)

View file

@ -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_approval_request
from ietf.meeting.helpers import send_interim_cancellation_notice from ietf.meeting.helpers import send_interim_cancellation_notice
from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates 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.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import finalize from ietf.meeting.utils import finalize
from ietf.name.models import SessionStatusName from ietf.name.models import SessionStatusName
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent 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.utils.text import xslugify
from ietf.person.factories import PersonFactory from ietf.person.factories import PersonFactory
@ -1997,3 +1997,24 @@ class SessionTests(TestCase):
self.assertEqual(r.status_code,200) self.assertEqual(r.status_code,200)
self.assertTrue(requested_session.group.acronym in unicontent(r)) self.assertTrue(requested_session.group.acronym in unicontent(r))
self.assertTrue(not_meeting.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)

View file

@ -83,6 +83,7 @@ type_ietf_only_patterns_id_optional = [
url(r'^week-view(?:.html)?/?$', views.week_view), url(r'^week-view(?:.html)?/?$', views.week_view),
url(r'^room-view(?:.html)?/?$', views.room_view), url(r'^room-view(?:.html)?/?$', views.room_view),
url(r'^materials(?:.html)?/?$', views.materials), url(r'^materials(?:.html)?/?$', views.materials),
url(r'^request_minutes/?$', views.request_minutes),
url(r'^materials/%(document)s((?P<ext>\.[a-z0-9]+)|/)?$' % settings.URL_REGEXPS, views.materials_document), url(r'^materials/%(document)s((?P<ext>\.[a-z0-9]+)|/)?$' % settings.URL_REGEXPS, views.materials_document),
url(r'^session/?$', views.materials_editable_groups), url(r'^session/?$', views.materials_editable_groups),
url(r'^proceedings(?:.html)?/?$', views.proceedings), url(r'^proceedings(?:.html)?/?$', views.proceedings),

View file

@ -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, from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
import_youtube_video_urls, create_recording) import_youtube_video_urls, create_recording)
from ietf.utils.decorators import require_api_key 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.pipe import pipe
from ietf.utils.pdf import pdf_pages from ietf.utils.pdf import pdf_pages
from ietf.utils.text import xslugify from ietf.utils.text import xslugify
from ietf.utils.validators import get_mime_type from ietf.utils.validators import get_mime_type
from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm, from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
InterimCancelForm, InterimSessionInlineFormSet, FileUploadForm) InterimCancelForm, InterimSessionInlineFormSet, FileUploadForm, RequestMinutesForm,)
def get_menu_entries(request): 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]) if date_list: setattr(event, 'last_update', sorted(date_list, reverse=True)[0])
return render(request, "meeting/materials.html", { return render(request, "meeting/materials.html", {
'meeting_num': meeting.number, 'meeting': meeting,
'plenaries': plenaries, 'plenaries': plenaries,
'ietf': ietf, 'ietf': ietf,
'training': training, '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}) 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)

View file

@ -10,14 +10,14 @@
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %} {% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
{% block title %}IETF {{ meeting_num }} preliminary &amp; interim materials{% endblock %} {% block title %}IETF {{ meeting.number }} preliminary &amp; interim materials{% endblock %}
{% block content %} {% block content %}
{% origin %} {% origin %}
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">
<h1>IETF {{ meeting_num }} meeting materials</h1> <h1>IETF {{ meeting.number }} meeting materials</h1>
{% if submission_started %} {% if submission_started %}
<p class="alert alert-info"> <p class="alert alert-info">
@ -29,8 +29,11 @@
<p> <p>
{% if user|has_role:"Secretariat" %} {% if user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url 'ietf.secr.proceedings.views.main' %}">Secretariat proceedings functions</a> <a class="btn btn-default" href="{% url 'ietf.secr.proceedings.views.main' %}">Secretariat proceedings functions</a>
{% if meeting.end_date.today > meeting.end_date %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.request_minutes' num=meeting.number %}">Send request for minutes</a>
{% endif %}
{% endif %} {% endif %}
<a class="btn btn-default" href="/meeting/{{meeting_num}}/requests">Meeting requests/conflicts</a> <a class="btn btn-default" href="/meeting/{{meeting.number}}/requests">Meeting requests/conflicts</a>
</p> </p>
{% with "True" as show_agenda %} {% with "True" as show_agenda %}

View file

@ -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 %}
<div class="row">
<div class="col-md-10">
<h1 class="title">IETF {{ meeting.number }}: Request Minutes <br>
<small>{{meeting.city}}, {{meeting.country}} -- {{meeting.venue_name}}</small>
</h1>
</div>
</div>
<div class="row">
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-danger">Send</button>
{% endbuttons %}
</form>
</div>
{% endblock %}

View file

@ -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 %}