Add a review step when submitting milestones to try to help people

catch errors
 - Legacy-Id: 4543
This commit is contained in:
Ole Laursen 2012-06-28 17:33:52 +00:00
parent 4ef6cd7543
commit 471d5de21e
5 changed files with 163 additions and 122 deletions

View file

@ -37,6 +37,8 @@ tr.milestone.add { font-style: italic; }
{% load ietf_filters %}
<h1>{{ title }}</h1>
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
<p class="help">{% if forms %}Click a milestone to edit it.{% endif %}
{% if needs_review %}
@ -76,7 +78,8 @@ this list</a> to the currently in-use milestones for the {{ group.acronym }} {{
<div class="actions">
<a href="{% if milestone_set == "charter" %}{% url doc_view name=group.charter.canonical_name %}{% else %}{% url wg_charter acronym=group.acronym %}{% endif %}">Back</a>
<input type="submit" value="Save"/>
<input type="submit" data-label-save="Save" data-label-review="Review changes" value="Save"/>
<input type="hidden" name="action" value="save">
</div>
</form>

View file

@ -1,7 +1,6 @@
{# assumes group, form, needs_review are in the context #}
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
{{ form.id }}
{{ form.expanded_for_editing }}
<table cellspacing="0" cellpadding="0">
<tr>

View file

@ -39,8 +39,6 @@ class MilestoneForm(forms.Form):
accept = forms.ChoiceField(choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
required=False, initial="noaction", widget=forms.RadioSelect)
expanded_for_editing = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
m = self.milestone = kwargs.pop("instance", None)
@ -119,19 +117,124 @@ def edit_milestones(request, acronym, milestone_set="current"):
milestones_dict = dict((str(m.id), m) for m in milestones)
def add_event(m, desc):
if milestone_set == "charter":
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
by=login, desc=desc)
else:
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
by=login, desc=desc, milestone=m)
def set_attributes_from_form(f, m):
c = f.cleaned_data
m.group = group
if milestone_set == "current":
if needs_review:
m.state = GroupMilestoneStateName.objects.get(slug="review")
else:
m.state = GroupMilestoneStateName.objects.get(slug="active")
elif milestone_set == "charter":
m.state = GroupMilestoneStateName.objects.get(slug="charter")
m.desc = c["desc"]
m.due = c["due"]
m.resolved = c["resolved"]
def save_milestone_form(f):
c = f.cleaned_data
if f.milestone:
m = f.milestone
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if c["delete"]:
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
return 'Deleted %s' % named_milestone
# compute changes
history = None
changes = ['Changed %s' % named_milestone]
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
if not history:
history = save_milestone_in_history(m)
if c["accept"] == "accept":
m.state_id = "active"
changes.append("changed state from review to active, accepting new milestone")
elif c["accept"] == "reject":
m.state_id = "deleted"
changes.append("changed state from review to deleted, rejecting new milestone")
if c["desc"] != m.desc and not needs_review:
if not history:
history = save_milestone_in_history(m)
m.desc = c["desc"]
changes.append('changed description to "%s"' % m.desc)
if c["due"] != m.due:
if not history:
history = save_milestone_in_history(m)
m.due = c["due"]
changes.append('changed due date to %s' % m.due.strftime("%Y-%m-%d"))
resolved = c["resolved"]
if resolved != m.resolved:
if resolved and not m.resolved:
changes.append('resolved as "%s"' % resolved)
elif not resolved and m.resolved:
changes.append("reverted to not being resolved")
elif resolved and m.resolved:
changes.append('changed resolution to "%s"' % resolved)
if not history:
history = save_milestone_in_history(m)
m.resolved = resolved
new_docs = set(c["docs"])
old_docs = set(m.docs.all())
if new_docs != old_docs:
added = new_docs - old_docs
if added:
changes.append('added %s to milestone' % ", ".join(d.name for d in added))
removed = old_docs - new_docs
if removed:
changes.append('removed %s from milestone' % ", ".join(d.name for d in removed))
if not history:
history = save_milestone_in_history(m)
m.docs = new_docs
if len(changes) > 1:
m.save()
return ", ".join(changes)
else: # new milestone
m = f.milestone = GroupMilestone()
set_attributes_from_form(f, m)
m.save()
m.docs = c["docs"]
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if m.state_id in ("active", "charter"):
return 'Added %s, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d"))
elif m.state_id == "review":
return 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d"))
finished_milestone_text = "Done"
form_errors = False
if request.method == 'POST':
# parse out individual milestone forms
for prefix in request.POST.getlist("prefix"):
if not prefix: # empty form
continue
@ -144,116 +247,29 @@ def edit_milestones(request, acronym, milestone_set="current"):
form_errors = form_errors or not f.is_valid()
if not form_errors:
action = request.POST.get("action", "review")
if action == "review":
for f in forms:
c = f.cleaned_data
if not f.is_valid():
continue
if f.milestone:
m = f.milestone
# let's fill in the form milestone so we can output it in the template
if not f.milestone:
f.milestone = GroupMilestone()
set_attributes_from_form(f, f.milestone)
elif action == "save" and not form_errors:
for f in forms:
change = save_milestone_form(f)
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if not change:
continue
if c["delete"]:
save_milestone_in_history(m)
m.state_id = "deleted"
m.save()
add_event(m, 'Deleted %s' % named_milestone)
continue
# compute changes
history = None
changes = ['Changed %s' % named_milestone]
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
if not history:
history = save_milestone_in_history(m)
if c["accept"] == "accept":
m.state_id = "active"
changes.append("changed state from review to active, accepting new milestone")
elif c["accept"] == "reject":
m.state_id = "deleted"
changes.append("changed state from review to deleted, rejecting new milestone")
if c["desc"] != m.desc and not needs_review:
if not history:
history = save_milestone_in_history(m)
m.desc = c["desc"]
changes.append('changed description to "%s"' % m.desc)
if c["due"] != m.due:
if not history:
history = save_milestone_in_history(m)
m.due = c["due"]
changes.append('changed due date to %s' % m.due.strftime("%Y-%m-%d"))
resolved = c["resolved"]
if resolved != m.resolved:
if resolved and not m.resolved:
changes.append('resolved as "%s"' % resolved)
elif not resolved and m.resolved:
changes.append("reverted to not being resolved")
elif resolved and m.resolved:
changes.append('changed resolution to "%s"' % resolved)
if not history:
history = save_milestone_in_history(m)
m.resolved = resolved
new_docs = set(c["docs"])
old_docs = set(m.docs.all())
if new_docs != old_docs:
added = new_docs - old_docs
if added:
changes.append('added %s to milestone' % ", ".join(d.name for d in added))
removed = old_docs - new_docs
if removed:
changes.append('removed %s from milestone' % ", ".join(d.name for d in removed))
if not history:
history = save_milestone_in_history(m)
m.docs = new_docs
if len(changes) > 1:
add_event(m, ", ".join(changes))
m.save()
else: # new milestone
m = GroupMilestone()
m.group = group
if milestone_set == "current":
if needs_review:
m.state = GroupMilestoneStateName.objects.get(slug="review")
else:
m.state = GroupMilestoneStateName.objects.get(slug="active")
elif milestone_set == "charter":
m.state = GroupMilestoneStateName.objects.get(slug="charter")
m.desc = c["desc"]
m.due = c["due"]
m.resolved = c["resolved"]
m.save()
m.docs = c["docs"]
named_milestone = 'milestone "%s"' % m.desc
if milestone_set == "charter":
named_milestone = "charter " + named_milestone
if m.state_id in ("active", "charter"):
add_event(m, 'Added %s, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d")))
elif m.state_id == "review":
add_event(m, 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d")))
if milestone_set == "charter":
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
by=login, desc=change)
else:
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
by=login, desc=change, milestone=f.milestone)
if milestone_set == "charter":
return redirect('doc_view', name=group.charter.canonical_name())
@ -267,6 +283,8 @@ def edit_milestones(request, acronym, milestone_set="current"):
empty_form = MilestoneForm(needs_review=needs_review)
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
return render_to_response('wginfo/edit_milestones.html',
dict(group=group,
title=title,

View file

@ -316,6 +316,7 @@ class MilestoneTestCase(django.test.TestCase):
'm-1-due': due.strftime("%Y-%m-%d"),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
@ -329,6 +330,7 @@ class MilestoneTestCase(django.test.TestCase):
'm-1-due': due.strftime("%Y-%m-%d"),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
@ -362,6 +364,7 @@ class MilestoneTestCase(django.test.TestCase):
'm-1-due': due.strftime("%Y-%m-%d"),
'm-1-resolved': "",
'm-1-docs': "",
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
@ -394,6 +397,7 @@ class MilestoneTestCase(django.test.TestCase):
'm1-resolved': m1.resolved,
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-accept': "accept",
'action': "save",
})
self.assertEquals(r.status_code, 302)
@ -419,6 +423,7 @@ class MilestoneTestCase(django.test.TestCase):
'm1-resolved': "",
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-delete': "checked",
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
@ -447,6 +452,7 @@ class MilestoneTestCase(django.test.TestCase):
'm1-due': due.strftime("%Y-%m-%d"),
'm1-resolved': "",
'm1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
@ -463,6 +469,7 @@ class MilestoneTestCase(django.test.TestCase):
'm1-resolved': "Done",
'm1-resolved_checkbox': "checked",
'm1-docs': ",".join(docs),
'action': "save",
})
self.assertEquals(r.status_code, 302)
self.assertEquals(GroupMilestone.objects.count(), milestones_before)

View file

@ -8,8 +8,19 @@ jQuery(function () {
idCounter = v - 1;
});
function setSubmitButtonState() {
var action, label;
if (jQuery("#milestones-form input[name$=desc]:visible").length > 0)
action = "review";
else
action = "save";
jQuery("tr.milestone").click(function () {
var submit = jQuery("#milestones-form input[type=submit]");
submit.val(submit.data("label-" + action));
jQuery("#milestones-form input[name=action]").val(action);
}
jQuery("#milestones-form tr.milestone").click(function () {
var row = jQuery(this), editRow = row.next("tr.edit-milestone");
if (row.hasClass("add")) {
@ -45,6 +56,8 @@ jQuery(function () {
editRow.find('input[name$="expanded_for_editing"]').val("True");
editRow.find('input[name$="desc"]').focus();
setSubmitButtonState();
});
function setResolvedState() {
@ -74,8 +87,10 @@ jQuery(function () {
var top = jQuery(this).closest(".edit-milestone");
if (jQuery(this).is(":checked")) {
if (+top.find('input[name$="id"]').val() < 0)
if (+top.find('input[name$="id"]').val() < 0) {
top.remove();
setSubmitButtonState();
}
else
top.addClass("delete")
}
@ -93,8 +108,7 @@ jQuery(function () {
setInputMasks(jQuery("#milestone-form .edit-milestone").not(".template"));
jQuery('#milestones-form .edit-milestone input[name$="expanded_for_editing"]').each(function () {
if (this.value == "True")
jQuery(this).closest(".edit-milestone").prev().click();
jQuery('#milestones-form .edit-milestone .errorlist').each(function () {
jQuery(this).closest(".edit-milestone").prev().click();
});
});