add approval and cancel notifications

- Legacy-Id: 11196
This commit is contained in:
Ryan Cross 2016-05-16 22:48:43 +00:00
parent 3366006653
commit 8fef55dc31
10 changed files with 180 additions and 13 deletions

View file

@ -9,6 +9,7 @@ from django.http import HttpRequest, Http404
from django.db.models import Max, Q, Prefetch, F
from django.conf import settings
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.utils.cache import get_cache_key
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
@ -20,9 +21,11 @@ from ietf.doc.utils import get_document_content
from ietf.group.models import Group
from ietf.ietfauth.utils import has_role, user_is_person
from ietf.liaisons.utils import get_person_for_user
from ietf.mailtrigger.utils import gather_address_lists
from ietf.person.models import Person
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment
from ietf.utils.history import find_history_active_at, find_history_replacements_active_at
from ietf.utils.mail import send_mail
from ietf.utils.pipe import pipe
def find_ads_for_meeting(meeting):
@ -278,7 +281,6 @@ def agenda_permissions(meeting, schedule, user):
return cansee, canedit, secretariat
def session_constraint_expire(request,session):
from django.core.urlresolvers import reverse
from ajax import session_constraints
path = reverse(session_constraints, args=[session.meeting.number, session.pk])
temp_request = HttpRequest()
@ -492,6 +494,61 @@ def get_next_agenda_name(meeting):
group=group.acronym,
sequence=str(last_sequence + 1).zfill(2))
def send_interim_approval_request(meetings):
"""Sends an email to the secretariat, group chairs, and resposnible area
director or the IRTF chair noting that approval has been requested for a
new interim meeting. Takes a list of one or more meetings."""
group = meetings[0].session_set.first().group
requester = meetings[0].session_set.first().requested_by
(to_email, cc_list) = gather_address_lists('session_requested',group=group,person=requester)
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
subject = '{group} - New Interim Meeting Request'.format(group=group.acronym)
template = 'meeting/interim_approval_request.txt'
approval_urls = []
for meeting in meetings:
url = settings.IDTRACKER_BASE_URL + reverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
approval_urls.append(url)
if len(meetings) > 1:
is_series = True
else:
is_series = False
context = locals()
send_mail(None,
to_email,
from_email,
subject,
template,
context,
cc=cc_list)
def send_interim_cancellation_notice(meeting):
"""Sends an email that a scheduled interim meeting has been cancelled."""
session = meeting.session_set.first()
group = session.group
to_email = settings.INTERIM_ANNOUNCE_TO_EMAIL
(_, cc_list) = gather_address_lists('session_request_cancelled',group=group)
from_email = settings.INTERIM_ANNOUNCE_FROM_EMAIL
subject = '{group} ({acronym}) {type} Interim Meeting Cancelled (was {date})'.format(
group=group.name,
acronym=group.acronym,
type=group.type.slug.upper(),
date=meeting.date.strftime('%Y-%m-%d'))
start_time = session.official_timeslotassignment().timeslot.time
end_time = start_time + session.requested_duration
if meeting.session_set.filter(status='sched').count() > 1:
is_multi_day = True
else:
is_multi_day = False
template = 'meeting/interim_cancellation_notice.txt'
context = locals()
send_mail(None,
to_email,
from_email,
subject,
template,
context,
cc=cc_list)
def sessions_post_save(forms):
"""Helper function to perform various post save operations on each form of a
InterimSessionModelForm formset"""

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import datetime
from django.db import migrations
def migrate_interim_meetings(apps, schema_editor):
"""For all existing interim meetings create an official schedule and timeslot assignments"""
Meeting = apps.get_model("meeting", "Meeting")
Schedule = apps.get_model("meeting", "Schedule")
TimeSlot = apps.get_model("meeting", "TimeSlot")
SchedTimeSessAssignment = apps.get_model("meeting", "SchedTimeSessAssignment")
Person = apps.get_model("person", "Person")
system = Person.objects.get(name="(system)")
meetings = Meeting.objects.filter(type='interim')
for meeting in meetings:
if not meeting.agenda:
meeting.agenda = Schedule.objects.create(
meeting=meeting,
owner=system,
name='Official')
meeting.save()
session = meeting.session_set.first() # all legacy interim meetings have one session
time = datetime.datetime.combine(meeting.date, datetime.time(0))
slot = TimeSlot.objects.create(
meeting=meeting,
type_id="session",
duration=session.requested_duration,
time=time)
SchedTimeSessAssignment.objects.create(
timeslot=slot,
session=session,
schedule=meeting.agenda)
class Migration(migrations.Migration):
dependencies = [
('meeting', '0019_session_remote_instructions'),
]
operations = [
migrations.RunPython(migrate_interim_meetings),
]

View file

@ -3,7 +3,7 @@ import datetime
from ietf.doc.models import Document, State
from ietf.group.models import Group
from ietf.meeting.models import Meeting, Room, TimeSlot, Session, Schedule, SchedTimeSessAssignment, ResourceAssociation, SessionPresentation
from ietf.meeting.helpers import create_interim_meeting, assign_interim_session
from ietf.meeting.helpers import create_interim_meeting
from ietf.name.models import RoomResourceName
from ietf.person.models import Person
from ietf.utils.test_data import make_test_data

View file

@ -12,6 +12,8 @@ from pyquery import PyQuery
from ietf.doc.models import Document
from ietf.group.models import Group
from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request
from ietf.meeting.helpers import send_interim_approval_request
from ietf.meeting.helpers import send_interim_cancellation_notice
from ietf.meeting.models import Session, TimeSlot, Meeting
from ietf.meeting.test_data import make_meeting_test_data
from ietf.name.models import SessionStatusName
@ -817,3 +819,19 @@ class InterimTests(TestCase):
login_testing_unauthorized(self,"plain",url)
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
def test_send_interim_approval_request(self):
make_meeting_test_data()
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
length_before = len(outbox)
send_interim_approval_request(meetings=[meeting])
self.assertEqual(len(outbox),length_before+1)
self.assertTrue('New Interim Meeting Request' in outbox[-1]['Subject'])
def test_send_interim_cancellation_notice(self):
make_meeting_test_data()
meeting = Meeting.objects.filter(type='interim',session__status='sched',session__group__acronym='mars').first()
length_before = len(outbox)
send_interim_cancellation_notice(meeting=meeting)
self.assertEqual(len(outbox),length_before+1)
self.assertTrue('Interim Meeting Cancelled' in outbox[-1]['Subject'])

View file

@ -41,6 +41,8 @@ from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
from ietf.meeting.helpers import send_interim_cancellation_notice
from ietf.meeting.helpers import send_interim_approval_request
from ietf.utils.mail import send_mail_message
from ietf.utils.pipe import pipe
from ietf.utils.pdf import pdf_pages
@ -959,7 +961,7 @@ def interim_send_announcement(request, number):
'RG Chair')
def interim_pending(request):
'''View which shows interim meeting requests pending approval'''
meetings = Meeting.objects.filter(type='interim', session__status='apprw').distinct()
meetings = Meeting.objects.filter(type='interim', session__status='apprw').distinct().order_by('date')
menu_entries = get_menu_entries(request)
selected_menu_entry = 'pending'
@ -1006,14 +1008,16 @@ def interim_request(request):
formset = SessionFormset(instance=meeting, data=request.POST)
formset.is_valid()
formset.save()
# post save
sessions_post_save(formset)
if not is_approved:
send_interim_approval_request(meetings=[meeting])
# series require special handling, each session gets it's own
# meeting object we won't see this on edit because series are
# subsequently dealt with individually
elif meeting_type == 'series':
series = []
SessionFormset.form = staticmethod(curry(
InterimSessionModelForm,
user=request.user,
@ -1032,10 +1036,12 @@ def interim_request(request):
session = session_form.save(commit=False)
session.meeting = meeting
session.save()
# post save
series.append(meeting)
sessions_post_save([session_form])
if not is_approved:
send_interim_approval_request(meetings=series)
messages.success(request, 'Interim meeting request submitted')
return redirect(upcoming)
else:
@ -1071,6 +1077,7 @@ def interim_request_details(request, number):
if request.POST.get('cancel'):
if meeting.session_set.first().status.slug == 'sched':
meeting.session_set.update(status_id='canceled')
send_interim_cancellation_notice(meeting)
else:
meeting.session_set.update(status_id='canceledpa')
messages.success(request, 'Interim meeting cancelled')
@ -1129,7 +1136,7 @@ def upcoming(request):
'''List of upcoming meetings'''
today = datetime.datetime.today()
meetings = Meeting.objects.filter(date__gte=today).exclude(
session__status__in=('apprw', 'schedpa')).order_by('date')
session__status__in=('apprw', 'schedpa', 'canceledpa')).order_by('date')
# extract groups hierarchy for display filter
seen = set()

View file

@ -224,7 +224,7 @@ def create_interim_directory():
# produce date sorted output
page = 'proceedings.html'
meetings = InterimMeeting.objects.order_by('-date')
meetings = InterimMeeting.objects.filter(session__status='sched').order_by('-date')
response = render(HttpRequest(), 'proceedings/interim_directory.html',{'meetings': meetings})
path = os.path.join(settings.SECR_INTERIM_LISTING_DIR, page)
f = open(path,'w')
@ -233,7 +233,7 @@ def create_interim_directory():
# produce group sorted output
page = 'proceedings-bygroup.html'
qs = InterimMeeting.objects.all()
qs = InterimMeeting.objects.filter(session__status='sched')
meetings = sorted(qs, key=lambda a: a.group().acronym)
response = render(HttpRequest(), 'proceedings/interim_directory.html',{'meetings': meetings})
path = os.path.join(settings.SECR_INTERIM_LISTING_DIR, page)

View file

@ -78,10 +78,11 @@ class BluesheetTestCase(TestCase):
shutil.rmtree(self.interim_listing_dir)
def test_upload(self):
make_test_data()
meeting = Meeting.objects.filter(type='interim').first()
make_meeting_test_data()
meeting = Meeting.objects.filter(type='interim',session__status='sched').first()
#self.assertTrue(meeting)
group = Group.objects.get(acronym='mars')
Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched',type_id='session')
#Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched',type_id='session')
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':'mars'})
upfile = StringIO('dummy file')
upfile.name = "scan1.pdf"

View file

@ -0,0 +1,9 @@
{% load ams_filters %}
A new interim meeting {% if is_series %}series {% endif %}request has just been submitted by {{ requester }}.
This request requires approval by the Area Director.
The meeting{{ meetings|pluralize }} can be approved here:
{% for url in approval_urls %}{{ url }}
{% endfor %}
{% for meeting in meetings %}{% if is_series %}Meeting: {{ forloop.counter }}{% endif %}{% include "meeting/interim_info.txt" %}{% endfor %}

View file

@ -0,0 +1,8 @@
{% load ams_filters %}
The {{ group.name }} ({{ group.acronym }}) {% if not meeting.city %}virtual {% endif %}{% if is_multi_day %}multi-day {% endif %}
interim meeting for {{ meeting.date|date:"Y-m-d" }} from {{ start_time|time:"H:i" }} to {{ end_time|time:"H:i" }} {{ meeting.time_zone }}
has been cancelled.
{{ additional_information }}

View file

@ -0,0 +1,20 @@
{% load ietf_filters %}
---------------------------------------------------------
Working Group Name: {{ group.name|safe }}
Area Name: {{ group.parent }}
Session Requester: {{ requester }}
{% if meeting.city %}City: {{ meeting.city }}
Country: {{ meeting.country }}
Timezone: {{ meeting.time_zone }}
{% else %}Meeting Type: Virtual Meeting{% endif %}
{% for session in meeting.session_set.all %}Session {{ forloop.counter }}:
Date: {{ session.official_timeslotassignment.timeslot.time|date:"Y-m-d" }}
Start Time: {{ session.official_timeslotassignment.timeslot.time|date:"H:i" }}
Duration: {{ session.requested_duration|format_timedelta }}
Remote Instructions: {{ session.remote_instructions }}
Agenda Note: {{ session.agenda_note }}
{% endfor %}
---------------------------------------------------------