Port ietfworkflows and wgchairs to new schema, fix missing state

setting in I-D submission, add tests
 - Legacy-Id: 3618
This commit is contained in:
Ole Laursen 2011-11-14 19:40:49 +00:00
parent ea7f45d56e
commit 3c2293d4e8
6 changed files with 441 additions and 44 deletions

View file

@ -0,0 +1,168 @@
{% extends "wginfo/wg_base.html" %}
{% block wg_titledetail %}Manage Workflow{% endblock %}
{% block pagehead %}
{{ block.super }}
<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
<script type="text/javascript" src="/js/base.js"></script>
{% endblock pagehead %}
{% block morecss %}
{{ block.super }}
.state-table .inactive,
.tag-table .inactive {
font-style: italic;
color: #666;
}
.state-table .state {
padding-right: 0.6em;
}
.set-next-states {
margin-top: 1em;
}
.set-next-states label {
display: block;
cursor: pointer;
}
.set-next-states label input {
vertical-align: middle;
}
.set-state input, .set-tag input {
width: 6em;
}
.toggled {
display: none;
}
.toggler {
color: #000;
text-decoration: none;
padding: 0px 3px;
display: inline-block;
margin-left: 0.5em;
font-size: 15px;
font-weight: bold;
}
.inactive .toggler {
color: #666;
}
.toggler:hover {
background-color: #999;
color: #fff;
}
{% endblock %}
{% block wg_content %}
<div class="wg-workflow-management">
<h2>Edit workflow</h2>
<p>Below you can customize the draft states and tags used in the {{ wg.acronym }} WG. Note that some states are mandatory for WG operation and cannot be deactivated.</p>
<p>You can see the default Working Group I-D State Diagram in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>.</p>
<h3>States</h3>
<table class="ietf-table state-table">
<tr>
<th style="width:26em">State</th>
<th style="auto">Turn on/off</th>
<th style="width:26em">Next states</th>
</tr>
{% for state in states %}
<tr class="{% cycle "oddrow" "evenrow" %}{% if not state.used %} inactive{% endif %}" style="vertical-align: top;">
<td>
<div>
<span>{{ state.name }} {% if not state.used %} (not used in {{ wg.acronym }}){% endif %}</span>
<a class="toggler" title="Click to show description of state" href="">+</a>
</div>
<div class="toggled">{{ state.desc|safe|linebreaks }}</div>
</td>
<td>
{% if state.mandatory %}
(mandatory)
{% else %}
<form class="set-state" method="post" action="">
{% if state.used %}
<input type="hidden" name="state" value="{{ state.pk }}" />
<input type="hidden" name="active" value="0" />
<input type="submit" title="Click to deactive this state" value="Deactivate" />
{% else %}
<input type="hidden" name="state" value="{{ state.pk }}" />
<input type="hidden" name="active" value="1" />
<input type="submit" title="Click to active this state" value="Activate" />
{% endif %}
<input type="hidden" name="action" value="setstateactive" />
</form>
{% endif %}
</td>
<td>
<div>
{% if state.used_next_states %}
{% for n in state.used_next_states %}<span class="state">{{ n.name }}</span>{% if not forloop.last %} {% endif %}{% endfor %}
{% else %}
<i>None</i>
{% endif %}
<a class="toggler" title="Click to set next states for state" href="">+</a>
</div>
<form class="toggled set-next-states" method="post" action="">
Select the next states:
{% for checked, default, s in state.next_states_checkboxes %}
<label{% if not s.used %} class="inactive"{% endif %}><input type="checkbox" name="next_states" value="{{ s.pk }}"{% if checked %} checked="checked"{% endif %} />{{ s.name }}{% if default %} (default){% endif %}</label>
{% endfor %}
<input type="hidden" name="state" value="{{ state.pk }}" />
<input type="hidden" name="action" value="setnextstates" />
<input class="submit" type="submit" value="Save" />
</form>
</td>
</tr>
{% endfor %}
</table>
<h3>Tags</h3>
<table class="ietf-table tag-table">
<tr>
<th>Tag</th>
<th>Turn on/off</th>
</tr>
{% for tag in tags %}
<tr class="{% cycle "oddrow" "evenrow" %}{% if not tag.used %} inactive{% endif %}" style="vertical-align: top;">
<td><span>{{ tag.name }} {% if not tag.used %} (not used in {{ wg.acronym }}){% endif %}</span></td>
<td>
<form class="set-tag" method="post" action="">
{% if tag.used %}
<input type="hidden" name="tag" value="{{ tag.pk }}" />
<input type="hidden" name="active" value="0" />
<input type="submit" value="Deactivate" />
{% else %}
<input type="hidden" name="tag" value="{{ tag.pk }}" />
<input type="hidden" name="active" value="1" />
<input type="submit" value="Activate" />
{% endif %}
<input type="hidden" name="action" value="settagactive" />
</form>
</td>
</tr>
{% endfor %}
</table>
<script type="text/javascript">
//<![CDATA[
jQuery('.state-table .toggler').click(function(e) {
e.preventDefault();
var toggler = jQuery(this),
toggled = toggler.parent().siblings(".toggled");
if (toggled.is(":hidden")) {
toggler.html("&ndash;");
toggled.slideDown(300);
}
else {
toggler.html("+");
toggled.slideUp(300);
}
});
//]]>
</script>
{% endblock %}

View file

@ -18,8 +18,10 @@ from ietf.utils.mail import send_mail_text
from workflows.models import Transition
from redesign.doc.models import WriteupDocEvent
from redesign.person.models import Person, Email
from redesign.group.models import Role, RoleName
from redesign.name.models import DocTagName
class RelatedWGForm(forms.Form):
@ -194,17 +196,20 @@ def assign_shepherd(user, internetdraft, shepherd):
doc = Document.objects.get(name=internetdraft.name)
save_document_in_history(doc)
e = DocEvent(doc=doc, by=user.get_profile())
doc.time = datetime.datetime.now()
doc.shepherd = shepherd
doc.save()
e = DocEvent(type="changed_document")
e.time = doc.time
e.doc = doc
e.by = user.get_profile()
if not shepherd:
e.desc = u"Unassigned shepherd"
else:
e.desc = u"Changed shepherd to %s" % shepherd.name
e.type = "changed_document"
e.save()
doc.time = e.time
doc.shepherd = shepherd
doc.save()
# update proxy too
internetdraft.shepherd = shepherd
@ -402,14 +407,25 @@ class WriteUpEditForm(RelatedWGForm):
return self.data.get('writeup', self.doc_writeup and self.doc_writeup.writeup or '')
def save(self):
if not self.doc_writeup:
self.doc_writeup = ProtoWriteUp.objects.create(
person=self.person,
draft=self.doc,
writeup=self.cleaned_data['writeup'])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
e = WriteupDocEvent(type="changed_protocol_writeup")
e.doc = self.doc
e.by = self.person
e.desc = e.get_type_display()
e.text = self.cleaned_data['writeup']
e.save()
from ietf.wgchairs.models import ProtoWriteUpProxy
self.doc_writeup = ProtoWriteUpProxy.objects.get(pk=e.pk)
else:
self.doc_writeup.writeup = self.cleaned_data['writeup']
self.doc_writeup.save()
if not self.doc_writeup:
self.doc_writeup = ProtoWriteUp.objects.create(
person=self.person,
draft=self.doc,
writeup=self.cleaned_data['writeup'])
else:
self.doc_writeup.writeup = self.cleaned_data['writeup']
self.doc_writeup.save()
if self.data.get('modify_tag', False):
followup = self.cleaned_data.get('followup', False)
comment = self.cleaned_data.get('comment', False)
@ -418,13 +434,20 @@ class WriteUpEditForm(RelatedWGForm):
except PersonOrOrgInfo.DoesNotExist:
shepherd = None
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()]
else:
extra_notify = []
if followup:
update_tags(self.doc, comment, self.person, set_tags=[FOLLOWUP_TAG], extra_notify=extra_notify)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
tags = DocTagName.objects.filter(slug="sheph-u")
else:
update_tags(self.doc, comment, self.person, reset_tags=[FOLLOWUP_TAG], extra_notify=extra_notify)
tags = [FOLLOWUP_TAG]
if followup:
update_tags(self.doc, comment, self.person, set_tags=tags, extra_notify=extra_notify)
else:
update_tags(self.doc, comment, self.person, reset_tags=tags, extra_notify=extra_notify)
return self.doc_writeup
def is_valid(self):

View file

@ -27,6 +27,9 @@ class ProtoWriteUp(models.Model):
null=False,
)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.idtracker.models import InternetDraftOld as InternetDraft
draft = models.ForeignKey(
InternetDraft,
blank=False,
@ -59,16 +62,26 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
class Meta:
proxy = True
from redesign.doc.models import DocEvent
class ProtoWriteUpProxy(DocEvent):
from redesign.doc.models import WriteupDocEvent
class ProtoWriteUpProxy(WriteupDocEvent):
#person = models.ForeignKey(PersonOrOrgInfo, blank=False, null=False)
@property
def person(self):
return self.by
#draft = models.ForeignKey(InternetDraft, blank=False, null=False)
@property
def draft(self):
return self.doc
#date = models.DateTimeField(default=datetime.datetime.now(), blank=False, null=False)
@property
def date(self):
return self.time
#writeup = models.TextField(blank=False, null=False)
@property
def writeup(self):
return self.text
class Meta:
proxy = True
#WGDelegateOld = WGDelegate
WGDelegate = WGDelegateProxy
ProtoWriteUpOld = ProtoWriteUp
ProtoWriteUp = ProtoWriteUpProxy

View file

@ -24,12 +24,8 @@ def wgchairs_admin_options(context, wg):
@register.simple_tag
def writeup(doc):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
e = doc.latest_event(type="changed_protocol_writeup")
return e.text if e else ""
writeup = doc.protowriteup_set.all()
if not writeup.count():
if not writeup:
return ''
else:
return writeup[0].writeup
@ -37,12 +33,8 @@ def writeup(doc):
@register.simple_tag
def writeupdate(doc):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
e = doc.latest_event(type="changed_protocol_writeup")
return e.time if e else ""
writeup = doc.protowriteup_set.all()
if not writeup.count():
if not writeup:
return ''
else:
return writeup[0].date

View file

@ -13,8 +13,9 @@ 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
from redesign.group.models import Group, Role, GroupStateTransitions
from redesign.doc.models import Document, State, WriteupDocEvent
from redesign.name.models import DocTagName
class ManageDelegatesTestCase(django.test.TestCase):
fixtures = ['names']
@ -181,7 +182,118 @@ class ManageShepherdsTestCase(django.test.TestCase):
self.assertTrue(Email.objects.get(address="plain@example.com").person.name in r.content)
self.assertEquals(draft.docevent_set.count(), events_before + 1)
class ManageWorkflowTestCase(django.test.TestCase):
fixtures = ['names']
def test_manage_workflows(self):
make_test_data()
group = Group.objects.get(acronym="mars")
url = urlreverse('manage_workflow', kwargs=dict(acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
state = State.objects.get(type="draft-stream-ietf", slug="wg-lc")
self.assertTrue(state not in group.unused_states.all())
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q("form.set-state").find("input[name=state][value=%s]" % state.pk).parents("form").find("input[name=active][value=0]")), 1)
# deactivate state
r = self.client.post(url,
dict(action="setstateactive",
state=state.pk,
active="0"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q("form.set-state").find("input[name=state][value=%s]" % state.pk).parents("form").find("input[name=active][value=1]")), 1)
group = Group.objects.get(acronym=group.acronym)
self.assertTrue(state in group.unused_states.all())
# change next states
state = State.objects.get(type="draft-stream-ietf", slug="wg-doc")
next_states = State.objects.filter(type=b"draft-stream-ietf", slug__in=["parked", "dead", "wait-wgw", 'sub-pub']).values_list('pk', flat=True)
r = self.client.post(url,
dict(action="setnextstates",
state=state.pk,
next_states=next_states))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q("form.set-next-states").find("input[name=state][value=%s]" % state.pk).parents('form').find("input[name=next_states][checked=checked]")), len(next_states))
transitions = GroupStateTransitions.objects.filter(group=group, state=state)
self.assertEquals(len(transitions), 1)
self.assertEquals(set(transitions[0].next_states.values_list("pk", flat=True)), set(next_states))
# change them back to default
next_states = state.next_states.values_list("pk", flat=True)
r = self.client.post(url,
dict(action="setnextstates",
state=state.pk,
next_states=next_states))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
transitions = GroupStateTransitions.objects.filter(group=group, state=state)
self.assertEquals(len(transitions), 0)
# deactivate tag
tag = DocTagName.objects.get(slug="w-expert")
r = self.client.post(url,
dict(action="settagactive",
tag=tag.pk,
active="0"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form').find('input[name=tag][value="%s"]' % tag.pk).parents("form").find("input[name=active]")), 1)
group = Group.objects.get(acronym=group.acronym)
self.assertTrue(tag in group.unused_tags.all())
class ManageWriteupTestCase(django.test.TestCase):
fixtures = ['names']
def test_manage_writeup(self):
draft = make_test_data()
self.assertTrue(not draft.tags.filter(slug="sheph-u"))
url = urlreverse('doc_managing_writeup', kwargs=dict(acronym=draft.group.acronym, 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("input[type=submit][value*=Change]")), 1)
# post text
r = self.client.post(url,
dict(writeup="New writeup"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q("input[name=followup]")), 1)
self.assertEquals(len(q("input[name=confirm]")), 1)
self.assertEquals(q("input[name=writeup]").val(), "New writeup")
# update tag and confirm
r = self.client.post(url,
dict(writeup="New writeup",
confirm="1",
followup="1",
comment="Starting on write up",
modify_tag="Modify"))
self.assertEquals(r.status_code, 200)
e = draft.latest_event(WriteupDocEvent, type="changed_protocol_writeup")
self.assertTrue(e)
self.assertEquals(e.text, "New writeup")
self.assertEquals(e.by.user.username, "secretary")
self.assertTrue(draft.tags.filter(slug="sheph-u"))
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
# the above tests only work with the new schema
del ManageDelegatesTestCase
del ManageShepherdsTestCase
del ManageWorkflowTestCase
del ManageWriteupCase

View file

@ -7,7 +7,7 @@ from django.http import HttpResponseForbidden, Http404
from ietf.idrfc.views_search import SearchForm, search_query
from ietf.wgchairs.forms import (RemoveDelegateForm, add_form_factory,
workflow_form_factory, TransitionFormSet,
WriteUpEditForm)
WriteUpEditForm, assign_shepherd)
from ietf.wgchairs.accounts import (can_manage_delegates_in_group, get_person_for_user,
can_manage_shepherds_in_group,
can_manage_workflow_in_group,
@ -22,7 +22,9 @@ from ietf.ietfworkflows.utils import (get_workflow_for_wg,
get_annotation_tags_for_draft,
get_state_for_draft, WAITING_WRITEUP,
FOLLOWUP_TAG)
from redesign.name.models import DocTagName
from redesign.doc.models import State
from redesign.doc.utils import get_tags_for_stream_id
def manage_delegates(request, acronym):
wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
@ -54,7 +56,7 @@ def manage_workflow(request, acronym):
wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
user = request.user
if not can_manage_workflow_in_group(user, wg):
return HttpResponseForbidden('You have no permission to access this view')
return HttpResponseForbidden("You don't have permission to access this view")
workflow = get_workflow_for_wg(wg)
default_workflow = get_default_workflow_for_wg()
formset = None
@ -91,10 +93,92 @@ def manage_workflow(request, acronym):
'selected': 'manage_workflow',
}, RequestContext(request))
def manage_workflowREDESIGN(request, acronym):
from redesign.doc.models import State
from redesign.group.models import GroupStateTransitions
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
user = request.user
if not can_manage_workflow_in_group(user, wg):
return HttpResponseForbidden("You don't have permission to access this view")
if request.method == 'POST':
action = request.POST.get("action")
if action == "setstateactive":
active = request.POST.get("active") == "1"
try:
state = State.objects.exclude(slug__in=MANDATORY_STATES).get(pk=request.POST.get("state"))
except State.DoesNotExist:
return HttpResponse("Invalid state %s" % request.POST.get("state"))
if active:
wg.unused_states.remove(state)
else:
wg.unused_states.add(state)
if action == "setnextstates":
try:
state = State.objects.get(pk=request.POST.get("state"))
except State.DoesNotExist:
return HttpResponse("Invalid state %s" % request.POST.get("state"))
next_states = State.objects.filter(type='draft-stream-ietf', pk__in=request.POST.getlist("next_states"))
unused = wg.unused_states.all()
if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)):
# just use the default
wg.groupstatetransitions_set.filter(state=state).delete()
else:
transitions, _ = GroupStateTransitions.objects.get_or_create(group=wg, state=state)
transitions.next_states = next_states
if action == "settagactive":
active = request.POST.get("active") == "1"
try:
tag = DocTagName.objects.get(pk=request.POST.get("tag"))
except DocTagName.DoesNotExist:
return HttpResponse("Invalid tag %s" % request.POST.get("tag"))
if active:
wg.unused_tags.remove(tag)
else:
wg.unused_tags.add(tag)
# put some info for the template on tags and states
unused_tags = wg.unused_tags.all().values_list('slug', flat=True)
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id("ietf"))
for t in tags:
t.used = t.slug not in unused_tags
unused_states = wg.unused_states.all().values_list('slug', flat=True)
states = State.objects.filter(type="draft-stream-ietf")
transitions = dict((o.state, o) for o in wg.groupstatetransitions_set.all())
for s in states:
s.used = s.slug not in unused_states
s.mandatory = s.slug in MANDATORY_STATES
default_n = s.next_states.all()
if s in transitions:
n = transitions[s].next_states.all()
else:
n = default_n
s.next_states_checkboxes = [(x in n, x in default_n, x) for x in states]
s.used_next_states = [x for x in n if x.slug not in unused_states]
return render_to_response('wgchairs/manage_workflowREDESIGN.html',
{'wg': wg,
'states': states,
'tags': tags,
'selected': 'manage_workflow',
}, RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.wgchairs.forms import assign_shepherd
manage_workflow = manage_workflowREDESIGN
def managing_shepherd(request, acronym, name):
"""
View for managing the assigned shepherd of a document.
@ -168,13 +252,18 @@ def managing_writeup(request, acronym, name):
user = request.user
doc = get_object_or_404(InternetDraft, filename=name)
if not can_manage_writeup_of_a_document(user, doc):
raise Http404
current_state = get_state_for_draft(doc)
can_edit = True
if current_state != get_state_by_name(WAITING_WRITEUP) and not can_manage_writeup_of_a_document_no_state(user, doc):
can_edit = False
return HttpResponseForbidden('You do not have permission to access this page')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
state = doc.get_state("draft-stream-%s" % doc.stream_id)
can_edit = (state and state.slug == "writeupw") or can_manage_writeup_of_a_document_no_state(user, doc)
else:
can_edit = True
current_state = get_state_for_draft(doc)
if current_state != get_state_by_name(WAITING_WRITEUP) and not can_manage_writeup_of_a_document_no_state(user, doc):
can_edit = False
writeup = doc.protowriteup_set.all()
if writeup.count():
if writeup:
writeup = writeup[0]
else:
writeup = None