datatracker/ietf/doc/views_ballot.py
Ole Laursen e63ac36b8f Remove shim layer from ietfauth, delete some dead code and migrate
remaining importers of ietfauth.decorators to ietfauth.utils
 - Legacy-Id: 6797
2013-12-05 16:24:03 +00:00

835 lines
35 KiB
Python

# ballot management (voting, commenting, writeups, ...) for Area
# Directors and Secretariat
import re, os, datetime
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
from django.shortcuts import render_to_response, get_object_or_404
from django.core.urlresolvers import reverse as urlreverse
from django.template.loader import render_to_string
from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
from django.utils import simplejson
from django.conf import settings
import debug
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.ietfauth.utils import has_role, role_required
from ietf.iesg.models import TelechatDate
from ietf.ipr.models import IprDetail
from ietf.ipr.search import iprs_from_docs
from ietf.doc.mails import *
from ietf.doc.lastcall import request_last_call
from ietf.doc.utils import *
from ietf.doc.models import *
from ietf.name.models import BallotPositionName
from ietf.message.utils import infer_message
from ietf.person.models import Person
BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"),
("discuss", "Discuss"),
("abstain", "Abstain"),
("recuse", "Recuse"),
("", "No Record"),
)
# -------------------------------------------------
# Helper Functions
# -------------------------------------------------
def do_undefer_ballot(request, doc):
'''
Helper function to perform undefer of ballot. Takes the Request object, for use in
logging, and the Document object.
'''
login = request.user.get_profile()
telechat_date = TelechatDate.objects.active().order_by("date")[0].date
save_document_in_history(doc)
prev_state = doc.friendly_state()
if doc.type_id == 'draft':
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='iesg-eva'))
prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(used=True, type='conflrev',slug='iesgeval'))
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
update_telechat(request, doc, login, telechat_date)
email_state_changed(request, doc, e.desc)
def position_to_ballot_choice(position):
for v, label in BALLOT_CHOICES:
if v and getattr(position, v):
return v
return ""
def position_label(position_value):
return dict(BALLOT_CHOICES).get(position_value, "")
def get_ballot_info(ballot, area_director):
pos = Position.objects.filter(ballot=ballot, ad=area_director)
pos = pos[0] if pos else None
discuss = IESGDiscuss.objects.filter(ballot=ballot, ad=area_director)
discuss = discuss[0] if discuss else None
comment = IESGComment.objects.filter(ballot=ballot, ad=area_director)
comment = comment[0] if comment else None
return (pos, discuss, comment)
# -------------------------------------------------
class EditPositionForm(forms.Form):
position = forms.ModelChoiceField(queryset=BallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True)
discuss = forms.CharField(required=False, widget=forms.Textarea)
comment = forms.CharField(required=False, widget=forms.Textarea)
return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
ballot_type = kwargs.pop("ballot_type")
super(EditPositionForm, self).__init__(*args, **kwargs)
self.fields['position'].queryset = ballot_type.positions.order_by('order')
def clean_discuss(self):
entered_discuss = self.cleaned_data["discuss"]
entered_pos = self.cleaned_data.get("position", "norecord")
if entered_pos.blocking and not entered_discuss:
raise forms.ValidationError("You must enter a non-empty discuss")
return entered_discuss
@role_required('Area Director','Secretariat')
def edit_position(request, name, ballot_id):
"""Vote and edit discuss and comment on document as Area Director."""
doc = get_object_or_404(Document, docalias__name=name)
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
ad = login = request.user.get_profile()
if 'HTTP_REFERER' in request.META:
return_to_url = request.META['HTTP_REFERER']
else:
return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot_id))
# if we're in the Secretariat, we can select an AD to act as stand-in for
if has_role(request.user, "Secretariat"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
ad = get_object_or_404(Person, pk=ad_id)
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
if request.method == 'POST':
if not has_role(request.user, "Secretariat") and not ad.role_set.filter(name="ad", group__type="area", group__state="active"):
# prevent pre-ADs from voting
return HttpResponseForbidden("Must be a proper Area Director in an active area to cast ballot")
form = EditPositionForm(request.POST, ballot_type=ballot.ballot_type)
if form.is_valid():
# save the vote
clean = form.cleaned_data
if clean['return_to_url']:
return_to_url = clean['return_to_url']
pos = BallotPositionDocEvent(doc=doc, by=login)
pos.type = "changed_ballot_position"
pos.ballot = ballot
pos.ad = ad
pos.pos = clean["position"]
pos.comment = clean["comment"].rstrip()
pos.comment_time = old_pos.comment_time if old_pos else None
pos.discuss = clean["discuss"].rstrip()
if not pos.pos.blocking:
pos.discuss = ""
pos.discuss_time = old_pos.discuss_time if old_pos else None
changes = []
added_events = []
# possibly add discuss/comment comments to history trail
# so it's easy to see what's happened
old_comment = old_pos.comment if old_pos else ""
if pos.comment != old_comment:
pos.comment_time = pos.time
changes.append("comment")
if pos.comment:
e = DocEvent(doc=doc)
e.by = ad # otherwise we can't see who's saying it
e.type = "added_comment"
e.desc = "[Ballot comment]\n" + pos.comment
added_events.append(e)
old_discuss = old_pos.discuss if old_pos else ""
if pos.discuss != old_discuss:
pos.discuss_time = pos.time
changes.append("discuss")
if pos.pos.blocking:
e = DocEvent(doc=doc, by=login)
e.by = ad # otherwise we can't see who's saying it
e.type = "added_comment"
e.desc = "[Ballot %s]\n" % pos.pos.name.lower()
e.desc += pos.discuss
added_events.append(e)
# figure out a description
if not old_pos and pos.pos.slug != "norecord":
pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name())
elif old_pos and pos.pos != old_pos.pos:
pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.plain_name(), pos.pos.name, old_pos.pos.name)
if not pos.desc and changes:
pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.plain_name())
# only add new event if we actually got a change
if pos.desc:
if login != ad:
pos.desc += u" by %s" % login.plain_name()
pos.save()
for e in added_events:
e.save() # save them after the position is saved to get later id for sorting order
if request.POST.get("send_mail"):
qstr = "?return_to_url=%s" % return_to_url
if request.GET.get('ad'):
qstr += "&ad=%s" % request.GET.get('ad')
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name, ballot_id=ballot_id)) + qstr)
elif request.POST.get("Defer"):
return HttpResponseRedirect(urlreverse("doc_defer_ballot", kwargs=dict(name=doc)))
elif request.POST.get("Undefer"):
return HttpResponseRedirect(urlreverse("doc_undefer_ballot", kwargs=dict(name=doc)))
else:
return HttpResponseRedirect(return_to_url)
else:
initial = {}
if old_pos:
initial['position'] = old_pos.pos.slug
initial['discuss'] = old_pos.discuss
initial['comment'] = old_pos.comment
if return_to_url:
initial['return_to_url'] = return_to_url
form = EditPositionForm(initial=initial, ballot_type=ballot.ballot_type)
blocking_positions = dict((p.pk, p.name) for p in form.fields["position"].queryset.all() if p.blocking)
ballot_deferred = doc.active_defer_event()
return render_to_response('doc/ballot/edit_position.html',
dict(doc=doc,
form=form,
ad=ad,
return_to_url=return_to_url,
old_pos=old_pos,
ballot_deferred=ballot_deferred,
ballot = ballot,
show_discuss_text=old_pos and old_pos.pos.blocking,
blocking_positions=simplejson.dumps(blocking_positions),
),
context_instance=RequestContext(request))
@role_required('Area Director','Secretariat')
def send_ballot_comment(request, name, ballot_id):
"""Email document ballot position discuss/comment for Area Director."""
doc = get_object_or_404(Document, docalias__name=name)
ballot = get_object_or_404(BallotDocEvent, type="created_ballot", pk=ballot_id, doc=doc)
ad = login = request.user.get_profile()
return_to_url = request.GET.get('return_to_url')
if not return_to_url:
return_to_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot_id))
if 'HTTP_REFERER' in request.META:
back_url = request.META['HTTP_REFERER']
else:
back_url = urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot_id))
# if we're in the Secretariat, we can select an AD to act as stand-in for
if not has_role(request.user, "Area Director"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
ad = get_object_or_404(Person, pk=ad_id)
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
if not pos:
raise Http404()
subj = []
d = ""
blocking_name = "DISCUSS"
if pos.pos.blocking and pos.discuss:
d = pos.discuss
blocking_name = pos.pos.name.upper()
subj.append(blocking_name)
c = ""
if pos.comment:
c = pos.comment
subj.append("COMMENT")
ad_name_genitive = ad.plain_name() + "'" if ad.plain_name().endswith('s') else ad.plain_name() + "'s"
subject = "%s %s on %s" % (ad_name_genitive, pos.pos.name if pos.pos else "No Position", doc.name + "-" + doc.rev)
if subj:
subject += ": (with %s)" % " and ".join(subj)
body = render_to_string("doc/ballot/ballot_comment_mail.txt",
dict(discuss=d,
comment=c,
ad=ad.plain_name(),
doc=doc,
pos=pos.pos,
blocking_name=blocking_name,
settings=settings))
frm = ad.role_email("ad").formatted_email()
to = "The IESG <iesg@ietf.org>"
if request.method == 'POST':
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
if request.POST.get("cc_state_change") and doc.notify:
cc.extend(doc.notify.split(','))
send_mail_text(request, to, frm, subject, body, cc=u", ".join(cc))
return HttpResponseRedirect(return_to_url)
return render_to_response('doc/ballot/send_ballot_comment.html',
dict(doc=doc,
subject=subject,
body=body,
frm=frm,
to=to,
ad=ad,
can_send=d or c,
back_url=back_url,
),
context_instance=RequestContext(request))
@role_required('Secretariat')
def clear_ballot(request, name):
"""Clear all positions and discusses on every open ballot for a document."""
doc = get_object_or_404(Document, name=name)
if request.method == 'POST':
by = request.user.get_profile()
for t in BallotType.objects.filter(doc_type=doc.type_id):
close_ballot(doc, by, t.slug)
create_ballot_if_not_open(doc, by, t.slug)
if doc.get_state('draft-iesg').slug == 'defer':
do_undefer_ballot(request,doc)
return HttpResponseRedirect(urlreverse("doc_view", kwargs=dict(name=doc.name)))
return render_to_response('doc/ballot/clear_ballot.html',
dict(doc=doc,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
@role_required('Area Director','Secretariat')
def defer_ballot(request, name):
"""Signal post-pone of ballot, notifying relevant parties."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id not in ('draft','conflrev'):
raise Http404()
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
telechat_date = TelechatDate.objects.active().order_by("date")[1].date
if request.method == 'POST':
save_document_in_history(doc)
prev_state = doc.friendly_state()
if doc.type_id == 'draft':
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='defer'))
prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
elif doc.type_id == 'conflrev':
doc.set_state(State.objects.get(used=True, type='conflrev', slug='defer'))
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
update_telechat(request, doc, login, telechat_date)
email_ballot_deferred(request, doc, login.plain_name(), telechat_date)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('doc/ballot/defer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
@role_required('Area Director','Secretariat')
def undefer_ballot(request, name):
"""undo deferral of ballot ballot."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id not in ('draft','conflrev'):
raise Http404()
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
raise Http404()
telechat_date = TelechatDate.objects.active().order_by("date")[0].date
if request.method == 'POST':
do_undefer_ballot(request,doc)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('doc/ballot/undefer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
class LastCallTextForm(forms.Form):
last_call_text = forms.CharField(widget=forms.Textarea, required=True)
def clean_last_call_text(self):
lines = self.cleaned_data["last_call_text"].split("\r\n")
for l, next in zip(lines, lines[1:]):
if l.startswith('Subject:') and next.strip():
raise forms.ValidationError("Subject line appears to have a line break, please make sure there is no line breaks in the subject line and that it is followed by an empty line.")
return self.cleaned_data["last_call_text"].replace("\r", "")
@role_required('Area Director','Secretariat')
def lastcalltext(request, name):
"""Editing of the last call text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
existing = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
if not existing:
existing = generate_last_call_announcement(request, doc)
form = LastCallTextForm(initial=dict(last_call_text=existing.text))
if request.method == 'POST':
if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST:
form = LastCallTextForm(request.POST)
if form.is_valid():
t = form.cleaned_data['last_call_text']
if t != existing.text:
e = WriteupDocEvent(doc=doc, by=login)
e.by = login
e.type = "changed_last_call_text"
e.desc = "Last call announcement was changed"
e.text = t
e.save()
if "send_last_call_request" in request.POST:
save_document_in_history(doc)
prev_state = doc.friendly_state()
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='lc-req'))
prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
email_ad(request, doc, doc.ad, login, e.desc)
request_last_call(request, doc)
return render_to_response('doc/draft/last_call_requested.html',
dict(doc=doc),
context_instance=RequestContext(request))
if "regenerate_last_call_text" in request.POST:
e = generate_last_call_announcement(request, doc)
# make sure form has the updated text
form = LastCallTextForm(initial=dict(last_call_text=e.text))
s = doc.get_state("draft-iesg")
can_request_last_call = s.order < 27
can_make_last_call = s.order < 20
can_announce = s.order > 19
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('doc/ballot/lastcalltext.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
last_call_form=form,
can_request_last_call=can_request_last_call,
can_make_last_call=can_make_last_call,
need_intended_status=need_intended_status,
),
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"""
doc = get_object_or_404(Document, docalias__name=name)
login = request.user.get_profile()
existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if not existing:
existing = generate_ballot_writeup(request, doc)
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_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=doc, by=login)
e.by = login
e.type = "changed_ballot_writeup_text"
e.desc = "Ballot writeup was changed"
e.text = t
e.save()
if "issue_ballot" in request.POST:
create_ballot_if_not_open(doc, login, "approve")
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot):
# sending the ballot counts as a yes
pos = BallotPositionDocEvent(doc=doc, by=login)
pos.ballot = ballot
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()
approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not approval:
approval = generate_approval_mail(request, doc)
msg = generate_issue_ballot_mail(request, doc, ballot)
send_mail_preformatted(request, msg)
send_mail_preformatted(request, msg, extra=extra_automation_headers(doc),
override={ "To": "IANA <%s>"%settings.IANA_EVAL_EMAIL, "CC": None, "Bcc": None , "Reply-To": None})
e = DocEvent(doc=doc, by=login)
e.by = login
e.type = "sent_ballot_announcement"
e.desc = "Ballot has been issued"
e.save()
return render_to_response('doc/ballot/ballot_issued.html',
dict(doc=doc,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('doc/ballot/writeupnotes.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")),
ballot_writeup_form=form,
need_intended_status=need_intended_status,
),
context_instance=RequestContext(request))
class ApprovalTextForm(forms.Form):
approval_text = forms.CharField(widget=forms.Textarea, required=True)
def clean_approval_text(self):
return self.cleaned_data["approval_text"].replace("\r", "")
@role_required('Area Director','Secretariat')
def ballot_approvaltext(request, name):
"""Editing of approval text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not existing:
existing = generate_approval_mail(request, doc)
form = ApprovalTextForm(initial=dict(approval_text=existing.text))
if request.method == 'POST':
if "save_approval_text" in request.POST:
form = ApprovalTextForm(request.POST)
if form.is_valid():
t = form.cleaned_data['approval_text']
if t != existing.text:
e = WriteupDocEvent(doc=doc, by=login)
e.by = login
e.type = "changed_ballot_approval_text"
e.desc = "Ballot approval text was changed"
e.text = t
e.save()
if "regenerate_approval_text" in request.POST:
e = generate_approval_mail(request, doc)
# make sure form has the updated text
form = ApprovalTextForm(initial=dict(approval_text=e.text))
can_announce = doc.get_state("draft-iesg").order > 19
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('doc/ballot/approvaltext.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
approval_text_form=form,
can_announce=can_announce,
need_intended_status=need_intended_status,
),
context_instance=RequestContext(request))
@role_required('Secretariat')
def approve_ballot(request, name):
"""Approve ballot, sending out announcement, changing state."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not e:
e = generate_approval_mail(request, doc)
approval_text = e.text
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if not e:
e = generate_ballot_writeup(request, doc)
ballot_writeup = e.text
if "NOT be published" in approval_text:
action = "do_not_publish"
elif "To: RFC Editor" in approval_text:
action = "to_rfc_editor"
else:
action = "to_announcement_list"
# NOTE: according to Michelle Cotton <michelle.cotton@icann.org>
# (as per 2011-10-24) IANA is scraping these messages for
# information so would like to know beforehand if the format
# changes
announcement = approval_text + "\n\n" + ballot_writeup
if request.method == 'POST':
if action == "do_not_publish":
new_state = State.objects.get(used=True, type="draft-iesg", slug="dead")
else:
new_state = State.objects.get(used=True, type="draft-iesg", slug="ann")
prev_friendly_state = doc.friendly_state()
prev_state = doc.get_state("draft-iesg")
if new_state.slug == "ann" and new_state.slug != prev_state.slug and not request.REQUEST.get("skiprfceditorpost"):
# start by notifying the RFC Editor
import ietf.sync.rfceditor
response, error = ietf.sync.rfceditor.post_approved_draft(settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name)
if error:
return render_to_response('doc/draft/rfceditor_post_approved_draft_failed.html',
dict(name=doc.name,
response=response,
error=error),
context_instance=RequestContext(request))
# fixup document
close_open_ballots(doc, login)
save_document_in_history(doc)
doc.set_state(new_state)
prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
e = DocEvent(doc=doc, by=login)
if action == "do_not_publish":
e.type = "iesg_disapproved"
e.desc = "Do Not Publish note has been sent to the RFC Editor"
else:
e.type = "iesg_approved"
e.desc = "IESG has approved the document"
e.save()
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_friendly_state)
doc.time = e.time
doc.save()
email_state_changed(request, doc, change_description)
email_ad(request, doc, doc.ad, login, change_description)
# send announcement
send_mail_preformatted(request, announcement)
if action == "to_announcement_list":
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
override={ "To": "IANA <%s>"%settings.IANA_APPROVE_EMAIL, "CC": None, "Bcc": None, "Reply-To": None})
msg = infer_message(announcement)
msg.by = login
msg.save()
msg.related_docs.add(doc)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('doc/ballot/approve_ballot.html',
dict(doc=doc,
action=action,
announcement=announcement),
context_instance=RequestContext(request))
class MakeLastCallForm(forms.Form):
last_call_sent_date = forms.DateField(required=True)
last_call_expiration_date = forms.DateField(required=True)
@role_required('Secretariat')
def make_last_call(request, name):
"""Make last call for Internet Draft, sending out announcement."""
doc = get_object_or_404(Document, docalias__name=name)
if not (doc.get_state("draft-iesg") or doc.get_state("statchg")):
raise Http404
login = request.user.get_profile()
e = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
if not e:
if doc.type.slug != 'draft':
raise Http404
e = generate_last_call_announcement(request, doc)
announcement = e.text
if request.method == 'POST':
form = MakeLastCallForm(request.POST)
if form.is_valid():
send_mail_preformatted(request, announcement)
if doc.type.slug == 'draft':
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
override={ "To": "IANA <drafts-lastcall@icann.org>", "CC": None, "Bcc": None, "Reply-To": None})
msg = infer_message(announcement)
msg.by = login
msg.save()
msg.related_docs.add(doc)
save_document_in_history(doc)
prev_state = doc.get_state("draft-iesg")
if doc.type.slug == 'draft':
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='lc'))
prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, doc.get_state("draft-iesg").name)
elif doc.type.slug == 'statchg':
doc.set_state(State.objects.get(used=True, type="statchg", slug='in-lc'))
e = log_state_changed(request, doc, login, doc.friendly_state(), prev_state)
change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, doc.friendly_state())
doc.time = e.time
doc.save()
email_state_changed(request, doc, change_description)
email_ad(request, doc, doc.ad, login, change_description)
e = LastCallDocEvent(doc=doc, by=login)
e.type = "sent_last_call"
e.desc = "The following Last Call announcement was sent out:<br><br>"
e.desc += announcement
if form.cleaned_data['last_call_sent_date'] != e.time.date():
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time())
e.expires = form.cleaned_data['last_call_expiration_date']
e.save()
# update IANA Review state
if doc.type.slug == 'draft':
prev_state = doc.get_state("draft-iana-review")
if not prev_state:
next_state = State.objects.get(used=True, type="draft-iana-review", slug="need-rev")
doc.set_state(next_state)
add_state_change_event(doc, login, prev_state, next_state)
return HttpResponseRedirect(doc.get_absolute_url())
else:
initial = {}
initial["last_call_sent_date"] = datetime.date.today()
if doc.type.slug == 'draft':
# This logic is repeated in the code that edits last call text - why?
expire_days = 14
if doc.group.type_id in ("individ", "area"):
expire_days = 28
templ = 'doc/draft/make_last_call.html'
else:
expire_days=28
templ = 'doc/status_change/make_last_call.html'
initial["last_call_expiration_date"] = datetime.date.today() + datetime.timedelta(days=expire_days)
form = MakeLastCallForm(initial=initial)
return render_to_response(templ,
dict(doc=doc,
form=form,
announcement=announcement,
),
context_instance=RequestContext(request))