Added an explicit ID-Exists state for the IESG state machine. Reworked code so that the IESG state machine always has a state. Added the ability to release a document from a working group, research group, or the independent stream. Releasing a document removes all stream state, and sets the document to have no stream.

- Legacy-Id: 15809
This commit is contained in:
Robert Sparks 2018-12-11 22:38:05 +00:00
parent f69ad28e4c
commit 6118975620
25 changed files with 10935 additions and 10605 deletions

View file

@ -5,6 +5,7 @@ from django.conf import settings
import datetime, os, shutil, glob, re
from pathlib import Path
from ietf.utils import log
from ietf.utils.mail import send_mail
from ietf.doc.models import Document, DocEvent, State, IESG_SUBSTATE_TAGS
from ietf.person.models import Person
@ -17,8 +18,11 @@ def expirable_draft(draft):
"""Return whether draft is in an expirable state or not. This is
the single draft version of the logic in expirable_drafts. These
two functions need to be kept in sync."""
if draft.type_id != 'draft':
return False
log.assertion('draft.get_state_slug("draft-iesg")')
return (draft.expires and draft.get_state_slug() == "active"
and draft.get_state_slug("draft-iesg") in (None, "watching", "dead")
and draft.get_state_slug("draft-iesg") in ("idexists", "watching", "dead")
and draft.get_state_slug("draft-stream-%s" % draft.stream_id) not in ("rfc-edit", "pub")
and not draft.tags.filter(slug="rfc-rev"))
@ -29,8 +33,8 @@ def expirable_drafts():
d = Document.objects.filter(states__type="draft", states__slug="active").exclude(expires=None)
nonexpirable_states = []
# all IESG states except AD Watching and Dead block expiry
nonexpirable_states += list(State.objects.filter(used=True, type="draft-iesg").exclude(slug__in=("watching", "dead")))
# all IESG states except I-D Exists, AD Watching, and Dead block expiry
nonexpirable_states += list(State.objects.filter(used=True, type="draft-iesg").exclude(slug__in=("idexists","watching", "dead")))
# sent to RFC Editor and RFC Published block expiry (the latter
# shouldn't be possible for an active draft, though)
nonexpirable_states += list(State.objects.filter(used=True, type__in=("draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"), slug__in=("rfc-edit", "pub")))
@ -75,7 +79,8 @@ def send_expire_warning_for_draft(doc):
(to,cc) = gather_address_lists('doc_expires_soon',doc=doc)
s = doc.get_state("draft-iesg")
state = s.name if s else "I-D Exists"
log.assertion('s')
state = s.name if s else "I-D Exists" # TODO remove the if clause after some runtime shows no assertions
frm = None
request = None
@ -94,7 +99,8 @@ def send_expire_notice_for_draft(doc):
return
s = doc.get_state("draft-iesg")
state = s.name if s else "I-D Exists"
log.assertion('s')
state = s.name if s else "I-D Exists" # TODO remove the if clause after some rintime shows no assertions
request = None
(to,cc) = gather_address_lists('doc_expired',doc=doc)

View file

@ -46,6 +46,9 @@ class BaseDocumentFactory(factory.DjangoModelFactory):
if create and extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if obj.type_id == 'draft':
if not obj.states.filter(type_id='draft-iesg').exists():
obj.set_state(State.objects.get(type_id='draft-iesg', slug='idexists'))
@factory.post_generation
def authors(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
@ -90,8 +93,11 @@ class IndividualDraftFactory(BaseDocumentFactory):
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='active'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class IndividualRfcFactory(IndividualDraftFactory):
@ -120,9 +126,12 @@ class WgDraftFactory(BaseDocumentFactory):
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='active'))
obj.set_state(State.objects.get(type_id='draft-stream-ietf',slug='wg-doc'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class WgRfcFactory(WgDraftFactory):
@ -137,8 +146,33 @@ class WgRfcFactory(WgDraftFactory):
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='rfc'))
obj.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
class RgDraftFactory(BaseDocumentFactory):
type_id = 'draft'
group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='rg')
stream_id = 'irtf'
@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
if not obj.get_state('draft-iesg'):
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
else:
obj.set_state(State.objects.get(type_id='draft',slug='active'))
obj.set_state(State.objects.get(type_id='draft-stream-irtf',slug='active'))
obj.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
class CharterFactory(BaseDocumentFactory):

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2018-11-04 10:56
from __future__ import unicode_literals
from tqdm import tqdm
from django.db import migrations
def forward(apps, schema_editor):
State = apps.get_model('doc','State')
Document = apps.get_model('doc','Document')
DocHistory = apps.get_model('doc','DocHistory')
idexists = State.objects.create(
type_id = 'draft-iesg',
slug = 'idexists',
name = 'I-D Exists',
used = True,
desc = 'The IESG has not started processing this draft, or has stopped processing it without publicastion.',
order = 0,
)
idexists.next_states.set(State.objects.filter(type='draft-iesg',slug__in=['pub-req','watching']))
#for doc in tqdm(Document.objects.filter(type='draft'):
# if not doc.states.filter(type='draft-iesg').exists():
# doc.states.add(idexists)
# for dh in doc.history_set.all():
# if not dh.states.filter(type='draft-iesg').exists():
# dh.states.add(idexists)
for doc in tqdm(Document.objects.filter(type_id='draft').exclude(states__type_id='draft-iesg')):
doc.states.add(idexists)
for history in tqdm(DocHistory.objects.filter(type_id='draft').exclude(states__type_id='draft-iesg')):
history.states.add(idexists)
def reverse(apps, schema_editor):
State = apps.get_model('doc','State')
State.objects.filter(slug='idexists').delete()
class Migration(migrations.Migration):
dependencies = [
('doc', '0006_ballotpositiondocevent_send_email'),
]
operations = [
migrations.RunPython(forward, reverse)
]

View file

@ -267,6 +267,7 @@ class DocumentInfo(models.Model):
def unset_state(self, state_type):
"""Unset state of type so no state of that type is any longer set."""
log.assertion('state_type != "draft-iesg"')
self.states.remove(*self.states.filter(type=state_type))
self.state_cache = None # invalidate cache
self._cached_state_slug = {}
@ -325,6 +326,7 @@ class DocumentInfo(models.Model):
else:
return "Replaced"
elif state.slug == "active":
log.assertion('iesg_state')
if iesg_state:
if iesg_state.slug == "dead":
# Many drafts in the draft-iesg "Dead" state are not dead

View file

@ -42,6 +42,7 @@ from django.utils.safestring import mark_safe
from ietf.ietfauth.utils import user_is_person, has_role
from ietf.doc.models import BallotPositionDocEvent, IESG_BALLOT_ACTIVE_STATES
from ietf.name.models import BallotPositionName
from ietf.utils import log
register = template.Library()
@ -157,10 +158,11 @@ def state_age_colored(doc):
# Don't show anything for expired/withdrawn/replaced drafts
return ""
iesg_state = doc.get_state_slug('draft-iesg')
log.assertion('iesg_state')
if not iesg_state:
return ""
if iesg_state in ["dead", "watching", "pub"]:
if iesg_state in ["dead", "watching", "pub", "idexists"]:
return ""
try:
state_date = doc.docevent_set.filter(

View file

@ -10,14 +10,14 @@ from django.conf import settings
import debug # pyflakes:ignore
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, DocEventFactory
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, RgDraftFactory, DocEventFactory
from ietf.doc.models import ( Document, DocReminder, DocEvent,
ConsensusDocEvent, LastCallDocEvent, RelatedDocument, State, TelechatDocEvent,
WriteupDocEvent, DocRelationshipName)
from ietf.doc.utils import get_tags_for_stream_id, create_ballot_if_not_open
from ietf.name.models import StreamName, DocTagName
from ietf.group.factories import GroupFactory, RoleFactory
from ietf.group.models import Group
from ietf.group.models import Group, Role
from ietf.person.factories import PersonFactory
from ietf.person.models import Person, Email
from ietf.meeting.models import Meeting, MeetingTypeName
@ -419,7 +419,7 @@ class EditInfoTests(TestCase):
self.assertTrue('draft-ietf-mars-test2@' in outbox[-1]['To'])
# 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_id='draft-iesg', slug='idexists'))
draft.set_state(State.objects.get(type='draft-stream-ietf',slug='writeupw'))
draft.stream = StreamName.objects.get(slug='ietf')
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
@ -589,7 +589,7 @@ class ExpireIDsTests(TestCase):
self.assertEqual(len(list(get_soon_to_expire_drafts(14))), 0)
# hack into expirable state
draft.unset_state("draft-iesg")
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
draft.expires = datetime.datetime.now() + datetime.timedelta(days=10)
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
@ -616,7 +616,7 @@ class ExpireIDsTests(TestCase):
self.assertEqual(len(list(get_expired_drafts())), 0)
# hack into expirable state
draft.unset_state("draft-iesg")
draft.set_state(State.objects.get(type_id='draft-iesg',slug='idexists'))
draft.expires = datetime.datetime.now()
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_document", by=Person.objects.get(user__username="secretary"), desc="Test")])
@ -1114,7 +1114,7 @@ class SubmitToIesgTests(TestCase):
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(name=self.docname)
self.assertTrue(doc.get_state('draft-iesg')==None)
self.assertEqual(doc.get_state_slug('draft-iesg'),'idexists')
def test_confirm_submission(self):
url = urlreverse('ietf.doc.views_draft.to_iesg', kwargs=dict(name=self.docname))
@ -1180,6 +1180,88 @@ class RequestPublicationTests(TestCase):
self.assertTrue("Document Action" in draft.message_set.order_by("-time")[0].subject)
class ReleaseDraftTests(TestCase):
def test_release_wg_draft(self):
chair_role = RoleFactory(group__type_id='wg',name_id='chair')
draft = WgDraftFactory(group = chair_role.group)
draft.tags.set(DocTagName.objects.filter(slug__in=('sh-f-up','w-merge')))
other_chair_role = RoleFactory(group__type_id='wg',name_id='chair')
url = urlreverse('ietf.doc.views_draft.release_draft', kwargs=dict(name=draft.name))
r = self.client.get(url)
self.assertEqual(r.status_code, 302) # redirect to login
self.client.login(username=other_chair_role.person.user.username,password=other_chair_role.person.user.username+"+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
self.client.logout()
self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
events_before = list(draft.docevent_set.all())
empty_outbox()
r = self.client.post(url,{"comment": "Here are some comments"})
self.assertEqual(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.stream, None)
self.assertEqual(draft.group.type_id, "individ")
self.assertFalse(draft.get_state('draft-stream-ietf'))
self.assertEqual(len(outbox),3)
subjects = [msg["Subject"] for msg in outbox]
cat_subjects = "".join(subjects)
self.assertIn("Tags changed", cat_subjects)
self.assertIn("State Update", cat_subjects)
self.assertIn("Stream Change", cat_subjects)
descs = sorted([event.desc for event in set(list(draft.docevent_set.all())) - set(events_before)])
self.assertEqual("Changed stream to <b>None</b> from IETF",descs[0])
self.assertEqual("Here are some comments",descs[1])
self.assertEqual("State changed to <b>None</b> from WG Document",descs[2])
self.assertEqual("Tags Awaiting Merge with Other Document, Document Shepherd Followup cleared.",descs[3])
def test_release_rg_draft(self):
chair_role = RoleFactory(group__type_id='rg',name_id='chair')
draft = RgDraftFactory(group = chair_role.group)
url = urlreverse('ietf.doc.views_draft.release_draft', kwargs=dict(name=draft.name))
self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password")
r = self.client.post(url,{"comment": "Here are some comments"})
self.assertEqual(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.stream, None)
self.assertEqual(draft.group.type_id, "individ")
self.assertFalse(draft.get_state('draft-stream-irtf'))
def test_release_ise_draft(self):
ise = Role.objects.get(name_id='chair', group__acronym='ise')
draft = IndividualDraftFactory(stream_id='ise')
draft.set_state(State.objects.get(type_id='draft-stream-ise',slug='ise-rev'))
draft.tags.set(DocTagName.objects.filter(slug='w-dep'))
url = urlreverse('ietf.doc.views_draft.release_draft', kwargs=dict(name=draft.name))
self.client.login(username=ise.person.user.username, password=ise.person.user.username+'+password')
events_before = list(draft.docevent_set.all())
empty_outbox()
r = self.client.post(url,{"comment": "Here are some comments"})
self.assertEqual(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.stream, None)
self.assertEqual(draft.group.type_id, "individ")
self.assertFalse(draft.get_state('draft-stream-ise'))
self.assertEqual(len(outbox),3)
subjects = [msg["Subject"] for msg in outbox]
cat_subjects = "".join(subjects)
self.assertIn("Tags changed", cat_subjects)
self.assertIn("State Update", cat_subjects)
self.assertIn("Stream Change", cat_subjects)
descs = sorted([event.desc for event in set(list(draft.docevent_set.all())) - set(events_before)])
self.assertEqual("Changed stream to <b>None</b> from ISE",descs[0])
self.assertEqual("Here are some comments",descs[1])
self.assertEqual("State changed to <b>None</b> from In ISE Review",descs[2])
self.assertEqual("Tag Waiting for Dependency on Other Document cleared.",descs[3])
class AdoptDraftTests(TestCase):
def test_adopt_document(self):
RoleFactory(group__acronym='mars',group__list_email='mars-wg@ietf.org',person__user__username='marschairman',name_id='chair')

View file

@ -107,6 +107,7 @@ urlpatterns = [
url(r'^%(name)s/edit/shepherdwriteup/$' % settings.URL_REGEXPS, views_draft.edit_shepherd_writeup),
url(r'^%(name)s/edit/requestpublication/$' % settings.URL_REGEXPS, views_draft.request_publication),
url(r'^%(name)s/edit/adopt/$' % settings.URL_REGEXPS, views_draft.adopt_draft),
url(r'^%(name)s/edit/release/$' % settings.URL_REGEXPS, views_draft.release_draft),
url(r'^%(name)s/edit/state/(?P<state_type>draft-stream-[a-z]+)/$' % settings.URL_REGEXPS, views_draft.change_stream_state),
url(r'^%(name)s/edit/clearballot/(?P<ballot_type_slug>[\w-]+)/$' % settings.URL_REGEXPS, views_ballot.clear_ballot),

View file

@ -120,6 +120,25 @@ def can_adopt_draft(user, doc):
and (doc.group.type_id == "individ" or (doc.group in role_groups and len(role_groups)>1))
and roles.exists())
def can_unadopt_draft(user, doc):
if not user.is_authenticated:
return False
if has_role(user, "Secretariat"):
return True
if doc.stream_id == 'irtf':
if has_role(user, "IRTF Chair"):
return True
return user.person.role_set.filter(name__in=('chair','delegate','secr'),group=doc.group).exists()
elif doc.stream_id == 'ietf':
return user.person.role_set.filter(name__in=('chair','delegate','secr'),group=doc.group).exists()
elif doc.stream_id == 'ise':
return user.person.role_set.filter(name='chair',group__acronym='ise').exists()
elif doc.stream_id == 'iab':
return False # Right now only the secretariat can add a document to the IAB stream, so we'll
# leave it where only the secretariat can take it out.
else:
return False
def two_thirds_rule( recused=0 ):
# For standards-track, need positions from 2/3 of the non-recused current IESG.
active = Role.objects.filter(name="ad",group__type="area",group__state="active").count()

View file

@ -44,6 +44,7 @@ def fill_in_document_sessions(docs, doc_dict, doc_ids):
def fill_in_document_table_attributes(docs, have_telechat_date=False):
# fill in some attributes for the document table results to save
# some hairy template code and avoid repeated SQL queries
# TODO - this function evolved from something that assumed it was handling only drafts. It still has places where it assumes all docs are drafts where that is not a correct assumption
doc_dict = dict((d.pk, d) for d in docs)
doc_ids = doc_dict.keys()

View file

@ -47,7 +47,7 @@ from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDo
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent,
IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS )
from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_with_revision,
can_adopt_draft, get_chartering_type, get_tags_for_stream_id,
can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
add_events_message_info, get_unicode_document_content, build_doc_meta_block)
@ -66,7 +66,7 @@ from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions,
from ietf.review.models import ReviewRequest
from ietf.review.utils import can_request_review_of_doc, review_requests_to_list_for_docs
from ietf.review.utils import no_review_from_teams_on_doc
from ietf.utils import markup_txt
from ietf.utils import markup_txt, log
from ietf.utils.text import maybe_split
@ -329,6 +329,15 @@ def document_main(request, name, rev=None):
button_text = "Manage Document Adoption in Group"
actions.append((button_text, urlreverse('ietf.doc.views_draft.adopt_draft', kwargs=dict(name=doc.name))))
if can_unadopt_draft(request.user, doc) and not doc.get_state_slug() in ["rfc"] and not snapshot:
if doc.get_state_slug('draft-iesg') == 'idexists':
if doc.group and doc.group.acronym != 'none':
actions.append(("Release document from group", urlreverse('ietf.doc.views_draft.release_draft', kwargs=dict(name=doc.name))))
elif doc.stream_id == 'ise':
actions.append(("Release document from stream", urlreverse('ietf.doc.views_draft.release_draft', kwargs=dict(name=doc.name))))
else:
pass
if doc.get_state_slug() == "expired" and not resurrected_by and can_edit and not snapshot:
actions.append(("Request Resurrect", urlreverse('ietf.doc.views_draft.request_resurrect', kwargs=dict(name=doc.name))))
@ -348,14 +357,15 @@ def document_main(request, name, rev=None):
label = "Request Publication"
if not doc.intended_std_level:
label += " (note that intended status is not set)"
if iesg_state and iesg_state.slug != 'dead':
if iesg_state and iesg_state.slug not in ('idexists','dead'):
label += " (Warning: the IESG state indicates ongoing IESG processing)"
actions.append((label, urlreverse('ietf.doc.views_draft.request_publication', kwargs=dict(name=doc.name))))
if doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("ietf",) and not snapshot:
if not iesg_state and can_edit:
log.assertion('iesg_state')
if iesg_state.slug == 'idexists' and can_edit:
actions.append(("Begin IESG Processing", urlreverse('ietf.doc.views_draft.edit_info', kwargs=dict(name=doc.name)) + "?new=1"))
elif (can_edit_stream_info or is_shepherd) and (not iesg_state or iesg_state.slug == 'watching'):
elif (can_edit_stream_info or is_shepherd) and (iesg_state.slug in ('idexists','watching')):
actions.append(("Submit to IESG for Publication", urlreverse('ietf.doc.views_draft.to_iesg', kwargs=dict(name=doc.name))))
augment_docs_with_tracking_info([doc], request.user)

View file

@ -18,13 +18,14 @@ from django.template.defaultfilters import pluralize
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS)
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, StateDocEvent,
IESG_SUBSTATE_TAGS)
from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested,
email_resurrection_completed, email_state_changed, email_stream_changed,
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
generate_publication_request, email_adopted, email_intended_status_changed,
email_iesg_processing_document, email_ad_approved_doc )
from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
from ietf.doc.utils import ( add_state_change_event, can_adopt_draft, can_unadopt_draft,
get_tags_for_stream_id, nice_consensus,
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify,
set_replaces_for_document, default_consensus, tags_suffix, )
@ -62,6 +63,10 @@ class ChangeStateForm(forms.Form):
if state == prev and tag == prev_tag:
self._errors['comment'] = ErrorList([u'State not changed. Comments entered will be lost with no state change. Please go back and use the Add Comment feature on the history tab to add comments without changing state.'])
if state != '(None)' and state.slug == 'idexists' and tag:
self._errors['substate'] = ErrorList([u'Clear substate before setting the document to the idexists state.'])
return retclean
@role_required('Area Director','Secretariat')
@ -141,6 +146,10 @@ def change_state(request, name):
return render(request, 'doc/draft/last_call_requested.html',
dict(doc=doc,
url=doc.get_absolute_url()))
if new_state.slug == "idexists" and doc.stream:
msg = "Note that this document is still in the %s stream. Please ensure the stream state settings make sense, or consider removing the document from the stream." % doc.stream.name
messages.info(request, msg)
return HttpResponseRedirect(doc.get_absolute_url())
@ -612,7 +621,7 @@ def edit_info(request, name):
raise Http404
new_document = False
if not doc.get_state("draft-iesg"): # FIXME: should probably receive "new document" as argument to view instead of this
if doc.get_state_slug("draft-iesg") == "idexists": # FIXME: should probably receive "new document" as argument to view instead of this
new_document = True
doc.notify = get_initial_notify(doc)
@ -1413,6 +1422,79 @@ def adopt_draft(request, name):
'form': form,
})
class ReleaseDraftForm(forms.Form):
comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for releasing the document." )
@login_required
def release_draft(request, name):
doc = get_object_or_404(Document, type="draft", name=name)
if doc.get_state_slug('draft-iesg') != 'idexists':
raise Http404
if not can_unadopt_draft(request.user, doc):
return HttpResponseForbidden("You don't have permission to access this page")
if request.method == 'POST':
form = ReleaseDraftForm(request.POST)
if form.is_valid():
comment = form.cleaned_data["comment"]
by = request.user.person
events = []
if doc.stream.slug == 'ise' or doc.group.type_id != 'individ':
existing_tags = set(doc.tags.all())
if existing_tags:
doc.tags.clear()
e = DocEvent(type="changed_document", doc=doc, rev=doc.rev, by=by)
l = []
l.append(u"Tag%s %s cleared." % (pluralize(existing_tags), ", ".join(t.name for t in existing_tags)))
e.desc = " ".join(l)
e.save()
events.append(e)
email_stream_tags_changed(request, doc, set(), existing_tags, by, comment)
prev_state = doc.get_state("draft-stream-%s" % doc.stream_id)
if prev_state:
doc.unset_state("draft-stream-%s" % doc.stream_id)
e = StateDocEvent(doc=doc, rev=doc.rev, by=by)
e.type = "changed_state"
e.state_type = (prev_state).type
e.state = None
e.desc = "State changed to <b>None</b> from %s" % prev_state.name
e.save()
events.append(e)
email_state_changed(request,doc,e.desc)
if doc.stream.slug != 'ise':
old_group = doc.group
doc.group = Group.objects.get(acronym='none')
e = DocEvent(type="changed_document", doc=doc, rev=doc.rev, by=by)
e.desc = "Document removed from group %s." % old_group.acronym.upper()
if doc.stream:
e = DocEvent(type="changed_stream", doc=doc, rev=doc.rev, by=by)
e.desc = u"Changed stream to <b>None</b> from %s" % doc.stream.name
e.save()
events.append(e)
old_stream = doc.stream
doc.stream = None
email_stream_changed(request, doc, old_stream, None)
if comment:
e = DocEvent(type="added_comment", doc=doc, rev=doc.rev, by=by)
e.desc = comment
e.save()
events.append(e)
doc.save_with_history(events)
return HttpResponseRedirect(doc.get_absolute_url())
else:
form = ReleaseDraftForm()
return render(request, 'doc/draft/release_draft.html', {'doc':doc, 'form':form })
class ChangeStreamStateForm(forms.Form):
new_state = forms.ModelChoiceField(queryset=State.objects.filter(used=True), label='State' )
weeks = forms.IntegerField(label='Expected weeks in state',required=False)

View file

@ -31,14 +31,6 @@ def state_help(request, type):
tags = []
if state_type.slug == "draft-iesg":
# legacy hack
states = list(states)
fake_state = dict(name="I-D Exists",
desc="Initial (default) state for all internet drafts. Such documents are not being tracked by the IESG as no request has been made of the IESG to do anything with the document.",
next_states=dict(all=State.objects.filter(type="draft-iesg", slug__in=("watching", "pub-req")))
)
states.insert(0, fake_state)
tags = DocTagName.objects.filter(slug__in=IESG_SUBSTATE_TAGS)
elif state_type.slug.startswith("draft-stream-"):
possible = get_tags_for_stream_id(state_type.slug.replace("draft-stream-", ""))

View file

@ -426,7 +426,7 @@ def drafts_in_last_call(request):
})
def drafts_in_iesg_process(request):
states = State.objects.filter(type="draft-iesg").exclude(slug__in=('pub', 'dead', 'watching', 'rfcqueue'))
states = State.objects.filter(type="draft-iesg").exclude(slug__in=('idexists', 'pub', 'dead', 'watching', 'rfcqueue'))
title = "Documents in IESG process"
grouped_docs = []

View file

@ -6,6 +6,7 @@ from django.db.models import Q
from django.template.loader import render_to_string
from ietf.doc.models import RelatedDocument
from ietf.utils import log
class Edge(object):
@ -54,9 +55,10 @@ def get_node_styles(node, group):
styles['style'] = 'filled'
log.assertion('node.get_state("draft_iesg")')
if node.get_state('draft').slug == 'rfc':
styles['shape'] = 'box'
elif node.get_state('draft-iesg') and not node.get_state('draft-iesg').slug in ['watching', 'dead']:
elif not node.get_state('draft-iesg').slug in ['idexists', 'watching', 'dead']:
styles['shape'] = 'parallelogram'
elif node.get_state('draft').slug == 'expired':
styles['shape'] = 'house'

View file

@ -16,6 +16,7 @@ from ietf.doc.models import LastCallDocEvent, NewRevisionDocEvent
from ietf.doc.models import IESG_SUBSTATE_TAGS
from ietf.group.models import Group
from ietf.person.models import Person, Email
from ietf.utils import log
def all_id_txt():
# this returns a lot of data so try to be efficient
@ -44,7 +45,7 @@ def all_id_txt():
res.append(f1 + "\t" + f2 + "\t" + f3 + "\t" + f4)
inactive_states = ["pub", "watching", "dead"]
inactive_states = ["idexists", "pub", "watching", "dead"]
excludes = list(State.objects.filter(type="draft", slug__in=["rfc","repl"]))
includes = list(State.objects.filter(type="draft-iesg").exclude(slug__in=inactive_states))
@ -149,6 +150,7 @@ def all_id2_txt():
# 3
if state == "active":
s = "I-D Exists"
log.assertion('iesg_state')
if iesg_state:
s = iesg_state.name
tags = d.tags.filter(slug__in=IESG_SUBSTATE_TAGS).values_list("name", flat=True)

View file

@ -39,7 +39,7 @@ class IndexTests(TestCase):
self.assertTrue(draft.get_state("draft-iesg").name in txt)
# not active in IESG process
draft.unset_state("draft-iesg")
draft.set_state(State.objects.get(type_id="draft-iesg", slug="idexists"))
txt = all_id_txt()
self.assertTrue(draft.name + "-" + draft.rev in txt)

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,7 @@ from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key,
HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE)
from ietf.ietfauth.utils import role_required
from ietf.person.models import Person
from ietf.utils import log
import debug # pyflakes:ignore
@ -1238,10 +1239,7 @@ def eligible(request, year):
# the date of the announcement of the Call for Volunteers, instead
date = datetime.date.today()
previous_five = Meeting.objects.filter(type='ietf',date__lte=date).order_by('-date')[:5]
if not len(previous_five) == 5:
debug.show('year')
debug.show('date')
debug.show('previous_five')
log.assertion("len(previous_five) == 5")
attendees = {}
potentials = set()
for m in previous_five:

View file

@ -114,7 +114,7 @@ class AuthorForm(forms.Form):
class EditModelForm(forms.ModelForm):
#expiration_date = forms.DateField(required=False)
state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft'),empty_label=None)
iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),required=False)
iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),empty_label=None)
group = GroupModelChoiceField(required=True)
review_by_rfc_editor = forms.BooleanField(required=False)
shepherd = SearchableEmailField(required=False, only_users=True)
@ -132,8 +132,7 @@ class EditModelForm(forms.ModelForm):
self.fields['rev'].widget.attrs['size'] = 2
self.fields['abstract'].widget.attrs['cols'] = 72
self.initial['state'] = self.instance.get_state().pk
if self.instance.get_state('draft-iesg'):
self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk
self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk
# setup special fields
if self.instance:
@ -150,10 +149,7 @@ class EditModelForm(forms.ModelForm):
# note we're not sending notices here, is this desired
if 'iesg_state' in self.changed_data:
if iesg_state == None:
m.unset_state('draft-iesg')
else:
m.set_state(iesg_state)
m.set_state(iesg_state)
if 'review_by_rfc_editor' in self.changed_data:
if self.cleaned_data.get('review_by_rfc_editor',''):

View file

@ -69,8 +69,7 @@ class SecrDraftsTestCase(TestCase):
self.client.login(username="secretary", password="secretary+password")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
response = self.client.post(url,{'title':draft.title,'name':draft.name,'rev':draft.rev,'state':4,'group':draft.group.pk})
#debug.show('response')
response = self.client.post(url,{'title':draft.title,'name':draft.name,'rev':draft.rev,'state':4,'group':draft.group.pk,'iesg_state':draft.get_state('draft-iesg').pk})
self.assertEqual(response.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.get_state().slug,'repl')

View file

@ -331,6 +331,9 @@ def post_submission(request, submission, approvedDesc):
desc = settings.SUBMIT_YANG_CATALOG_MODULE_DESC.format(module=module)
draft.documenturl_set.create(url=url, tag_id='yang-module-metadata', desc=desc)
if not draft.get_state('draft-iesg'):
draft.states.add(State.objects.get(type_id='draft-iesg', slug='idexists'))
# save history now that we're done with changes to the draft itself
draft.save_with_history(events)

View file

@ -444,10 +444,12 @@ def update_docs_from_rfc_index(data, skip_older_than_date=None):
for t in ("draft-iesg", "draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"):
slug = doc.get_state_slug(t)
if slug and slug != "pub":
if slug and slug not in ("pub", "idexists"):
new_state = State.objects.select_related("type").get(used=True, type=t, slug="pub")
doc.set_state(new_state)
changes.append("changed %s to %s" % (new_state.type.label, new_state))
if t == 'draft-iesg' and not slug:
doc.set_state(State.objects.get(type_id='draft-iesg', slug='idexists'))
def parse_relation_list(l):
res = []

View file

@ -404,6 +404,7 @@ class RFCSyncTests(TestCase):
self.assertEqual(len(changed), 1)
self.assertEqual(len(warnings), 0)
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.get_state_slug("draft-rfceditor"), "edit")
self.assertEqual(draft.get_state_slug("draft-iesg"), "rfcqueue")
self.assertEqual(set(draft.tags.all()), set(DocTagName.objects.filter(slug__in=("iana", "ref"))))

View file

@ -418,7 +418,7 @@
<th>IESG</th>
<th><a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iesg" %}">IESG state</a></th>
<td class="edit">
{% if iesg_state and can_edit %}
{% if iesg_state.slug != 'idexists' and can_edit %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_state' name=doc.name %}">Edit</a>
{% endif %}
</td>
@ -481,7 +481,7 @@
</td>
</tr>
{% if iesg_state %}
{% if iesg_state.slug != 'idexists' %}
{% if doc.note or can_edit %}
<tr>
<th></th>
@ -583,7 +583,7 @@
<a class="btn btn-default btn-xs track-untrack-doc {% if doc.tracked_in_personal_community_list %}hide{% endif %}" href="{% url "ietf.community.views.track_document" username=user.username name=doc.name %}" title="Add to your personal ID list"><span class="fa fa-bookmark-o"></span> Track</a>
{% endif %}
{% if can_edit and iesg_state %}
{% if can_edit and iesg_state.slug != 'idexists' %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_ballot.lastcalltext' name=doc.name %}">Last call text</a>
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_ballot.ballot_writeupnotes' name=doc.name %}">Ballot text</a>
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_ballot.ballot_approvaltext' name=doc.name %}">Announcement text</a>

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% load origin %}
{% load bootstrap3 %}
{% block morecss %}
.center-button-text { text-align: center;}
{% endblock %}
{% block content %}
<div class='col-md-12 center-button-text button btn-warning'>Warning</div>
<div class='col-md-12'>
{% if doc.stream.slug == 'ise' %}
This action will unset all Independent stream state and remove the document from the Independent stream.
{% else %}
This action will unset all {{doc.group.acronym|upper}} group state, and remove the document from the {{doc.stream.name}} stream.<br>
This is appropriate, for example, if the group decided not to adopt a document after considering it.<br>
It may also be appropriate if the group is abandoning the document.
{% endif %}
</div>
<div class='col-md-12 center-button-text button btn-warning'>Warning</div>
<div>
<form method='POST'>
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Proceed</button>
<a class="btn btn-default pull-right" href="{% url "ietf.doc.views_doc.document_main" name=doc.name %}">Cancel</a>
</form>
</div>
{% endblock %}