datatracker/ietf/wgcharter/views.py
2012-06-29 00:37:26 +00:00

581 lines
25 KiB
Python

import re, os, string, datetime, shutil
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse as urlreverse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from django import forms
from django.forms.util import ErrorList
from django.utils import simplejson
from django.utils.html import strip_tags, escape
from django.utils.safestring import mark_safe
from django.conf import settings
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.ietfauth.decorators import has_role, role_required
from ietf.iesg.models import TelechatDate
from ietf.doc.models import *
from ietf.doc.utils import *
from ietf.name.models import *
from ietf.person.models import *
from ietf.group.models import *
from ietf.group.utils import save_group_in_history
from ietf.wgcharter.mails import *
from ietf.wgcharter.utils import *
class ChangeStateForm(forms.Form):
charter_state = forms.ModelChoiceField(State.objects.filter(type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"]), label="Charter state", empty_label=None, required=False)
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
def __init__(self, *args, **kwargs):
self.hide = kwargs.pop('hide', None)
super(ChangeStateForm, self).__init__(*args, **kwargs)
# hide requested fields
if self.hide:
for f in self.hide:
self.fields[f].widget = forms.HiddenInput
@role_required("Area Director", "Secretariat")
def change_state(request, name, option=None):
"""Change state of WG and charter, notifying parties as necessary
and logging the change as a comment."""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering":
initial_review = None
login = request.user.get_profile()
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
clean = form.cleaned_data
charter_rev = charter.rev
if option in ("initcharter", "recharter"):
charter_state = State.objects.get(type="charter", slug="infrev")
# make sure we have the latest revision set, if we
# abandoned a charter before, we could have reset the
# revision to latest approved
prev_revs = charter.history_set.order_by('-rev')[:1]
if prev_revs and prev_revs[0].rev > charter_rev:
charter_rev = prev_revs[0].rev
if "-" not in charter_rev:
charter_rev = charter_rev + "-00"
elif option == "abandon":
if wg.state_id == "proposed":
charter_state = State.objects.get(type="charter", slug="notrev")
else:
charter_state = State.objects.get(type="charter", slug="approved")
charter_rev = approved_revision(charter.rev)
else:
charter_state = clean['charter_state']
comment = clean['comment'].rstrip()
message = clean['message']
if charter_state != charter.get_state():
# Charter state changed
save_document_in_history(charter)
prev = charter.get_state()
charter.set_state(charter_state)
charter.rev = charter_rev
if option != "abandon":
log_state_changed(request, charter, login, prev)
else:
# kill hanging ballots
close_open_ballots(charter, login)
# Special log for abandoned efforts
e = DocEvent(type="changed_document", doc=charter, by=login)
e.desc = "IESG has abandoned the chartering effort"
e.save()
if comment:
c = DocEvent(type="added_comment", doc=charter, by=login)
c.desc = comment
c.save()
charter.time = datetime.datetime.now()
charter.save()
if message:
email_secretariat(request, wg, "state-%s" % charter_state.slug, message)
email_state_changed(request, charter, "State changed to %s." % charter_state)
if charter_state.slug == "intrev":
if request.POST.get("ballot_wo_extern"):
create_ballot_if_not_open(charter, login, "r-wo-ext")
else:
create_ballot_if_not_open(charter, login, "r-extrev")
default_review_text(wg, charter, login)
default_action_text(wg, charter, login)
elif charter_state.slug == "iesgrev":
create_ballot_if_not_open(charter, login, "approve")
if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter)
e.expires = datetime.datetime.now() + datetime.timedelta(weeks=clean["initial_time"])
e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
e.save()
return redirect('doc_view', name=charter.name)
else:
if option == "recharter":
hide = ['initial_time', 'charter_state', 'message']
init = dict()
elif option == "initcharter":
hide = ['charter_state']
init = dict(initial_time=1, message='%s has initiated chartering of the proposed WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
elif option == "abandon":
hide = ['initial_time', 'charter_state']
init = dict(message='%s has abandoned the chartering effort on the WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
else:
hide = ['initial_time']
s = charter.get_state()
init = dict(charter_state=s.pk if s else None)
form = ChangeStateForm(hide=hide, initial=init)
prev_charter_state = None
charter_hists = DocHistory.objects.filter(doc=charter).exclude(states__type="charter", states__slug=charter.get_state_slug()).order_by("-time")[:1]
if charter_hists:
prev_charter_state = charter_hists[0].get_state()
title = {
"initcharter": "Initiate chartering of WG %s" % wg.acronym,
"recharter": "Recharter WG %s" % wg.acronym,
"abandon": "Abandon effort on WG %s" % wg.acronym,
}.get(option)
if not title:
title = "Change state of WG %s" % wg.acronym
def state_pk(slug):
return State.objects.get(type="charter", slug=slug).pk
messages = {
state_pk("infrev"): 'The WG "%s" (%s) has been set to Informal IESG review by %s.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("intrev"): 'The WG "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("extrev"): 'The WG "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (wg.name, wg.acronym, login.plain_name()),
}
states_for_ballot_wo_extern = State.objects.filter(type="charter", slug="intrev").values_list("pk", flat=True)
return render_to_response('wgcharter/change_state.html',
dict(form=form,
doc=wg.charter,
login=login,
option=option,
prev_charter_state=prev_charter_state,
title=title,
initial_review=initial_review,
chartering_type=chartering_type,
messages=simplejson.dumps(messages),
states_for_ballot_wo_extern=simplejson.dumps(list(states_for_ballot_wo_extern)),
),
context_instance=RequestContext(request))
class TelechatForm(forms.Form):
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False)
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
dates = [d.date for d in TelechatDate.objects.active().order_by('date')]
init = kwargs['initial'].get("telechat_date")
if init and init not in dates:
dates.insert(0, init)
self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates]
@role_required("Area Director", "Secretariat")
def telechat_date(request, name):
doc = get_object_or_404(Document, type="charter", name=name)
login = request.user.get_profile()
e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
initial = dict(telechat_date=e.telechat_date if e else None)
if request.method == "POST":
form = TelechatForm(request.POST, initial=initial)
if form.is_valid():
update_telechat(request, doc, login, form.cleaned_data['telechat_date'])
return redirect("doc_view", name=doc.name)
else:
form = TelechatForm(initial=initial)
return render_to_response('wgcharter/edit_telechat_date.html',
dict(doc=doc,
form=form,
user=request.user,
login=login),
context_instance=RequestContext(request))
class NotifyForm(forms.Form):
notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma", label="Notification list", required=False)
def clean_notify(self):
return self.cleaned_data["notify"].strip()
@role_required("Area Director", "Secretariat")
def edit_notify(request, name):
doc = get_object_or_404(Document, type="charter", name=name)
login = request.user.get_profile()
init = {'notify': doc.notify}
if request.method == "POST":
form = NotifyForm(request.POST, initial=init)
if form.is_valid():
n = form.cleaned_data["notify"]
if n != doc.notify:
save_document_in_history(doc)
e = DocEvent(doc=doc, by=login)
e.desc = "Notification list changed to %s" % (escape(n) or "none")
if doc.notify:
e.desc += " from %s" % escape(doc.notify)
e.type = "changed_document"
e.save()
doc.notify = n
doc.time = e.time
doc.save()
return redirect("doc_view", name=doc.name)
else:
form = NotifyForm(initial=init)
return render_to_response('wgcharter/edit_notify.html',
dict(doc=doc,
form=form,
user=request.user,
login=login),
context_instance=RequestContext(request))
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
def clean_content(self):
return self.cleaned_data["content"].replace("\r", "")
def clean_txt(self):
return get_cleaned_text_file_content(self.cleaned_data["txt"])
def save(self, wg, rev):
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (wg.charter.canonical_name(), rev))
with open(filename, 'wb') as destination:
if self.cleaned_data['txt']:
destination.write(self.cleaned_data['txt'])
else:
destination.write(self.cleaned_data['content'])
@role_required('Area Director','Secretariat')
def submit(request, name=None, acronym=None, option=None):
if name:
if not name.startswith('charter-'):
name = "charter-ietf-" + name
elif acronym:
name = "charter-ietf-" + acronym
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
login = request.user.get_profile()
path = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev))
not_uploaded_yet = charter.rev.endswith("-00") and not os.path.exists(path)
if not_uploaded_yet:
# this case is special - we recently chartered or rechartered and have no file yet
next_rev = charter.rev
else:
# search history for possible collisions with abandoned efforts
prev_revs = list(charter.history_set.order_by('-time').values_list('rev', flat=True))
next_rev = next_revision(charter.rev)
while next_rev in prev_revs:
next_rev = next_revision(next_rev)
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
save_document_in_history(charter)
# Also save group history so we can search for it
save_group_in_history(wg)
charter.rev = next_rev
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
e.rev = charter.rev
e.save()
# Save file on disk
form.save(wg, charter.rev)
charter.time = datetime.datetime.now()
charter.save()
if option:
return redirect('charter_startstop_process', name=charter.name, option=option)
else:
return redirect("doc_view", name=charter.name)
else:
init = { "content": ""}
c = charter
if not_uploaded_yet:
# use text from last approved revision
last_approved = charter.rev.split("-")[0]
h = charter.history_set.filter(rev=last_approved).order_by("-time", "-id")
if h:
c = h[0]
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (c.canonical_name(), c.rev))
try:
with open(filename, 'r') as f:
init["content"] = f.read()
except IOError:
pass
form = UploadForm(initial=init)
return render_to_response('wgcharter/submit.html',
{'form': form,
'next_rev': next_rev,
'wg': wg },
context_instance=RequestContext(request))
class AnnouncementTextForm(forms.Form):
announcement_text = forms.CharField(widget=forms.Textarea, required=True)
def clean_announcement_text(self):
return self.cleaned_data["announcement_text"].replace("\r", "")
@role_required('Area Director','Secretariat')
def announcement_text(request, name, ann):
"""Editing of announcement text"""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
login = request.user.get_profile()
if ann == "action":
existing = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
elif ann == "review":
existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
if not existing:
if ann == "action":
existing = default_action_text(wg, charter, login)
elif ann == "review":
existing = default_review_text(wg, charter, login)
form = AnnouncementTextForm(initial=dict(announcement_text=existing.text))
if request.method == 'POST':
form = AnnouncementTextForm(request.POST)
if "save_text" in request.POST and form.is_valid():
t = form.cleaned_data['announcement_text']
if t != existing.text:
e = WriteupDocEvent(doc=charter, by=login)
e.by = login
e.type = "changed_%s_announcement" % ann
e.desc = "WG %s text was changed" % ann
e.text = t
e.save()
charter.time = e.time
charter.save()
if request.GET.get("next", "") == "approve":
return redirect('charter_approve', name=charter.canonical_name())
return redirect('doc_writeup', name=charter.canonical_name())
if "regenerate_text" in request.POST:
if ann == "action":
e = default_action_text(wg, charter, login)
elif ann == "review":
e = default_review_text(wg, charter, login)
# make sure form has the updated text
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
if "send_text" in request.POST and form.is_valid():
msg = form.cleaned_data['announcement_text']
import email
parsed_msg = email.message_from_string(msg.encode("utf-8"))
send_mail_text(request, parsed_msg["To"],
parsed_msg["From"], parsed_msg["Subject"],
parsed_msg.get_payload())
return redirect('doc_writeup', name=charter.name)
return render_to_response('wgcharter/announcement_text.html',
dict(charter=charter,
announcement=ann,
back_url=urlreverse("doc_writeup", kwargs=dict(name=charter.name)),
announcement_text_form=form,
),
context_instance=RequestContext(request))
class BallotWriteupForm(forms.Form):
ballot_writeup = forms.CharField(widget=forms.Textarea, required=True)
def clean_ballot_writeup(self):
return self.cleaned_data["ballot_writeup"].replace("\r", "")
@role_required('Area Director','Secretariat')
def ballot_writeupnotes(request, name):
"""Editing of ballot write-up and notes"""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
ballot = charter.latest_event(BallotDocEvent, type="created_ballot")
if not ballot:
raise Http404()
login = request.user.get_profile()
approval = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
existing = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if not existing:
existing = generate_ballot_writeup(request, charter)
reissue = charter.latest_event(DocEvent, type="sent_ballot_announcement")
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
if request.method == 'POST' and ("save_ballot_writeup" in request.POST or "send_ballot" in request.POST):
form = BallotWriteupForm(request.POST)
if form.is_valid():
t = form.cleaned_data["ballot_writeup"]
if t != existing.text:
e = WriteupDocEvent(doc=charter, by=login)
e.by = login
e.type = "changed_ballot_writeup_text"
e.desc = "Ballot writeup was changed"
e.text = t
e.save()
if "send_ballot" in request.POST and approval:
if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=login, ballot=ballot):
# sending the ballot counts as a yes
pos = BallotPositionDocEvent(doc=charter, by=login)
pos.type = "changed_ballot_position"
pos.ad = login
pos.pos_id = "yes"
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
pos.save()
msg = generate_issue_ballot_mail(request, charter, ballot)
send_mail_preformatted(request, msg)
e = DocEvent(doc=charter, by=login)
e.by = login
e.type = "sent_ballot_announcement"
e.desc = "Ballot has been sent"
e.save()
return render_to_response('wgcharter/ballot_issued.html',
dict(doc=charter,
),
context_instance=RequestContext(request))
return render_to_response('wgcharter/ballot_writeupnotes.html',
dict(charter=charter,
ballot_issued=bool(charter.latest_event(type="sent_ballot_announcement")),
ballot_writeup_form=form,
reissue=reissue,
approval=approval,
),
context_instance=RequestContext(request))
@role_required("Secretariat")
def approve(request, name):
"""Approve charter, changing state, fixing revision, copying file to final location."""
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
login = request.user.get_profile()
e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
if not e:
announcement = default_action_text(wg, charter, login).text
else:
announcement = e.text
if request.method == 'POST':
new_charter_state = State.objects.get(type="charter", slug="approved")
prev_charter_state = charter.get_state()
save_document_in_history(charter)
charter.set_state(new_charter_state)
close_open_ballots(charter, login)
# approve
e = DocEvent(doc=charter, by=login)
e.type = "iesg_approved"
e.desc = "IESG has approved the charter"
e.save()
change_description = e.desc
new_state = GroupStateName.objects.get(slug="active")
if wg.state != new_state:
save_group_in_history(wg)
prev_state = wg.state
wg.state = new_state
wg.time = e.time
wg.save()
change_description += " and WG state has been changed to %s" % new_state.name
e = log_state_changed(request, charter, login, prev_charter_state)
# according to spec, 00-02 becomes 01, so copy file and record new revision
try:
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
shutil.copy(old, new)
except IOError:
raise Http404("Charter text %s" % filename)
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
e.rev = next_approved_revision(charter.rev)
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
e.save()
charter.rev = e.rev
charter.time = e.time
charter.save()
email_secretariat(request, wg, "state-%s" % new_charter_state.slug, change_description)
# send announcement
send_mail_preformatted(request, announcement)
return HttpResponseRedirect(charter.get_absolute_url())
return render_to_response('wgcharter/approve.html',
dict(charter=charter,
announcement=announcement,
wg=wg),
context_instance=RequestContext(request))