Port ietfworkflows and wgchairs to new schema, fix missing state

setting in I-D submission, add tests
 - Legacy-Id: 3617
This commit is contained in:
Ole Laursen 2011-11-14 19:40:20 +00:00
parent e01ba98af1
commit ea7f45d56e
21 changed files with 934 additions and 121 deletions

View file

@ -27,7 +27,7 @@ def is_wgchairREDESIGN(person):
def is_wgdelegate(person):
return bool(person.wgdelegate_set.all())
def is_delegateREDESIGN(person):
def is_wgdelegateREDESIGN(person):
return bool(Role.objects.filter(name="delegate", group__type="wg", group__state="active", person=person))
@ -51,14 +51,14 @@ def is_chair_of_draftREDESIGN(user, draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.wgchairs.accounts import is_secretariat, get_person_for_user
is_delegate = is_delegateREDESIGN
is_wgdelegate = is_wgdelegateREDESIGN
is_wgchair = is_wgchairREDESIGN
is_chair_of_draft = is_chair_of_draftREDESIGN
def can_edit_state(user, draft):
streamed = get_streamed_draft(draft)
if not streamed or not streamed.stream:
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
@ -71,3 +71,13 @@ def can_edit_state(user, draft):
def can_edit_stream(user, draft):
return is_secretariat(user)
def can_adopt(user, draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES and draft.stream_id == "ise":
person = get_person_for_user(user)
if not person:
return False
return is_wgchair(person) or is_wgdelegate(person)
else:
return is_secretariat(user)

View file

@ -1,12 +1,12 @@
import datetime
from django.conf import settings
from django import forms
from django.template.loader import render_to_string
from workflows.models import State
from workflows.utils import set_workflow_for_object
from ietf.idtracker.models import PersonOrOrgInfo, IETFWG
from ietf.idtracker.models import PersonOrOrgInfo, IETFWG, InternetDraft
from ietf.wgchairs.accounts import get_person_for_user
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.utils import (get_workflow_for_draft, get_workflow_for_wg,
@ -18,7 +18,10 @@ from ietf.ietfworkflows.accounts import is_secretariat
from ietf.ietfworkflows.streams import (get_stream_from_draft, get_streamed_draft,
get_stream_by_name, set_stream_for_draft)
from ietf.ietfworkflows.constants import CALL_FOR_ADOPTION, IETF_STREAM
from redesign.doc.utils import get_tags_for_stream_id
from redesign.doc.models import save_document_in_history, DocEvent, Document
from redesign.name.models import DocTagName, DocStreamName
from redesign.group.models import Group, GroupStateTransitions
class StreamDraftForm(forms.Form):
@ -55,45 +58,91 @@ class NoWorkflowStateForm(StreamDraftForm):
def __init__(self, *args, **kwargs):
super(NoWorkflowStateForm, self).__init__(*args, **kwargs)
self.wgs = None
self.onlywg = None
if is_secretariat(self.user):
wgs = IETFWG.objects.all()
wgs = IETFWG.objects.all().order_by('group_acronym__acronym')
else:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
wgs = IETFWG.objects.filter(type="wg", state="active", role__name__in=("chair", "delegate"), role__person__user=self.user).order_by('acronym').distinct()
else:
wgs = set([i.group_acronym for i in self.person.wgchair_set.all()]).union(set([i.wg for i in self.person.wgdelegate_set.all()]))
wgs = list(wgs)
wgs.sort(lambda x,y: cmp(x.group_acronym.acronym, y.group_acronym.acronym))
self.wgs = wgs
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
self.fields['wg'].choices = [(i.pk, '%s - %s' % (i.acronym, i.name)) for i in self.wgs]
else:
wgs = set([i.group_acronym for i in self.person.wgchair_set.all()]).union(set([i.wg for i in self.person.wgdelegate_set.all()]))
if len(wgs) > 1:
self.wgs = list(wgs)
self.wgs.sort(lambda x,y: cmp(x.group_acronym.acronym, y.group_acronym.acronym))
self.fields['wg'].choices = [(i.pk, '%s - %s' % (i.group_acronym.acronym, i.group_acronym.name)) for i in self.wgs]
else:
self.onlywg = list(wgs)[0].group_acronym
def save(self):
comment = self.cleaned_data.get('comment')
comment = self.cleaned_data.get('comment').strip()
weeks = self.cleaned_data.get('weeks')
if self.onlywg:
wg = self.onlywg
else:
wg = IETFWG.objects.get(pk=self.cleaned_data.get('wg'))
wg = IETFWG.objects.get(pk=self.cleaned_data.get('wg'))
estimated_date = None
if weeks:
now = datetime.date.today()
estimated_date = now + datetime.timedelta(weeks=weeks)
workflow = get_workflow_for_wg(wg)
set_workflow_for_object(self.draft, workflow)
stream = get_stream_by_name(IETF_STREAM)
streamed = get_streamed_draft(self.draft)
if not streamed:
set_stream_for_draft(self.draft, stream)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# do changes on real Document object instead of proxy to avoid trouble
doc = Document.objects.get(pk=self.draft.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
new_stream = DocStreamName.objects.get(slug="ietf")
if doc.stream != new_stream:
e = DocEvent(type="changed_stream")
e.time = doc.time
e.by = self.user.get_profile()
e.doc = doc
e.desc = u"Stream changed to <b>%s</b> from %s" % (new_stream.name, doc.stream.name)
e.save()
doc.stream = new_stream
if doc.group.pk != wg.pk:
e = DocEvent(type="changed_group")
e.time = doc.time
e.by = self.user.get_profile()
e.doc = doc
e.desc = u"Changed group to <b>%s (%s)</b>" % (wg.name, wg.acronym.upper())
if doc.group.type_id != "individ":
e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym)
e.save()
doc.group_id = wg.pk
doc.save()
self.draft = InternetDraft.objects.get(pk=doc.pk) # make sure proxy object is updated
else:
workflow = get_workflow_for_wg(wg)
set_workflow_for_object(self.draft, workflow)
stream = get_stream_by_name(IETF_STREAM)
streamed = get_streamed_draft(self.draft)
streamed.stream = stream
streamed.group = wg
streamed.save()
update_state(obj=self.draft,
if not streamed:
set_stream_for_draft(self.draft, stream)
streamed = get_streamed_draft(self.draft)
streamed.stream = stream
streamed.group = wg
streamed.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
to_state = State.objects.get(slug="c-adopt", type="draft-stream-%s" % self.draft.stream_id)
else:
to_state = get_state_by_name(CALL_FOR_ADOPTION)
update_state(self.draft,
comment=comment,
person=self.person,
to_state=get_state_by_name(CALL_FOR_ADOPTION),
to_state=to_state,
estimated_date=estimated_date)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()
class DraftTagsStateForm(StreamDraftForm):
@ -115,8 +164,20 @@ class DraftTagsStateForm(StreamDraftForm):
if new_state:
self.data = self.data.copy()
self.data.update({'new_state': new_state.id})
self.available_tags = self.workflow.get_tags()
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
if key.startswith('new_state_'): # hack to get value from submit buttons
self.data = self.data.copy()
self.data['new_state'] = key.replace('new_state_', '')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
possible_tags = get_tags_for_stream_id(self.draft.stream_id)
if self.draft.stream_id == "ietf" and self.draft.group:
unused_tags = self.draft.group.unused_tags.values_list("slug", flat=True)
possible_tags = [t for t in possible_tags if t not in unused_tags]
self.available_tags = DocTagName.objects.filter(slug__in=possible_tags)
self.tags = self.draft.tags.filter(slug__in=possible_tags)
else:
self.available_tags = self.workflow.get_tags()
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
self.fields['tags'].initial = [i.pk for i in self.tags]
@ -128,9 +189,46 @@ class DraftTagsStateForm(StreamDraftForm):
return None
def get_transitions(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return []
return self.state.transitions.filter(workflow=self.workflow)
def get_next_states(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
state_type = "draft-stream-%s" % self.draft.stream_id
s = self.draft.get_state(state_type)
next_states = []
if s:
next_states = s.next_states.all()
if self.draft.stream_id == "ietf" and self.draft.group:
transitions = self.draft.group.groupstatetransitions_set.filter(state=s)
if transitions:
next_states = transitions[0].next_states.all()
else:
# return the initial state
states = State.objects.filter(type=state_type).order_by('order')
if states:
next_states = states[:1]
unused = []
if self.draft.group:
unused = self.draft.group.unused_states.values_list("pk", flat=True)
return [n for n in next_states if n.pk not in unused]
return []
def get_states(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
states = State.objects.filter(type="draft-stream-%s" % self.draft.stream_id)
if self.draft.stream_id == "ietf" and self.draft.group:
unused_states = self.draft.group.unused_states.values_list("pk", flat=True)
states = [s for s in states if s.pk not in unused_states]
return [(i.pk, i.name) for i in states]
return [(i.pk, i.name) for i in self.workflow.get_states()]
def save_tags(self):
@ -145,7 +243,10 @@ class DraftTagsStateForm(StreamDraftForm):
try:
shepherd = self.draft.shepherd
if shepherd:
extra_notify = ['%s <%s>' % shepherd.email()]
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
extra_notify = [shepherd.formatted_email()]
else:
extra_notify = ['%s <%s>' % shepherd.email()]
except PersonOrOrgInfo.DoesNotExist:
pass
if not set_tags and not reset_tags:
@ -159,13 +260,16 @@ class DraftTagsStateForm(StreamDraftForm):
def save_state(self):
comment = self.cleaned_data.get('comment')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
state = State.objects.get(pk=self.cleaned_data.get('new_state'))
weeks = self.cleaned_data.get('weeks')
estimated_date = None
if weeks:
now = datetime.date.today()
estimated_date = now + datetime.timedelta(weeks=weeks)
update_state(obj=self.draft,
update_state(self.draft,
comment=comment,
person=self.person,
to_state=state,
@ -173,15 +277,27 @@ class DraftTagsStateForm(StreamDraftForm):
def save(self):
self.save_tags()
if 'only_tags' in self.data.keys():
return
self.save_state()
if 'only_tags' not in self.data.keys():
self.save_state()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
comment = self.cleaned_data.get('comment').strip()
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()
class DraftStreamForm(StreamDraftForm):
comment = forms.CharField(widget=forms.Textarea)
stream = forms.ModelChoiceField(Stream.objects.all())
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
stream = forms.ModelChoiceField(DocStreamName.objects.exclude(slug="legacy"))
else:
stream = forms.ModelChoiceField(Stream.objects.all())
template = 'ietfworkflows/stream_form.html'
@ -193,10 +309,18 @@ class DraftStreamForm(StreamDraftForm):
self.fields['stream'].initial = self.stream.pk
def save(self):
comment = self.cleaned_data.get('comment')
comment = self.cleaned_data.get('comment').strip()
to_stream = self.cleaned_data.get('stream')
update_stream(self.draft,
comment=comment,
person=self.person,
to_stream=to_stream)
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()

View file

@ -1,10 +1,20 @@
from django.db import models
from django.conf import settings
from ietf.idtracker.models import InternetDraft
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.models import StreamedID, Stream
def get_streamed_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
class Dummy: pass
o = Dummy()
o.draft = draft
o.stream = super(InternetDraft, draft).stream
o.group = draft.group
return o
if not draft:
return None
try:
@ -14,6 +24,11 @@ def get_streamed_draft(draft):
def get_stream_from_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
s = super(InternetDraft, draft).stream
s.with_groups = s.slug in ["ietf", "irtf"]
return s
streamedid = get_streamed_draft(draft)
if streamedid:
return streamedid.stream
@ -21,6 +36,9 @@ def get_stream_from_draft(draft):
def get_stream_by_name(stream_name):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
try:
return Stream.objects.get(name=stream_name)
except Stream.DoesNotExist:
@ -28,6 +46,9 @@ def get_stream_by_name(stream_name):
def get_stream_from_id(stream_id):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
try:
return Stream.objects.get(id=stream_id)
except Stream.DoesNotExist:
@ -35,6 +56,9 @@ def get_stream_from_id(stream_id):
def get_chair_model(stream):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
model_str = stream.group_chair_model
if not model_str:
return None
@ -49,6 +73,9 @@ def get_chair_model(stream):
def _get_group_from_acronym(group_model_str, acronym):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
try:
app, model = group_model_str.split('.', 1)
except ValueError:
@ -71,6 +98,9 @@ def _get_group_from_acronym(group_model_str, acronym):
def _set_stream_automatically(draft, stream):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
streamed = StreamedID.objects.create(stream=stream, draft=draft)
if not stream or not stream.with_groups:
return
@ -94,6 +124,9 @@ def get_stream_from_wrapper(idrfc_wrapper):
if not idwrapper:
return None
draft = idwrapper._draft
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return super(InternetDraft, draft).stream
stream = get_stream_from_draft(draft)
if stream == False:
stream_id = idwrapper.stream_id()
@ -106,6 +139,9 @@ def get_stream_from_wrapper(idrfc_wrapper):
def set_stream_for_draft(draft, stream):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
(streamed, created) = StreamedID.objects.get_or_create(draft=draft)
if streamed.stream != stream:
streamed.stream = stream

View file

@ -1,5 +1,6 @@
from django import template
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.utils import (get_workflow_for_draft,
@ -7,16 +8,13 @@ from ietf.ietfworkflows.utils import (get_workflow_for_draft,
from ietf.wgchairs.accounts import (can_manage_shepherd_of_a_document,
can_manage_writeup_of_a_document)
from ietf.ietfworkflows.streams import get_stream_from_wrapper
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream)
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream, can_adopt)
register = template.Library()
@register.inclusion_tag('ietfworkflows/stream_state.html', takes_context=True)
def stream_state(context, doc):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return settings.TEMPLATE_STRING_IF_INVALID # FIXME: temporary work-around
request = context.get('request', None)
data = {}
stream = get_stream_from_wrapper(doc)
@ -55,9 +53,6 @@ def workflow_history_entry(context, entry):
@register.inclusion_tag('ietfworkflows/edit_actions.html', takes_context=True)
def edit_actions(context, wrapper):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return settings.TEMPLATE_STRING_IF_INVALID # FIXME: temporary work-around
request = context.get('request', None)
user = request and request.user
if not user:
@ -70,11 +65,12 @@ def edit_actions(context, wrapper):
if not idwrapper:
return None
draft = idwrapper._draft
return {
'can_edit_state': can_edit_state(user, draft),
'can_edit_stream': can_edit_stream(user, draft),
'can_writeup': can_manage_writeup_of_a_document(user, draft),
'can_shepherd': can_manage_shepherd_of_a_document(user, draft),
'draft': draft,
'doc': wrapper,
}
doc = wrapper
possible_actions = [
("Adopt in WG", can_adopt(user, draft), urlreverse('edit_adopt', kwargs=dict(name=doc.draft_name))) if settings.USE_DB_REDESIGN_PROXY_CLASSES else ("", False, ""),
("Change stream state", can_edit_state(user, draft), urlreverse('edit_state', kwargs=dict(name=doc.draft_name))),
("Change stream", can_edit_stream(user, draft), urlreverse('edit_stream', kwargs=dict(name=doc.draft_name))),
("Change shepherd", can_manage_shepherd_of_a_document(user, draft), urlreverse('doc_managing_shepherd', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))),
("Change stream writeup", can_manage_writeup_of_a_document(user, draft), urlreverse('doc_managing_writeup', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))),
]
return dict(actions=[(url, action_name) for action_name, active, url, in possible_actions if active])

173
ietf/ietfworkflows/tests.py Normal file
View file

@ -0,0 +1,173 @@
import datetime, os, shutil
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse as urlreverse
import django.test
from StringIO import StringIO
from pyquery import PyQuery
from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.test_runner import mail_outbox
from ietf.utils.test_data import make_test_data
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.person.models import Person, Email
from redesign.group.models import Group, Role
from redesign.doc.models import Document, State
from redesign.doc.utils import *
from redesign.name.models import DocTagName
class EditStreamInfoTestCase(django.test.TestCase):
fixtures = ['names']
def test_adopt_document(self):
draft = make_test_data()
draft.stream_id = "ise"
draft.group = Group.objects.get(type="individ")
draft.unset_state(State.objects.get(type="draft-stream-ietf", slug="wg-doc"))
draft.save()
url = urlreverse('edit_adopt', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[type=submit][value*=adopt]')), 1)
self.assertEquals(len(q('form select[name="wg"] option')), 1) # we can only select "mars"
# adopt in mars WG
mailbox_before = len(mail_outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
wg=Group.objects.get(acronym="mars").pk,
weeks="10"))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.group.acronym, "mars")
self.assertEquals(draft.stream_id, "ietf")
self.assertEquals(draft.docevent_set.count() - events_before, 4)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("state changed" in mail_outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(mail_outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(mail_outbox[-1]))
def test_set_tags(self):
draft = make_test_data()
draft.tags = DocTagName.objects.filter(slug="w-expert")
draft.group.unused_tags.add("w-refdoc")
url = urlreverse('edit_state', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
# make sure the unused tags are hidden
unused = draft.group.unused_tags.values_list("slug", flat=True)
for t in q("input[name=tags]"):
self.assertTrue(t.attrib["value"] not in unused)
self.assertEquals(len(q('form input[type=submit][name=only_tags]')), 1)
# set tags
mailbox_before = len(mail_outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
weeks="10",
tags=["need-aut", "sheph-u"],
only_tags="1",
# unused but needed for validation
new_state=draft.get_state("draft-stream-%s" % draft.stream_id).pk,
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.tags.count(), 2)
self.assertEquals(draft.tags.filter(slug="w-expert").count(), 0)
self.assertEquals(draft.tags.filter(slug="need-aut").count(), 1)
self.assertEquals(draft.tags.filter(slug="sheph-u").count(), 1)
self.assertEquals(draft.docevent_set.count() - events_before, 2)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("tags changed" in mail_outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(mail_outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(mail_outbox[-1]))
self.assertTrue("plain@example.com" in unicode(mail_outbox[-1]))
def test_set_state(self):
draft = make_test_data()
url = urlreverse('edit_state', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
# make sure the unused states are hidden
unused = draft.group.unused_states.values_list("pk", flat=True)
for t in q("select[name=new_state]").find("option[name=tags]"):
self.assertTrue(t.attrib["value"] not in unused)
self.assertEquals(len(q('select[name=new_state]')), 1)
# set state
new_state = State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="parked")
mailbox_before = len(mail_outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
weeks="10",
tags=[x.pk for x in draft.tags.filter(slug__in=get_tags_for_stream_id(draft.stream_id))],
new_state=new_state.pk,
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.get_state("draft-stream-%s" % draft.stream_id), new_state)
self.assertEquals(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEquals(len(reminder), 1)
due = datetime.datetime.now() + datetime.timedelta(weeks=10)
self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("state changed" in mail_outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(mail_outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(mail_outbox[-1]))
def test_set_stream(self):
draft = make_test_data()
url = urlreverse('edit_stream', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('select[name=stream]')), 1)
# set state
mailbox_before = len(mail_outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
stream="irtf",
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.stream_id, "irtf")
self.assertEquals(draft.docevent_set.count() - events_before, 2)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("stream changed" in mail_outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(mail_outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(mail_outbox[-1]))
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
# the above tests only work with the new schema
del EditStreamInfoTestCase

View file

@ -4,6 +4,7 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('ietf.ietfworkflows.views',
url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
url(r'^(?P<name>[^/]+)/edit/adopt/$', 'edit_adopt', name='edit_adopt'),
url(r'^(?P<name>[^/]+)/edit/state/$', 'edit_state', name='edit_state'),
url(r'^(?P<name>[^/]+)/edit/stream/$', 'edit_stream', name='edit_stream'),
)

View file

@ -5,6 +5,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.template.defaultfilters import pluralize
from workflows.models import State, StateObjectRelation
from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
@ -16,7 +17,10 @@ from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
AnnotationTag, ObjectAnnotationTagHistoryEntry,
ObjectHistoryEntry, StateObjectRelationMetadata,
ObjectWorkflowHistoryEntry, ObjectStreamHistoryEntry)
from ietf.idtracker.models import InternetDraft
from ietf.utils.mail import send_mail
from redesign.doc.models import Document, DocEvent, save_document_in_history, DocReminder, DocReminderTypeName
from redesign.group.models import Role
WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
FOLLOWUP_TAG = 'Doc Shepherd Follow-up Underway'
@ -77,6 +81,9 @@ def get_workflow_for_wg(wg, default=None):
def get_workflow_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return True if get_streamed_draft(draft) else None
workflow = get_workflow_for_object(draft)
try:
workflow = workflow and workflow.wgworkflow
@ -99,6 +106,10 @@ def get_workflow_for_draft(draft):
def get_workflow_history_for_draft(draft, entry_type=None):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import ObjectHistoryEntryProxy
return ObjectHistoryEntryProxy.objects.filter(doc=draft).order_by('-time', '-id').select_related('by')
ctype = ContentType.objects.get_for_model(draft)
filter_param = {'content_type': ctype,
'content_id': draft.pk}
@ -111,12 +122,18 @@ def get_workflow_history_for_draft(draft, entry_type=None):
def get_annotation_tags_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.name.proxy import AnnotationTagObjectRelationProxy
return AnnotationTagObjectRelationProxy.objects.filter(document=draft.pk)
ctype = ContentType.objects.get_for_model(draft)
tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk)
return tags
def get_state_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return draft.get_state("draft-stream-%s" % draft.stream_id)
return get_state(draft)
@ -221,8 +238,55 @@ def notify_state_entry(entry, extra_notify=[]):
def notify_stream_entry(entry, extra_notify=[]):
return notify_entry(entry, 'ietfworkflows/stream_updated_mail.txt', extra_notify)
def get_notification_receivers(doc, extra_notify):
persons = set()
res = []
for r in Role.objects.filter(group=doc.group, name__in=("chair", "delegate")):
res.append(u'"%s" <%s>' % (r.person.name, r.email.address))
persons.add(r.person)
for email in doc.authors.all():
if email.person not in persons:
res.append(email.formatted_email())
persons.add(email.person)
for x in extra_notify:
if not x in res:
res.append(x)
return res
def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=obj.pk)
save_document_in_history(doc)
obj.tags.remove(*reset_tags)
obj.tags.add(*set_tags)
doc.time = datetime.datetime.now()
e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc)
l = []
if set_tags:
l.append(u"Annotation tag%s %s set." % (pluralize(set_tags), ", ".join(x.name for x in set_tags)))
if reset_tags:
l.append(u"Annotation tag%s %s cleared." % (pluralize(reset_tags), ", ".join(x.name for x in reset_tags)))
e.desc = " ".join(l)
e.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"Annotations tags changed for draft %s" % doc.name,
'ietfworkflows/annotation_tags_updated_mail.txt',
dict(doc=doc,
entry=dict(setted=", ".join(x.name for x in set_tags),
unsetted=", ".join(x.name for x in reset_tags),
change_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(obj)
setted = []
resetted = []
@ -251,15 +315,53 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
notify_tag_entry(entry, extra_notify)
def update_state(obj, comment, person, to_state, estimated_date=None, extra_notify=[]):
ctype = ContentType.objects.get_for_model(obj)
from_state = get_state_for_draft(obj)
to_state = set_state_for_draft(obj, to_state, estimated_date)
def update_state(doc, comment, person, to_state, estimated_date=None, extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=doc.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
from_state = doc.get_state("draft-stream-%s" % doc.stream_id)
doc.set_state(to_state)
e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc)
e.desc = u"%s changed to <b>%s</b> from %s" % (to_state.type.label, to_state, from_state)
e.save()
if estimated_date:
t = DocReminderTypeName.objects.get(slug="stream-s")
try:
reminder = DocReminder.objects.get(event__doc=doc, type=t,
active=True)
except DocReminder.DoesNotExist:
reminder = DocReminder(type=t)
reminder.event = e
reminder.due = estimated_date
reminder.active = True
reminder.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"State changed for draft %s" % doc.name,
'ietfworkflows/state_updated_mail.txt',
dict(doc=doc,
entry=dict(from_state=from_state,
to_state=to_state,
transition_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(doc)
from_state = get_state_for_draft(doc)
to_state = set_state_for_draft(doc, to_state, estimated_date)
if not to_state:
return False
entry = ObjectWorkflowHistoryEntry.objects.create(
content_type=ctype,
content_id=obj.pk,
content_id=doc.pk,
from_state=from_state and from_state.name or '',
to_state=to_state and to_state.name or '',
date=datetime.datetime.now(),
@ -268,13 +370,38 @@ def update_state(obj, comment, person, to_state, estimated_date=None, extra_noti
notify_state_entry(entry, extra_notify)
def update_stream(obj, comment, person, to_stream, extra_notify=[]):
ctype = ContentType.objects.get_for_model(obj)
from_stream = get_stream_from_draft(obj)
to_stream = set_stream_for_draft(obj, to_stream)
def update_stream(doc, comment, person, to_stream, extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=doc.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
from_stream = doc.stream
doc.stream = to_stream
doc.save()
e = DocEvent(type="changed_stream", time=doc.time, by=person, doc=doc)
e.desc = u"Stream changed to <b>%s</b> from %s" % (to_stream.name, from_stream.name)
e.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"Stream changed for draft %s" % doc.name,
'ietfworkflows/stream_updated_mail.txt',
dict(doc=doc,
entry=dict(from_stream=from_stream,
to_stream=to_stream,
transition_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(doc)
from_stream = get_stream_from_draft(doc)
to_stream = set_stream_for_draft(doc, to_stream)
entry = ObjectStreamHistoryEntry.objects.create(
content_type=ctype,
content_id=obj.pk,
content_id=doc.pk,
from_stream=from_stream and from_stream.name or '',
to_stream=to_stream and to_stream.name or '',
date=datetime.datetime.now(),
@ -284,16 +411,6 @@ def update_stream(obj, comment, person, to_stream, extra_notify=[]):
def get_full_info_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return dict(# FIXME: temporary work-around
streamed=settings.TEMPLATE_STRING_IF_INVALID,
stream=settings.TEMPLATE_STRING_IF_INVALID,
workflow=settings.TEMPLATE_STRING_IF_INVALID,
tags=[settings.TEMPLATE_STRING_IF_INVALID],
state=settings.TEMPLATE_STRING_IF_INVALID,
shepherd=draft.shepherd,
)
return dict(
streamed=get_streamed_draft(draft),
stream=get_stream_from_draft(draft),

View file

@ -1,6 +1,7 @@
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.conf import settings
from ietf.idtracker.models import InternetDraft
from ietf.ietfworkflows.forms import (DraftTagsStateForm,
@ -12,7 +13,9 @@ from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
get_workflow_for_draft,
get_annotation_tags_for_draft,
get_state_for_draft)
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream)
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream, can_adopt)
from redesign.doc.utils import get_tags_for_stream_id
from redesign.name.models import DocTagName
REDUCED_HISTORY_LEN = 20
@ -24,16 +27,23 @@ def stream_history(request, name):
stream = get_stream_from_draft(draft)
workflow = get_workflow_for_draft(draft)
tags = []
if workflow:
tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
for tag in workflow.get_tags():
tag.setted = tag.pk in tags_setted
tags.append(tag)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
used = list(draft.tags.all())
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id(draft.stream_id))
for t in tags:
t.setted = t.slug in used
else:
if workflow:
tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
for tag in workflow.get_tags():
tag.setted = tag.pk in tags_setted
tags.append(tag)
state = get_state_for_draft(draft)
history = get_workflow_history_for_draft(draft)
show_more = False
if history.count > REDUCED_HISTORY_LEN:
if len(history) > REDUCED_HISTORY_LEN:
show_more = True
return render_to_response('ietfworkflows/stream_history.html',
{'stream': stream,
'streamed': streamed,
@ -74,15 +84,26 @@ def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
},
context_instance=RequestContext(request))
# these three views are reusing the same view really, which apart from
# being somewhat obscure means that there are subtle bugs (like the
# title being wrong) - would probably be better to switch to a model
# where each part is edited on its own, we come from an overview page
# anyway, so there's not a big win in putting in a common
# overview/edit page here
def edit_adopt(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_adopt(request.user, draft):
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, NoWorkflowStateForm)
def edit_state(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_edit_state(request.user, draft):
return HttpResponseForbidden('You have no permission to access this view')
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, DraftTagsStateForm)
def edit_stream(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_edit_stream(request.user, draft):
return HttpResponseForbidden('You have no permission to access this view')
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, DraftStreamForm)

View file

@ -134,7 +134,9 @@ class SubmitTestCase(django.test.TestCase):
self.assertEquals(new_revision.by.name, "Test Name")
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.authors.count(), 1)
self.assertEquals(draft.authors.all()[0].get_name(), "Test Name")
self.assertEquals(draft.authors.all()[0].address, "testname@example.com")
@ -201,7 +203,9 @@ class SubmitTestCase(django.test.TestCase):
self.assertEquals(new_revision.by.name, "Test Name")
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.authors.count(), 1)
self.assertEquals(draft.authors.all()[0].get_name(), "Test Name")
self.assertEquals(draft.authors.all()[0].address, "testname@example.com")

View file

@ -96,11 +96,12 @@ def perform_postREDESIGN(submission):
try:
draft = Document.objects.get(name=submission.filename)
save_document_in_history(draft)
draft.tags.remove(DocInfoTagName.objects.get(slug="exp-tomb"))
draft.tags.remove(DocTagName.objects.get(slug="exp-tomb"))
except Document.DoesNotExist:
draft = Document(name=submission.filename)
draft.intended_std_level = None
draft.type_id = "draft"
draft.time = datetime.datetime.now()
draft.title = submission.id_document_name
draft.group_id = group_id
@ -120,6 +121,9 @@ def perform_postREDESIGN(submission):
stream_slug = "ietf"
draft.stream = DocStreamName.objects.get(slug=stream_slug)
if draft.stream_id == "ietf":
# automatically set state "WG Document"
draft.set_state(State.objects.get(type="draft_stream_%s" % draft.stream_id, slug="wg-doc"))
draft.save()
DocAlias.objects.get_or_create(name=submission.filename, document=draft)

View file

@ -1,7 +1,6 @@
<div style="margin-bottom: 1em;">
{% if can_edit_state %} <a href="{% url edit_state doc.draft_name %}">Change WG state</a> {% endif %}
{% if can_edit_stream %}{% if can_edit_state %} | {% endif %}<a href="{% url edit_stream doc.draft_name %}">Change draft stream</a>{% endif %}
{% if can_shepherd %}{% if can_edit_state or can_edit_stream %} | {% endif %}<a href="{% url doc_managing_shepherd draft.group.acronym draft.filename %}">Change draft shepherd</a>{% endif %}
{% if can_writeup %}{% if can_edit_state or can_edit_stream or can_writeup %} | {% endif %}<a href="{% url doc_managing_writeup draft.group.acronym draft.filename %}">Change draft writeup</a>{% endif %}
{% for url, action_name in actions %}
{% if not forloop.first %} | {% endif%} <a href="{{ url}}">{{ action_name }}</a>
{% endfor %}
</div>

View file

@ -5,13 +5,13 @@
<th>2. Change annotation tags if needed</th>
</tr>
<tr style="vertical-align: top;"><td style="width: 50%;">
<div class="field{% if form.errors.comment %} error{% endif %}">
{{ form.errors.comment }}
<div class="field{% if form.comment.errors %} error{% endif %}">
{{ form.comment.errors }}
Comment: <span class="required">*</span><br />
<textarea name="comment">{{ form.data.comment }}</textarea>
</div>
<div class="field{% if form.errors.weeks %} error{% endif %}">
{{ form.errors.weeks }}
<div class="field{% if form.weeks.errors %} error{% endif %}">
{{ form.weeks.errors }}
Estimated time in next status:<br />
<input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
</div>
@ -46,14 +46,27 @@
{% endif %}
</ul>
{% endwith %}
<div class="free-change field{% if form.errors.new_state %} error{% endif %}">
{{ form.errors.new_state }}
{% with form.get_next_states as next_states %}
{% if next_states %}
<ul>
Jump to the next state:
{% for state in next_states %}
<input type="submit" name="new_state_{{ state.pk }}" value="{{ state.name }}" />
{% endfor %}
{% endif %}
</ul>
{% endwith %}
<div class="free-change field{% if form.new_state.errors %} error{% endif %}">
{{ form.new_state.errors }}
Change to another state:
<select name="new_state">
{% for value, name in form.get_states %}
<option value="{{ value }}">{{ name }}</option>
{% endfor %}
</select>
<input type="submit" name="change" value="State change" />
<input type="submit" name="change" value="Change state" />
</div>
</td></tr>
</table>

View file

@ -4,6 +4,7 @@ from ietf.iesg.models import TelechatDates, WGAction
from ietf.ipr.models import IprDetail, IprDocAlias
from ietf.meeting.models import Meeting
from redesign.doc.models import *
from redesign.doc.utils import *
from redesign.name.models import *
from redesign.group.models import *
from redesign.person.models import *
@ -29,6 +30,12 @@ def make_test_data():
type_id="wg",
parent=area,
)
individ = Group.objects.create(
name="Individual submissions",
acronym="none",
state_id="active",
type_id="individ",
parent=None)
# persons
@ -49,13 +56,13 @@ def make_test_data():
# plain IETF'er
u = User.objects.create(username="plain")
p = Person.objects.create(
plainman = Person.objects.create(
name="Plain Man",
ascii="Plain Man",
user=u)
email = Email.objects.create(
address="plain@example.com",
person=p)
person=plainman)
# ad
u = User.objects.create(username="ad")
@ -154,11 +161,14 @@ def make_test_data():
rev="01",
pages=2,
intended_std_level_id="ps",
shepherd=plainman,
ad=ad,
notify="aliens@example.mars",
note="",
)
draft.set_state(State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="wg-doc"))
doc_alias = DocAlias.objects.create(
document=draft,
name=draft.name,

View file

@ -217,7 +217,12 @@ class InternetDraft(Document):
@property
def authors(self):
return IDAuthor.objects.filter(document=self)
@property
def protowriteup_set(self):
from ietf.wgchairs.models import ProtoWriteUpProxy
return ProtoWriteUpProxy.objects.filter(doc=self, type="changed_protocol_writeup")
# methods from InternetDraft
def displayname(self):
return self.name
@ -888,3 +893,26 @@ class DraftLikeDocAlias(DocAlias):
class Meta:
proxy = True
class ObjectHistoryEntryProxy(DocEvent):
#date = models.DateTimeField(_('Date'), auto_now_add=True)
@property
def date(self):
return self.time
#comment = models.TextField(_('Comment'))
@property
def comment(self):
return ""
#person = models.ForeignKey(PersonOrOrgInfo)
@property
def person(self):
return self.by
def get_real_instance(self):
return self
def describe_change(self):
return u"<p>%s</p>" % self.desc
class Meta:
proxy = True

View file

@ -17,7 +17,7 @@ class GroupAdmin(admin.ModelAdmin):
list_display = ["acronym", "name", "type", "role_list"]
list_display_links = ["acronym", "name"]
list_filter = ["type"]
search_fields = ["name"]
search_fields = ["acronym", "name"]
ordering = ["name"]
raw_id_fields = ["charter", "parent", "ad"]

View file

@ -19,6 +19,10 @@ class GroupInfo(models.Model):
list_subscribe = models.CharField(max_length=255, blank=True)
list_archive = models.CharField(max_length=255, blank=True)
comments = models.TextField(blank=True)
unused_states = models.ManyToManyField('doc.State', help_text="Document states that have been disabled for the group")
unused_tags = models.ManyToManyField(DocTagName, help_text="Document tags that have been disabled for the group")
def __unicode__(self):
return self.name
@ -62,6 +66,13 @@ class GroupMilestone(models.Model):
class Meta:
ordering = ['expected_due_date']
class GroupStateTransitions(models.Model):
"""Captures that a group has overriden the default available
document state transitions for a certain state."""
group = models.ForeignKey(Group)
state = models.ForeignKey('doc.State', help_text="State for which the next states should be overridden")
next_states = models.ManyToManyField('doc.State', related_name='previous_groupstatetransitions_states')
GROUP_EVENT_CHOICES = [("proposed", "Proposed group"),
("started", "Started group"),
("concluded", "Concluded group"),
@ -101,3 +112,6 @@ class RoleHistory(models.Model):
email = models.ForeignKey(Email, help_text="Email address used by person for this role")
def __unicode__(self):
return u"%s is %s in %s" % (self.email.get_name(), self.name.name, self.group.acronym)
class Meta:
verbose_name_plural = "role histories"

View file

@ -98,6 +98,7 @@ class IETFWG(Group):
objects = TranslatingManager(dict(group_acronym="id",
group_acronym__acronym="acronym",
group_acronym__acronym__in="acronym__in",
group_acronym__acronym__contains="acronym__contains",
email_archive__startswith="list_archive__startswith",
group_type=lambda v: ("type", { 1: "wg" }[int(v)]),
status=lambda v: ("state", { 1: "active" }[int(v)]),

View file

@ -3,6 +3,7 @@
# basic dependencies
set -e
python import-persons.py
python import-states.py
python import-groups.py
python import-roles.py

View file

@ -11,7 +11,10 @@ settings.USE_DB_REDESIGN_PROXY_CLASSES = False
from django.core import management
management.setup_environ(settings)
from django.template.defaultfilters import pluralize
from redesign.doc.models import *
from redesign.doc.utils import get_tags_for_stream_id
from redesign.group.models import *
from redesign.name.models import *
from redesign.importing.utils import old_person_to_person, person_name
@ -19,8 +22,9 @@ from redesign.name.utils import name
from ietf.idtracker.models import InternetDraft, IDInternal, IESGLogin, DocumentComment, PersonOrOrgInfo, Rfc, IESGComment, IESGDiscuss, BallotInfo, Position
from ietf.idrfc.models import RfcIndex, DraftVersions
from ietf.idrfc.mirror_rfc_index import get_std_level_mapping, get_stream_mapping
#from ietf.ietfworkflows.utils import get_state_for_draft
from ietf.ietfworkflows.models import StreamedID, AnnotationTag, ContentType, ObjectHistoryEntry, ObjectWorkflowHistoryEntry, ObjectAnnotationTagHistoryEntry, ObjectStreamHistoryEntry, StateObjectRelationMetadata
import workflows.utils
from ietf.wgchairs.models import ProtoWriteUp
document_name_to_import = None
if len(sys.argv) > 1:
@ -28,13 +32,14 @@ if len(sys.argv) > 1:
# prevent memory from leaking when settings.DEBUG=True
from django.db import connection
class DummyQueries(object):
class DontSaveQueries(object):
def append(self, x):
pass
connection.queries = DummyQueries()
connection.queries = DontSaveQueries()
# assumptions:
# - states have been imported
# - groups have been imported
# - IESG login emails/roles have been imported
# - IDAuthor emails/persons have been imported
@ -44,7 +49,7 @@ connection.queries = DummyQueries()
# imports InternetDraft, IDInternal, BallotInfo, Position,
# IESGComment, IESGDiscuss, DocumentComment, IDAuthor, idrfc.RfcIndex,
# idrfc.DraftVersions
# idrfc.DraftVersions, StreamedID
def alias_doc(name, doc):
@ -55,6 +60,7 @@ def alias_doc(name, doc):
type_draft = name(DocTypeName, "draft", "Draft")
stream_mapping = get_stream_mapping()
stream_mapping["ISE"] = stream_mapping["INDEPENDENT"]
relationship_replaces = name(DocRelationshipName, "replaces", "Replaces")
relationship_updates = name(DocRelationshipName, "updates", "Updates")
@ -108,13 +114,14 @@ iesg_state_mapping = {
None: None, # FIXME: consider introducing the ID-exists state
}
ballot_position_mapping = {
'No Objection': name(BallotPositionName, 'noobj', 'No Objection'),
'Yes': name(BallotPositionName, 'yes', 'Yes'),
'Abstain': name(BallotPositionName, 'abstain', 'Abstain'),
'Discuss': name(BallotPositionName, 'discuss', 'Discuss'),
'Recuse': name(BallotPositionName, 'recuse', 'Recuse'),
'No Record': name(BallotPositionName, 'norecord', 'No record'),
'No Record': name(BallotPositionName, 'norecord', 'No Record'),
}
ballot_position_mapping["no"] = ballot_position_mapping['No Objection']
ballot_position_mapping["yes"] = ballot_position_mapping['Yes']
@ -124,26 +131,49 @@ ballot_position_mapping["recuse"] = ballot_position_mapping['Recuse']
ballot_position_mapping[None] = ballot_position_mapping["No Record"]
ballot_position_mapping["Undefined"] = ballot_position_mapping["No Record"]
# tags
substate_mapping = {
"External Party": name(DocInfoTagName, 'extpty', "External Party", 'The document is awaiting review or input from an external party (i.e, someone other than the shepherding AD, the authors, or the WG). See the "note" field for more details on who has the action.', 3),
"Revised ID Needed": name(DocInfoTagName, 'need-rev', "Revised ID Needed", 'An updated ID is needed to address the issues that have been raised.', 5),
"AD Followup": name(DocInfoTagName, 'ad-f-up', "AD Followup", """A generic substate indicating that the shepherding AD has the action item to determine appropriate next steps. In particular, the appropriate steps (and the corresponding next state or substate) depend entirely on the nature of the issues that were raised and can only be decided with active involvement of the shepherding AD. Examples include:
"External Party": name(DocTagName, 'extpty', "External Party", 'The document is awaiting review or input from an external party (i.e, someone other than the shepherding AD, the authors, or the WG). See the "note" field for more details on who has the action.', 3),
"Revised ID Needed": name(DocTagName, 'need-rev', "Revised ID Needed", 'An updated ID is needed to address the issues that have been raised.', 5),
"AD Followup": name(DocTagName, 'ad-f-up', "AD Followup", """A generic substate indicating that the shepherding AD has the action item to determine appropriate next steps. In particular, the appropriate steps (and the corresponding next state or substate) depend entirely on the nature of the issues that were raised and can only be decided with active involvement of the shepherding AD. Examples include:
- if another AD raises an issue, the shepherding AD may first iterate with the other AD to get a better understanding of the exact issue. Or, the shepherding AD may attempt to argue that the issue is not serious enough to bring to the attention of the authors/WG.
- if a documented issue is forwarded to a WG, some further iteration may be needed before it can be determined whether a new revision is needed or whether the WG response to an issue clarifies the issue sufficiently.
- when a new revision appears, the shepherding AD will first look at the changes to determine whether they believe all outstanding issues have been raised satisfactorily, prior to asking the ADs who raised the original issues to verify the changes.""", 2),
"Point Raised - writeup needed": name(DocInfoTagName, 'point', "Point Raised - writeup needed", 'IESG discussions on the document have raised some issues that need to be brought to the attention of the authors/WG, but those issues have not been written down yet. (It is common for discussions during a telechat to result in such situations. An AD may raise a possible issue during a telechat and only decide as a result of that discussion whether the issue is worth formally writing up and bringing to the attention of the authors/WG). A document stays in the "Point Raised - Writeup Needed" state until *ALL* IESG comments that have been raised have been documented.', 1)
"Point Raised - writeup needed": name(DocTagName, 'point', "Point Raised - writeup needed", 'IESG discussions on the document have raised some issues that need to be brought to the attention of the authors/WG, but those issues have not been written down yet. (It is common for discussions during a telechat to result in such situations. An AD may raise a possible issue during a telechat and only decide as a result of that discussion whether the issue is worth formally writing up and bringing to the attention of the authors/WG). A document stays in the "Point Raised - Writeup Needed" state until *ALL* IESG comments that have been raised have been documented.', 1)
}
#wg_state_mapping = dict([(s.slug, s) for s in WGDocStateName.objects.all()] + [(None, None)])
tag_review_by_rfc_editor = name(DocTagName, 'rfc-rev', "Review by RFC Editor")
tag_via_rfc_editor = name(DocTagName, 'via-rfc', "Via RFC Editor")
tag_expired_tombstone = name(DocTagName, 'exp-tomb', "Expired tombstone")
tag_approved_in_minute = name(DocTagName, 'app-min', "Approved in minute")
tag_has_errata = name(DocTagName, 'errata', "Has errata")
name(DocTagName, "w-expert", "Awaiting Expert Review/Resolution of Issues Raised", order=1)
name(DocTagName, "w-extern", "Awaiting External Review/Resolution of Issues Raised", order=2)
name(DocTagName, "w-merge", "Awaiting Merge with Other Document", order=3)
name(DocTagName, "need-aut", "Author or Editor Needed", order=4)
name(DocTagName, "w-refdoc", "Waiting for Referenced Document", order=5)
name(DocTagName, "w-refing", "Waiting for Referencing Document", order=6)
name(DocTagName, "rev-wglc", "Revised I-D Needed - Issue raised by WGLC", order=7)
name(DocTagName, "rev-ad", "Revised I-D Needed - Issue raised by AD", order=8)
name(DocTagName, "rev-iesg", "Revised I-D Needed - Issue raised by IESG", order=9)
name(DocTagName, "sheph-u", "Doc Shepherd Follow-up Underway", order=10)
name(DocTagName, "other", "Other - see Comment Log", order=11)
name(DocTagName, "need-ed", "Editor Needed", order=1)
name(DocTagName, "w-part", "Waiting for Partner Feedback", order=2)
name(DocTagName, "w-review", "Awaiting Reviews", order=3)
name(DocTagName, "sh-f-up", "Document Shepherd Followup", order=4)
name(DocTagName, "need-sh", "Shepherd Needed")
name(DocTagName, "w-dep", "Waiting for Dependency on Other Document")
name(DocTagName, "iesg-com", "IESG Review Completed")
stream_state_reminder_type = name(DocReminderTypeName, "stream-s", "Stream state should change")
#tag_mapping = dict((t.name, t) for t in DocTagName.objects.all())
tag_review_by_rfc_editor = name(DocInfoTagName, 'rfc-rev', "Review by RFC Editor")
tag_via_rfc_editor = name(DocInfoTagName, 'via-rfc', "Via RFC Editor")
tag_expired_tombstone = name(DocInfoTagName, 'exp-tomb', "Expired tombstone")
tag_approved_in_minute = name(DocInfoTagName, 'app-min', "Approved in minute")
tag_has_errata = name(DocInfoTagName, 'errata', "Has errata")
# helpers
def save_docevent(doc, event, comment):
@ -770,6 +800,8 @@ for index, o in enumerate(all_drafts.iterator()):
d.title = o.title
d.state = state_mapping[o.status.status]
d.group = Group.objects.get(acronym=o.group.acronym)
# try guess stream to have a default for old submissions
if o.filename.startswith("draft-iab-"):
d.stream = stream_mapping["IAB"]
elif o.filename.startswith("draft-irtf-"):
@ -778,10 +810,25 @@ for index, o in enumerate(all_drafts.iterator()):
d.stream = stream_mapping["INDEPENDENT"]
else:
d.stream = stream_mapping["IETF"]
d.wg_state = None #wg_state_mapping[get_state_for_draft(o)]
try:
d.stream = stream_mapping[StreamedID.objects.get(draft=o).stream.name]
except StreamedID.DoesNotExist:
pass
d.iesg_state = iesg_state_mapping[None]
d.iana_state = None
d.rfc_state = None
s = workflows.utils.get_state(o)
if s:
try:
# there may be a mismatch between the stream type and the
# state because of a bug in the ietfworkflows code so try
# first without type constraint
d.set_state(State.objects.get(name=s.name))
except State.MultipleObjectsReturned:
d.set_state(State.objects.get(type="draft-stream-%s" % d.stream_id, name=s.name))
d.rev = o.revision
d.abstract = o.abstract
d.pages = o.txt_page_count
@ -836,7 +883,79 @@ for index, o in enumerate(all_drafts.iterator()):
e.desc = "New version available"
e.save()
known_revisions.add(v.revision)
# ietfworkflows history entries
ctype = ContentType.objects.get_for_model(o)
for h in ObjectHistoryEntry.objects.filter(content_type=ctype, content_id=o.pk).order_by('date', 'id'):
e = DocEvent(type="changed_document")
e.time = h.date
e.by = old_person_to_person(h.person)
e.doc = d
r = h.get_real_instance()
if r:
if isinstance(r, ObjectWorkflowHistoryEntry):
s = State.objects.filter(type="draft-stream-%s" % d.stream_id, name=r.to_state)
if not s:
s = State.objects.filter(name=r.to_state)
start = "State changed"
if s:
start = "%s changed" % s[0].type.label
e.desc = u"%s to <b>%s</b> from %s" % (start, r.to_state, r.from_state)
elif isinstance(r, ObjectAnnotationTagHistoryEntry):
l = []
if r.setted:
s = r.setted.split(",")
l.append(u"Annotation tag%s %s set." % (pluralize(s), ", ".join(s)))
if r.unsetted:
s = r.unsetted.split(",")
l.append(u"Annotation tag%s %s cleared." % (pluralize(s), ", ".join(s)))
e.desc = " ".join(l)
elif isinstance(r, ObjectStreamHistoryEntry):
e.type = "changed_stream"
e.desc = u"Stream changed to <b>%s</b> from %s" % (r.to_stream, r.from_stream)
else:
raise Exception("Unknown ObjectHistoryEntry type: %s" % type(r))
e.save()
if r and isinstance(r, ObjectWorkflowHistoryEntry):
# may need to add reminder
try:
metadata = StateObjectRelationMetadata.objects.get(relation__state__name=r.to_state,
relation__content_id=o.pk,
relation__content_type=ContentType.objects.get_for_model(o))
if metadata.estimated_date:
try:
reminder = DocReminder.objects.get(event__doc=d, type=stream_state_reminder_type)
except DocReminder.DoesNotExist:
reminder = DocReminder(type=stream_state_reminder_type)
reminder.event = e
reminder.due = metadata.estimated_date
reminder.active = metadata.estimated_date > datetime.datetime.now()
reminder.save()
except StateObjectRelationMetadata.DoesNotExist:
pass
if h.comment and h.comment.strip() and not d.docevent_set.filter(type="added_comment", desc=h.comment.strip(), time=h.date):
e = DocEvent(type="added_comment")
e.time = h.date
e.by = old_person_to_person(h.person)
e.doc = d
e.desc = h.comment.strip()
e.save()
# wgchairs protocol writeups
for w in ProtoWriteUp.objects.filter(draft=o).order_by('date'):
e = WriteupDocEvent(type="changed_protocol_writeup")
e.time = w.date
e.by = old_person_to_person(w.person)
e.doc = d
e.desc = e.get_type_display()
e.text = w.writeup
e.save()
# import events that might be missing, we can't be sure who did
# them or when but if we don't generate them, we'll be missing the
# information completely
@ -871,6 +990,15 @@ for index, o in enumerate(all_drafts.iterator()):
sync_tag(d, o.review_by_rfc_editor, tag_review_by_rfc_editor)
sync_tag(d, o.expired_tombstone, tag_expired_tombstone)
ctype = ContentType.objects.get_for_model(o)
used_tags = AnnotationTag.objects.filter(annotationtagobjectrelation__content_type=ctype, annotationtagobjectrelation__content_id=o.pk).values_list('name', flat=True)
possible_tags = get_tags_for_stream_id(d.stream_id)
for name in possible_tags:
if name == "need-rev" and o.idinternal and o.idinternal.cur_sub_state and o.idinternal.cur_sub_state.sub_state == "Revised ID Needed":
continue # don't overwrite tag from IESG substate
sync_tag(d, name in used_tags, name)
# replacements
if o.replaced_by:
replacement, _ = Document.objects.get_or_create(name=o.replaced_by.filename, defaults=dict(time=datetime.datetime(1970, 1, 1, 0, 0, 0)))

View file

@ -14,16 +14,19 @@ management.setup_environ(settings)
from redesign.group.models import *
from redesign.name.models import *
from redesign.doc.models import State, StateType
from redesign.doc.utils import get_tags_for_stream_id
from redesign.name.utils import name
from redesign.importing.utils import old_person_to_person
from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role, AreaDirector
from ietf.liaisons.models import SDOs
import workflows.utils
# imports IETFWG, Area, AreaGroup, Acronym, IRTF, AreaWGURL, SDOs
# also creates nomcom groups
# assumptions: persons have been imported
# assumptions: persons and states have been imported
state_names = dict(
bof=name(GroupStateName, slug="bof", name="BOF"),
@ -91,7 +94,6 @@ iab_group.save()
system = Person.objects.get(name="(System)")
# NomCom
for o in ChairsHistory.objects.filter(chair_type=Role.NOMCOM_CHAIR).order_by("start_year"):
print "importing ChairsHistory/Nomcom", o.pk, "nomcom%s" % o.start_year
@ -300,7 +302,28 @@ for o in IETFWG.objects.all().order_by("pk"):
milestone.done_date = m.done_date
milestone.time = datetime.datetime.combine(m.last_modified_date, datetime.time(12, 0, 0))
milestone.save()
# import workflow states and transitions
w = workflows.utils.get_workflow_for_object(o)
if w:
try:
w = w.wgworkflow
except WGWorkflow.DoesNotExist:
w = None
if w:
w.unused_states = State.objects.filter(type="draft-stream-ietf").exclude(name__in=[x.name for x in w.selected_states.all()])
w.unused_tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id("draft-stream-ietf")).exclude(name__in=[x.name for x in w.selected_tags.all()])
# custom transitions
states = dict((s.name, s) for s in State.objects.filter(type="draft-stream-ietf"))
old_states = dict((s.name, s) for s in w.states.filter(name__in=[name for name in states]).select_related('transitions'))
for name in old_states:
s = states[name]
o = old_states[name]
n = [states[t.destination.name] for t in o.transitions.filter(workflow=workflow)]
if set(s.next_states) != set(n):
g, _ = GroupStateTransitions.objects.get_or_create(group=group, state=s)
g.next_states = n
# import events
group.groupevent_set.all().delete()

View file

@ -0,0 +1,110 @@
#!/usr/bin/python
import sys, os, datetime
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
from ietf import settings
settings.USE_DB_REDESIGN_PROXY_CLASSES = False
from django.core import management
management.setup_environ(settings)
import workflows.models
from ietf.ietfworkflows.models import StateDescription
from redesign.doc.models import *
# import states for documents from workflows.Workflow and
# ietfworkflows.StateDescription
# state types
ietf_state_type, _ = StateType.objects.get_or_create(slug="draft-stream-ietf", label="WG state")
irtf_state_type, _ = StateType.objects.get_or_create(slug="draft-stream-irtf", label="RG state")
ise_state_type, _ = StateType.objects.get_or_create(slug="draft-stream-ise", label="ISE state")
iab_state_type, _ = StateType.objects.get_or_create(slug="draft-stream-iab", label="IAB state")
# WG states, we can get them from the state descriptions
wg_doc_state_slug = {
"Call For Adoption By WG Issued": 'c-adopt',
"Adopted by a WG": 'adopt-wg',
"Adopted for WG Info Only": 'info',
"WG Document": 'wg-doc',
"Parked WG Document": 'parked',
"Dead WG Document": 'dead',
"In WG Last Call": 'wg-lc',
"Waiting for WG Chair Go-Ahead": 'chair-w',
"WG Consensus: Waiting for Write-Up": 'writeupw',
"Submitted to IESG for Publication": 'sub-pub',
}
for o in StateDescription.objects.all().order_by('order'):
print "importing StateDescription", o.state.name
s, _ = State.objects.get_or_create(type=ietf_state_type, slug=wg_doc_state_slug[o.state.name], name=o.state.name)
s.desc = o.definition.replace(" ", " ").replace("\n ", "\n").replace("\n\n", "DUMMY").replace("\n", "").replace("DUMMY", "\n\n") # get rid of linebreaks, but keep paragraphs
s.order = o.order
s.save()
# IAB
print "importing IAB stream states"
State.objects.get_or_create(type=iab_state_type, slug="candidat", name="Candidate IAB Document", desc="A document being considered for the IAB stream.", order=1)
State.objects.get_or_create(type=iab_state_type, slug="active", name="Active IAB Document", desc="This document has been adopted by the IAB and is being actively developed.", order=2)
State.objects.get_or_create(type=iab_state_type, slug="parked", name="Parked IAB Document", desc="This document has lost its author or editor, is waiting for another document to be written, or cannot currently be worked on by the IAB for some other reason. Annotations probably explain why this document is parked.", order=3)
State.objects.get_or_create(type=iab_state_type, slug="review-i", name="IAB Review", desc="This document is awaiting the IAB itself to come to internal consensus.", order=4)
State.objects.get_or_create(type=iab_state_type, slug="review-c", name="Community Review", desc="This document has completed internal consensus within the IAB and is now under community review.", order=5)
State.objects.get_or_create(type=iab_state_type, slug="approved", name="Approved by IAB, To Be Sent to RFC Editor", desc="The consideration of this document is complete, but it has not yet been sent to the RFC Editor for publication (although that is going to happen soon).", order=6)
State.objects.get_or_create(type=iab_state_type, slug="diff-org", name="Sent to a Different Organization for Publication", desc="The IAB does not expect to publish the document itself, but has passed it on to a different organization that might continue work on the document. The expectation is that the other organization will eventually publish the document.", order=7)
State.objects.get_or_create(type=iab_state_type, slug="rfc-edit", name="Sent to the RFC Editor", desc="The IAB processing of this document is complete and it has been sent to the RFC Editor for publication. The document may be in the RFC Editor's queue, or it may have been published as an RFC; this state doesn't distinguish between different states occurring after the document has left the IAB.", order=8)
State.objects.get_or_create(type=iab_state_type, slug="pub", name="Published RFC", desc="The document has been published as an RFC.", order=9)
State.objects.get_or_create(type=iab_state_type, slug="dead", name="Dead IAB Document", desc="This document was an active IAB document, but for some reason it is no longer being pursued for the IAB stream. It is possible that the document might be revived later, possibly in another stream.", order=10)
# IRTF
print "importing IRTF stream states"
State.objects.get_or_create(type=irtf_state_type, slug="candidat", name="Candidate RG Document", desc="This document is under consideration in an RG for becoming an IRTF document. A document in this state does not imply any RG consensus and does not imply any precedence or selection. It's simply a way to indicate that somebody has asked for a document to be considered for adoption by an RG.", order=1)
State.objects.get_or_create(type=irtf_state_type, slug="active", name="Active RG Document", desc="This document has been adopted by the RG and is being actively developed.", order=2)
State.objects.get_or_create(type=irtf_state_type, slug="parked", name="Parked RG Document", desc="This document has lost its author or editor, is waiting for another document to be written, or cannot currently be worked on by the RG for some other reason.", order=3)
State.objects.get_or_create(type=irtf_state_type, slug="rg-lc", name="In RG Last Call", desc="The document is in its final review in the RG.", order=4)
State.objects.get_or_create(type=irtf_state_type, slug="sheph-w", name="Waiting for Document Shepherd", desc="IRTF documents have document shepherds who help RG documents through the process after the RG has finished with the document.", order=5)
State.objects.get_or_create(type=irtf_state_type, slug="chair-w", name="Waiting for IRTF Chair", desc="The IRTF Chair is meant to be performing some task such as sending a request for IESG Review.", order=6)
State.objects.get_or_create(type=irtf_state_type, slug="irsg-w", name="Awaiting IRSG Reviews", desc="The document shepherd has taken the document to the IRSG and solicited reviews from one or more IRSG members.", order=7)
State.objects.get_or_create(type=irtf_state_type, slug="irsgpoll", name="In IRSG Poll", desc="The IRSG is taking a poll on whether or not the document is ready to be published.", order=8)
State.objects.get_or_create(type=irtf_state_type, slug="iesg-rev", name="In IESG Review", desc="The IRSG has asked the IESG to do a review of the document, as described in RFC5742.", order=9)
State.objects.get_or_create(type=irtf_state_type, slug="rfc-edit", name="Sent to the RFC Editor", desc="The RG processing of this document is complete and it has been sent to the RFC Editor for publication. The document may be in the RFC Editor's queue, or it may have been published as an RFC; this state doesn't distinguish between different states occurring after the document has left the RG.", order=10)
State.objects.get_or_create(type=irtf_state_type, slug="pub", name="Published RFC", desc="The document has been published as an RFC.", order=11)
State.objects.get_or_create(type=irtf_state_type, slug="iesghold", name="Document on Hold Based On IESG Request", desc="The IESG has requested that the document be held pending further review, as specified in RFC 5742, and the IRTF has agreed to such a hold.", order=12)
State.objects.get_or_create(type=irtf_state_type, slug="dead", name="Dead IRTF Document", desc="This document was an active IRTF document, but for some reason it is no longer being pursued for the IRTF stream. It is possible that the document might be revived later, possibly in another stream.", order=13)
# ISE
print "importing ISE stream states"
State.objects.get_or_create(type=ise_state_type, slug="receive", name="Submission Received", desc="The draft has been sent to the ISE with a request for publication.", order=1)
State.objects.get_or_create(type=ise_state_type, slug="find-rev", name="Finding Reviewers", desc=" The ISE is finding initial reviewers for the document.", order=2)
State.objects.get_or_create(type=ise_state_type, slug="ise-rev", name="In ISE Review", desc="The ISE is actively working on the document.", order=3)
State.objects.get_or_create(type=ise_state_type, slug="need-res", name="Response to Review Needed", desc=" One or more reviews have been sent to the author, and the ISE is awaiting response.", order=4)
State.objects.get_or_create(type=ise_state_type, slug="iesg-rev", name="In IESG Review", desc="The ISE has asked the IESG to do a review of the document, as described in RFC5742.", order=5)
State.objects.get_or_create(type=ise_state_type, slug="rfc-edit", name="Sent to the RFC Editor", desc="The ISE processing of this document is complete and it has been sent to the RFC Editor for publication. The document may be in the RFC Editor's queue, or it may have been published as an RFC; this state doesn't distinguish between different states occurring after the document has left the ISE.", order=6)
State.objects.get_or_create(type=ise_state_type, slug="pub", name="Published RFC", desc="The document has been published as an RFC.", order=7)
State.objects.get_or_create(type=ise_state_type, slug="dead", name="No Longer In Independent Submission Stream", desc="This document was actively considered in the Independent Submission stream, but the ISE chose not to publish it. It is possible that the document might be revived later. A document in this state may have a comment explaining the reasoning of the ISE (such as if the document was going to move to a different stream).", order=8)
State.objects.get_or_create(type=ise_state_type, slug="iesghold", name="Document on Hold Based On IESG Request", desc="The IESG has requested that the document be held pending further review, as specified in RFC 5742, and the ISE has agreed to such a hold.", order=9)
# now import the next_states; we only go for the default ones, the
# WG-specific are handled in the group importer
workflows = [(ietf_state_type, workflows.models.Workflow.objects.get(name="Default WG Workflow")),
(irtf_state_type, workflows.models.Workflow.objects.get(name="IRTF Workflow")),
(ise_state_type, workflows.models.Workflow.objects.get(name="ISE Workflow")),
(iab_state_type, workflows.models.Workflow.objects.get(name="IAB Workflow")),
]
for state_type, workflow in workflows:
states = dict((s.name, s) for s in State.objects.filter(type=state_type))
old_states = dict((s.name, s) for s in workflow.states.filter(name__in=[name for name in states]).select_related('transitions'))
for name in states:
print "importing workflow transitions", workflow.name, name
s = states[name]
try:
o = old_states[name]
except KeyError:
print "MISSING state", name, "in workflow", workflow.name
continue
s.next_states = [states[t.destination.name] for t in o.transitions.filter(workflow=workflow)]