Sync discrepancies
{% endif %}
{% if user %}
{% get_user_managed_streams user as stream_list %}
@@ -62,6 +61,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{% endfor %}
{% endif %}
{% endif %}
+{% if user|has_role:"IANA" %}
+
From 513c2bcf4cc52974e50dfc4310a700ec4d16f84d Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 17 Sep 2012 16:06:21 +0000
Subject: [PATCH 10/47] Handle IANA Review state intelligently upon draft
submission, email IRSG/ISE/IAB on uploads to alternate streams, email RFC
Editor in case a draft under publication has a new upload - Legacy-Id: 4855
---
ietf/submit/tests.py | 20 ++++++++++++-----
ietf/submit/utils.py | 53 +++++++++++++++++++++++++++++---------------
2 files changed, 50 insertions(+), 23 deletions(-)
diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py
index 7a980d8d9..09b1116b5 100644
--- a/ietf/submit/tests.py
+++ b/ietf/submit/tests.py
@@ -16,7 +16,7 @@ from ietf.utils.mail import outbox
from ietf.person.models import Person, Email
from ietf.group.models import Group, Role
-from ietf.doc.models import Document, BallotDocEvent, BallotPositionDocEvent
+from ietf.doc.models import *
from ietf.submit.models import IdSubmissionDetail, Preapproval
class SubmitTestCase(django.test.TestCase):
@@ -161,6 +161,16 @@ class SubmitTestCase(django.test.TestCase):
# submit new revision of existing -> supply submitter info -> confirm
draft = make_test_data()
+ # pretend IANA reviewed it
+ draft.set_state(State.objects.get(type="draft-iana-review", slug="not-ok"))
+
+ # pretend it was approved to check that we notify the RFC Editor
+ e = DocEvent(type="iesg_approved", doc=draft)
+ e.time = draft.time
+ e.by = Person.objects.get(name="(System)")
+ e.desc = "The IESG approved the document"
+ e.save()
+
# make a discuss to see if the AD gets an email
ballot_position = BallotPositionDocEvent()
ballot_position.ballot = draft.latest_event(BallotDocEvent, type="created_ballot")
@@ -214,16 +224,16 @@ class SubmitTestCase(django.test.TestCase):
draft = Document.objects.get(docalias__name=name)
self.assertEquals(draft.rev, rev)
- new_revision = draft.latest_event()
- self.assertEquals(new_revision.type, "new_revision")
- self.assertEquals(new_revision.by.name, "Test Name")
+ self.assertEquals(draft.docevent_set.all()[1].type, "new_revision")
+ self.assertEquals(draft.docevent_set.all()[1].by.name, "Test Name")
self.assertTrue(not os.path.exists(os.path.join(self.repository_dir, "%s-%s.txt" % (name, old_rev))))
self.assertTrue(os.path.exists(os.path.join(self.archive_dir, "%s-%s.txt" % (name, old_rev))))
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, u"%s-%s.txt" % (name, rev))))
self.assertEquals(draft.type_id, "draft")
self.assertEquals(draft.stream_id, "ietf")
- self.assertEquals(draft.get_state("draft-stream-%s" % draft.stream_id).slug, "wg-doc")
+ self.assertEquals(draft.get_state_slug("draft-stream-%s" % draft.stream_id), "wg-doc")
+ self.assertEquals(draft.get_state_slug("draft-iana-review"), "changed")
self.assertEquals(draft.authors.count(), 1)
self.assertEquals(draft.authors.all()[0].get_name(), "Test Name")
self.assertEquals(draft.authors.all()[0].address, "testname@example.com")
diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py
index ca4b09623..8f6f97eaf 100644
--- a/ietf/submit/utils.py
+++ b/ietf/submit/utils.py
@@ -17,7 +17,7 @@ from ietf.ietfauth.decorators import has_role
from ietf.doc.models import *
from ietf.person.models import Person, Alias, Email
-from ietf.doc.utils import active_ballot_positions
+from ietf.doc.utils import active_ballot_positions, add_state_change_event
from ietf.message.models import Message
# Some useful states
@@ -133,28 +133,34 @@ def perform_postREDESIGN(request, submission):
draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE)
draft.save()
- draft.set_state(State.objects.get(type="draft", slug="active"))
- if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
- # automatically set state "WG Document"
- draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
-
- DocAlias.objects.get_or_create(name=submission.filename, document=draft)
-
- update_authors(draft, submission)
-
- # new revision event
a = submission.tempidauthors_set.filter(author_order=0)
if a:
submitter = ensure_person_email_info_exists(a[0]).person
else:
submitter = system
+ draft.set_state(State.objects.get(type="draft", slug="active"))
+ DocAlias.objects.get_or_create(name=submission.filename, document=draft)
+
+ update_authors(draft, submission)
+
+ # new revision event
e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev)
e.time = draft.time #submission.submission_date
e.by = submitter
e.desc = "New revision available"
e.save()
+ if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00":
+ # automatically set state "WG Document"
+ draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
+
+ if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"):
+ prev_state = draft.get_state("draft-iana-review")
+ next_state = State.objects.get(type="draft-iana-review", slug="changed")
+ draft.set_state(next_state)
+ add_state_change_event(draft, submitter, prev_state, next_state)
+
# clean up old files
if prev_rev != draft.rev:
from ietf.idrfc.expire import move_draft_files_to_archive
@@ -178,8 +184,7 @@ def perform_postREDESIGN(request, submission):
submission.status_id = POSTED
announce_to_lists(request, submission)
- if draft.get_state("draft-iesg") != None and not was_rfc:
- announce_new_version(request, submission, draft, state_change_msg)
+ announce_new_version(request, submission, draft, state_change_msg)
announce_to_authors(request, submission)
submission.save()
@@ -262,15 +267,27 @@ def announce_new_versionREDESIGN(request, submission, draft, state_change_msg):
if draft.ad:
to_email.append(draft.ad.role_email("ad").address)
+ if draft.stream_id == "iab":
+ to_email.append("IAB Chair ")
+ elif draft.stream_id == "ise":
+ to_email.append("Independent Submission Editor ")
+ elif draft.stream_id == "irtf":
+ to_email.append("IRSG ")
+
+ # if it has been sent to the RFC Editor, keep them in the loop
+ if draft.get_state_slug("draft-iesg") in ("ann", "rfcqueue"):
+ to_email.append("RFC Editor ")
+
for ad, pos in active_ballot_positions(draft).iteritems():
if pos and pos.pos_id == "discuss":
to_email.append(ad.role_email("ad").address)
- subject = 'New Version Notification - %s-%s.txt' % (submission.filename, submission.revision)
- from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
- send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt',
- {'submission': submission,
- 'msg': state_change_msg})
+ if to_email:
+ subject = 'New Version Notification - %s-%s.txt' % (submission.filename, submission.revision)
+ from_email = settings.IDSUBMIT_ANNOUNCE_FROM_EMAIL
+ send_mail(request, to_email, from_email, subject, 'submit/announce_new_version.txt',
+ {'submission': submission,
+ 'msg': state_change_msg})
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
announce_new_version = announce_new_versionREDESIGN
From 5c89b8a51d7eb7cc502c492b2bae981aee27532b Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 17 Sep 2012 16:07:24 +0000
Subject: [PATCH 11/47] Add RFC Editor role handling - Legacy-Id: 4856
---
ietf/ietfauth/decorators.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/ietf/ietfauth/decorators.py b/ietf/ietfauth/decorators.py
index 933338a43..a49b1bc03 100644
--- a/ietf/ietfauth/decorators.py
+++ b/ietf/ietfauth/decorators.py
@@ -89,6 +89,7 @@ def has_role(user, role_names):
"Area Director": Q(person=person, name__in=("pre-ad", "ad"), group__type="area", group__state="active"),
"Secretariat": Q(person=person, name="secr", group__acronym="secretariat"),
"IANA": Q(person=person, name="auth", group__acronym="iana"),
+ "RFC Editor": Q(email__person=person, name="auth", group__acronym="rfceditor"),
"IAD": Q(person=person, name="admdir", group__acronym="ietf"),
"IETF Chair": Q(person=person, name="chair", group__acronym="ietf"),
"IAB Chair": Q(person=person, name="chair", group__acronym="iab"),
From 6062e16c2d39ba5b7aa804154462f4b5cf9d5635 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 17 Sep 2012 16:15:45 +0000
Subject: [PATCH 12/47] Email IANA and RFC Editor when a draft is pulled from
the queue at the Datatracker, add page for editing IANA states, add more info
to the /doc/draft-XXXXX/doc.json dump for the RFC Editor, add page for
editing consensus, add page for requesting publication at the RFC Editor for
alternate streams (this will email the RFC Editor and set the draft in the
appropriate state), make it possible for alternate streams to change the
intended RFC status of a draft in the stream, refactor how IANA copies are
handled slightly so it's less code, put drafts automatically in IANA Review
"Need Review" state upon last call, fix a bug in ballot issuing, remove a bit
of dead code - Legacy-Id: 4857
---
ietf/idrfc/mails.py | 69 +--
ietf/idrfc/testsREDESIGN.py | 470 +++++-------------
ietf/idrfc/urls.py | 5 +-
ietf/idrfc/utils.py | 22 +
ietf/idrfc/views_ballot.py | 77 +--
ietf/idrfc/views_doc.py | 44 +-
ietf/idrfc/views_edit.py | 200 +++++++-
ietf/ietfworkflows/accounts.py | 8 -
.../templatetags/ietf_streams.py | 2 +
ietf/name/fixtures/names.xml | 178 +++++--
ietf/templates/idrfc/ballot_writeup.txt | 4 +-
ietf/templates/idrfc/change_consensus.html | 21 +
ietf/templates/idrfc/change_iana_state.html | 24 +
.../templates/idrfc/change_stateREDESIGN.html | 14 +-
ietf/templates/idrfc/doc_tab_document_id.html | 14 +-
ietf/templates/idrfc/doc_tab_history.html | 2 +-
ietf/templates/idrfc/document_history.html | 2 +-
.../idrfc/issue_ballot_mailREDESIGN.txt | 2 +-
ietf/templates/idrfc/publication_request.txt | 16 +
.../idrfc/pulled_from_rfc_queue_email.txt | 9 +
ietf/templates/idrfc/request_publication.html | 37 ++
ietf/utils/mail.py | 34 +-
22 files changed, 740 insertions(+), 514 deletions(-)
create mode 100644 ietf/templates/idrfc/change_consensus.html
create mode 100644 ietf/templates/idrfc/change_iana_state.html
create mode 100644 ietf/templates/idrfc/publication_request.txt
create mode 100644 ietf/templates/idrfc/pulled_from_rfc_queue_email.txt
create mode 100644 ietf/templates/idrfc/request_publication.html
diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py
index b75e7af95..33edd1137 100644
--- a/ietf/idrfc/mails.py
+++ b/ietf/idrfc/mails.py
@@ -62,8 +62,26 @@ def email_stream_changed(request, doc, old_stream, new_stream, text=""):
"idrfc/stream_changed_email.txt",
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
+
+def email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state):
+ send_mail(request, ['IANA ', 'rfc-editor@rfc-editor.org'], None,
+ "%s changed state from %s to %s" % (doc.name, prev_state.name, next_state.name),
+ "idrfc/pulled_from_rfc_queue_email.txt",
+ dict(doc=doc,
+ prev_state=prev_state,
+ next_state=next_state,
+ comment=comment,
+ url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
+ extra=extra_automation_headers(doc))
+
+
+def email_authors(request, doc, subject, text):
+ to = [x.strip() for x in doc.author_list().split(',')]
+ if not to:
+ return
-
+ send_mail_text(request, to, None, subject, text)
+
def html_to_text(html):
return strip_tags(html.replace("<", "<").replace(">", ">").replace("&", "&").replace(" ", "\n"))
@@ -98,12 +116,15 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def generate_ballot_writeup(request, doc):
+ e = doc.latest_event(type="iana_review")
+ iana = e.desc if e else ""
+
e = WriteupDocEvent()
e.type = "changed_ballot_writeup_text"
e.by = request.user.get_profile()
e.doc = doc
e.desc = u"Ballot writeup was generated"
- e.text = unicode(render_to_string("idrfc/ballot_writeup.txt"))
+ e.text = unicode(render_to_string("idrfc/ballot_writeup.txt", {'iana': iana}))
e.save()
return e
@@ -263,6 +284,20 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_approval_mail = generate_approval_mailREDESIGN
generate_approval_mail_rfc_editor = generate_approval_mail_rfc_editorREDESIGN
+def generate_publication_request(request, doc):
+ group_description = ""
+ if doc.group and doc.group.acronym != "none":
+ group_description = doc.group.name
+ if doc.group.type_id in ("wg", "rg", "area"):
+ group_description += " %s (%s)" % (doc.group.type, doc.group.acronym)
+
+
+ return render_to_string("idrfc/publication_request.txt",
+ dict(doc=doc,
+ doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
+ group_description=group_description,
+ )
+ )
def send_last_call_request(request, doc, ballot):
to = "iesg-secretary@ietf.org"
@@ -425,39 +460,13 @@ def generate_issue_ballot_mailREDESIGN(request, doc, ballot):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_issue_ballot_mail = generate_issue_ballot_mailREDESIGN
-def email_iana(request, doc, to, msg):
- # fix up message and send message to IANA for each in ballot set
- import email
- parsed_msg = email.message_from_string(msg.encode("utf-8"))
-
- for i in doc.idinternal.ballot_set():
- extra = {}
- extra["Reply-To"] = "noreply@ietf.org"
- extra["X-IETF-Draft-string"] = i.document().filename
- extra["X-IETF-Draft-revision"] = i.document().revision_display()
-
- send_mail_text(request, "To: IANA <%s>" % to,
- parsed_msg["From"], parsed_msg["Subject"],
- parsed_msg.get_payload(),
- extra=extra)
-
-def email_ianaREDESIGN(request, doc, to, msg):
- # fix up message and send it with extra info on doc in headers
- import email
- parsed_msg = email.message_from_string(msg.encode("utf-8"))
-
+def extra_automation_headers(doc):
extra = {}
extra["Reply-To"] = "noreply@ietf.org"
extra["X-IETF-Draft-string"] = doc.name
extra["X-IETF-Draft-revision"] = doc.rev
-
- send_mail_text(request, "IANA <%s>" % to,
- parsed_msg["From"], parsed_msg["Subject"],
- parsed_msg.get_payload(),
- extra=extra)
-if settings.USE_DB_REDESIGN_PROXY_CLASSES:
- email_iana = email_ianaREDESIGN
+ return extra
def email_last_call_expired(doc):
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.idinternal.cur_state.state
diff --git a/ietf/idrfc/testsREDESIGN.py b/ietf/idrfc/testsREDESIGN.py
index 734cd6bb7..0cc597415 100644
--- a/ietf/idrfc/testsREDESIGN.py
+++ b/ietf/idrfc/testsREDESIGN.py
@@ -119,7 +119,61 @@ class ChangeStateTestCase(django.test.TestCase):
q = PyQuery(r.content)
self.assertEquals(len(q('.prev-state form input[name="state"]')), 1)
+ def test_pull_from_rfc_queue(self):
+ draft = make_test_data()
+ draft.set_state(State.objects.get(type="draft-iesg", slug="rfcqueue"))
+
+ url = urlreverse('doc_change_state', kwargs=dict(name=draft.name))
+ login_testing_unauthorized(self, "secretary", url)
+
+ # change state
+ mailbox_before = len(outbox)
+
+ r = self.client.post(url,
+ dict(state=State.objects.get(type="draft-iesg", slug="review-e").pk,
+ substate="",
+ comment="Test comment"))
+ self.assertEquals(r.status_code, 302)
+
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.get_state_slug("draft-iesg"), "review-e")
+ self.assertEquals(len(outbox), mailbox_before + 2 + 1)
+ self.assertTrue(draft.name in outbox[-1]['Subject'])
+ self.assertTrue("changed state" in outbox[-1]['Subject'])
+ self.assertTrue("is no longer" in str(outbox[-1]))
+ self.assertTrue("Test comment" in str(outbox[-1]))
+
+ def test_change_iana_state(self):
+ draft = make_test_data()
+
+ first_state = State.objects.get(type="draft-iana-review", slug="need-rev")
+ next_state = State.objects.get(type="draft-iana-review", slug="ok-noact")
+ draft.set_state(first_state)
+
+ url = urlreverse('doc_change_iana_state', kwargs=dict(name=draft.name, state_type="iana-review"))
+ login_testing_unauthorized(self, "iana", url)
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ self.assertEquals(len(q('form select[name=state]')), 1)
+ # faulty post
+ r = self.client.post(url, dict(state="foobarbaz"))
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ self.assertTrue(len(q('form ul.errorlist')) > 0)
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.get_state("draft-iana-review"), first_state)
+
+ # change state
+ r = self.client.post(url, dict(state=next_state.pk))
+ self.assertEquals(r.status_code, 302)
+
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.get_state("draft-iana-review"), next_state)
+
def test_request_last_call(self):
draft = make_test_data()
draft.set_state(State.objects.get(type="draft-iesg", slug="ad-eval"))
@@ -202,7 +256,7 @@ class EditInfoTestCase(django.test.TestCase):
draft = Document.objects.get(name=draft.name)
self.assertEquals(draft.ad, new_ad)
self.assertEquals(draft.note, "New note")
- self.assertTrue(not draft.latest_event(TelechatDocEvent, type="telechat_date"))
+ self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
self.assertEquals(draft.docevent_set.count(), events_before + 3)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(draft.name in outbox[-1]['Subject'])
@@ -221,14 +275,14 @@ class EditInfoTestCase(django.test.TestCase):
)
# add to telechat
- self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
+ self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
data["telechat_date"] = TelechatDate.objects.active()[0].date.isoformat()
r = self.client.post(url, data)
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
- self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
- self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date)
+ self.assertTrue(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
+ self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[0].date)
# change telechat
data["telechat_date"] = TelechatDate.objects.active()[1].date.isoformat()
@@ -236,7 +290,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
- self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date)
+ self.assertEqual(draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date, TelechatDate.objects.active()[1].date)
# remove from agenda
data["telechat_date"] = ""
@@ -244,7 +298,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(name=draft.name)
- self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date)
+ self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date)
def test_start_iesg_process_on_draft(self):
make_test_data()
@@ -311,6 +365,18 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(events[-3].type, "started_iesg_process")
self.assertEquals(len(outbox), mailbox_before)
+ def test_edit_consensus(self):
+ draft = make_test_data()
+
+ url = urlreverse('doc_edit_consensus', kwargs=dict(name=draft.name))
+ login_testing_unauthorized(self, "secretary", url)
+
+ self.assertTrue(not draft.latest_event(ConsensusDocEvent, type="changed_consensus"))
+ r = self.client.post(url, dict(consensus="Yes"))
+ self.assertEquals(r.status_code, 302)
+
+ self.assertEqual(draft.latest_event(ConsensusDocEvent, type="changed_consensus").consensus, True)
+
class ResurrectTestCase(django.test.TestCase):
fixtures = ['names']
@@ -406,6 +472,16 @@ class AddCommentTestCase(django.test.TestCase):
self.assertTrue("updated" in outbox[-1]['Subject'])
self.assertTrue(draft.name in outbox[-1]['Subject'])
+ # Make sure we can also do it as IANA
+ self.client.login(remote_user="iana")
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ self.assertEquals(len(q('form textarea[name=comment]')), 1)
+
+
class EditPositionTestCase(django.test.TestCase):
fixtures = ['names']
@@ -670,12 +746,21 @@ class BallotWriteupsTestCase(django.test.TestCase):
url = urlreverse('doc_ballot_writeupnotes', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
+ # add a IANA review note
+ draft.set_state(State.objects.get(type="draft-iana-review", slug="not-ok"))
+ DocEvent.objects.create(type="iana_review",
+ doc=draft,
+ by=Person.objects.get(user__username="iana"),
+ desc="IANA does not approve of this document, it does not make sense.",
+ )
+
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('textarea[name=ballot_writeup]')), 1)
self.assertEquals(len(q('input[type=submit][value*="Save Ballot Writeup"]')), 1)
+ self.assertTrue("IANA does not" in r.content)
# save
r = self.client.post(url, dict(
@@ -869,6 +954,44 @@ class MakeLastCallTestCase(django.test.TestCase):
self.assertTrue("Last Call" in outbox[-3]['Subject'])
self.assertTrue("Last Call" in draft.message_set.order_by("-time")[0].subject)
+class RequestPublicationTestCase(django.test.TestCase):
+ fixtures = ['names']
+
+ def test_request_publication(self):
+ draft = make_test_data()
+ draft.stream = StreamName.objects.get(slug="iab")
+ draft.group = Group.objects.get(acronym="iab")
+ draft.intended_std_level = IntendedStdLevelName.objects.get(slug="inf")
+ draft.save()
+ draft.set_state(State.objects.get(type="draft-stream-iab", slug="approved"))
+
+ url = urlreverse('doc_request_publication', kwargs=dict(name=draft.name))
+ login_testing_unauthorized(self, "iabchair", url)
+
+ # normal get
+ r = self.client.get(url)
+ self.assertEquals(r.status_code, 200)
+ q = PyQuery(r.content)
+ subject = q('input#id_subject')[0].get("value")
+ self.assertTrue("Document Action" in subject)
+ body = q('.request-publication #id_body').text()
+ self.assertTrue("Informational" in body)
+ self.assertTrue("IAB" in body)
+
+ # approve
+ mailbox_before = len(outbox)
+
+ r = self.client.post(url, dict(subject=subject, body=body))
+ self.assertEquals(r.status_code, 302)
+
+ draft = Document.objects.get(name=draft.name)
+ self.assertEquals(draft.get_state_slug("draft-stream-iab"), "rfc-edit")
+ self.assertEquals(len(outbox), mailbox_before + 2)
+ self.assertTrue("Document Action" in outbox[-2]['Subject'])
+ self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject)
+ # the IANA copy
+ self.assertTrue("Document Action" in outbox[-1]['Subject'])
+
class ExpireIDsTestCase(django.test.TestCase):
fixtures = ['names']
@@ -1099,341 +1222,6 @@ class ExpireLastCallTestCase(django.test.TestCase):
self.assertEquals(draft.docevent_set.count(), events_before + 1)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Last Call Expired" in outbox[-1]["Subject"])
-
-
-
-TEST_RFC_INDEX = '''
-
-
- BCP0110
-
- RFC4170
-
-
-
- BCP0111
-
- RFC4181
- RFC4841
-
-
-
- FYI0038
-
- RFC3098
-
-
-
- RFC1938
- A One-Time Password System
-
- N. Haller
-
-
- C. Metz
-
-
- May
- 1996
-
-
- ASCII
- 44844
- 18
-
-
- OTP
- authentication
- S/KEY
-
-
This document describes a one-time password authentication system (OTP). [STANDARDS-TRACK]
-
- RFC2289
-
- PROPOSED STANDARD
- PROPOSED STANDARD
- Legacy
-
-
- RFC2289
- A One-Time Password System
-
- N. Haller
-
-
- C. Metz
-
-
- P. Nesser
-
-
- M. Straw
-
-
- February
- 1998
-
-
- ASCII
- 56495
- 25
-
-
- ONE-PASS
- authentication
- OTP
- replay
- attach
-
-
This document describes a one-time password authentication system (OTP). The system provides authentication for system access (login) and other applications requiring authentication that is secure against passive attacks based on replaying captured reusable passwords. [STANDARDS- TRACK]
-
- RFC1938
-
-
- STD0061
-
- STANDARD
- DRAFT STANDARD
- Legacy
-
-
- RFC3098
- How to Advertise Responsibly Using E-Mail and Newsgroups or - how NOT to $$$$$ MAKE ENEMIES FAST! $$$$$
-
- T. Gavin
-
-
- D. Eastlake 3rd
-
-
- S. Hambridge
-
-
- April
- 2001
-
-
- ASCII
- 64687
- 28
-
-
- internet
- marketing
- users
- service
- providers
- isps
-
-
This memo offers useful suggestions for responsible advertising techniques that can be used via the internet in an environment where the advertiser, recipients, and the Internet Community can coexist in a productive and mutually respectful fashion. This memo provides information for the Internet community.
This document describes a method to improve the bandwidth utilization of RTP streams over network paths that carry multiple Real-time Transport Protocol (RTP) streams in parallel between two endpoints, as in voice trunking. The method combines standard protocols that provide compression, multiplexing, and tunneling over a network path for the purpose of reducing the bandwidth used when multiple RTP streams are carried over that path. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.
- draft-ietf-avt-tcrtp-08
-
- BCP0110
-
- BEST CURRENT PRACTICE
- BEST CURRENT PRACTICE
- IETF
- rai
- avt
-
-
- RFC4181
- Guidelines for Authors and Reviewers of MIB Documents
-
- C. Heard
- Editor
-
-
- September
- 2005
-
-
- ASCII
- 102521
- 42
-
-
- standards-track specifications
- management information base
- review
-
-
This memo provides guidelines for authors and reviewers of IETF standards-track specifications containing MIB modules. Applicable portions may be used as a basis for reviews of other MIB documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.
- draft-ietf-ops-mib-review-guidelines-04
-
- RFC4841
-
-
- BCP0111
-
- BEST CURRENT PRACTICE
- BEST CURRENT PRACTICE
- IETF
- rtg
- ospf
- http://www.rfc-editor.org/errata_search.php?rfc=4181
-
-
- RFC4841
- RFC 4181 Update to Recognize the IETF Trust
-
- C. Heard
- Editor
-
-
- March
- 2007
-
-
- ASCII
- 4414
- 3
-
-
- management information base
- standards-track specifications
- mib review
-
-
This document updates RFC 4181, "Guidelines for Authors and Reviewers of MIB Documents", to recognize the creation of the IETF Trust. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.
- draft-heard-rfc4181-update-00
-
- RFC4181
-
-
- BCP0111
-
- BEST CURRENT PRACTICE
- BEST CURRENT PRACTICE
- IETF
- NON WORKING GROUP
-
-
- STD0061
- A One-Time Password System
-
- RFC2289
-
-
-
-'''
-
-TEST_QUEUE = '''
-
-
-draft-ietf-sipping-app-interaction-framework-05.txt
-2005-10-17
-EDIT
-
-draft-ietf-sip-gruu
-IN-QUEUE
-
-J. Rosenberg
-
-A Framework for Application Interaction in the Session Initiation Protocol (SIP)
-
-94672
-Session Initiation Proposal Investigation
-
-
-
-
-draft-ietf-sip-gruu-15.txt
-2007-10-15
-MISSREF
-
-draft-ietf-sip-outbound
-NOT-RECEIVED
-
-J. Rosenberg
-
-Obtaining and Using Globally Routable User Agent (UA) URIs (GRUU) in the Session Initiation Protocol (SIP)
-
-95501
-Session Initiation Protocol
-
-
-
-
-
-
-draft-thomson-beep-async-02.txt
-2009-05-12
-EDIT
-IANA
-M. Thomson
-
-Asynchronous Channels for the Blocks Extensible Exchange Protocol (BEEP)
-
-17237
-IETF - NON WORKING GROUP
-
-
-
-
-
-
-
-
-
-'''
-
-class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
-
- def setUp(self):
- self.setUpRealDatabase()
- def tearDown(self):
- self.tearDownRealDatabase()
-
- def testRfcIndex(self):
- print " Testing rfc-index.xml parsing"
- from ietf.idrfc.mirror_rfc_index import parse
- data = parse(StringIO.StringIO(TEST_RFC_INDEX))
- self.assertEquals(len(data), 6)
- print "OK"
-
- def testRfcEditorQueue(self):
- print " Testing queue2.xml parsing"
- from ietf.idrfc.mirror_rfc_editor_queue import parse_all
- (drafts,refs) = parse_all(StringIO.StringIO(TEST_QUEUE))
- self.assertEquals(len(drafts), 3)
- self.assertEquals(len(refs), 3)
- print "OK"
-
class IndividualInfoFormsTestCase(django.test.TestCase):
diff --git a/ietf/idrfc/urls.py b/ietf/idrfc/urls.py
index b520d98ea..2a0477be8 100644
--- a/ietf/idrfc/urls.py
+++ b/ietf/idrfc/urls.py
@@ -49,12 +49,13 @@ urlpatterns = patterns('',
url(r'^(?P[A-Za-z0-9._+-]+)/ballot/(?P[0-9]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
url(r'^(?P[A-Za-z0-9._+-]+)/ballot/(?P[0-9]+)/$', views_doc.document_ballot, name="doc_ballot"),
url(r'^(?P[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
- (r'^(?P[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_debug),
+ (r'^(?P[A-Za-z0-9._+-]+)/doc.json$', views_doc.document_json),
(r'^(?P[A-Za-z0-9._+-]+)/ballotpopup/$', views_doc.ballot_for_popup),
(r'^(?P[A-Za-z0-9._+-]+)/ballot.tsv$', views_doc.ballot_tsv),
(r'^(?P[A-Za-z0-9._+-]+)/ballot.json$', views_doc.ballot_json),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='doc_change_state'), # IESG state
+ url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/(?Piana-action|iana-review)/$', views_edit.change_iana_state, name='doc_change_iana_state'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/info/$', views_edit.edit_info, name='doc_edit_info'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestresurrect/$', views_edit.request_resurrect, name='doc_request_resurrect'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/resurrect/$', views_edit.resurrect, name='doc_resurrect'),
@@ -66,6 +67,8 @@ urlpatterns = patterns('',
url(r'^(?P[A-Za-z0-9._+-]+)/edit/telechat/$', views_edit.telechat_date, name='doc_change_telechat_date'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/iesgnote/$', views_edit.edit_iesg_note, name='doc_change_iesg_note'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/ad/$', views_edit.edit_ad, name='doc_change_ad'),
+ url(r'^(?P[A-Za-z0-9._+-]+)/edit/consensus/$', views_edit.edit_consensus, name='doc_edit_consensus'),
+ url(r'^(?P[A-Za-z0-9._+-]+)/edit/requestpublication/$', views_edit.request_publication, name='doc_request_publication'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/clearballot/$', views_ballot.clear_ballot, name='doc_clear_ballot'),
url(r'^(?P[A-Za-z0-9._+-]+)/edit/deferballot/$', views_ballot.defer_ballot, name='doc_defer_ballot'),
diff --git a/ietf/idrfc/utils.py b/ietf/idrfc/utils.py
index 9fc376753..318880493 100644
--- a/ietf/idrfc/utils.py
+++ b/ietf/idrfc/utils.py
@@ -2,6 +2,7 @@ from django.conf import settings
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
+from ietf.ietfauth.decorators import has_role
def add_document_comment(request, doc, text, ballot=None):
if request:
@@ -175,3 +176,24 @@ def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_i
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
update_telechat = update_telechatREDESIGN
+
+def can_edit_intended_std_level(doc, user):
+ return user.is_authenticated() and (
+ has_role(user, ["Secretariat", "Area Director"]) or
+ doc.group.role_set.filter(name__in=("chair", "auth", "delegate"), person__user=user)
+ )
+
+def can_edit_consensus(doc, user):
+ return user.is_authenticated() and (
+ has_role(user, ["Secretariat", "Area Director"]) or
+ doc.group.role_set.filter(name__in=("chair", "auth", "delegate"), person__user=user)
+ )
+
+def nice_consensus(consensus):
+ mapping = {
+ None: "Unknown",
+ True: "Yes",
+ False: "No"
+ }
+ return mapping[consensus]
+
diff --git a/ietf/idrfc/views_ballot.py b/ietf/idrfc/views_ballot.py
index 2afc08bab..503bda8bf 100644
--- a/ietf/idrfc/views_ballot.py
+++ b/ietf/idrfc/views_ballot.py
@@ -818,8 +818,8 @@ def ballot_writeupnotesREDESIGN(request, name):
msg = generate_issue_ballot_mail(request, doc, ballot)
send_mail_preformatted(request, msg)
-
- email_iana(request, doc, 'drafts-eval@icann.org', msg)
+ send_mail_preformatted(request, msg, extra=extra_automation_headers(doc),
+ override={ "To": "IANA " })
e = DocEvent(doc=doc, by=login)
e.by = login
@@ -1106,7 +1106,8 @@ def approve_ballotREDESIGN(request, name):
send_mail_preformatted(request, announcement)
if action == "to_announcement_list":
- email_iana(request, doc, "drafts-approval@icann.org", announcement)
+ send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
+ override={ "To": "IANA " })
msg = infer_message(announcement)
msg.by = login
@@ -1131,62 +1132,10 @@ class MakeLastCallForm(forms.Form):
@group_required('Secretariat')
def make_last_call(request, name):
- """Make last call for Internet Draft, sending out announcement."""
- doc = get_object_or_404(InternetDraft, filename=name)
- if not doc.idinternal:
- raise Http404()
-
- login = IESGLogin.objects.get(login_name=request.user.username)
-
- ballot = doc.idinternal.ballot
- docs = [i.document() for i in doc.idinternal.ballot_set()]
-
- announcement = ballot.last_call_text
-
- if request.method == 'POST':
- form = MakeLastCallForm(request.POST)
- if form.is_valid():
- send_mail_preformatted(request, announcement)
- email_iana(request, doc, "drafts-lastcall@icann.org", announcement)
-
- doc.idinternal.change_state(IDState.objects.get(document_state_id=IDState.IN_LAST_CALL), None)
- doc.idinternal.event_date = date.today()
- doc.idinternal.save()
-
- log_state_changed(request, doc, login)
-
- doc.lc_sent_date = form.cleaned_data['last_call_sent_date']
- doc.lc_expiration_date = form.cleaned_data['last_call_expiration_date']
- doc.save()
-
- comment = "Last call has been made for %s ballot and state has been changed to %s" % (doc.filename, doc.idinternal.cur_state.state)
- email_owner(request, doc, doc.idinternal.job_owner, login, comment)
-
- return HttpResponseRedirect(doc.idinternal.get_absolute_url())
- else:
- initial = {}
- initial["last_call_sent_date"] = date.today()
- expire_days = 14
- if doc.group_id == Acronym.INDIVIDUAL_SUBMITTER:
- expire_days = 28
-
- initial["last_call_expiration_date"] = date.today() + timedelta(days=expire_days)
-
- form = MakeLastCallForm(initial=initial)
-
- return render_to_response('idrfc/make_last_call.html',
- dict(doc=doc,
- docs=docs,
- form=form),
- context_instance=RequestContext(request))
-
-
-@group_required('Secretariat')
-def make_last_callREDESIGN(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"):
- raise Http404()
+ raise Http404
login = request.user.get_profile()
@@ -1199,7 +1148,8 @@ def make_last_callREDESIGN(request, name):
form = MakeLastCallForm(request.POST)
if form.is_valid():
send_mail_preformatted(request, announcement)
- email_iana(request, doc, "drafts-lastcall@icann.org", announcement)
+ send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc),
+ override={ "To": "IANA " })
msg = infer_message(announcement)
msg.by = login
@@ -1234,7 +1184,14 @@ def make_last_callREDESIGN(request, name):
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
+ prev_state = doc.get_state("draft-iana-review")
+ if not prev_state:
+ next_state = State.objects.get(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 = {}
@@ -1251,7 +1208,3 @@ def make_last_callREDESIGN(request, name):
dict(doc=doc,
form=form),
context_instance=RequestContext(request))
-
-
-if settings.USE_DB_REDESIGN_PROXY_CLASSES:
- make_last_call = make_last_callREDESIGN
diff --git a/ietf/idrfc/views_doc.py b/ietf/idrfc/views_doc.py
index c96bd9800..5e9c43451 100644
--- a/ietf/idrfc/views_doc.py
+++ b/ietf/idrfc/views_doc.py
@@ -46,6 +46,7 @@ from django.conf import settings
from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, DocumentComment
from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
from ietf.idrfc import markup_txt
+from ietf.idrfc.utils import *
from ietf.idrfc.models import RfcIndex, DraftVersions
from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper
from ietf.ietfworkflows.utils import get_full_info_for_draft
@@ -371,7 +372,8 @@ def document_ballot(request, name, ballot_id=None):
),
context_instance=RequestContext(request))
-def document_debug(request, name):
+def document_json(request, name):
+ # old interface
r = re.compile("^rfc([1-9][0-9]*)$")
m = r.match(name)
if m:
@@ -381,7 +383,36 @@ def document_debug(request, name):
else:
id = get_object_or_404(InternetDraft, filename=name)
doc = IdWrapper(draft=id)
- return HttpResponse(doc.to_json(), mimetype='text/plain')
+
+ from idrfc_wrapper import jsonify_helper
+
+ if isinstance(doc, RfcWrapper):
+ data = jsonify_helper(doc, ['rfc_number', 'title', 'publication_date', 'maturity_level', 'obsoleted_by','obsoletes','updated_by','updates','also','has_errata','stream_name','file_types','in_ietf_process', 'friendly_state'])
+ else:
+ data = jsonify_helper(doc, ['draft_name', 'draft_status', 'latest_revision', 'rfc_number', 'title', 'tracker_id', 'publication_date','rfc_editor_state', 'replaced_by', 'replaces', 'in_ietf_process', 'file_types', 'group_acronym', 'stream_id','friendly_state', 'abstract', 'ad_name'])
+ if doc.in_ietf_process():
+ data['ietf_process'] = doc.ietf_process.dict()
+
+ # add some more fields using the new interface
+ d = get_object_or_404(Document, docalias__name=name)
+ data["authors"] = [
+ dict(name=e.person.name,
+ email=e.address,
+ affiliation=e.person.affiliation)
+ for e in Email.objects.filter(documentauthor__document=d).select_related("person").order_by("documentauthor__order")
+ ]
+ e = d.latest_event(ConsensusDocEvent, type="changed_consensus")
+ data["consensus"] = e.consensus if e else None
+ data["stream"] = d.stream.name if d.stream else None
+ data["shepherd"] = d.shepherd.formatted_email() if d.shepherd else None
+
+ def state_name(s):
+ return s.name if s else None
+
+ data["iana_review_state"] = state_name(d.get_state("draft-iana-review"))
+ data["iana_action_state"] = state_name(d.get_state("draft-iana-action"))
+
+ return HttpResponse(json.dumps(data, indent=2), mimetype='text/plain')
def _get_html(key, filename, split=True):
return get_document_content(key, filename, split=split, markup=True)
@@ -437,7 +468,14 @@ def document_main_idrfc(request, name, tab):
info['is_rfc'] = False
info['conflict_reviews'] = [ rel.source for alias in id.docalias_set.all() for rel in alias.relateddocument_set.filter(relationship='conflrev') ]
-
+ info['rfc_editor_state'] = id.get_state("draft-rfceditor")
+ info['iana_review_state'] = id.get_state("draft-iana-review")
+ info['iana_action_state'] = id.get_state("draft-iana-action")
+ e = id.latest_event(ConsensusDocEvent, type="changed_consensus")
+ info["consensus"] = nice_consensus(e and e.consensus)
+ info["can_edit_consensus"] = can_edit_consensus(id, request.user)
+ info["can_edit_intended_std_level"] = can_edit_intended_std_level(id, request.user)
+
(content1, content2) = _get_html(
str(name)+","+str(id.revision)+",html",
os.path.join(settings.INTERNET_DRAFT_PATH, name+"-"+id.revision+".txt"))
diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py
index 60b57014d..1e4ecb5e6 100644
--- a/ietf/idrfc/views_edit.py
+++ b/ietf/idrfc/views_edit.py
@@ -3,7 +3,7 @@
import re, os
from datetime import datetime, date, time, timedelta
-from django.http import HttpResponse, HttpResponseRedirect, Http404
+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
@@ -13,10 +13,10 @@ from django.utils.html import strip_tags
from django.db.models import Max
from django.conf import settings
-from ietf.utils.mail import send_mail_text
+from ietf.utils.mail import send_mail_text, send_mail_message
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
-from ietf.ietfauth.decorators import has_role
+from ietf.ietfauth.decorators import has_role, role_required
from ietf.idtracker.models import *
from ietf.iesg.models import *
from ietf.idrfc.mails import *
@@ -26,10 +26,13 @@ from ietf.idrfc.lastcall import request_last_call
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.utils import update_stream
from ietf.ietfworkflows.streams import get_stream_from_draft
+from ietf.ietfworkflows.accounts import can_edit_state
from ietf.doc.models import *
+from ietf.doc.utils import *
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
from ietf.person.models import Person, Email
+from ietf.message.models import Message
class ChangeStateForm(forms.Form):
pass
@@ -58,20 +61,21 @@ def change_stateREDESIGN(request, name):
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
- state = form.cleaned_data['state']
+ next_state = form.cleaned_data['state']
+ prev_state = doc.get_state("draft-iesg")
+
tag = form.cleaned_data['substate']
comment = form.cleaned_data['comment'].strip()
- prev = doc.get_state("draft-iesg")
# tag handling is a bit awkward since the UI still works
# as if IESG tags are a substate
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 state != prev or tag != prev_tag:
+ if next_state != prev_state or tag != prev_tag:
save_document_in_history(doc)
- doc.set_state(state)
+ doc.set_state(next_state)
if prev_tag:
doc.tags.remove(prev_tag)
@@ -79,7 +83,7 @@ def change_stateREDESIGN(request, name):
if tag:
doc.tags.add(tag)
- e = log_state_changed(request, doc, login, prev, prev_tag)
+ e = log_state_changed(request, doc, login, prev_state, prev_tag)
if comment:
c = DocEvent(type="added_comment")
@@ -96,7 +100,15 @@ def change_stateREDESIGN(request, name):
email_state_changed(request, doc, e.desc)
email_owner(request, doc, doc.ad, login, e.desc)
- if state.slug == "lc-req":
+
+ if prev_state and prev_state.slug in ("ann", "rfcqueue") and next_state.slug not in ("rfcqueue", "pub"):
+ email_pulled_from_rfc_queue(request, doc, comment, prev_state, next_state)
+
+ if next_state.slug in ("iesg-eva", "lc"):
+ if not doc.get_state_slug("draft-iana-review"):
+ doc.set_state(State.objects.get(type="draft-iana-review", slug="rev-need"))
+
+ if next_state.slug == "lc-req":
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
@@ -129,6 +141,7 @@ def change_stateREDESIGN(request, name):
return render_to_response('idrfc/change_stateREDESIGN.html',
dict(form=form,
doc=doc,
+ state=state,
prev_state=prev_state,
next_states=next_states,
to_iesg_eval=to_iesg_eval),
@@ -138,6 +151,52 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
change_state = change_stateREDESIGN
ChangeStateForm = ChangeStateFormREDESIGN
+class ChangeIanaStateForm(forms.Form):
+ state = forms.ModelChoiceField(State.objects.all(), required=False)
+
+ def __init__(self, state_type, *args, **kwargs):
+ super(self.__class__, self).__init__(*args, **kwargs)
+
+ choices = State.objects.filter(type=state_type).order_by("order").values_list("pk", "name")
+ self.fields['state'].choices = [("", "-------")] + list(choices)
+
+@role_required('Secretariat', 'IANA')
+def change_iana_state(request, name, state_type):
+ """Change IANA review state of Internet Draft. Normally, this is done via
+ automatic sync, but this form allows one to set it manually."""
+ doc = get_object_or_404(Document, docalias__name=name)
+
+ state_type = doc.type_id + "-" + state_type
+
+ prev_state = doc.get_state(state_type)
+
+ if request.method == 'POST':
+ form = ChangeIanaStateForm(state_type, request.POST)
+ if form.is_valid():
+ next_state = form.cleaned_data['state']
+
+ if next_state != prev_state:
+ save_document_in_history(doc)
+
+ doc.set_state(next_state)
+
+ e = add_state_change_event(doc, request.user.get_profile(), prev_state, next_state)
+
+ doc.time = e.time
+ doc.save()
+
+ return HttpResponseRedirect(doc.get_absolute_url())
+
+ else:
+ form = ChangeIanaStateForm(state_type, initial=dict(state=prev_state.pk if prev_state else None))
+
+ return render_to_response('idrfc/change_iana_state.html',
+ dict(form=form,
+ doc=doc),
+ context_instance=RequestContext(request))
+
+
+
class ChangeStreamForm(forms.Form):
stream = forms.ModelChoiceField(StreamName.objects.exclude(slug="legacy"), required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
@@ -197,13 +256,15 @@ class ChangeIntentionForm(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
comment = forms.CharField(widget=forms.Textarea, required=False)
-@group_required('Area_Director','Secretariat')
def change_intention(request, name):
"""Change the intended publication status of a Document of type 'draft' , notifying parties
as necessary and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
- if not doc.type_id=='draft':
- raise Http404()
+ if doc.type_id != 'draft':
+ raise Http404
+
+ if not can_edit_intended_std_level(doc, request.user):
+ return HttpResponseForbidden("You do not have the necessary permissions to view this page")
login = request.user.get_profile()
@@ -617,7 +678,7 @@ def add_comment(request, name):
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
-@group_required('Area_Director', 'Secretariat', 'IANA')
+@group_required('Area_Director', 'Secretariat', 'IANA', 'RFC Editor')
def add_commentREDESIGN(request, name):
"""Add comment to history of document."""
doc = get_object_or_404(Document, docalias__name=name)
@@ -827,3 +888,116 @@ def edit_ad(request, name):
},
context_instance = RequestContext(request))
+class ConsensusForm(forms.Form):
+ consensus = forms.ChoiceField(choices=(("", "Unknown"), ("Yes", "Yes"), ("No", "No")), required=True)
+
+def edit_consensus(request, name):
+ """Change whether the draft is a consensus document or not."""
+
+ doc = get_object_or_404(Document, type="draft", name=name)
+
+ if not can_edit_consensus(doc, request.user):
+ return HttpResponseForbidden("You do not have the necessary permissions to view this page")
+
+ e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
+ prev_consensus = e and e.consensus
+
+ if request.method == 'POST':
+ form = ConsensusForm(request.POST)
+ if form.is_valid():
+ if form.cleaned_data["consensus"] != bool(prev_consensus):
+ e = ConsensusDocEvent(doc=doc, type="changed_consensus", by=request.user.get_profile())
+ e.consensus = form.cleaned_data["consensus"] == "Yes"
+
+ e.desc = "Changed consensus to %s from %s" % (nice_consensus(e.consensus),
+ nice_consensus(prev_consensus))
+
+ e.save()
+
+ return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
+
+ else:
+ form = ConsensusForm(initial=dict(consensus=nice_consensus(prev_consensus).replace("Unknown", "")))
+
+ return render_to_response('idrfc/change_consensus.html',
+ {'form': form,
+ 'doc': doc,
+ },
+ context_instance = RequestContext(request))
+
+class PublicationForm(forms.Form):
+ subject = forms.CharField(max_length=200, required=True)
+ body = forms.CharField(widget=forms.Textarea, required=True)
+
+def request_publication(request, name):
+ """Request publication by RFC Editor for a document which hasn't
+ been through the IESG ballot process."""
+
+ doc = get_object_or_404(Document, type="draft", name=name, stream__in=("iab", "ise", "irtf"))
+
+ if not can_edit_state(request.user, doc):
+ return HttpResponseForbidden("You do not have the necessary permissions to view this page")
+
+ m = Message()
+ m.frm = request.user.get_profile().formatted_email()
+ m.to = "RFC Editor "
+ m.by = request.user.get_profile()
+
+ next_state = State.objects.get(type="draft-stream-%s" % doc.stream.slug, slug="rfc-edit")
+
+ if request.method == 'POST' and not request.POST.get("reset"):
+ form = PublicationForm(request.POST)
+ if form.is_valid():
+ m.subject = form.cleaned_data["subject"]
+ m.body = form.cleaned_data["body"]
+ m.save()
+
+ if doc.group.acronym != "none":
+ m.related_groups = [doc.group]
+ m.related_docs = [doc]
+
+ send_mail_message(request, m)
+
+ # IANA copy
+ m.to = "IANA "
+ send_mail_message(request, m, extra=extra_automation_headers(doc))
+
+ e = DocEvent(doc=doc, type="requested_publication", by=request.user.get_profile())
+ e.desc = "Sent request for publication to the RFC Editor"
+ e.save()
+
+ # change state
+ prev_state = doc.get_state(next_state.type)
+
+ doc.set_state(next_state)
+
+ e = add_state_change_event(doc, request.user.get_profile(), prev_state, next_state)
+
+ doc.time = e.time
+ doc.save()
+
+ return HttpResponseRedirect(urlreverse('doc_view', kwargs={'name': doc.name}))
+
+ else:
+ if doc.intended_std_level_id in ("std", "ds", "ps", "bcp"):
+ action = "Protocol Action"
+ else:
+ action = "Document Action"
+
+ from ietf.idrfc.templatetags.mail_filters import std_level_prompt
+
+ subject = "%s: '%s' to %s (%s-%s.txt)" % (action, doc.title, std_level_prompt(doc), doc.name, doc.rev)
+
+ body = generate_publication_request(request, doc)
+
+ form = PublicationForm(initial=dict(subject=subject,
+ body=body))
+
+ return render_to_response('idrfc/request_publication.html',
+ dict(form=form,
+ doc=doc,
+ message=m,
+ next_state=next_state,
+ ),
+ context_instance = RequestContext(request))
+
diff --git a/ietf/ietfworkflows/accounts.py b/ietf/ietfworkflows/accounts.py
index 4b7046462..1e0762d17 100644
--- a/ietf/ietfworkflows/accounts.py
+++ b/ietf/ietfworkflows/accounts.py
@@ -108,14 +108,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def can_edit_state(user, draft):
- streamed = get_streamed_draft(draft)
- if not settings.USE_DB_REDESIGN_PROXY_CLASSES and (not streamed or not streamed.stream):
- person = get_person_for_user(user)
- if not person:
- return False
- return (is_secretariat(user) or
- is_wgchair(person) or
- is_wgdelegate(person))
return (is_secretariat(user) or
is_authorized_in_draft_stream(user, draft))
diff --git a/ietf/ietfworkflows/templatetags/ietf_streams.py b/ietf/ietfworkflows/templatetags/ietf_streams.py
index 13a040554..25b0fc959 100644
--- a/ietf/ietfworkflows/templatetags/ietf_streams.py
+++ b/ietf/ietfworkflows/templatetags/ietf_streams.py
@@ -74,6 +74,8 @@ def edit_actions(context, wrapper):
if can_edit_state(user, draft):
actions.append(("Change stream state", urlreverse('edit_state', kwargs=dict(name=doc.draft_name))))
+ if draft.stream_id in ("iab", "ise", "irtf"):
+ actions.append(("Request publication", urlreverse('doc_request_publication', kwargs=dict(name=doc.draft_name))))
if can_manage_shepherd_of_a_document(user, draft):
actions.append(("Change shepherd", urlreverse('doc_managing_shepherd', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))))
diff --git a/ietf/name/fixtures/names.xml b/ietf/name/fixtures/names.xml
index 09393cf04..a979ab819 100644
--- a/ietf/name/fixtures/names.xml
+++ b/ietf/name/fixtures/names.xml
@@ -97,7 +97,7 @@
True0
-