add approval and cancel notifications
- Legacy-Id: 11196
This commit is contained in:
parent
3366006653
commit
8fef55dc31
|
@ -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"""
|
||||
|
|
47
ietf/meeting/migrations/0020_migrate_interim_meetings.py
Normal file
47
ietf/meeting/migrations/0020_migrate_interim_meetings.py
Normal 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),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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'])
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
9
ietf/templates/meeting/interim_approval_request.txt
Normal file
9
ietf/templates/meeting/interim_approval_request.txt
Normal 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 %}
|
8
ietf/templates/meeting/interim_cancellation_notice.txt
Normal file
8
ietf/templates/meeting/interim_cancellation_notice.txt
Normal 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 }}
|
||||
|
||||
|
20
ietf/templates/meeting/interim_info.txt
Normal file
20
ietf/templates/meeting/interim_info.txt
Normal 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 %}
|
||||
---------------------------------------------------------
|
Loading…
Reference in a new issue