diff --git a/ietf/templates/wgchairs/manage_workflowREDESIGN.html b/ietf/templates/wgchairs/manage_workflowREDESIGN.html new file mode 100644 index 000000000..2e88a7930 --- /dev/null +++ b/ietf/templates/wgchairs/manage_workflowREDESIGN.html @@ -0,0 +1,168 @@ +{% extends "wginfo/wg_base.html" %} + +{% block wg_titledetail %}Manage Workflow{% endblock %} + +{% block pagehead %} +{{ block.super }} + + + +{% 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 %} +
+

Edit workflow

+ +

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.

+ +

You can see the default Working Group I-D State Diagram in Section 4.1 of RFC6174.

+ +

States

+ + + + + + + + {% for state in states %} + + + + + +{% endfor %} +
StateTurn on/offNext states
+
+ {{ state.name }} {% if not state.used %} (not used in {{ wg.acronym }}){% endif %} + + +
+
{{ state.desc|safe|linebreaks }}
+
+ {% if state.mandatory %} + (mandatory) + {% else %} +
+ {% if state.used %} + + + + {% else %} + + + + {% endif %} + +
+ {% endif %} +
+
+ {% if state.used_next_states %} + {% for n in state.used_next_states %}{{ n.name }}{% if not forloop.last %} {% endif %}{% endfor %} + {% else %} + None + {% endif %} + + +
+
+ Select the next states: + {% for checked, default, s in state.next_states_checkboxes %} + {{ s.name }}{% if default %} (default){% endif %} + {% endfor %} + + + + +
+ +

Tags

+ + + + + + + {% for tag in tags %} + + + + + {% endfor %} +
TagTurn on/off
{{ tag.name }} {% if not tag.used %} (not used in {{ wg.acronym }}){% endif %} +
+ {% if tag.used %} + + + + {% else %} + + + + {% endif %} + +
+
+ + + +{% endblock %} diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py index 5519bf6db..a94f02d34 100644 --- a/ietf/wgchairs/forms.py +++ b/ietf/wgchairs/forms.py @@ -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): diff --git a/ietf/wgchairs/models.py b/ietf/wgchairs/models.py index a9367ff04..36115bde0 100644 --- a/ietf/wgchairs/models.py +++ b/ietf/wgchairs/models.py @@ -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 diff --git a/ietf/wgchairs/templatetags/wgchairs_tags.py b/ietf/wgchairs/templatetags/wgchairs_tags.py index 0bcb4cda4..df51dafae 100644 --- a/ietf/wgchairs/templatetags/wgchairs_tags.py +++ b/ietf/wgchairs/templatetags/wgchairs_tags.py @@ -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 diff --git a/ietf/wgchairs/tests.py b/ietf/wgchairs/tests.py index 9c40c79b9..b0e7d94b7 100644 --- a/ietf/wgchairs/tests.py +++ b/ietf/wgchairs/tests.py @@ -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 diff --git a/ietf/wgchairs/views.py b/ietf/wgchairs/views.py index 4f2344892..62a1c994f 100644 --- a/ietf/wgchairs/views.py +++ b/ietf/wgchairs/views.py @@ -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