From 63139ab860ba2ca31d6f93fc3855ed0820374987 Mon Sep 17 00:00:00 2001
From: Robert Sparks
Date: Mon, 9 Sep 2013 18:25:44 +0000
Subject: [PATCH] Made Publication Request (for documents from IETF working
groups) an explicit action rather than a side-effect.
Simplified the working group state edit form.
Added hints to the WG state edit form to use the document's main page to request publication.
If a document is moved into IESG processing directly by the secretariat or an AD (old processing path), set working group state accordingly.
- Legacy-Id: 6119
---
ietf/doc/tests_draft.py | 78 +++++++++++++
ietf/doc/urls.py | 1 +
ietf/doc/views_doc.py | 7 +-
ietf/doc/views_draft.py | 111 +++++++++++++++++++
ietf/ietfworkflows/forms.py | 7 +-
ietf/ietfworkflows/tests.py | 19 +++-
ietf/ietfworkflows/views.py | 10 +-
ietf/templates/doc/submit_to_iesg.html | 80 +++++++++++++
ietf/templates/doc/submit_to_iesg_email.txt | 4 +
ietf/templates/ietfworkflows/state_edit.html | 52 ++-------
ietf/templates/ietfworkflows/state_form.html | 85 ++++----------
11 files changed, 340 insertions(+), 114 deletions(-)
create mode 100644 ietf/templates/doc/submit_to_iesg.html
create mode 100644 ietf/templates/doc/submit_to_iesg_email.txt
diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py
index 0fc63b589..03ca24c72 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -328,6 +328,24 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(events[-3].type, "started_iesg_process")
self.assertEquals(len(outbox), mailbox_before)
+ # Redo, starting in publication requested to make sure WG state is also set
+ draft.unset_state('draft-iesg')
+ draft.set_state(State.objects.get(type='draft-stream-ietf',slug='writeupw'))
+ draft.stream = StreamName.objects.get(slug='ietf')
+ draft.save()
+ r = self.client.post(url,
+ dict(intended_std_level=str(draft.intended_std_level_id),
+ ad=ad.pk,
+ create_in_state=State.objects.get(used=True, type="draft-iesg", slug="pub-req").pk,
+ notify="test@example.com",
+ note="This is a note",
+ telechat_date="",
+ ))
+ self.assertEquals(r.status_code, 302)
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.get_state_slug('draft-iesg'),'pub-req')
+ self.assertEquals(draft.get_state_slug('draft-stream-ietf'),'sub-pub')
+
def test_edit_consensus(self):
draft = make_test_data()
@@ -867,6 +885,66 @@ class IndividualInfoFormsTestCase(django.test.TestCase):
self.docname='draft-ietf-mars-test'
self.doc = Document.objects.get(name=self.docname)
+class SubmitToIesgTestCase(django.test.TestCase):
+ fixtures = ['names']
+
+ def verify_permissions(self):
+
+ def verify_fail(remote_user):
+ if remote_user:
+ self.client.login(remote_user=remote_user)
+ r = self.client.get(url)
+ self.assertEquals(r.status_code,404)
+
+ def verify_can_see(remote_user):
+ self.client.login(remote_user=remote_user)
+ r = self.client.get(url)
+ self.assertEquals(r.status_code,200)
+ q = PyQuery(r.content)
+ self.assertEquals(len(q('form input[name="confirm"]')),1)
+
+ url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname))
+
+ for username in [None,'plain','iana','iab chair']:
+ verify_fail(username)
+
+ for username in ['marschairman','secretary','ad']:
+ verify_can_see(username)
+
+ def cancel_submission(self):
+ url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname))
+ self.client.login(remote_user='marschairman')
+
+ r = self.client.post(url, dict(cancel="1"))
+ self.assertEquals(r.status_code, 302)
+
+ doc = Document.objects.get(pk=self.doc.pk)
+ self.assertTrue(doc.get_state('draft-iesg')==None)
+
+ def confirm_submission(self):
+ url = urlreverse('doc_to_iesg', kwargs=dict(name=self.docname))
+ self.client.login(remote_user='marschairman')
+
+ docevent_count_pre = self.doc.docevent_set.count()
+ mailbox_before = len(outbox)
+
+ r = self.client.post(url, dict(confirm="1"))
+ self.assertEquals(r.status_code, 302)
+
+ doc = Document.objects.get(pk=self.doc.pk)
+ self.assertTrue(doc.get_state('draft-iesg').slug=='pub-req')
+ self.assertTrue(doc.get_state('draft-stream-ietf').slug=='sub-pub')
+ self.assertTrue(doc.ad!=None)
+ self.assertTrue(doc.docevent_set.count() != docevent_count_pre)
+ self.assertEquals(len(outbox), mailbox_before + 1)
+ self.assertTrue("Publication has been requested" in outbox[-1]['Subject'])
+
+ def setUp(self):
+ make_test_data()
+ self.docname='draft-ietf-mars-test'
+ self.doc = Document.objects.get(name=self.docname)
+ self.doc.unset_state('draft-iesg')
+
class RequestPublicationTestCase(django.test.TestCase):
fixtures = ['names']
diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py
index e68517f71..8a8cb2ae7 100644
--- a/ietf/doc/urls.py
+++ b/ietf/doc/urls.py
@@ -69,6 +69,7 @@ urlpatterns = patterns('',
url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Piana-action|iana-review)/$', views_draft.change_iana_state, name='doc_change_iana_state'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/info/$', views_draft.edit_info, name='doc_edit_info'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestresurrect/$', views_draft.request_resurrect, name='doc_request_resurrect'),
+ url(r'^(?P[A-Za-z0-9._+-]+)/edit/submit-to-iesg/$', views_draft.to_iesg, name='doc_to_iesg'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/resurrect/$', views_draft.resurrect, name='doc_resurrect'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/addcomment/$', views_doc.add_comment, name='doc_add_comment'),
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 9be513d0d..6328fcc1c 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -318,8 +318,11 @@ def document_main(request, name, rev=None):
label += " (note that intended status is not set)"
actions.append((label, urlreverse('doc_request_publication', kwargs=dict(name=doc.name))))
- if doc.get_state_slug() != "expired" and doc.stream_id in ("ietf",) and can_edit and not iesg_state:
- actions.append(("Begin IESG Processing", urlreverse('doc_edit_info', kwargs=dict(name=doc.name)) + "?new=1"))
+ if doc.get_state_slug() != "expired" and doc.stream_id in ("ietf",):
+ if not iesg_state and can_edit:
+ actions.append(("Begin IESG Processing", urlreverse('doc_edit_info', kwargs=dict(name=doc.name)) + "?new=1"))
+ elif can_edit_stream_info and (not iesg_state or iesg_state.slug == 'watching'):
+ actions.append(("Submit to IESG for Publication", urlreverse('doc_to_iesg', kwargs=dict(name=doc.name))))
return render_to_response("doc/document_draft.html",
dict(doc=doc,
diff --git a/ietf/doc/views_draft.py b/ietf/doc/views_draft.py
index 8095155ba..24ae62f08 100644
--- a/ietf/doc/views_draft.py
+++ b/ietf/doc/views_draft.py
@@ -374,6 +374,105 @@ def get_initial_notify(doc):
receivers.append("%s@%s" % (doc.name, settings.TOOLS_SERVER))
return ", ".join(receivers)
+def to_iesg(request,name):
+ """ Submit an IETF stream document to the IESG for publication """
+ doc = get_object_or_404(Document, docalias__name=name, stream='ietf')
+
+ if doc.get_state_slug('draft') == "expired" or doc.get_state_slug('draft-iesg') == 'pub-req' :
+ raise Http404()
+
+ if not is_authorized_in_doc_stream(request.user, doc):
+ raise Http404()
+
+ target_state={
+ 'iesg' : State.objects.get(type='draft-iesg',slug='pub-req'),
+ 'wg' : State.objects.get(type='draft-stream-ietf',slug='sub-pub'),
+ }
+
+ warn={}
+ if not doc.intended_std_level:
+ warn['intended_std_level'] = True
+ if not doc.shepherd:
+ warn['shepherd'] = True
+ shepherd_writeup = doc.latest_event(WriteupDocEvent, type="changed_protocol_writeup")
+ if not shepherd_writeup:
+ warn['shepherd_writeup'] = True
+ tags = doc.tags.filter(slug__in=get_tags_for_stream_id(doc.stream_id))
+ if tags:
+ warn['tags'] = True
+ notify = doc.notify
+ if not notify:
+ notify = get_initial_notify(doc)
+ ad = doc.ad or doc.group.ad
+
+ if request.method == 'POST':
+
+ if request.POST.get("confirm", ""):
+
+ save_document_in_history(doc)
+
+ login = request.user.get_profile()
+
+ changes = []
+
+ if not doc.get_state("draft-iesg"):
+
+ e = DocEvent()
+ e.type = "started_iesg_process"
+ e.by = login
+ e.doc = doc
+ e.desc = "IESG process started in state %s" % target_state['iesg'].name
+ e.save()
+
+ if not doc.get_state('draft-iesg')==target_state['iesg']:
+ doc.set_state(target_state['iesg'])
+ changes.append("IESG state set to %s" % target_state['iesg'].name)
+ if not doc.get_state('draft-ietf-stream')==target_state['wg']:
+ doc.set_state(target_state['wg'])
+ changes.append("Working group state set to %s" % target_state['wg'].name)
+
+ if not doc.ad == ad :
+ doc.ad = ad
+ changes.append("Responsible AD changed to %s" % doc.ad)
+
+ if not doc.notify == notify :
+ doc.notify = notify
+ changes.append("State Change Notice email list changed to %s" % doc.notify)
+
+ for c in changes:
+ e = DocEvent(doc=doc, by=login)
+ e.desc = c
+ e.type = "changed_document"
+ e.save()
+
+ # Is this still necessary? I remember Henrik planning to have the model take care of this.
+ doc.time = datetime.datetime.now()
+
+ doc.save()
+
+ extra = {}
+ extra['Cc'] = "%s-chairs@tools.ietf.org, iesg-secretary@ietf.org, %s" % (doc.group.acronym,doc.notify)
+ send_mail(request=request,
+ to = doc.ad.email_address(),
+ frm = login.formatted_email(),
+ subject = "Publication has been requested for %s-%s" % (doc.name,doc.rev),
+ template = "doc/submit_to_iesg_email.txt",
+ context = dict(doc=doc,login=login,url="%s%s"%(settings.IDTRACKER_BASE_URL,doc.get_absolute_url()),),
+ extra = extra)
+
+ return HttpResponseRedirect(doc.get_absolute_url())
+
+ return render_to_response('doc/submit_to_iesg.html',
+ dict(doc=doc,
+ warn=warn,
+ target_state=target_state,
+ ad=ad,
+ shepherd_writeup=shepherd_writeup,
+ tags=tags,
+ notify=notify,
+ ),
+ context_instance=RequestContext(request))
+
@role_required('Area Director','Secretariat')
def edit_info(request, name):
"""Edit various Internet Draft attributes, notifying parties as
@@ -404,6 +503,18 @@ def edit_info(request, name):
if new_document:
doc.set_state(r['create_in_state'])
+ # Is setting the WG state here too much of a hidden side-effect?
+ if r['create_in_state'].slug=='pub-req':
+ if doc.stream and ( doc.stream.slug=='ietf' ) and doc.group and ( doc.group.type.name=='WG'):
+ submitted_state = State.objects.get(type='draft-stream-ietf',slug='sub-pub')
+ doc.set_state(submitted_state)
+ e = DocEvent()
+ e.type = "changed_document"
+ e.by = login
+ e.doc = doc
+ e.desc = "Working group state set to %s" % submitted_state.name
+ e.save()
+
# fix so Django doesn't barf in the diff below because these
# fields can't be NULL
doc.ad = r['ad']
diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py
index 7f9dd1fdf..b4db79bda 100644
--- a/ietf/ietfworkflows/forms.py
+++ b/ietf/ietfworkflows/forms.py
@@ -156,9 +156,9 @@ class NoWorkflowStateForm(StreamDraftForm):
class DraftTagsStateForm(StreamDraftForm):
+ new_state = forms.ChoiceField(label='State')
+ weeks = forms.IntegerField(label='Expected weeks in state',required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
- new_state = forms.ChoiceField()
- weeks = forms.IntegerField(required=False)
tags = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False)
template = 'ietfworkflows/state_form.html'
@@ -167,6 +167,9 @@ class DraftTagsStateForm(StreamDraftForm):
super(DraftTagsStateForm, self).__init__(*args, **kwargs)
self.state = get_state_for_draft(self.draft)
self.fields['new_state'].choices = self.get_states()
+ self.fields['new_state'].initial = self.state.pk
+ if self.draft.stream_id == 'ietf':
+ self.fields['new_state'].help_text = "Only select 'Submitted to IESG for Publication' to correct errors. Use the document's main page to request publication."
if self.is_bound:
for key, value in self.data.items():
if key.startswith('transition_'):
diff --git a/ietf/ietfworkflows/tests.py b/ietf/ietfworkflows/tests.py
index 56721b156..ad467a9e7 100644
--- a/ietf/ietfworkflows/tests.py
+++ b/ietf/ietfworkflows/tests.py
@@ -72,7 +72,6 @@ class EditStreamInfoTestCase(django.test.TestCase):
unused = draft.group.unused_tags.values_list("slug", flat=True)
for t in q("input[name=tags]"):
self.assertTrue(t.attrib["value"] not in unused)
- self.assertEquals(len(q('form input[type=submit][name=only_tags]')), 1)
# set tags
mailbox_before = len(outbox)
@@ -115,10 +114,26 @@ class EditStreamInfoTestCase(django.test.TestCase):
self.assertTrue(t.attrib["value"] not in unused)
self.assertEquals(len(q('select[name=new_state]')), 1)
- # set state
+ old_state = draft.get_state("draft-stream-%s" % draft.stream_id )
new_state = State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="parked")
+ self.assertTrue(old_state!=new_state)
mailbox_before = len(outbox)
events_before = draft.docevent_set.count()
+
+ # First make sure cancel doesn't change anything
+ r = self.client.post(url,
+ dict(comment="some comment",
+ weeks="10",
+ tags=[x.pk for x in draft.tags.filter(slug__in=get_tags_for_stream_id(draft.stream_id))],
+ new_state=new_state.pk,
+ cancel="1",
+ ))
+ self.assertEquals(r.status_code, 302)
+
+ draft = Document.objects.get(pk=draft.pk)
+ self.assertEquals(draft.get_state("draft-stream-%s" % draft.stream_id), old_state)
+
+ # Set new state
r = self.client.post(url,
dict(comment="some comment",
weeks="10",
diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py
index 26dc6f859..c976c634b 100644
--- a/ietf/ietfworkflows/views.py
+++ b/ietf/ietfworkflows/views.py
@@ -70,12 +70,16 @@ def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
if request.method == 'POST':
form = form_class(user=user, draft=draft, data=request.POST)
form.request = request
+ if request.POST.get("cancel",""):
+ return HttpResponseRedirect(draft.get_absolute_url())
if form.is_valid():
form.save()
- if form_class == NoWorkflowStateForm and settings.USE_DB_REDESIGN_PROXY_CLASSES:
- return HttpResponseRedirect(urlreverse('ietf.ietfworkflows.views.edit_state', kwargs={ 'name': draft.filename } ))
+
+ # This behavior surprises folks. Let's try running awhile without it.
+ #if form_class == NoWorkflowStateForm and settings.USE_DB_REDESIGN_PROXY_CLASSES:
+ # return HttpResponseRedirect(urlreverse('ietf.ietfworkflows.views.edit_state', kwargs={ 'name': draft.filename } ))
- return HttpResponseRedirect('.')
+ return HttpResponseRedirect(draft.get_absolute_url())
else:
form = form_class(user=user, draft=draft)
form.request = request
diff --git a/ietf/templates/doc/submit_to_iesg.html b/ietf/templates/doc/submit_to_iesg.html
new file mode 100644
index 000000000..6dfa0561c
--- /dev/null
+++ b/ietf/templates/doc/submit_to_iesg.html
@@ -0,0 +1,80 @@
+{% extends "base.html" %}
+
+{% block title %}
+Publication Request for {{doc.name}}-{{doc.rev}}
+{% endblock %}
+
+{% block content %}
+Publication Request for {{doc.name}}-{{doc.rev}}
+
+
+Please verify the following information:
+
+
+
+
+
+
+{% if warn %}
+
+

indicates the document might not be ready for submission. Please check each instance carefully to see if changes need to be made to the document's state before submitting.
+
+{% endif %}
+
+
+Upon submission:
+
+- the document will be placed into the IESG '{{target_state.iesg}}' state
+- the document will be placed into the working group '{{target_state.wg}}' state
+{% if not ad == doc.ad %}- the responsible AD will be set as above
{% endif %}
+{% if not notify == doc.notify %}- the document's state change notification list will be set as above
{% endif %}
+- an entry will be made noting the publication request in the document's history
+- an email message will be sent to the working group chairs, the secretariat, and everyone listed above
+
+
+
+
+{% endblock %}
diff --git a/ietf/templates/doc/submit_to_iesg_email.txt b/ietf/templates/doc/submit_to_iesg_email.txt
new file mode 100644
index 000000000..807cdd351
--- /dev/null
+++ b/ietf/templates/doc/submit_to_iesg_email.txt
@@ -0,0 +1,4 @@
+{{login}} has requested publication of {{doc.name}}-{{doc.rev}} as {{doc.intended_std_level}} on behalf of the {{doc.group.acronym|upper}} working group.
+
+Please verify the document's state at {{url}}
+
diff --git a/ietf/templates/ietfworkflows/state_edit.html b/ietf/templates/ietfworkflows/state_edit.html
index 19f0265a4..5cef5eb00 100644
--- a/ietf/templates/ietfworkflows/state_edit.html
+++ b/ietf/templates/ietfworkflows/state_edit.html
@@ -17,6 +17,16 @@ table.edit-form-tags tr { vertical-align: top; }
table.edit-form-tags textarea { height: 200px; }
table.edit-form-tags ul { border-width: 0px; padding: 0px 2em; }
table.edit-form-tags ul li { padding: 0px; }
+
+#new-edit-form { clear:both; padding-top: 1em; }
+#new-edit-form th { padding-left:0; max-width: 8em;}
+#new-edit-form #id_comment { width: 30em; height: 5em; }
+#new-edit-form #id_weeks { width: 2em;}
+#new-edit-form ul {list-style-type: none; padding-left:0; margin-top:0; margin-bottom:0; }
+.rec-state-header { float:left; padding-left:2px; padding-right:.5em; }
+.rec-statelist {list-style-type: none; padding-left:0; margin-top:0; margin-bottom:0; }
+.rec-state { float:left; padding-right:.5em; }
+
{% endblock morecss %}
{% block title %}Change state for {{ draft }}{% endblock %}
@@ -24,12 +34,6 @@ table.edit-form-tags ul li { padding: 0px; }
{% block content %}
Change state for {{ draft }}
-
-
{% if state and state.slug == "wg-doc" and not milestones %}
This document is not part of any milestone. You may wish to add it to one.
{% endif %}
@@ -42,42 +46,6 @@ submitted to IESG for publication, you may wish to
milestone{{ milestones|pluralize }}.
{% endif %}
-
-
-Current stream |
-Current state |
-Annotation tags |
-
-
- {{ stream.name|default:"None" }}
- |
-
- {{ state.name|default:"None" }}
- |
-
- {% for tag in tags%}
- - {{ tag.annotation_tag }}
- {% endfor %}
-
- |
-
-
{{ form }}
-
-State history
-
- {% if history %}
- Date | Person | Change | Comment |
- {% for baseentry in history %}
- {% with baseentry.get_real_instance as entry %}
- {{ entry.date }} | {{ entry.person }} |
- {{ entry.describe_change|safe }} | {{ entry.comment }} |
-
- {% endwith %}
- {% endfor %}
- {% else %}
- There is no state history for this document. |
- {% endif %}
-
{% endblock %}
diff --git a/ietf/templates/ietfworkflows/state_form.html b/ietf/templates/ietfworkflows/state_form.html
index 15fa7a0fd..a96b20f3e 100644
--- a/ietf/templates/ietfworkflows/state_form.html
+++ b/ietf/templates/ietfworkflows/state_form.html
@@ -1,73 +1,32 @@