datatracker/ietf/iesg/views.py
Ole Laursen 53c59e56e3 Remove vary_on_cookie on agenda view, it's not needed anymore, Django
already adds the vary: cookie header automatically
 - Legacy-Id: 6399
2013-10-09 12:55:28 +00:00

580 lines
24 KiB
Python

# Copyright The IETF Trust 2007, All Rights Reserved
# Portion Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * Neither the name of the Nokia Corporation and/or its
# subsidiary(-ies) nor the names of its contributors may be used
# to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import codecs, re, os, glob
import datetime
import tarfile
from django.views.generic.simple import direct_to_template
from django.core.urlresolvers import reverse as urlreverse
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.template import RequestContext, Context, loader
from django.shortcuts import render_to_response, get_object_or_404
from django.conf import settings
from django.utils import simplejson as json
from django import forms
from ietf.idtracker.models import IDInternal, InternetDraft, AreaGroup, Position, IESGLogin, Acronym
from ietf.iesg.models import TelechatDates, TelechatAgendaItem, WGAction
from ietf.idrfc.idrfc_wrapper import IdWrapper, RfcWrapper
from ietf.iesg.models import TelechatDate, TelechatAgendaItem
from ietf.ipr.models import IprDocAlias
from ietf.doc.models import Document, TelechatDocEvent, LastCallDocEvent, ConsensusDocEvent, DocEvent
from ietf.group.models import Group, GroupMilestone
from ietf.person.models import Person
from ietf.doc.utils import update_telechat
from ietf.ietfauth.utils import has_role, role_required
from ietf.iesg.agenda import get_agenda_date, agenda_data, agenda_docs, agenda_wg_actions, agenda_management_issues
def review_decisions(request, year=None):
events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved"))
years = sorted((d.year for d in events.dates('time', 'year')), reverse=True)
if year:
year = int(year)
events = events.filter(time__year=year)
else:
d = datetime.date.today() - datetime.timedelta(days=185)
d = datetime.date(d.year, d.month, 1)
events = events.filter(time__gte=d)
events = events.select_related("doc", "doc__intended_std_level").order_by("-time", "-id")
#proto_levels = ["bcp", "ds", "ps", "std"]
#doc_levels = ["exp", "inf"]
timeframe = u"%s" % year if year else u"the past 6 months"
return render_to_response('iesg/review_decisions.html',
dict(events=events,
years=years,
year=year,
timeframe=timeframe),
context_instance=RequestContext(request))
def agenda_json(request, date=None):
date = get_agenda_date(date)
data = {'telechat-date':str(date),
'as-of':str(datetime.datetime.utcnow()),
'sections':{}}
data['sections']['1'] = {'title':"Administrivia"}
data['sections']['1.1'] = {'title':"Roll Call"}
data['sections']['1.2'] = {'title':"Bash the Agenda"}
data['sections']['1.3'] = {'title':"Approval of the Minutes of Past Telechats"}
data['sections']['1.4'] = {'title':"List of Remaining Action Items from Last Telechat"}
data['sections']['2'] = {'title':"Protocol Actions"}
data['sections']['2.1'] = {'title':"WG Submissions"}
data['sections']['2.1.1'] = {'title':"New Items", 'docs':[]}
data['sections']['2.1.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['2.2'] = {'title':"Individual Submissions"}
data['sections']['2.2.1'] = {'title':"New Items", 'docs':[]}
data['sections']['2.2.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['2.3'] = {'title':"Individual Submissions"}
data['sections']['2.3.1'] = {'title':"New Items", 'docs':[]}
data['sections']['2.3.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['3'] = {'title':"Document Actions"}
data['sections']['3.1'] = {'title':"WG Submissions"}
data['sections']['3.1.1'] = {'title':"New Items", 'docs':[]}
data['sections']['3.1.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['3.2'] = {'title':"Individual Submissions Via AD"}
data['sections']['3.2.1'] = {'title':"New Items", 'docs':[]}
data['sections']['3.2.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['3.3'] = {'title':"Status Changes"}
data['sections']['3.3.1'] = {'title':"New Items", 'docs':[]}
data['sections']['3.3.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['3.4'] = {'title':"IRTF and Independent Submission Stream Documents"}
data['sections']['3.4.1'] = {'title':"New Items", 'docs':[]}
data['sections']['3.4.2'] = {'title':"Returning Items", 'docs':[]}
data['sections']['4'] = {'title':"Working Group Actions"}
data['sections']['4.1'] = {'title':"WG Creation"}
data['sections']['4.1.1'] = {'title':"Proposed for IETF Review", 'wgs':[]}
data['sections']['4.1.2'] = {'title':"Proposed for Approval", 'wgs':[]}
data['sections']['4.2'] = {'title':"WG Rechartering"}
data['sections']['4.2.1'] = {'title':"Under Evaluation for IETF Review", 'wgs':[]}
data['sections']['4.2.2'] = {'title':"Proposed for Approval", 'wgs':[]}
data['sections']['5'] = {'title':"IAB News We Can Use"}
data['sections']['6'] = {'title':"Management Issues"}
data['sections']['7'] = {'title':"Working Group News"}
docs = agenda_docs(date)
for section in docs.keys():
# in case the document is in a state that does not have an agenda section
if section == 's':
continue
s = str(".".join(list(section)[1:]))
if s[0:1] == '4':
# ignore these; not sure why they are included by agenda_docs
continue
if not docs[section]:
continue
# If needed, add a "For Action" section to agenda
if s[4:5] == '3':
data['sections'][s] = {'title':"For Action", 'docs':[]}
for d in docs[section]:
docinfo = {'docname':d.canonical_name(),
'title':d.title,
'ad':d.ad.name if d.ad else None }
if d.note:
docinfo['note'] = d.note
defer = d.active_defer_event()
if defer:
docinfo['defer-by'] = defer.by.name
docinfo['defer-at'] = str(defer.time)
if d.type_id == "draft":
docinfo['rev'] = d.rev
docinfo['intended-std-level'] = str(d.intended_std_level)
if d.rfc_number():
docinfo['rfc-number'] = d.rfc_number()
iana_state = d.get_state("draft-iana-review")
if iana_state and iana_state.slug in ("not-ok", "changed", "need-rev"):
docinfo['iana-review-state'] = str(iana_state)
if d.get_state_slug("draft-iesg") == "lc":
e = d.latest_event(LastCallDocEvent, type="sent_last_call")
if e:
docinfo['lastcall-expires'] = e.expires.strftime("%Y-%m-%d")
docinfo['consensus'] = None
e = d.latest_event(ConsensusDocEvent, type="changed_consensus")
if e:
docinfo['consensus'] = e.consensus
elif d.type_id == 'conflrev':
docinfo['rev'] = d.rev
td = d.relateddocument_set.get(relationship__slug='conflrev').target.document
docinfo['target-docname'] = td.canonical_name()
docinfo['target-title'] = td.title
docinfo['target-rev'] = td.rev
docinfo['intended-std-level'] = str(td.intended_std_level)
docinfo['stream'] = str(td.stream)
else:
# XXX check this -- is there nothing to set for
# all other documents here?
pass
data['sections'][s]['docs'] += [docinfo, ]
wgs = agenda_wg_actions(date)
for section in wgs.keys():
# in case the charter is in a state that does not have an agenda section
if section == 's':
continue
s = str(".".join(list(section)[1:]))
if s[0:1] != '4':
# ignore these; not sure why they are included by agenda_wg_actions
continue
if not wgs[section]:
continue
for doc in wgs[section]:
wginfo = {'docname': doc.canonical_name(),
'rev': doc.rev,
'wgname': doc.group.name,
'acronym': doc.group.acronym,
'ad': doc.group.ad.name}
data['sections'][s]['wgs'] += [wginfo, ]
mgmt = agenda_management_issues(date)
num = 0
for m in mgmt:
num += 1
data['sections']["6.%d" % num] = {'title':m.title}
return HttpResponse(json.dumps(data, indent=2), mimetype='text/plain')
def agenda(request, date=None):
data = agenda_data(request, date)
data['settings'] = settings
return render_to_response("iesg/agenda.html", data, context_instance=RequestContext(request))
def agenda_txt(request, date=None):
data = agenda_data(request, date)
return render_to_response("iesg/agenda.txt", data, context_instance=RequestContext(request), mimetype="text/plain")
def agenda_scribe_template(request, date=None):
date = get_agenda_date(date)
docs = agenda_docs(date)
return render_to_response('iesg/scribe_template.html', { 'date':str(date), 'docs':docs }, context_instance=RequestContext(request) )
@role_required('Area Director', 'Secretariat')
def agenda_moderator_package(request, date=None):
data = agenda_data(request, date)
data['ads'] = sorted(Person.objects.filter(role__name="ad", role__group__state="active"),
key=lambda p: p.name_parts()[3])
return render_to_response("iesg/moderator_package.html", data, context_instance=RequestContext(request))
@role_required('Area Director', 'Secretariat')
def agenda_package(request, date=None):
data = agenda_data(request)
return render_to_response("iesg/agenda_package.txt", data, context_instance=RequestContext(request), mimetype='text/plain')
def agenda_documents_txt(request):
dates = TelechatDates.objects.all()[0].dates()
docs = []
for date in dates:
from ietf.doc.models import TelechatDocEvent
for d in Document.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
t = loader.get_template('iesg/agenda_documents.txt')
c = Context({'docs':docs,'special_stream_list':['ise','irtf']})
return HttpResponse(t.render(c), mimetype='text/plain')
class RescheduleForm(forms.Form):
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False)
clear_returning_item = forms.BooleanField(initial=False, required=False)
def __init__(self, *args, **kwargs):
dates = kwargs.pop('telechat_dates')
super(self.__class__, self).__init__(*args, **kwargs)
# telechat choices
init = kwargs['initial']['telechat_date']
if init and init not in dates:
dates.insert(0, init)
choices = [("", "(not on agenda)")]
for d in dates:
choices.append((d, d.strftime("%Y-%m-%d")))
self.fields['telechat_date'].choices = choices
def handle_reschedule_form(request, doc, dates):
initial = dict(telechat_date=doc.telechat_date())
formargs = dict(telechat_dates=dates,
prefix="%s" % doc.name,
initial=initial)
if request.method == 'POST':
form = RescheduleForm(request.POST, **formargs)
if form.is_valid():
login = request.user.get_profile()
update_telechat(request, doc, login,
form.cleaned_data['telechat_date'],
False if form.cleaned_data['clear_returning_item'] else None)
doc.time = datetime.datetime.now()
doc.save()
else:
form = RescheduleForm(**formargs)
form.show_clear = doc.returning_item()
return form
def agenda_documents(request):
dates = TelechatDates.objects.all()[0].dates()
from ietf.doc.models import TelechatDocEvent
docs = []
for d in Document.objects.filter(docevent__telechatdocevent__telechat_date__in=dates).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date in dates:
docs.append(d)
e = d.latest_event(type="started_iesg_process")
d.balloting_started = e.time if e else datetime.datetime.min
docs.sort(key=lambda d: d.balloting_started)
for i in docs:
i.reschedule_form = handle_reschedule_form(request, i, dates)
# some may have been taken off the schedule by the reschedule form
docs = [d for d in docs if d.telechat_date()]
telechats = []
for date in dates:
matches = filter(lambda x: x.telechat_date() == date, docs)
res = {}
for i in matches:
section_key = "s" + get_doc_section(i)
if section_key not in res:
res[section_key] = []
if i.type_id=='draft':
if i.get_state_slug()!="rfc":
i.iprUrl = "/ipr/search?option=document_search&id_document_tag=" + str(i.name)
else:
i.iprUrl = "/ipr/search?option=rfc_search&rfc_search=" + str(i.rfc_number())
i.iprCount = len(i.ipr())
res[section_key].append(i)
telechats.append({'date':date, 'docs':res})
return direct_to_template(request, 'iesg/agenda_documents_redesign.html', {'telechats':telechats, 'hide_telechat_date':True})
def telechat_docs_tarfile(request,year,month,day):
from tempfile import mkstemp
date=datetime.date(int(year),int(month),int(day))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.doc.models import TelechatDocEvent
docs = []
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date).distinct():
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
else:
docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
response = HttpResponse(mimetype='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename=telechat-%s-%s-%s-docs.tgz'%(year, month, day)
tarstream = tarfile.open('','w:gz',response)
mfh, mfn = mkstemp()
manifest = open(mfn, "w")
for doc in docs:
doc_path = os.path.join(settings.INTERNET_DRAFT_PATH, doc.draft.filename+"-"+doc.draft.revision_display()+".txt")
if os.path.exists(doc_path):
try:
tarstream.add(doc_path, str(doc.draft.filename+"-"+doc.draft.revision_display()+".txt"))
manifest.write("Included: "+doc_path+"\n")
except Exception, e:
manifest.write(("Failed (%s): "%e)+doc_path+"\n")
else:
manifest.write("Not found: "+doc_path+"\n")
manifest.close()
tarstream.add(mfn, "manifest.txt")
tarstream.close()
os.unlink(mfn)
return response
def discusses(request):
res = []
for d in IDInternal.objects.filter(states__type="draft-iesg", states__slug__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), docevent__ballotpositiondocevent__pos="discuss").distinct():
found = False
for p in d.positions.all():
if p.discuss:
found = True
break
if not found:
continue
if d.rfc_flag:
doc = RfcWrapper(d)
else:
doc = IdWrapper(draft=d)
if doc.in_ietf_process() and doc.ietf_process.has_active_iesg_ballot():
# quick hack - to be removed when the proxy code is removed
doc.underlying = doc.underlying_document()
doc.underlying.milestones = d.groupmilestone_set.filter(state="active").order_by("time").select_related("group")
res.append(doc)
return direct_to_template(request, 'iesg/discusses.html', {'docs':res})
@role_required('Area Director', 'Secretariat')
def milestones_needing_review(request):
# collect milestones, grouped on AD and group
ads = {}
for m in GroupMilestone.objects.filter(state="review").exclude(group__state="concluded", group__ad=None).distinct().select_related("group", "group__ad"):
groups = ads.setdefault(m.group.ad, {})
milestones = groups.setdefault(m.group, [])
milestones.append(m)
ad_list = []
for ad, groups in ads.iteritems():
ad_list.append(ad)
ad.groups_needing_review = sorted(groups, key=lambda g: g.acronym)
for g, milestones in groups.iteritems():
g.milestones_needing_review = sorted(milestones, key=lambda m: m.due)
return render_to_response('iesg/milestones_needing_review.html',
dict(ads=sorted(ad_list, key=lambda ad: ad.plain_name()),
),
context_instance=RequestContext(request))
def parse_wg_action_file(path):
f = open(path, 'rU')
line = f.readline()
while line and not line.strip():
line = f.readline()
# name
m = re.search(r'([^\(]*) \(', line)
if not m:
return None
name = m.group(1)
# acronym
m = re.search(r'\((\w+)\)', line)
if not m:
return None
acronym = m.group(1)
# date
line = f.readline()
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', line)
while line and not m:
line = f.readline()
m = re.search(r'(\d{4})-(\d{2})-(\d{2})', line)
last_updated = None
if m:
try:
last_updated = datetime.date(int(m.group(1)), int(m.group(2)), int(m.group(3)))
except:
pass
# token
line = f.readline()
while line and not 'area director' in line.lower():
line = f.readline()
line = f.readline()
line = f.readline()
m = re.search(r'\s*(\w+)\s*', line)
token = ""
if m:
token = m.group(1)
return dict(filename=os.path.basename(path), name=name, acronym=acronym,
status_date=last_updated, token=token)
def get_possible_wg_actions():
res = []
charters = glob.glob(os.path.join(settings.IESG_WG_EVALUATION_DIR, '*-charter.txt'))
for path in charters:
d = parse_wg_action_file(path)
if d:
if not d['status_date']:
d['status_date'] = datetime.date(1900,1,1)
res.append(d)
res.sort(key=lambda x: x['status_date'])
return res
@role_required('Area Director', 'Secretariat')
def working_group_actions(request):
current_items = WGAction.objects.order_by('status_date').select_related()
if request.method == 'POST' and has_role(request.user, 'Secretariat'):
filename = request.POST.get('filename')
if filename and filename in os.listdir(settings.IESG_WG_EVALUATION_DIR):
if 'delete' in request.POST:
os.unlink(os.path.join(settings.IESG_WG_EVALUATION_DIR, filename))
if 'add' in request.POST:
d = parse_wg_action_file(os.path.join(settings.IESG_WG_EVALUATION_DIR, filename))
qstr = "?" + "&".join("%s=%s" % t for t in d.iteritems())
return HttpResponseRedirect(urlreverse('iesg_add_working_group_action') + qstr)
skip = [c.group_acronym.acronym for c in current_items]
possible_items = filter(lambda x: x['acronym'] not in skip,
get_possible_wg_actions())
return render_to_response("iesg/working_group_actions.html",
dict(current_items=current_items,
possible_items=possible_items),
context_instance=RequestContext(request))
class EditWGActionForm(forms.ModelForm):
token_name = forms.ChoiceField(required=True)
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False)
class Meta:
model = WGAction
fields = ['status_date', 'token_name', 'category', 'note']
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
# token name choices
self.fields['token_name'].choices = [("", "(None)")] + [(p.plain_name(), p.plain_name()) for p in IESGLogin.active_iesg().order_by('first_name')]
# telechat choices
dates = TelechatDates.objects.all()[0].dates()
init = kwargs['initial']['telechat_date']
if init and init not in dates:
dates.insert(0, init)
choices = [("", "(not on agenda)")]
for d in dates:
choices.append((d, d.strftime("%Y-%m-%d")))
self.fields['telechat_date'].choices = choices
@role_required('Secretariat')
def edit_working_group_action(request, wga_id):
if wga_id != None:
wga = get_object_or_404(WGAction, pk=wga_id)
else:
wga = WGAction()
try:
wga.group_acronym = Acronym.objects.get(acronym=request.GET.get('acronym'))
except Acronym.DoesNotExist:
pass
wga.token_name = request.GET.get('token')
try:
d = datetime.datetime.strptime(request.GET.get('status_date'), '%Y-%m-%d').date()
except:
d = datetime.date.today()
wga.status_date = d
wga.telechat_date = TelechatDates.objects.all()[0].date1
wga.agenda = True
initial = dict(telechat_date=wga.telechat_date if wga.agenda else None)
if request.method == 'POST':
if "delete" in request.POST:
wga.delete()
return HttpResponseRedirect(urlreverse('iesg_working_group_actions'))
form = EditWGActionForm(request.POST, instance=wga, initial=initial)
if form.is_valid():
form.save(commit=False)
wga.agenda = bool(form.cleaned_data['telechat_date'])
if wga.category in (11, 21):
wga.agenda = False
if wga.agenda:
wga.telechat_date = form.cleaned_data['telechat_date']
wga.save()
return HttpResponseRedirect(urlreverse('iesg_working_group_actions'))
else:
form = EditWGActionForm(instance=wga, initial=initial)
return render_to_response("iesg/edit_working_group_action.html",
dict(wga=wga,
form=form),
context_instance=RequestContext(request))