Skip to content

Commit c3d4cc1

Browse files
committed
Add meeting proceedings introduction pages: Progress Report and Attendees. Commit ready for merge.
- Legacy-Id: 12111
1 parent 55febb5 commit c3d4cc1

12 files changed

Lines changed: 285 additions & 91 deletions

File tree

ietf/meeting/tests_views.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from django.core.urlresolvers import reverse as urlreverse
1111
from django.conf import settings
1212
from django.contrib.auth.models import User
13+
from django.http import HttpRequest
1314

15+
from mock import patch, MagicMock
1416
from pyquery import PyQuery
1517
from StringIO import StringIO
1618

@@ -22,6 +24,7 @@
2224
from ietf.meeting.helpers import send_interim_minutes_reminder
2325
from ietf.meeting.models import Session, TimeSlot, Meeting
2426
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
27+
from ietf.meeting.utils import finalize
2528
from ietf.name.models import SessionStatusName
2629
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
2730
from ietf.utils.mail import outbox
@@ -33,6 +36,7 @@
3336
MeetingFactory, FloorPlanFactory )
3437
from ietf.doc.factories import DocumentFactory
3538

39+
3640
class MeetingTests(TestCase):
3741
def setUp(self):
3842
self.materials_dir = os.path.abspath(settings.TEST_MATERIALS_DIR)
@@ -270,6 +274,44 @@ def test_proceedings_acknowledgements(self):
270274
r = self.client.get(url)
271275
self.assertEqual(r.status_code, 200)
272276

277+
@patch('urllib2.urlopen')
278+
def test_proceedings_attendees(self, mock_urlopen):
279+
mock_urlopen.return_value = StringIO('[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]')
280+
make_meeting_test_data()
281+
282+
# add recent meeting
283+
date = datetime.date(2016,4,3)
284+
Meeting.objects.create(type_id='ietf',date=date,number=95)
285+
url = urlreverse('ietf.secr.meetings.views.add')
286+
post_data = dict(number='96',city='Berlin',date='2016-07-14',country='DE',
287+
time_zone='Europe/Berlin',venue_name='Intercontinental Berlin',
288+
venue_addr='',
289+
idsubmit_cutoff_day_offset_00=13,
290+
idsubmit_cutoff_day_offset_01=20,
291+
idsubmit_cutoff_time_utc =datetime.timedelta(hours=23, minutes=59, seconds=59),
292+
idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
293+
submission_start_day_offset=90,
294+
submission_cutoff_day_offset=26,
295+
submission_correction_day_offset=50,
296+
)
297+
self.client.login(username='secretary', password='secretary+password')
298+
response = self.client.post(url, post_data)
299+
self.assertRedirects(response,urlreverse('ietf.secr.meetings.views.main'))
300+
self.assertTrue(Meeting.objects.filter(number=96).exists())
301+
meeting = Meeting.objects.get(number=96)
302+
303+
# finalize the meeting proceedings
304+
finalize(HttpRequest(),meeting)
305+
306+
# check attendees
307+
url = urlreverse('ietf.meeting.views.proceedings_attendees',kwargs={'num':96})
308+
response = self.client.get(url)
309+
self.assertEqual(response.status_code, 200)
310+
self.assertTrue('Attendee List' in response.content)
311+
q = PyQuery(response.content)
312+
self.assertEqual(1,len(q("#id_attendees tbody tr")))
313+
314+
273315
def test_proceedings_overview(self):
274316
'''Test proceedings IETF Overview page.
275317
Note: old meetings aren't supported so need to add a new meeting then test.
@@ -299,6 +341,36 @@ def test_proceedings_overview(self):
299341
self.assertEqual(response.status_code, 200)
300342
self.assertTrue('The Internet Engineering Task Force' in response.content)
301343

344+
def test_proceedings_progress_report(self):
345+
make_meeting_test_data()
346+
347+
# add recent meeting
348+
date = datetime.date(2016,4,3)
349+
Meeting.objects.create(type_id='ietf',date=date,number=95)
350+
url = urlreverse('ietf.secr.meetings.views.add')
351+
post_data = dict(number='96',city='Berlin',date='2016-07-14',country='DE',
352+
time_zone='Europe/Berlin',venue_name='Intercontinental Berlin',
353+
venue_addr='',
354+
idsubmit_cutoff_day_offset_00=13,
355+
idsubmit_cutoff_day_offset_01=20,
356+
idsubmit_cutoff_time_utc =datetime.timedelta(hours=23, minutes=59, seconds=59),
357+
idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
358+
submission_start_day_offset=90,
359+
submission_cutoff_day_offset=26,
360+
submission_correction_day_offset=50,
361+
)
362+
self.client.login(username='secretary', password='secretary+password')
363+
response = self.client.post(url, post_data)
364+
self.assertRedirects(response,urlreverse('ietf.secr.meetings.views.main'))
365+
self.assertTrue(Meeting.objects.filter(number=96).exists())
366+
meeting = Meeting.objects.get(number=96)
367+
368+
# check progress report
369+
url = urlreverse('ietf.meeting.views.proceedings_progress_report',kwargs={'num':96})
370+
response = self.client.get(url)
371+
self.assertEqual(response.status_code, 200)
372+
self.assertTrue('Progress Report' in response.content)
373+
302374
def test_feed(self):
303375
meeting = make_meeting_test_data()
304376
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
@@ -1252,7 +1324,9 @@ def test_iphone_app_json(self):
12521324
self.assertEqual(r.status_code,200)
12531325

12541326
class FinalizeProceedingsTests(TestCase):
1255-
def test_finalize_proceedings(self):
1327+
@patch('urllib2.urlopen')
1328+
def test_finalize_proceedings(self, mock_urlopen):
1329+
mock_urlopen.return_value = StringIO('[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]')
12561330
make_meeting_test_data()
12571331
meeting = Meeting.objects.filter(type_id='ietf').order_by('id').last()
12581332
meeting.session_set.filter(group__acronym='mars').first().sessionpresentation_set.create(document=Document.objects.filter(type='draft').first(),rev=None)

ietf/meeting/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@
7878
url(r'^proceedings(?:.html)?/?$', views.proceedings),
7979
url(r'^proceedings(?:.html)?/finalize/?$', views.finalize_proceedings),
8080
url(r'^proceedings/acknowledgements/$', views.proceedings_acknowledgements),
81+
url(r'^proceedings/attendees/$', views.proceedings_attendees),
8182
url(r'^proceedings/overview/$', views.proceedings_overview),
83+
url(r'^proceedings/progress-report/$', views.proceedings_progress_report),
8284
]
8385

8486
urlpatterns = [

ietf/meeting/utils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import datetime
2+
import json
3+
import urllib2
4+
import urlparse
25

6+
from django.conf import settings
7+
from django.contrib import messages
8+
from django.template.loader import render_to_string
9+
10+
from ietf.dbtemplate.models import DBTemplate
311
from ietf.meeting.models import Session
412
from ietf.group.utils import can_manage_materials
513

@@ -72,7 +80,7 @@ def time_sort_key(session):
7280

7381
return meeting_sorted
7482

75-
def finalize(meeting):
83+
def finalize(request, meeting):
7684
end_date = meeting.end_date()
7785
end_time = datetime.datetime.combine(end_date, datetime.datetime.min.time())+datetime.timedelta(days=1)
7886
for session in meeting.session_set.all():
@@ -83,6 +91,24 @@ def finalize(meeting):
8391
else:
8492
sp.rev = '00'
8593
sp.save()
94+
# get attendees
95+
url = urlparse.urljoin(settings.REGISTRATION_ATTENDEES_BASE_URL,meeting.number)
96+
try:
97+
attendees = json.load(urllib2.urlopen(url))
98+
except (ValueError, urllib2.HTTPError):
99+
messages.warning(request,'Could not retrieve attendee list from registration system (%s)' % url, fail_silently=True)
100+
attendees = []
101+
102+
if attendees:
103+
attendees = sorted(attendees, key = lambda a: a['LastName'])
104+
content = render_to_string('meeting/proceedings_attendees_table.html', {
105+
'attendees':attendees})
106+
template = DBTemplate.objects.create(
107+
path='/meeting/proceedings/%s/attendees.html' % meeting.number,
108+
title='IETF %s Attendee List' % meeting.number,
109+
type_id='django',
110+
content=content)
111+
86112
meeting.proceedings_final = True
87113
meeting.save()
88114
return

ietf/meeting/views.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from ietf.meeting.helpers import send_interim_announcement_request
5757
from ietf.meeting.utils import finalize
5858
from ietf.secr.proceedings.utils import handle_upload_file
59+
from ietf.secr.proceedings.proc_utils import get_progress_stats
5960
from ietf.utils.mail import send_mail_message
6061
from ietf.utils.pipe import pipe
6162
from ietf.utils.pdf import pdf_pages
@@ -1984,7 +1985,7 @@ def finalize_proceedings(request, num=None):
19841985
raise Http404
19851986

19861987
if request.method=='POST':
1987-
finalize(meeting)
1988+
finalize(request, meeting)
19881989
return HttpResponseRedirect(reverse('ietf.meeting.views.proceedings',kwargs={'num':meeting.number}))
19891990

19901991
return render(request, "meeting/finalize.html", {'meeting':meeting,})
@@ -1999,6 +2000,22 @@ def proceedings_acknowledgements(request, num=None):
19992000
'meeting': meeting,
20002001
})
20012002

2003+
2004+
@role_required('Secretariat')
2005+
def proceedings_attendees(request, num=None):
2006+
2007+
meeting = get_meeting(num)
2008+
if meeting.number < 95:
2009+
return HttpResponseRedirect( 'https://www.ietf.org/proceedings/%s/attendees.html' % num )
2010+
overview_template = '/meeting/proceedings/%s/attendees.html' % meeting.number
2011+
template = render_to_string(overview_template, {})
2012+
2013+
return render(request, "meeting/proceedings_attendees.html", {
2014+
'meeting': meeting,
2015+
'template': template,
2016+
})
2017+
2018+
20022019
@role_required('Secretariat')
20032020
def proceedings_overview(request, num=None):
20042021
'''Display Overview for given meeting'''
@@ -2013,6 +2030,17 @@ def proceedings_overview(request, num=None):
20132030
'template': template,
20142031
})
20152032

2033+
@role_required('Secretariat')
2034+
def proceedings_progress_report(request, num=None):
2035+
meeting = get_meeting(num)
2036+
if meeting.number < 95:
2037+
return HttpResponseRedirect( 'https://www.ietf.org/proceedings/%s/progress-report.html' % num )
2038+
sdate = meeting.previous_meeting().date
2039+
edate = meeting.date
2040+
context = get_progress_stats(sdate,edate)
2041+
context['meeting'] = meeting
2042+
return render(request, "meeting/proceedings_progress_report.html", context)
2043+
20162044
class OldUploadRedirect(RedirectView):
20172045
def get_redirect_url(self, **kwargs):
20182046
return reverse_lazy('ietf.meeting.views.session_details',kwargs=self.kwargs)

ietf/secr/proceedings/proc_utils.py

Lines changed: 54 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -106,97 +106,67 @@ def mycomp(timeslot):
106106

107107
def get_progress_stats(sdate,edate):
108108
'''
109-
This function takes a date range and produces a dictionary of statistics / objects for use
110-
in a progress report. Generally the end date will be the date of the last meeting
109+
This function takes a date range and produces a dictionary of statistics / objects for
110+
use in a progress report. Generally the end date will be the date of the last meeting
111111
and the start date will be the date of the meeting before that.
112112
'''
113113
data = {}
114114
data['sdate'] = sdate
115115
data['edate'] = edate
116116

117-
# Activty Report Section
118-
new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
119-
docevent__newrevisiondocevent__rev='00',
120-
docevent__time__gte=sdate,
121-
docevent__time__lt=edate)
122-
data['new'] = new_docs.count()
123-
data['updated'] = 0
124-
data['updated_more'] = 0
125-
for d in new_docs:
126-
updates = d.docevent_set.filter(type='new_revision',time__gte=sdate,time__lt=edate).count()
127-
if updates > 1:
128-
data['updated'] += 1
129-
if updates > 2:
130-
data['updated_more'] +=1
131-
132-
# calculate total documents updated, not counting new, rev=00
133-
result = set()
134117
events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lt=edate)
135-
for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'):
136-
result.add(e.doc)
137-
data['total_updated'] = len(result)
138-
139-
# calculate sent last call
140-
data['last_call'] = events.filter(type='sent_last_call').count()
141-
142-
# calculate approved
143-
data['approved'] = events.filter(type='iesg_approved').count()
144-
145-
# get 4 weeks
146-
ff1_date = edate - datetime.timedelta(days=28)
147-
ff_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
148-
docevent__newrevisiondocevent__rev='00',
149-
docevent__time__gte=ff1_date,
150-
docevent__time__lt=edate)
151-
ff_new_count = ff_docs.count()
152-
ff_new_percent = format(ff_new_count / float(data['new']),'.0%')
153-
154-
# calculate total documents updated in final four weeks, not counting new, rev=00
155-
result = set()
156-
events = DocEvent.objects.filter(doc__type='draft',time__gte=ff1_date,time__lt=edate)
157-
for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'):
158-
result.add(e.doc)
159-
ff_update_count = len(result)
160-
ff_update_percent = format(ff_update_count / float(data['total_updated']),'.0%')
161-
162-
data['ff_new_count'] = ff_new_count
163-
data['ff_new_percent'] = ff_new_percent
164-
data['ff_update_count'] = ff_update_count
165-
data['ff_update_percent'] = ff_update_percent
166-
167-
# Progress Report Section
168-
data['docevents'] = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lt=edate)
169-
data['action_events'] = data['docevents'].filter(type='iesg_approved')
170-
data['lc_events'] = data['docevents'].filter(type='sent_last_call')
171-
172-
data['new_groups'] = Group.objects.filter(type='wg',
173-
groupevent__changestategroupevent__state='active',
174-
groupevent__time__gte=sdate,
175-
groupevent__time__lt=edate)
176-
177-
data['concluded_groups'] = Group.objects.filter(type='wg',
178-
groupevent__changestategroupevent__state='conclude',
179-
groupevent__time__gte=sdate,
180-
groupevent__time__lt=edate)
181-
182-
data['new_docs'] = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
183-
docevent__time__gte=sdate,
184-
docevent__time__lt=edate).distinct()
185-
186-
data['rfcs'] = DocEvent.objects.filter(type='published_rfc',
187-
doc__type='draft',
188-
time__gte=sdate,
189-
time__lt=edate)
190-
191-
# attach the ftp URL for use in the template
192-
for event in data['rfcs']:
193-
num = get_rfc_num(event.doc)
194-
event.ftp_url = 'ftp://ftp.ietf.org/rfc/rfc%s.txt' % num
195-
196-
data['counts'] = {'std':data['rfcs'].filter(doc__intended_std_level__in=('ps','ds','std')).count(),
197-
'bcp':data['rfcs'].filter(doc__intended_std_level='bcp').count(),
198-
'exp':data['rfcs'].filter(doc__intended_std_level='exp').count(),
199-
'inf':data['rfcs'].filter(doc__intended_std_level='inf').count()}
118+
119+
data['actions_count'] = events.filter(type='iesg_approved').count()
120+
data['last_calls_count'] = events.filter(type='sent_last_call').count()
121+
new_draft_events = events.filter(newrevisiondocevent__rev='00')
122+
new_drafts = list(set([ e.doc_id for e in new_draft_events ]))
123+
data['new_drafts_count'] = len(new_drafts)
124+
data['new_drafts_updated_count'] = events.filter(doc__in=new_drafts,newrevisiondocevent__rev='01').count()
125+
data['new_drafts_updated_more_count'] = events.filter(doc__in=new_drafts,newrevisiondocevent__rev='02').count()
126+
127+
update_events = events.filter(type='new_revision').exclude(doc__in=new_drafts)
128+
data['updated_drafts_count'] = len(set([ e.doc_id for e in update_events ]))
129+
130+
# Calculate Final Four Weeks stats (ffw)
131+
ffwdate = edate - datetime.timedelta(days=28)
132+
ffw_new_count = events.filter(time__gte=ffwdate,newrevisiondocevent__rev='00').count()
133+
try:
134+
ffw_new_percent = format(ffw_new_count / float(data['new_drafts_count']),'.0%')
135+
except ZeroDivisionError:
136+
ffw_new_percent = 0
137+
138+
data['ffw_new_count'] = ffw_new_count
139+
data['ffw_new_percent'] = ffw_new_percent
140+
141+
ffw_update_events = events.filter(time__gte=ffwdate,type='new_revision').exclude(doc__in=new_drafts)
142+
ffw_update_count = len(set([ e.doc_id for e in ffw_update_events ]))
143+
try:
144+
ffw_update_percent = format(ffw_update_count / float(data['updated_drafts_count']),'.0%')
145+
except ZeroDivisionError:
146+
ffw_update_percent = 0
147+
148+
data['ffw_update_count'] = ffw_update_count
149+
data['ffw_update_percent'] = ffw_update_percent
150+
151+
rfcs = events.filter(type='published_rfc')
152+
data['rfcs'] = rfcs.select_related('doc').select_related('doc__group').select_related('doc__intended_std_level')
153+
154+
data['counts'] = {'std':rfcs.filter(doc__intended_std_level__in=('ps','ds','std')).count(),
155+
'bcp':rfcs.filter(doc__intended_std_level='bcp').count(),
156+
'exp':rfcs.filter(doc__intended_std_level='exp').count(),
157+
'inf':rfcs.filter(doc__intended_std_level='inf').count()}
158+
159+
data['new_groups'] = Group.objects.filter(
160+
type='wg',
161+
groupevent__changestategroupevent__state='active',
162+
groupevent__time__gte=sdate,
163+
groupevent__time__lt=edate)
164+
165+
data['concluded_groups'] = Group.objects.filter(
166+
type='wg',
167+
groupevent__changestategroupevent__state='conclude',
168+
groupevent__time__gte=sdate,
169+
groupevent__time__lt=edate)
200170

201171
return data
202172

ietf/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ def skip_unreadable_post(record):
599599
SECR_MAX_UPLOAD_SIZE = 40960000
600600
SECR_PROCEEDINGS_DIR = '/a/www/www6s/proceedings/'
601601
SECR_PPT2PDF_COMMAND = ['/usr/bin/soffice','--headless','--convert-to','pdf','--outdir']
602-
602+
REGISTRATION_ATTENDEES_BASE_URL = 'https://ietf.org/registration/attendees/'
603603
USE_ETAGS=True
604604

605605
PRODUCTION_TIMEZONE = "America/Los_Angeles"

0 commit comments

Comments
 (0)