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:
parent
e01ba98af1
commit
ea7f45d56e
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
173
ietf/ietfworkflows/tests.py
Normal 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
|
|
@ -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'),
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]),
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# basic dependencies
|
||||
set -e
|
||||
python import-persons.py
|
||||
python import-states.py
|
||||
python import-groups.py
|
||||
python import-roles.py
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
110
redesign/importing/import-states.py
Executable file
110
redesign/importing/import-states.py
Executable 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)]
|
Loading…
Reference in a new issue