Skip to content

Commit

Permalink
Added editing last call messages, requesting, issuing and tracking IE…
Browse files Browse the repository at this point in the history
…TF LCs to status-change documents

Added a Cancel button to the form that allows editing the relations for status-change documents
Added instructions to the agenda section 3.3

This adds states to status-change- documents and has a migration that must be applied.

This fixes bug #1039
 - Legacy-Id: 5770
  • Loading branch information
rjsparks committed Jun 1, 2013
1 parent 5bee7ac commit a741642
Show file tree
Hide file tree
Showing 15 changed files with 846 additions and 55 deletions.
477 changes: 477 additions & 0 deletions ietf/doc/migrations/0010_more_statchg_states.py

Large diffs are not rendered by default.

59 changes: 54 additions & 5 deletions ietf/doc/tests_status_change.py
Expand Up @@ -16,7 +16,7 @@
from ietf.doc.utils import create_ballot_if_not_open
from ietf.doc.views_status_change import default_approval_text

from ietf.doc.models import Document,DocEvent,NewRevisionDocEvent,BallotPositionDocEvent,TelechatDocEvent,DocAlias,State
from ietf.doc.models import Document,DocEvent,NewRevisionDocEvent,BallotPositionDocEvent,TelechatDocEvent,WriteupDocEvent,DocAlias,State
from ietf.name.models import StreamName
from ietf.group.models import Person
from ietf.iesg.models import TelechatDate
Expand Down Expand Up @@ -202,6 +202,51 @@ def test_edit_telechat_date(self):
doc = Document.objects.get(name='status-change-imaginary-mid-review')
self.assertEquals(doc.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date,None)

def test_edit_lc(self):
doc = Document.objects.get(name='status-change-imaginary-mid-review')
url = urlreverse('status_change_last_call',kwargs=dict(name=doc.name))

login_testing_unauthorized(self, "ad", url)

# additional setup
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
doc.ad = Person.objects.get(name='Ad No2')
doc.save()

# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form.edit-last-call-text')),1)

self.assertTrue( 'RFC9999 from Proposed Standard to Internet Standard' in ''.join(wrap(r.content,2**16)))
self.assertTrue( 'RFC9998 from Informational to Historic' in ''.join(wrap(r.content,2**16)))

# save
r = self.client.post(url,dict(last_call_text="Bogus last call text",save_last_call_text="1"))
self.assertEquals(r.status_code, 200)

last_call_event = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
self.assertEquals(last_call_event.text,"Bogus last call text")

# reset
r = self.client.post(url,dict(regenerate_last_call_text="1"))
self.assertEquals(r.status_code,200)
self.assertTrue( 'RFC9999 from Proposed Standard to Internet Standard' in ''.join(wrap(r.content,2**16)))
self.assertTrue( 'RFC9998 from Informational to Historic' in ''.join(wrap(r.content,2**16)))

# request last call
messages_before = len(outbox)
r = self.client.post(url,dict(last_call_text='stuff',send_last_call_request='Save+and+Request+Last+Call'))
self.assertEquals(r.status_code,200)
self.assertTrue( 'Last Call Requested' in ''.join(wrap(r.content,2**16)))
self.assertEquals(len(outbox), messages_before + 1)
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
self.assertTrue('Last Call:' in outbox[-1]['Subject'])
self.assertTrue('Last Call Request has been submitted' in ''.join(wrap(unicode(outbox[-1]),2**16)))


def test_approve(self):
doc = Document.objects.get(name='status-change-imaginary-mid-review')
url = urlreverse('status_change_approve',kwargs=dict(name=doc.name))
Expand Down Expand Up @@ -263,21 +308,24 @@ def test_edit_relations(self):

# Try to add a relation to an RFC that doesn't exist
r = self.client.post(url,dict(new_relation_row_blah="rfc9997",
statchg_relation_row_blah="tois"))
statchg_relation_row_blah="tois",
Submit="Submit"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)

# Try to add a relation leaving the relation type blank
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
statchg_relation_row_blah=""))
statchg_relation_row_blah="",
Submit="Submit"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)

# Try to add a relation with an unknown relationship type
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
statchg_relation_row_blah="badslug"))
statchg_relation_row_blah="badslug",
Submit="Submit"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
Expand All @@ -286,7 +334,8 @@ def test_edit_relations(self):
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
statchg_relation_row_blah="toexp",
new_relation_row_foo="rfc9998",
statchg_relation_row_foo="tobcp"))
statchg_relation_row_foo="tobcp",
Submit="Submit"))
self.assertEquals(r.status_code, 302)
doc = Document.objects.get(name='status-change-imaginary-mid-review')
self.assertEquals(doc.relateddocument_set.count(),2)
Expand Down
1 change: 1 addition & 0 deletions ietf/doc/urls_status_change.py
Expand Up @@ -8,5 +8,6 @@
url(r'^approve/$', "approve", name='status_change_approve'),
url(r'^telechat/$', "telechat_date", name='status_change_telechat_date'),
url(r'^relations/$', "edit_relations", name='status_change_relations'),
url(r'^last-call/$', "last_call", name='status_change_last_call'),
)

102 changes: 98 additions & 4 deletions ietf/doc/views_status_change.py
Expand Up @@ -25,6 +25,9 @@

from ietf.doc.forms import TelechatForm, AdForm, NotifyForm

from ietf.idrfc.views_ballot import LastCallTextForm
from ietf.idrfc.lastcall import request_last_call

class ChangeStateForm(forms.Form):
new_state = forms.ModelChoiceField(State.objects.filter(type="statchg", used=True), label="Status Change Evaluation State", empty_label=None, required=True)
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history", required=False)
Expand Down Expand Up @@ -588,8 +591,8 @@ def edit_relations(request, name):

if request.method == 'POST':
form = EditStatusChangeForm(request.POST)
if form.is_valid():

if 'Submit' in request.POST and form.is_valid():
old_relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS):
old_relations[rel.target.document.canonical_name()]=rel.relationship.slug
Expand All @@ -605,13 +608,14 @@ def edit_relations(request, name):
c.desc += "\nNEW:"
for relname,relslug in (set(new_relations.items())-set(old_relations.items())):
c.desc += "\n "+relname+": "+DocRelationshipName.objects.get(slug=relslug).name
#for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS):
# c.desc +="\n"+rel.relationship.name+": "+rel.target.document.canonical_name()
c.desc += "\n"
c.save()

return HttpResponseRedirect(status_change.get_absolute_url())

elif 'Cancel' in request.POST:
return HttpResponseRedirect(status_change.get_absolute_url())

else:
relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS):
Expand All @@ -627,3 +631,93 @@ def edit_relations(request, name):
'relation_slugs': relation_slugs,
},
context_instance = RequestContext(request))

def generate_last_call_text(request, doc):

# requester should be set based on doc.group once the group for a status change can be set to something other than the IESG
# and when groups are set, vary the expiration time accordingly

requester = "an individual participant"
expiration_date = datetime.date.today() + datetime.timedelta(days=28)
cc = []

new_text = render_to_string("doc/status_change/last_call_announcement.txt",
dict(doc=doc,
settings=settings,
requester=requester,
expiration_date=expiration_date.strftime("%Y-%m-%d"),
changes=['%s from %s to %s'%(rel.target.name.upper(),rel.target.document.std_level.name,newstatus(rel)) for rel in doc.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS)],
urls=[rel.target.document.get_absolute_url() for rel in doc.relateddocument_set.filter(relationship__slug__in=RELATION_SLUGS)],
cc=cc
)
)

e = WriteupDocEvent()
e.type = 'changed_last_call_text'
e.by = request.user.get_profile()
e.doc = doc
e.desc = 'Last call announcement was generated'
e.text = unicode(new_text)
e.save()

return e

@role_required("Area Director", "Secretariat")
def last_call(request, name):
"""Edit the Last Call Text for this status change and possibly request IETF LC"""

status_change = get_object_or_404(Document, type="statchg", name=name)

login = request.user.get_profile()

last_call_event = status_change.latest_event(WriteupDocEvent, type="changed_last_call_text")
if not last_call_event:
last_call_event = generate_last_call_text(request, status_change)

form = LastCallTextForm(initial=dict(last_call_text=last_call_event.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 != last_call_event.text:
e = WriteupDocEvent(doc=status_change, 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(status_change)

old_description = status_change.friendly_state()
status_change.set_state(State.objects.get(type='statchg', slug='lc-req'))
new_description = status_change.friendly_state()

e = log_state_changed(request, status_change, login, new_description, old_description)

status_change.time = e.time
status_change.save()

request_last_call(request, status_change)

return render_to_response('idrfc/last_call_requested.html',
dict(doc=status_change,
url = status_change.get_absolute_url(),
),
context_instance=RequestContext(request))

if "regenerate_last_call_text" in request.POST:
e = generate_last_call_text(request,status_change)
form = LastCallTextForm(initial=dict(last_call_text=e.text))

return render_to_response('doc/status_change/last_call.html',
dict(doc=status_change,
back_url = status_change.get_absolute_url(),
last_call_event = last_call_event,
last_call_form = form,
),
context_instance = RequestContext(request))

63 changes: 42 additions & 21 deletions ietf/idrfc/views_ballot.py
Expand Up @@ -1094,22 +1094,25 @@ class MakeLastCallForm(forms.Form):
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"):
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)
send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
override={ "To": "IANA <drafts-lastcall@icann.org>", "CC": None, "Bcc": None, "Reply-To": None})
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
Expand All @@ -1118,20 +1121,29 @@ def make_last_call(request, name):

save_document_in_history(doc)

prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='lc'))
if doc.type.slug == 'draft':

prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(used=True, type="draft-iesg", slug='lc'))

e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
prev_tag = doc.tags.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))
prev_tag = prev_tag[0] if prev_tag else None
if prev_tag:
doc.tags.remove(prev_tag)

e = idrfcutil_log_state_changed(request, doc, login, prev, prev_tag)
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':

prev = doc.friendly_state()
doc.set_state(State.objects.get(used=True, type="statchg", slug='in-lc'))
e = docutil_log_state_changed(request, doc, login, doc.friendly_state(), prev)
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()

change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, doc.get_state("draft-iesg").name)
email_state_changed(request, doc, change_description)
email_owner(request, doc, doc.ad, login, change_description)

Expand All @@ -1146,25 +1158,34 @@ def make_last_call(request, name):
e.save()

# update IANA Review state
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)
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"] = date.today()
expire_days = 14
if doc.group.type_id in ("individ", "area"):
expire_days = 28
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 = 'idrfc/make_last_callREDESIGN.html'
else:
expire_days=28
templ = 'doc/status_change/make_last_call.html'

initial["last_call_expiration_date"] = date.today() + timedelta(days=expire_days)

form = MakeLastCallForm(initial=initial)

return render_to_response('idrfc/make_last_callREDESIGN.html',
return render_to_response(templ,
dict(doc=doc,
form=form),
form=form,
announcement=announcement,
),
context_instance=RequestContext(request))
9 changes: 9 additions & 0 deletions ietf/idrfc/views_search.py
Expand Up @@ -640,6 +640,10 @@ def ad_dashboard_sort_key(doc):
elif doc.get_state_slug('charter') == 'iesgrev':
state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva')
return "1%d%s" % (state.order,seed)

if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'adrev':
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
return "1%d%s" % (state.order,seed)

if seed.startswith('Needs Shepherd'):
return "100%s" % seed
Expand All @@ -664,6 +668,7 @@ def ad_dashboard_sort_key(doc):

def by_ad2(request, name):
responsible = Document.objects.values_list('ad', flat=True).distinct()
ad_id = None
for p in Person.objects.filter(Q(role__name__in=("pre-ad", "ad"),
role__group__type="area",
role__group__state="active")
Expand All @@ -672,6 +677,10 @@ def by_ad2(request, name):
ad_id = p.id
ad_name = p.plain_name()
break

if not ad_id:
raise Http404

docqueryset = Document.objects.filter(ad__id=ad_id)
docs=[]
for doc in docqueryset:
Expand Down

0 comments on commit a741642

Please sign in to comment.