diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index d72441b4a..d3954632b 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -346,7 +346,7 @@ class BallotWriteupsTests(TestCase): self.assertTrue('aread@' in outbox[-1]['Cc']) def test_edit_ballot_writeup(self): - draft = IndividualDraftFactory() + draft = IndividualDraftFactory(states=[('draft','active'),('draft-iesg','iesg-eva')]) url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name)) login_testing_unauthorized(self, "secretary", url) @@ -372,8 +372,32 @@ class BallotWriteupsTests(TestCase): ballot_writeup="This is a simple test.", save_ballot_writeup="1")) self.assertEqual(r.status_code, 200) - draft = Document.objects.get(name=draft.name) - self.assertTrue("This is a simple test" in draft.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) + d = Document.objects.get(name=draft.name) + self.assertTrue("This is a simple test" in d.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) + self.assertTrue('iesg-eva' == d.get_state_slug('draft-iesg')) + + def test_edit_ballot_writeup_already_approved(self): + draft = IndividualDraftFactory(states=[('draft','active'),('draft-iesg','approved')]) + url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1) + self.assertTrue(q('[type=submit]:contains("Save")')) + + # save + r = self.client.post(url, dict( + ballot_writeup="This is a simple test.", + save_ballot_writeup="1")) + self.assertEqual(r.status_code, 200) + msgs = [m for m in r.context['messages']] + self.assertTrue(1 == len(msgs)) + self.assertTrue("Writeup not changed" in msgs[0].message) + d = Document.objects.get(name=draft.name) + self.assertTrue('approved' == d.get_state_slug('draft-iesg')) def test_edit_ballot_rfceditornote(self): draft = IndividualDraftFactory() @@ -467,6 +491,41 @@ class BallotWriteupsTests(TestCase): self.assertIn('call expires', get_payload_text(outbox[-1])) self.client.logout() + def test_issue_ballot_auto_state_change(self): + ad = Person.objects.get(user__username="ad") + draft = IndividualDraftFactory(ad=ad, states=[('draft','active'),('draft-iesg','writeupw')]) + url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # normal get + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1) + self.assertFalse(q('[class=help-block]:contains("not completed IETF Last Call")')) + self.assertTrue(q('[type=submit]:contains("Save")')) + + # save + r = self.client.post(url, dict( + ballot_writeup="This is a simple test.", + issue_ballot="1")) + self.assertEqual(r.status_code, 200) + d = Document.objects.get(name=draft.name) + self.assertTrue('iesg-eva' == d.get_state_slug('draft-iesg')) + + def test_issue_ballot_warn_if_early(self): + ad = Person.objects.get(user__username="ad") + draft = IndividualDraftFactory(ad=ad, states=[('draft','active'),('draft-iesg','lc')]) + url = urlreverse('ietf.doc.views_ballot.ballot_writeupnotes', kwargs=dict(name=draft.name)) + login_testing_unauthorized(self, "secretary", url) + + # expect warning about issuing a ballot before IETF Last Call is done + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1) + self.assertTrue(q('[class=help-block]:contains("not completed IETF Last Call")')) + self.assertTrue(q('[type=submit]:contains("Save")')) def test_edit_approval_text(self): ad = Person.objects.get(user__username="ad") diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index f8a992e14..a4693e5f2 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -8,6 +8,7 @@ import datetime, json from django import forms from django.conf import settings +from django.contrib import messages from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.shortcuts import render, get_object_or_404, redirect from django.template.defaultfilters import striptags @@ -592,6 +593,7 @@ class BallotWriteupForm(forms.Form): def ballot_writeupnotes(request, name): """Editing of ballot write-up and notes""" doc = get_object_or_404(Document, docalias__name=name) + prev_state = doc.get_state("draft-iesg") login = request.user.person @@ -604,61 +606,76 @@ def ballot_writeupnotes(request, name): 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, rev=doc.rev, by=login) - e.by = login - e.type = "changed_ballot_writeup_text" - e.desc = "Ballot writeup was changed" - e.text = t - e.save() - elif existing.pk == None: - existing.save() + if prev_state.slug in ['ann', 'approved', 'rfcqueue', 'pub']: + ballot_already_approved = True + messages.warning(request, "There is an approved ballot for %s. Writeup not changed." % doc.name) + else: + ballot_already_approved = False + t = form.cleaned_data["ballot_writeup"] + if t != existing.text: + e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login) + e.by = login + e.type = "changed_ballot_writeup_text" + e.desc = "Ballot writeup was changed" + e.text = t + e.save() + elif existing.pk == None: + existing.save() - if "issue_ballot" in request.POST: - e = create_ballot_if_not_open(request, doc, login, "approve") # pyflakes:ignore - ballot = doc.latest_event(BallotDocEvent, type="created_ballot") - if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, balloter=login, ballot=ballot): - # sending the ballot counts as a yes - pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login) - pos.ballot = ballot - pos.type = "changed_ballot_position" - pos.balloter = login - pos.pos_id = "yes" - pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name()) - pos.save() + if "issue_ballot" in request.POST and not ballot_already_approved: + if prev_state.slug in ['watching', 'writeupw', 'goaheadw']: + new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva') + prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) + doc.set_state(new_state) + doc.tags.remove(*prev_tags) - # Consider mailing this position to 'iesg_ballot_saved' + sce = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) + if sce: + doc.save_with_history([sce]) - approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") - if not approval: - approval = generate_approval_mail(request, doc) - approval.save() + if not ballot_already_approved: + e = create_ballot_if_not_open(request, doc, login, "approve") # pyflakes:ignore + ballot = doc.latest_event(BallotDocEvent, type="created_ballot") + if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, balloter=login, ballot=ballot): + # sending the ballot counts as a yes + pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login) + pos.ballot = ballot + pos.type = "changed_ballot_position" + pos.balloter = login + pos.pos_id = "yes" + pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.balloter.plain_name()) + pos.save() - msg = generate_issue_ballot_mail(request, doc, ballot) + # Consider mailing this position to 'iesg_ballot_saved' - addrs = gather_address_lists('iesg_ballot_issued',doc=doc).as_strings() - override = {'To':addrs.to} - if addrs.cc: - override['CC'] = addrs.cc - send_mail_preformatted(request, msg, override=override) + approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") + if not approval: + approval = generate_approval_mail(request, doc) + approval.save() - addrs = gather_address_lists('ballot_issued_iana',doc=doc).as_strings() - override={ "To": "IANA <%s>"%settings.IANA_EVAL_EMAIL, "Bcc": None , "Reply-To": []} - if addrs.cc: - override['CC'] = addrs.cc - send_mail_preformatted(request, msg, extra=extra_automation_headers(doc), override=override) + msg = generate_issue_ballot_mail(request, doc, ballot) - e = DocEvent(doc=doc, rev=doc.rev, by=login) - e.by = login - e.type = "sent_ballot_announcement" - e.desc = "Ballot has been issued" - e.save() + addrs = gather_address_lists('iesg_ballot_issued',doc=doc).as_strings() + override = {'To':addrs.to} + if addrs.cc: + override['CC'] = addrs.cc + send_mail_preformatted(request, msg, override=override) - return render(request, 'doc/ballot/ballot_issued.html', - dict(doc=doc, - back_url=doc.get_absolute_url())) - + addrs = gather_address_lists('ballot_issued_iana',doc=doc).as_strings() + override={ "To": "IANA <%s>"%settings.IANA_EVAL_EMAIL, "Bcc": None , "Reply-To": []} + if addrs.cc: + override['CC'] = addrs.cc + send_mail_preformatted(request, msg, extra=extra_automation_headers(doc), override=override) + + e = DocEvent(doc=doc, rev=doc.rev, by=login) + e.by = login + e.type = "sent_ballot_announcement" + e.desc = "Ballot has been issued" + e.save() + + return render(request, 'doc/ballot/ballot_issued.html', + dict(doc=doc, + back_url=doc.get_absolute_url())) need_intended_status = "" if not doc.intended_std_level: @@ -668,6 +685,7 @@ def ballot_writeupnotes(request, name): dict(doc=doc, back_url=doc.get_absolute_url(), ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")), + ballot_issue_danger=bool(prev_state.slug in ['ad-eval', 'lc']), ballot_writeup_form=form, need_intended_status=need_intended_status, )) diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 07beee539..058819489 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -1903,6 +1903,9 @@ class ApiSubmitTests(TestCase): data['xml'], author = submission_file(name, rev, group, 'xml', "test_submission.xml", author=author, email=email, title=title, year=year) data['user'] = email r = self.client.post(url, data) + debug.show('url') + debug.show('r') + debug.show('r.content') return r, author, name def test_api_submit_info(self): diff --git a/ietf/templates/doc/ballot/writeupnotes.html b/ietf/templates/doc/ballot/writeupnotes.html index 229096e91..d18f2d676 100644 --- a/ietf/templates/doc/ballot/writeupnotes.html +++ b/ietf/templates/doc/ballot/writeupnotes.html @@ -17,11 +17,15 @@
This document has not completed IETF Last Call. Please do not issue the ballot early without good reason.
+ {% endif %}