feat: remove "AD is watching" state (#7960)
* feat: remove "AD is watching" state * chore: update names.json * refactor: use idexists state, not dead * fix: remove guidance to use watching state * chore: remove references to 'watching' state * feat: remove create_in_state from edit_info view * test: update test * style: Black + move class closer to use * refactor: remove lint * fix: restore missing admonition --------- Co-authored-by: Robert Sparks <rjsparks@nostrum.com>
This commit is contained in:
parent
5ea4cc13bb
commit
fbcfa19add
|
@ -34,8 +34,8 @@ def expirable_drafts(queryset=None):
|
||||||
|
|
||||||
# Populate this first time through (but after django has been set up)
|
# Populate this first time through (but after django has been set up)
|
||||||
if nonexpirable_states is None:
|
if nonexpirable_states is None:
|
||||||
# all IESG states except I-D Exists, AD Watching, and Dead block expiry
|
# all IESG states except I-D Exists and Dead block expiry
|
||||||
nonexpirable_states = list(State.objects.filter(used=True, type="draft-iesg").exclude(slug__in=("idexists","watching", "dead")))
|
nonexpirable_states = list(State.objects.filter(used=True, type="draft-iesg").exclude(slug__in=("idexists", "dead")))
|
||||||
# sent to RFC Editor and RFC Published block expiry (the latter
|
# sent to RFC Editor and RFC Published block expiry (the latter
|
||||||
# shouldn't be possible for an active draft, though)
|
# 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")))
|
nonexpirable_states += list(State.objects.filter(used=True, type__in=("draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"), slug__in=("rfc-edit", "pub")))
|
||||||
|
|
121
ietf/doc/migrations/0024_remove_ad_is_watching_states.py
Normal file
121
ietf/doc/migrations/0024_remove_ad_is_watching_states.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
# Copyright The IETF Trust 2024, All Rights Reserved
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def get_helper(DocHistory, RelatedDocument, RelatedDocHistory, DocumentAuthor, DocHistoryAuthor):
|
||||||
|
"""Dependency injection wrapper"""
|
||||||
|
|
||||||
|
def save_document_in_history(doc):
|
||||||
|
"""Save a snapshot of document and related objects in the database.
|
||||||
|
|
||||||
|
Local copy of ietf.doc.utils.save_document_in_history() to avoid depending on the
|
||||||
|
code base in a migration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_model_fields_as_dict(obj):
|
||||||
|
return dict((field.name, getattr(obj, field.name))
|
||||||
|
for field in obj._meta.fields
|
||||||
|
if field is not obj._meta.pk)
|
||||||
|
|
||||||
|
# copy fields
|
||||||
|
fields = get_model_fields_as_dict(doc)
|
||||||
|
fields["doc"] = doc
|
||||||
|
fields["name"] = doc.name
|
||||||
|
|
||||||
|
dochist = DocHistory(**fields)
|
||||||
|
dochist.save()
|
||||||
|
|
||||||
|
# copy many to many
|
||||||
|
for field in doc._meta.many_to_many:
|
||||||
|
if field.remote_field.through and field.remote_field.through._meta.auto_created:
|
||||||
|
hist_field = getattr(dochist, field.name)
|
||||||
|
hist_field.clear()
|
||||||
|
hist_field.set(getattr(doc, field.name).all())
|
||||||
|
|
||||||
|
# copy remaining tricky many to many
|
||||||
|
def transfer_fields(obj, HistModel):
|
||||||
|
mfields = get_model_fields_as_dict(item)
|
||||||
|
# map doc -> dochist
|
||||||
|
for k, v in mfields.items():
|
||||||
|
if v == doc:
|
||||||
|
mfields[k] = dochist
|
||||||
|
HistModel.objects.create(**mfields)
|
||||||
|
|
||||||
|
for item in RelatedDocument.objects.filter(source=doc):
|
||||||
|
transfer_fields(item, RelatedDocHistory)
|
||||||
|
|
||||||
|
for item in DocumentAuthor.objects.filter(document=doc):
|
||||||
|
transfer_fields(item, DocHistoryAuthor)
|
||||||
|
|
||||||
|
return dochist
|
||||||
|
|
||||||
|
return save_document_in_history
|
||||||
|
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
"""Mark watching draft-iesg state unused after removing it from Documents"""
|
||||||
|
StateDocEvent = apps.get_model("doc", "StateDocEvent")
|
||||||
|
Document = apps.get_model("doc", "Document")
|
||||||
|
State = apps.get_model("doc", "State")
|
||||||
|
StateType = apps.get_model("doc", "StateType")
|
||||||
|
Person = apps.get_model("person", "Person")
|
||||||
|
|
||||||
|
save_document_in_history = get_helper(
|
||||||
|
DocHistory=apps.get_model("doc", "DocHistory"),
|
||||||
|
RelatedDocument=apps.get_model("doc", "RelatedDocument"),
|
||||||
|
RelatedDocHistory=apps.get_model("doc", "RelatedDocHistory"),
|
||||||
|
DocumentAuthor=apps.get_model("doc", "DocumentAuthor"),
|
||||||
|
DocHistoryAuthor=apps.get_model("doc", "DocHistoryAuthor"),
|
||||||
|
)
|
||||||
|
|
||||||
|
draft_iesg_state_type = StateType.objects.get(slug="draft-iesg")
|
||||||
|
idexists_state = State.objects.get(type=draft_iesg_state_type, slug="idexists")
|
||||||
|
watching_state = State.objects.get(type=draft_iesg_state_type, slug="watching")
|
||||||
|
system_person = Person.objects.get(name="(System)")
|
||||||
|
|
||||||
|
# Remove state from documents that currently have it
|
||||||
|
for doc in Document.objects.filter(states=watching_state):
|
||||||
|
assert doc.type_id == "draft"
|
||||||
|
doc.states.remove(watching_state)
|
||||||
|
doc.states.add(idexists_state)
|
||||||
|
e = StateDocEvent.objects.create(
|
||||||
|
type="changed_state",
|
||||||
|
by=system_person,
|
||||||
|
doc=doc,
|
||||||
|
rev=doc.rev,
|
||||||
|
desc=f"{draft_iesg_state_type.label} changed to <b>{idexists_state.name}</b> from {watching_state.name}",
|
||||||
|
state_type=draft_iesg_state_type,
|
||||||
|
state=idexists_state,
|
||||||
|
)
|
||||||
|
doc.time = e.time
|
||||||
|
doc.save()
|
||||||
|
save_document_in_history(doc)
|
||||||
|
assert not Document.objects.filter(states=watching_state).exists()
|
||||||
|
|
||||||
|
# Mark state as unused
|
||||||
|
watching_state.used = False
|
||||||
|
watching_state.save()
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
"""Mark watching draft-iesg state as used
|
||||||
|
|
||||||
|
Does not try to re-apply the state to Documents modified by the forward migration. This
|
||||||
|
could be done in theory, but would either require dangerous history rewriting or add a
|
||||||
|
lot of history junk.
|
||||||
|
"""
|
||||||
|
State = apps.get_model("doc", "State")
|
||||||
|
StateType = apps.get_model("doc", "StateType")
|
||||||
|
State.objects.filter(
|
||||||
|
type=StateType.objects.get(slug="draft-iesg"), slug="watching"
|
||||||
|
).update(used=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("doc", "0023_bofreqspamstate"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(forward, reverse)]
|
|
@ -184,7 +184,7 @@ def state_age_colored(doc):
|
||||||
if not iesg_state:
|
if not iesg_state:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if iesg_state in ["dead", "watching", "pub", "idexists"]:
|
if iesg_state in ["dead", "pub", "idexists"]:
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
state_datetime = (
|
state_datetime = (
|
||||||
|
|
|
@ -559,7 +559,7 @@ class BallotWriteupsTests(TestCase):
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
self.assertFalse(q('[class=text-danger]:contains("not completed IETF Last Call")'))
|
self.assertFalse(q('[class=text-danger]:contains("not completed IETF Last Call")'))
|
||||||
|
|
||||||
for state_slug in ["lc", "watching", "ad-eval"]:
|
for state_slug in ["lc", "ad-eval"]:
|
||||||
draft.set_state(State.objects.get(type="draft-iesg",slug=state_slug))
|
draft.set_state(State.objects.get(type="draft-iesg",slug=state_slug))
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
|
@ -26,7 +26,7 @@ from ietf.doc.models import ( Document, DocReminder, DocEvent,
|
||||||
WriteupDocEvent, DocRelationshipName, IanaExpertDocEvent )
|
WriteupDocEvent, DocRelationshipName, IanaExpertDocEvent )
|
||||||
from ietf.doc.utils import get_tags_for_stream_id, create_ballot_if_not_open
|
from ietf.doc.utils import get_tags_for_stream_id, create_ballot_if_not_open
|
||||||
from ietf.doc.views_draft import AdoptDraftForm
|
from ietf.doc.views_draft import AdoptDraftForm
|
||||||
from ietf.name.models import StreamName, DocTagName, RoleName
|
from ietf.name.models import DocTagName, RoleName
|
||||||
from ietf.group.factories import GroupFactory, RoleFactory
|
from ietf.group.factories import GroupFactory, RoleFactory
|
||||||
from ietf.group.models import Group, Role
|
from ietf.group.models import Group, Role
|
||||||
from ietf.person.factories import PersonFactory, EmailFactory
|
from ietf.person.factories import PersonFactory, EmailFactory
|
||||||
|
@ -471,69 +471,61 @@ class EditInfoTests(TestCase):
|
||||||
self.assertIn("may not leave enough time", get_payload_text(outbox[-1]))
|
self.assertIn("may not leave enough time", get_payload_text(outbox[-1]))
|
||||||
|
|
||||||
def test_start_iesg_process_on_draft(self):
|
def test_start_iesg_process_on_draft(self):
|
||||||
|
|
||||||
draft = WgDraftFactory(
|
draft = WgDraftFactory(
|
||||||
name="draft-ietf-mars-test2",
|
name="draft-ietf-mars-test2",
|
||||||
group__acronym='mars',
|
group__acronym="mars",
|
||||||
intended_std_level_id="ps",
|
intended_std_level_id="ps",
|
||||||
authors=[Person.objects.get(user__username='ad')],
|
authors=[Person.objects.get(user__username="ad")],
|
||||||
)
|
)
|
||||||
|
|
||||||
url = urlreverse('ietf.doc.views_draft.edit_info', kwargs=dict(name=draft.name))
|
url = urlreverse("ietf.doc.views_draft.edit_info", kwargs=dict(name=draft.name))
|
||||||
login_testing_unauthorized(self, "secretary", url)
|
login_testing_unauthorized(self, "secretary", url)
|
||||||
|
|
||||||
# normal get
|
# normal get
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
self.assertEqual(len(q('form select[name=intended_std_level]')), 1)
|
self.assertEqual(len(q("form select[name=intended_std_level]")), 1)
|
||||||
self.assertEqual("", q('form textarea[name=notify]')[0].value.strip())
|
self.assertEqual("", q("form textarea[name=notify]")[0].value.strip())
|
||||||
|
|
||||||
# add
|
events_before = list(draft.docevent_set.values_list("id", flat=True))
|
||||||
events_before = draft.docevent_set.count()
|
|
||||||
mailbox_before = len(outbox)
|
mailbox_before = len(outbox)
|
||||||
|
|
||||||
ad = Person.objects.get(name="Areað Irector")
|
ad = Person.objects.get(name="Areað Irector")
|
||||||
|
|
||||||
r = self.client.post(url,
|
r = self.client.post(
|
||||||
dict(intended_std_level=str(draft.intended_std_level_id),
|
url,
|
||||||
ad=ad.pk,
|
dict(
|
||||||
create_in_state=State.objects.get(used=True, type="draft-iesg", slug="watching").pk,
|
intended_std_level=str(draft.intended_std_level_id),
|
||||||
notify="test@example.com",
|
ad=ad.pk,
|
||||||
telechat_date="",
|
notify="test@example.com",
|
||||||
))
|
telechat_date="",
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
|
|
||||||
draft = Document.objects.get(name=draft.name)
|
draft = Document.objects.get(name=draft.name)
|
||||||
self.assertEqual(draft.get_state_slug("draft-iesg"), "watching")
|
self.assertEqual(draft.get_state_slug("draft-iesg"), "pub-req")
|
||||||
|
self.assertEqual(draft.get_state_slug("draft-stream-ietf"), "sub-pub")
|
||||||
self.assertEqual(draft.ad, ad)
|
self.assertEqual(draft.ad, ad)
|
||||||
self.assertTrue(not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat"))
|
self.assertTrue(
|
||||||
self.assertEqual(draft.docevent_set.count(), events_before + 4)
|
not draft.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
|
||||||
self.assertCountEqual(draft.action_holders.all(), [draft.ad])
|
)
|
||||||
events = list(draft.docevent_set.order_by('time', 'id'))
|
# check that the expected events were created (don't insist on ordering)
|
||||||
self.assertEqual(events[-4].type, "started_iesg_process")
|
self.assertCountEqual(
|
||||||
self.assertEqual(len(outbox), mailbox_before+1)
|
draft.docevent_set.exclude(id__in=events_before).values_list("type", flat=True),
|
||||||
self.assertTrue('IESG processing' in outbox[-1]['Subject'])
|
[
|
||||||
self.assertTrue('draft-ietf-mars-test2@' in outbox[-1]['To'])
|
"changed_action_holders", # action holders set to AD
|
||||||
|
"changed_document", # WG state set to sub-pub
|
||||||
# Redo, starting in publication requested to make sure WG state is also set
|
"changed_document", # AD set
|
||||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='idexists'))
|
"changed_document", # state change notice email set
|
||||||
draft.set_state(State.objects.get(type='draft-stream-ietf',slug='writeupw'))
|
"started_iesg_process", # IESG state is now pub-req
|
||||||
draft.stream = StreamName.objects.get(slug='ietf')
|
],
|
||||||
draft.action_holders.clear()
|
)
|
||||||
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
|
|
||||||
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",
|
|
||||||
telechat_date="",
|
|
||||||
))
|
|
||||||
self.assertEqual(r.status_code, 302)
|
|
||||||
draft = Document.objects.get(name=draft.name)
|
|
||||||
self.assertEqual(draft.get_state_slug('draft-iesg'),'pub-req')
|
|
||||||
self.assertEqual(draft.get_state_slug('draft-stream-ietf'),'sub-pub')
|
|
||||||
self.assertCountEqual(draft.action_holders.all(), [draft.ad])
|
self.assertCountEqual(draft.action_holders.all(), [draft.ad])
|
||||||
|
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||||
|
self.assertTrue("IESG processing" in outbox[-1]["Subject"])
|
||||||
|
self.assertTrue("draft-ietf-mars-test2@" in outbox[-1]["To"])
|
||||||
|
|
||||||
def test_edit_consensus(self):
|
def test_edit_consensus(self):
|
||||||
draft = WgDraftFactory()
|
draft = WgDraftFactory()
|
||||||
|
@ -750,10 +742,6 @@ class ExpireIDsTests(DraftFileMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(len(list(get_expired_drafts())), 1)
|
self.assertEqual(len(list(get_expired_drafts())), 1)
|
||||||
|
|
||||||
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="watching"))
|
|
||||||
|
|
||||||
self.assertEqual(len(list(get_expired_drafts())), 1)
|
|
||||||
|
|
||||||
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva"))
|
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="iesg-eva"))
|
||||||
|
|
||||||
self.assertEqual(len(list(get_expired_drafts())), 0)
|
self.assertEqual(len(list(get_expired_drafts())), 0)
|
||||||
|
|
|
@ -641,7 +641,7 @@ def ballot_writeupnotes(request, name):
|
||||||
existing.save()
|
existing.save()
|
||||||
|
|
||||||
if "issue_ballot" in request.POST and not ballot_already_approved:
|
if "issue_ballot" in request.POST and not ballot_already_approved:
|
||||||
if prev_state.slug in ['watching', 'writeupw', 'goaheadw']:
|
if prev_state.slug in ['writeupw', 'goaheadw']:
|
||||||
new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva')
|
new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva')
|
||||||
prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
|
prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS)
|
||||||
doc.set_state(new_state)
|
doc.set_state(new_state)
|
||||||
|
@ -708,7 +708,7 @@ def ballot_writeupnotes(request, name):
|
||||||
back_url=doc.get_absolute_url(),
|
back_url=doc.get_absolute_url(),
|
||||||
ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")),
|
ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")),
|
||||||
warn_lc = not doc.docevent_set.filter(lastcalldocevent__expires__date__lt=date_today(DEADLINE_TZINFO)).exists(),
|
warn_lc = not doc.docevent_set.filter(lastcalldocevent__expires__date__lt=date_today(DEADLINE_TZINFO)).exists(),
|
||||||
warn_unexpected_state= prev_state if bool(prev_state.slug in ['watching', 'ad-eval', 'lc']) else None,
|
warn_unexpected_state= prev_state if bool(prev_state.slug in ['ad-eval', 'lc']) else None,
|
||||||
ballot_writeup_form=form,
|
ballot_writeup_form=form,
|
||||||
need_intended_status=need_intended_status,
|
need_intended_status=need_intended_status,
|
||||||
))
|
))
|
||||||
|
|
|
@ -587,7 +587,7 @@ def document_main(request, name, rev=None, document_html=False):
|
||||||
if doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("ietf",) and not snapshot:
|
if doc.get_state_slug() not in ["rfc", "expired"] and doc.stream_id in ("ietf",) and not snapshot:
|
||||||
if iesg_state_slug == 'idexists' and can_edit:
|
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"))
|
actions.append(("Begin IESG Processing", urlreverse('ietf.doc.views_draft.edit_info', kwargs=dict(name=doc.name)) + "?new=1"))
|
||||||
elif can_edit_stream_info and (iesg_state_slug in ('idexists','watching')):
|
elif can_edit_stream_info and (iesg_state_slug == 'idexists'):
|
||||||
actions.append(("Submit to IESG for Publication", urlreverse('ietf.doc.views_draft.to_iesg', kwargs=dict(name=doc.name))))
|
actions.append(("Submit to IESG for Publication", urlreverse('ietf.doc.views_draft.to_iesg', kwargs=dict(name=doc.name))))
|
||||||
|
|
||||||
if request.user.is_authenticated and hasattr(request.user, "person"):
|
if request.user.is_authenticated and hasattr(request.user, "person"):
|
||||||
|
|
|
@ -487,40 +487,6 @@ def change_intention(request, name):
|
||||||
doc=doc,
|
doc=doc,
|
||||||
))
|
))
|
||||||
|
|
||||||
class EditInfoForm(forms.Form):
|
|
||||||
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.filter(used=True), empty_label="(None)", required=True, label="Intended RFC status")
|
|
||||||
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area")
|
|
||||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True)
|
|
||||||
create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
|
|
||||||
notify = forms.CharField(
|
|
||||||
widget=forms.Textarea,
|
|
||||||
max_length=1023,
|
|
||||||
label="Notice emails",
|
|
||||||
help_text="Separate email addresses with commas.",
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
|
|
||||||
returning_item = forms.BooleanField(required=False)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(self.__class__, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# if previous AD is now ex-AD, append that person to the list
|
|
||||||
ad_pk = self.initial.get('ad')
|
|
||||||
choices = self.fields['ad'].choices
|
|
||||||
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
|
||||||
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
|
|
||||||
|
|
||||||
# telechat choices
|
|
||||||
dates = [d.date for d in TelechatDate.objects.active().order_by('date')]
|
|
||||||
init = kwargs['initial']['telechat_date']
|
|
||||||
if init and init not in dates:
|
|
||||||
dates.insert(0, init)
|
|
||||||
|
|
||||||
self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates]
|
|
||||||
|
|
||||||
# returning item is rendered non-standard
|
|
||||||
self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)]
|
|
||||||
|
|
||||||
def to_iesg(request,name):
|
def to_iesg(request,name):
|
||||||
""" Submit an IETF stream document to the IESG for publication """
|
""" Submit an IETF stream document to the IESG for publication """
|
||||||
|
@ -619,7 +585,71 @@ def to_iesg(request,name):
|
||||||
notify=notify,
|
notify=notify,
|
||||||
))
|
))
|
||||||
|
|
||||||
@role_required('Area Director','Secretariat')
|
class EditInfoForm(forms.Form):
|
||||||
|
intended_std_level = forms.ModelChoiceField(
|
||||||
|
IntendedStdLevelName.objects.filter(used=True),
|
||||||
|
empty_label="(None)",
|
||||||
|
required=True,
|
||||||
|
label="Intended RFC status",
|
||||||
|
)
|
||||||
|
area = forms.ModelChoiceField(
|
||||||
|
Group.objects.filter(type="area", state="active"),
|
||||||
|
empty_label="(None - individual submission)",
|
||||||
|
required=False,
|
||||||
|
label="Assigned to area",
|
||||||
|
)
|
||||||
|
ad = forms.ModelChoiceField(
|
||||||
|
Person.objects.filter(
|
||||||
|
role__name="ad", role__group__state="active", role__group__type="area"
|
||||||
|
).order_by("name"),
|
||||||
|
label="Responsible AD",
|
||||||
|
empty_label="(None)",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
notify = forms.CharField(
|
||||||
|
widget=forms.Textarea,
|
||||||
|
max_length=1023,
|
||||||
|
label="Notice emails",
|
||||||
|
help_text="Separate email addresses with commas.",
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
telechat_date = forms.TypedChoiceField(
|
||||||
|
coerce=lambda x: datetime.datetime.strptime(x, "%Y-%m-%d").date(),
|
||||||
|
empty_value=None,
|
||||||
|
required=False,
|
||||||
|
widget=forms.Select(attrs={"onchange": "make_bold()"}),
|
||||||
|
)
|
||||||
|
returning_item = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(self.__class__, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# if previous AD is now ex-AD, append that person to the list
|
||||||
|
ad_pk = self.initial.get("ad")
|
||||||
|
choices = self.fields["ad"].choices
|
||||||
|
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
||||||
|
self.fields["ad"].choices = list(choices) + [
|
||||||
|
("", "-------"),
|
||||||
|
(ad_pk, Person.objects.get(pk=ad_pk).plain_name()),
|
||||||
|
]
|
||||||
|
|
||||||
|
# telechat choices
|
||||||
|
dates = [d.date for d in TelechatDate.objects.active().order_by("date")]
|
||||||
|
init = kwargs["initial"]["telechat_date"]
|
||||||
|
if init and init not in dates:
|
||||||
|
dates.insert(0, init)
|
||||||
|
|
||||||
|
self.fields["telechat_date"].choices = [("", "(not on agenda)")] + [
|
||||||
|
(d, d.strftime("%Y-%m-%d")) for d in dates
|
||||||
|
]
|
||||||
|
|
||||||
|
# returning item is rendered non-standard
|
||||||
|
self.standard_fields = [
|
||||||
|
x for x in self.visible_fields() if x.name not in ("returning_item",)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@role_required("Area Director", "Secretariat")
|
||||||
def edit_info(request, name):
|
def edit_info(request, name):
|
||||||
"""Edit various Internet-Draft attributes, notifying parties as
|
"""Edit various Internet-Draft attributes, notifying parties as
|
||||||
necessary and logging changes as document events."""
|
necessary and logging changes as document events."""
|
||||||
|
@ -628,7 +658,8 @@ def edit_info(request, name):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
new_document = False
|
new_document = False
|
||||||
if doc.get_state_slug("draft-iesg") == "idexists": # FIXME: should probably receive "new document" as argument to view instead of this
|
# FIXME: should probably receive "new document" as argument to view instead of this
|
||||||
|
if doc.get_state_slug("draft-iesg") == "idexists":
|
||||||
new_document = True
|
new_document = True
|
||||||
doc.notify = get_initial_notify(doc)
|
doc.notify = get_initial_notify(doc)
|
||||||
|
|
||||||
|
@ -636,34 +667,45 @@ def edit_info(request, name):
|
||||||
initial_telechat_date = e.telechat_date if e else None
|
initial_telechat_date = e.telechat_date if e else None
|
||||||
initial_returning_item = bool(e and e.returning_item)
|
initial_returning_item = bool(e and e.returning_item)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == "POST":
|
||||||
form = EditInfoForm(request.POST,
|
form = EditInfoForm(
|
||||||
initial=dict(ad=doc.ad_id,
|
request.POST,
|
||||||
telechat_date=initial_telechat_date))
|
initial=dict(ad=doc.ad_id, telechat_date=initial_telechat_date),
|
||||||
|
)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
by = request.user.person
|
by = request.user.person
|
||||||
|
pubreq_state = State.objects.get(type="draft-iesg", slug="pub-req")
|
||||||
|
|
||||||
r = form.cleaned_data
|
r = form.cleaned_data
|
||||||
events = []
|
events = []
|
||||||
|
|
||||||
if new_document:
|
if new_document:
|
||||||
doc.set_state(r['create_in_state'])
|
doc.set_state(pubreq_state)
|
||||||
|
|
||||||
# Is setting the WG state here too much of a hidden side-effect?
|
# Is setting the WG state here too much of a hidden side-effect?
|
||||||
if r['create_in_state'].slug=='pub-req':
|
if (
|
||||||
if doc.stream and doc.stream.slug=='ietf' and doc.group and doc.group.type_id == 'wg':
|
doc.stream
|
||||||
submitted_state = State.objects.get(type='draft-stream-ietf',slug='sub-pub')
|
and doc.stream.slug == "ietf"
|
||||||
doc.set_state(submitted_state)
|
and doc.group
|
||||||
e = DocEvent()
|
and doc.group.type_id == "wg"
|
||||||
e.type = "changed_document"
|
):
|
||||||
e.by = by
|
submitted_state = State.objects.get(
|
||||||
e.doc = doc
|
type="draft-stream-ietf", slug="sub-pub"
|
||||||
e.rev = doc.rev
|
)
|
||||||
e.desc = "Working group state set to %s" % submitted_state.name
|
doc.set_state(submitted_state)
|
||||||
e.save()
|
e = DocEvent()
|
||||||
events.append(e)
|
e.type = "changed_document"
|
||||||
|
e.by = by
|
||||||
|
e.doc = doc
|
||||||
|
e.rev = doc.rev
|
||||||
|
e.desc = "Working group state set to %s" % submitted_state.name
|
||||||
|
e.save()
|
||||||
|
events.append(e)
|
||||||
|
|
||||||
replaces = Document.objects.filter(targets_related__source=doc, targets_related__relationship="replaces")
|
replaces = Document.objects.filter(
|
||||||
|
targets_related__source=doc,
|
||||||
|
targets_related__relationship="replaces",
|
||||||
|
)
|
||||||
if replaces:
|
if replaces:
|
||||||
# this should perhaps be somewhere else, e.g. the
|
# this should perhaps be somewhere else, e.g. the
|
||||||
# place where the replace relationship is established?
|
# place where the replace relationship is established?
|
||||||
|
@ -672,7 +714,10 @@ def edit_info(request, name):
|
||||||
e.by = Person.objects.get(name="(System)")
|
e.by = Person.objects.get(name="(System)")
|
||||||
e.doc = doc
|
e.doc = doc
|
||||||
e.rev = doc.rev
|
e.rev = doc.rev
|
||||||
e.desc = "Earlier history may be found in the Comment Log for <a href=\"%s\">%s</a>" % (replaces[0], replaces[0].get_absolute_url())
|
e.desc = (
|
||||||
|
'Earlier history may be found in the Comment Log for <a href="%s">%s</a>'
|
||||||
|
% (replaces[0], replaces[0].get_absolute_url())
|
||||||
|
)
|
||||||
e.save()
|
e.save()
|
||||||
events.append(e)
|
events.append(e)
|
||||||
|
|
||||||
|
@ -681,7 +726,10 @@ def edit_info(request, name):
|
||||||
e.by = by
|
e.by = by
|
||||||
e.doc = doc
|
e.doc = doc
|
||||||
e.rev = doc.rev
|
e.rev = doc.rev
|
||||||
e.desc = "Document is now in IESG state <b>%s</b>" % doc.get_state("draft-iesg").name
|
e.desc = (
|
||||||
|
"Document is now in IESG state <b>%s</b>"
|
||||||
|
% doc.get_state("draft-iesg").name
|
||||||
|
)
|
||||||
e.save()
|
e.save()
|
||||||
events.append(e)
|
events.append(e)
|
||||||
|
|
||||||
|
@ -691,9 +739,9 @@ def edit_info(request, name):
|
||||||
entry = "%(attr)s changed to <b>%(new)s</b> from <b>%(old)s</b>"
|
entry = "%(attr)s changed to <b>%(new)s</b> from <b>%(old)s</b>"
|
||||||
if new_document:
|
if new_document:
|
||||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||||
|
|
||||||
return entry % dict(attr=attr, new=new, old=old)
|
return entry % dict(attr=attr, new=new, old=old)
|
||||||
|
|
||||||
def diff(attr, name):
|
def diff(attr, name):
|
||||||
v = getattr(doc, attr)
|
v = getattr(doc, attr)
|
||||||
if r[attr] != v:
|
if r[attr] != v:
|
||||||
|
@ -701,9 +749,9 @@ def edit_info(request, name):
|
||||||
setattr(doc, attr, r[attr])
|
setattr(doc, attr, r[attr])
|
||||||
|
|
||||||
# update the attributes, keeping track of what we're doing
|
# update the attributes, keeping track of what we're doing
|
||||||
diff('intended_std_level', "Intended Status")
|
diff("intended_std_level", "Intended Status")
|
||||||
diff('ad', "Responsible AD")
|
diff("ad", "Responsible AD")
|
||||||
diff('notify', "State Change Notice email list")
|
diff("notify", "State Change Notice email list")
|
||||||
|
|
||||||
if doc.group.type_id in ("individ", "area"):
|
if doc.group.type_id in ("individ", "area"):
|
||||||
if not r["area"]:
|
if not r["area"]:
|
||||||
|
@ -717,12 +765,16 @@ def edit_info(request, name):
|
||||||
doc.group = r["area"]
|
doc.group = r["area"]
|
||||||
|
|
||||||
for c in changes:
|
for c in changes:
|
||||||
events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=by, desc=c, type="changed_document"))
|
events.append(
|
||||||
|
DocEvent.objects.create(
|
||||||
|
doc=doc, rev=doc.rev, by=by, desc=c, type="changed_document"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Todo - chase this
|
# Todo - chase this
|
||||||
e = update_telechat(request, doc, by,
|
e = update_telechat(
|
||||||
r['telechat_date'], r['returning_item'])
|
request, doc, by, r["telechat_date"], r["returning_item"]
|
||||||
|
)
|
||||||
if e:
|
if e:
|
||||||
events.append(e)
|
events.append(e)
|
||||||
|
|
||||||
|
@ -730,40 +782,44 @@ def edit_info(request, name):
|
||||||
|
|
||||||
if new_document:
|
if new_document:
|
||||||
# If we created a new doc, update the action holders as though it
|
# If we created a new doc, update the action holders as though it
|
||||||
# started in idexists and moved to its create_in_state. Do this
|
# started in idexists and moved to pub-req. Do this
|
||||||
# after the doc has been updated so, e.g., doc.ad is set.
|
# after the doc has been updated so, e.g., doc.ad is set.
|
||||||
update_action_holders(
|
update_action_holders(
|
||||||
doc,
|
doc,
|
||||||
State.objects.get(type='draft-iesg', slug='idexists'),
|
State.objects.get(type="draft-iesg", slug="idexists"),
|
||||||
r['create_in_state']
|
pubreq_state,
|
||||||
)
|
)
|
||||||
|
|
||||||
if changes:
|
if changes:
|
||||||
email_iesg_processing_document(request, doc, changes)
|
email_iesg_processing_document(request, doc, changes)
|
||||||
|
|
||||||
return HttpResponseRedirect(doc.get_absolute_url())
|
return HttpResponseRedirect(doc.get_absolute_url())
|
||||||
else:
|
else:
|
||||||
init = dict(intended_std_level=doc.intended_std_level_id,
|
init = dict(
|
||||||
area=doc.group_id,
|
intended_std_level=doc.intended_std_level_id,
|
||||||
ad=doc.ad_id,
|
area=doc.group_id,
|
||||||
notify=doc.notify,
|
ad=doc.ad_id,
|
||||||
telechat_date=initial_telechat_date,
|
notify=doc.notify,
|
||||||
returning_item=initial_returning_item,
|
telechat_date=initial_telechat_date,
|
||||||
)
|
returning_item=initial_returning_item,
|
||||||
|
)
|
||||||
|
|
||||||
form = EditInfoForm(initial=init)
|
form = EditInfoForm(initial=init)
|
||||||
|
|
||||||
# optionally filter out some fields
|
# optionally filter out some fields
|
||||||
if not new_document:
|
|
||||||
form.standard_fields = [x for x in form.standard_fields if x.name != "create_in_state"]
|
|
||||||
if doc.group.type_id not in ("individ", "area"):
|
if doc.group.type_id not in ("individ", "area"):
|
||||||
form.standard_fields = [x for x in form.standard_fields if x.name != "area"]
|
form.standard_fields = [x for x in form.standard_fields if x.name != "area"]
|
||||||
|
|
||||||
return render(request, 'doc/draft/edit_info.html',
|
return render(
|
||||||
dict(doc=doc,
|
request,
|
||||||
form=form,
|
"doc/draft/edit_info.html",
|
||||||
user=request.user,
|
dict(
|
||||||
ballot_issued=doc.latest_event(type="sent_ballot_announcement")))
|
doc=doc,
|
||||||
|
form=form,
|
||||||
|
user=request.user,
|
||||||
|
ballot_issued=doc.latest_event(type="sent_ballot_announcement"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@role_required('Area Director','Secretariat')
|
@role_required('Area Director','Secretariat')
|
||||||
def request_resurrect(request, name):
|
def request_resurrect(request, name):
|
||||||
|
|
|
@ -757,7 +757,7 @@ def drafts_in_last_call(request):
|
||||||
})
|
})
|
||||||
|
|
||||||
def drafts_in_iesg_process(request):
|
def drafts_in_iesg_process(request):
|
||||||
states = State.objects.filter(type="draft-iesg").exclude(slug__in=('idexists', 'pub', 'dead', 'watching', 'rfcqueue'))
|
states = State.objects.filter(type="draft-iesg").exclude(slug__in=('idexists', 'pub', 'dead', 'rfcqueue'))
|
||||||
title = "Documents in IESG process"
|
title = "Documents in IESG process"
|
||||||
|
|
||||||
grouped_docs = []
|
grouped_docs = []
|
||||||
|
|
|
@ -731,7 +731,7 @@ def dependencies(request, acronym, group_type=None):
|
||||||
{
|
{
|
||||||
"id": x.became_rfc().name if x.became_rfc() else x.name,
|
"id": x.became_rfc().name if x.became_rfc() else x.name,
|
||||||
"rfc": x.type_id == "rfc" or x.became_rfc() is not None,
|
"rfc": x.type_id == "rfc" or x.became_rfc() is not None,
|
||||||
"post-wg": x.get_state_slug("draft-iesg") not in ["idexists", "watching", "dead"],
|
"post-wg": x.get_state_slug("draft-iesg") not in ["idexists", "dead"],
|
||||||
"expired": x.get_state_slug("draft") == "expired",
|
"expired": x.get_state_slug("draft") == "expired",
|
||||||
"replaced": x.get_state_slug("draft") == "repl",
|
"replaced": x.get_state_slug("draft") == "repl",
|
||||||
"group": x.group.acronym if x.group and x.group.acronym != "none" else "",
|
"group": x.group.acronym if x.group and x.group.acronym != "none" else "",
|
||||||
|
|
|
@ -51,7 +51,7 @@ def all_id_txt():
|
||||||
res.append(f1 + "\t" + f2 + "\t" + f3 + "\t" + f4)
|
res.append(f1 + "\t" + f2 + "\t" + f3 + "\t" + f4)
|
||||||
|
|
||||||
|
|
||||||
inactive_states = ["idexists", "pub", "watching", "dead"]
|
inactive_states = ["idexists", "pub", "dead"]
|
||||||
|
|
||||||
excludes = list(State.objects.filter(type="draft", slug__in=["rfc","repl"]))
|
excludes = list(State.objects.filter(type="draft", slug__in=["rfc","repl"]))
|
||||||
includes = list(State.objects.filter(type="draft-iesg").exclude(slug__in=inactive_states))
|
includes = list(State.objects.filter(type="draft-iesg").exclude(slug__in=inactive_states))
|
||||||
|
|
|
@ -312,7 +312,7 @@
|
||||||
"order": 42,
|
"order": 42,
|
||||||
"slug": "watching",
|
"slug": "watching",
|
||||||
"type": "draft-iesg",
|
"type": "draft-iesg",
|
||||||
"used": true
|
"used": false
|
||||||
},
|
},
|
||||||
"model": "doc.state",
|
"model": "doc.state",
|
||||||
"pk": 11
|
"pk": 11
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
</h1>
|
</h1>
|
||||||
{% if state.slug == "dead" %}
|
{% if state.slug == "dead" %}
|
||||||
<p class="alert alert-warning my-3">
|
<p class="alert alert-warning my-3">
|
||||||
This document is in IESG state "Dead". It is unusual to change
|
This document is in IESG state "Dead". It is unusual to change this to
|
||||||
this to anything other than "AD is watching", and this should
|
anything other than "I-D Exists" and this should never be used as a
|
||||||
never be used as a replacement for Begin IESG Processing.
|
replacement for Begin IESG Processing.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="btn btn-info my-3"
|
<a class="btn btn-info my-3"
|
||||||
|
|
Loading…
Reference in a new issue