Skip to content

Commit 8fef55d

Browse files
committed
add approval and cancel notifications
- Legacy-Id: 11196
1 parent 3366006 commit 8fef55d

10 files changed

Lines changed: 180 additions & 13 deletions

File tree

ietf/meeting/helpers.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.db.models import Max, Q, Prefetch, F
1010
from django.conf import settings
1111
from django.core.cache import cache
12+
from django.core.urlresolvers import reverse
1213
from django.utils.cache import get_cache_key
1314
from django.shortcuts import get_object_or_404
1415
from django.template.loader import render_to_string
@@ -20,9 +21,11 @@
2021
from ietf.group.models import Group
2122
from ietf.ietfauth.utils import has_role, user_is_person
2223
from ietf.liaisons.utils import get_person_for_user
24+
from ietf.mailtrigger.utils import gather_address_lists
2325
from ietf.person.models import Person
2426
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment
2527
from ietf.utils.history import find_history_active_at, find_history_replacements_active_at
28+
from ietf.utils.mail import send_mail
2629
from ietf.utils.pipe import pipe
2730

2831
def find_ads_for_meeting(meeting):
@@ -278,7 +281,6 @@ def agenda_permissions(meeting, schedule, user):
278281
return cansee, canedit, secretariat
279282

280283
def session_constraint_expire(request,session):
281-
from django.core.urlresolvers import reverse
282284
from ajax import session_constraints
283285
path = reverse(session_constraints, args=[session.meeting.number, session.pk])
284286
temp_request = HttpRequest()
@@ -492,6 +494,61 @@ def get_next_agenda_name(meeting):
492494
group=group.acronym,
493495
sequence=str(last_sequence + 1).zfill(2))
494496

497+
def send_interim_approval_request(meetings):
498+
"""Sends an email to the secretariat, group chairs, and resposnible area
499+
director or the IRTF chair noting that approval has been requested for a
500+
new interim meeting. Takes a list of one or more meetings."""
501+
group = meetings[0].session_set.first().group
502+
requester = meetings[0].session_set.first().requested_by
503+
(to_email, cc_list) = gather_address_lists('session_requested',group=group,person=requester)
504+
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
505+
subject = '{group} - New Interim Meeting Request'.format(group=group.acronym)
506+
template = 'meeting/interim_approval_request.txt'
507+
approval_urls = []
508+
for meeting in meetings:
509+
url = settings.IDTRACKER_BASE_URL + reverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
510+
approval_urls.append(url)
511+
if len(meetings) > 1:
512+
is_series = True
513+
else:
514+
is_series = False
515+
context = locals()
516+
send_mail(None,
517+
to_email,
518+
from_email,
519+
subject,
520+
template,
521+
context,
522+
cc=cc_list)
523+
524+
def send_interim_cancellation_notice(meeting):
525+
"""Sends an email that a scheduled interim meeting has been cancelled."""
526+
session = meeting.session_set.first()
527+
group = session.group
528+
to_email = settings.INTERIM_ANNOUNCE_TO_EMAIL
529+
(_, cc_list) = gather_address_lists('session_request_cancelled',group=group)
530+
from_email = settings.INTERIM_ANNOUNCE_FROM_EMAIL
531+
subject = '{group} ({acronym}) {type} Interim Meeting Cancelled (was {date})'.format(
532+
group=group.name,
533+
acronym=group.acronym,
534+
type=group.type.slug.upper(),
535+
date=meeting.date.strftime('%Y-%m-%d'))
536+
start_time = session.official_timeslotassignment().timeslot.time
537+
end_time = start_time + session.requested_duration
538+
if meeting.session_set.filter(status='sched').count() > 1:
539+
is_multi_day = True
540+
else:
541+
is_multi_day = False
542+
template = 'meeting/interim_cancellation_notice.txt'
543+
context = locals()
544+
send_mail(None,
545+
to_email,
546+
from_email,
547+
subject,
548+
template,
549+
context,
550+
cc=cc_list)
551+
495552
def sessions_post_save(forms):
496553
"""Helper function to perform various post save operations on each form of a
497554
InterimSessionModelForm formset"""
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
import datetime
5+
6+
from django.db import migrations
7+
8+
9+
def migrate_interim_meetings(apps, schema_editor):
10+
"""For all existing interim meetings create an official schedule and timeslot assignments"""
11+
Meeting = apps.get_model("meeting", "Meeting")
12+
Schedule = apps.get_model("meeting", "Schedule")
13+
TimeSlot = apps.get_model("meeting", "TimeSlot")
14+
SchedTimeSessAssignment = apps.get_model("meeting", "SchedTimeSessAssignment")
15+
Person = apps.get_model("person", "Person")
16+
system = Person.objects.get(name="(system)")
17+
18+
meetings = Meeting.objects.filter(type='interim')
19+
for meeting in meetings:
20+
if not meeting.agenda:
21+
meeting.agenda = Schedule.objects.create(
22+
meeting=meeting,
23+
owner=system,
24+
name='Official')
25+
meeting.save()
26+
session = meeting.session_set.first() # all legacy interim meetings have one session
27+
time = datetime.datetime.combine(meeting.date, datetime.time(0))
28+
slot = TimeSlot.objects.create(
29+
meeting=meeting,
30+
type_id="session",
31+
duration=session.requested_duration,
32+
time=time)
33+
SchedTimeSessAssignment.objects.create(
34+
timeslot=slot,
35+
session=session,
36+
schedule=meeting.agenda)
37+
38+
39+
class Migration(migrations.Migration):
40+
41+
dependencies = [
42+
('meeting', '0019_session_remote_instructions'),
43+
]
44+
45+
operations = [
46+
migrations.RunPython(migrate_interim_meetings),
47+
]

ietf/meeting/test_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ietf.doc.models import Document, State
44
from ietf.group.models import Group
55
from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, SchedTimeSessAssignment, ResourceAssociation, SessionPresentation
6-
from ietf.meeting.helpers import create_interim_meeting, assign_interim_session
6+
from ietf.meeting.helpers import create_interim_meeting
77
from ietf.name.models import RoomResourceName
88
from ietf.person.models import Person
99
from ietf.utils.test_data import make_test_data

ietf/meeting/tests_views.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from ietf.doc.models import Document
1313
from ietf.group.models import Group
1414
from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request
15+
from ietf.meeting.helpers import send_interim_approval_request
16+
from ietf.meeting.helpers import send_interim_cancellation_notice
1517
from ietf.meeting.models import Session, TimeSlot, Meeting
1618
from ietf.meeting.test_data import make_meeting_test_data
1719
from ietf.name.models import SessionStatusName
@@ -817,3 +819,19 @@ def test_interim_request_details_permissions(self):
817819
login_testing_unauthorized(self,"plain",url)
818820
r = self.client.get(url)
819821
self.assertEqual(r.status_code, 403)
822+
823+
def test_send_interim_approval_request(self):
824+
make_meeting_test_data()
825+
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
826+
length_before = len(outbox)
827+
send_interim_approval_request(meetings=[meeting])
828+
self.assertEqual(len(outbox),length_before+1)
829+
self.assertTrue('New Interim Meeting Request' in outbox[-1]['Subject'])
830+
831+
def test_send_interim_cancellation_notice(self):
832+
make_meeting_test_data()
833+
meeting = Meeting.objects.filter(type='interim',session__status='sched',session__group__acronym='mars').first()
834+
length_before = len(outbox)
835+
send_interim_cancellation_notice(meeting=meeting)
836+
self.assertEqual(len(outbox),length_before+1)
837+
self.assertTrue('Interim Meeting Cancelled' in outbox[-1]['Subject'])

ietf/meeting/views.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
4242
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
4343
from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
44+
from ietf.meeting.helpers import send_interim_cancellation_notice
45+
from ietf.meeting.helpers import send_interim_approval_request
4446
from ietf.utils.mail import send_mail_message
4547
from ietf.utils.pipe import pipe
4648
from ietf.utils.pdf import pdf_pages
@@ -959,7 +961,7 @@ def interim_send_announcement(request, number):
959961
'RG Chair')
960962
def interim_pending(request):
961963
'''View which shows interim meeting requests pending approval'''
962-
meetings = Meeting.objects.filter(type='interim', session__status='apprw').distinct()
964+
meetings = Meeting.objects.filter(type='interim', session__status='apprw').distinct().order_by('date')
963965
menu_entries = get_menu_entries(request)
964966
selected_menu_entry = 'pending'
965967

@@ -1006,14 +1008,16 @@ def interim_request(request):
10061008
formset = SessionFormset(instance=meeting, data=request.POST)
10071009
formset.is_valid()
10081010
formset.save()
1009-
1010-
# post save
10111011
sessions_post_save(formset)
10121012

1013+
if not is_approved:
1014+
send_interim_approval_request(meetings=[meeting])
1015+
10131016
# series require special handling, each session gets it's own
10141017
# meeting object we won't see this on edit because series are
10151018
# subsequently dealt with individually
10161019
elif meeting_type == 'series':
1020+
series = []
10171021
SessionFormset.form = staticmethod(curry(
10181022
InterimSessionModelForm,
10191023
user=request.user,
@@ -1032,10 +1036,12 @@ def interim_request(request):
10321036
session = session_form.save(commit=False)
10331037
session.meeting = meeting
10341038
session.save()
1035-
1036-
# post save
1039+
series.append(meeting)
10371040
sessions_post_save([session_form])
10381041

1042+
if not is_approved:
1043+
send_interim_approval_request(meetings=series)
1044+
10391045
messages.success(request, 'Interim meeting request submitted')
10401046
return redirect(upcoming)
10411047
else:
@@ -1071,6 +1077,7 @@ def interim_request_details(request, number):
10711077
if request.POST.get('cancel'):
10721078
if meeting.session_set.first().status.slug == 'sched':
10731079
meeting.session_set.update(status_id='canceled')
1080+
send_interim_cancellation_notice(meeting)
10741081
else:
10751082
meeting.session_set.update(status_id='canceledpa')
10761083
messages.success(request, 'Interim meeting cancelled')
@@ -1129,7 +1136,7 @@ def upcoming(request):
11291136
'''List of upcoming meetings'''
11301137
today = datetime.datetime.today()
11311138
meetings = Meeting.objects.filter(date__gte=today).exclude(
1132-
session__status__in=('apprw', 'schedpa')).order_by('date')
1139+
session__status__in=('apprw', 'schedpa', 'canceledpa')).order_by('date')
11331140

11341141
# extract groups hierarchy for display filter
11351142
seen = set()

ietf/secr/proceedings/proc_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def create_interim_directory():
224224

225225
# produce date sorted output
226226
page = 'proceedings.html'
227-
meetings = InterimMeeting.objects.order_by('-date')
227+
meetings = InterimMeeting.objects.filter(session__status='sched').order_by('-date')
228228
response = render(HttpRequest(), 'proceedings/interim_directory.html',{'meetings': meetings})
229229
path = os.path.join(settings.SECR_INTERIM_LISTING_DIR, page)
230230
f = open(path,'w')
@@ -233,7 +233,7 @@ def create_interim_directory():
233233

234234
# produce group sorted output
235235
page = 'proceedings-bygroup.html'
236-
qs = InterimMeeting.objects.all()
236+
qs = InterimMeeting.objects.filter(session__status='sched')
237237
meetings = sorted(qs, key=lambda a: a.group().acronym)
238238
response = render(HttpRequest(), 'proceedings/interim_directory.html',{'meetings': meetings})
239239
path = os.path.join(settings.SECR_INTERIM_LISTING_DIR, page)

ietf/secr/proceedings/tests.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ def tearDown(self):
7878
shutil.rmtree(self.interim_listing_dir)
7979

8080
def test_upload(self):
81-
make_test_data()
82-
meeting = Meeting.objects.filter(type='interim').first()
81+
make_meeting_test_data()
82+
meeting = Meeting.objects.filter(type='interim',session__status='sched').first()
83+
#self.assertTrue(meeting)
8384
group = Group.objects.get(acronym='mars')
84-
Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched',type_id='session')
85+
#Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched',type_id='session')
8586
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':'mars'})
8687
upfile = StringIO('dummy file')
8788
upfile.name = "scan1.pdf"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% load ams_filters %}
2+
A new interim meeting {% if is_series %}series {% endif %}request has just been submitted by {{ requester }}.
3+
4+
This request requires approval by the Area Director.
5+
The meeting{{ meetings|pluralize }} can be approved here:
6+
{% for url in approval_urls %}{{ url }}
7+
{% endfor %}
8+
9+
{% for meeting in meetings %}{% if is_series %}Meeting: {{ forloop.counter }}{% endif %}{% include "meeting/interim_info.txt" %}{% endfor %}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{% load ams_filters %}
2+
The {{ group.name }} ({{ group.acronym }}) {% if not meeting.city %}virtual {% endif %}{% if is_multi_day %}multi-day {% endif %}
3+
interim meeting for {{ meeting.date|date:"Y-m-d" }} from {{ start_time|time:"H:i" }} to {{ end_time|time:"H:i" }} {{ meeting.time_zone }}
4+
has been cancelled.
5+
6+
{{ additional_information }}
7+
8+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% load ietf_filters %}
2+
---------------------------------------------------------
3+
Working Group Name: {{ group.name|safe }}
4+
Area Name: {{ group.parent }}
5+
Session Requester: {{ requester }}
6+
7+
{% if meeting.city %}City: {{ meeting.city }}
8+
Country: {{ meeting.country }}
9+
Timezone: {{ meeting.time_zone }}
10+
{% else %}Meeting Type: Virtual Meeting{% endif %}
11+
12+
{% for session in meeting.session_set.all %}Session {{ forloop.counter }}:
13+
14+
Date: {{ session.official_timeslotassignment.timeslot.time|date:"Y-m-d" }}
15+
Start Time: {{ session.official_timeslotassignment.timeslot.time|date:"H:i" }}
16+
Duration: {{ session.requested_duration|format_timedelta }}
17+
Remote Instructions: {{ session.remote_instructions }}
18+
Agenda Note: {{ session.agenda_note }}
19+
{% endfor %}
20+
---------------------------------------------------------

0 commit comments

Comments
 (0)