Skip to content

Commit 471d5de

Browse files
committed
Add a review step when submitting milestones to try to help people
catch errors - Legacy-Id: 4543
1 parent 4ef6cd7 commit 471d5de

5 files changed

Lines changed: 153 additions & 112 deletions

File tree

ietf/templates/wginfo/edit_milestones.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
{% load ietf_filters %}
3838
<h1>{{ title }}</h1>
3939

40+
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
41+
4042
<p class="help">{% if forms %}Click a milestone to edit it.{% endif %}
4143

4244
{% if needs_review %}
@@ -76,7 +78,8 @@ <h1>{{ title }}</h1>
7678

7779
<div class="actions">
7880
<a href="{% if milestone_set == "charter" %}{% url doc_view name=group.charter.canonical_name %}{% else %}{% url wg_charter acronym=group.acronym %}{% endif %}">Back</a>
79-
<input type="submit" value="Save"/>
81+
<input type="submit" data-label-save="Save" data-label-review="Review changes" value="Save"/>
82+
<input type="hidden" name="action" value="save">
8083
</div>
8184

8285
</form>

ietf/templates/wginfo/milestone_form.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{# assumes group, form, needs_review are in the context #}
22
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
33
{{ form.id }}
4-
{{ form.expanded_for_editing }}
54

65
<table cellspacing="0" cellpadding="0">
76
<tr>

ietf/wginfo/milestones.py

Lines changed: 123 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ class MilestoneForm(forms.Form):
3939
accept = forms.ChoiceField(choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
4040
required=False, initial="noaction", widget=forms.RadioSelect)
4141

42-
expanded_for_editing = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
43-
4442
def __init__(self, *args, **kwargs):
4543
m = self.milestone = kwargs.pop("instance", None)
4644

@@ -119,141 +117,159 @@ def edit_milestones(request, acronym, milestone_set="current"):
119117

120118
milestones_dict = dict((str(m.id), m) for m in milestones)
121119

122-
def add_event(m, desc):
123-
if milestone_set == "charter":
124-
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
125-
by=login, desc=desc)
126-
else:
127-
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
128-
by=login, desc=desc, milestone=m)
120+
def set_attributes_from_form(f, m):
121+
c = f.cleaned_data
122+
m.group = group
123+
if milestone_set == "current":
124+
if needs_review:
125+
m.state = GroupMilestoneStateName.objects.get(slug="review")
126+
else:
127+
m.state = GroupMilestoneStateName.objects.get(slug="active")
128+
elif milestone_set == "charter":
129+
m.state = GroupMilestoneStateName.objects.get(slug="charter")
130+
m.desc = c["desc"]
131+
m.due = c["due"]
132+
m.resolved = c["resolved"]
129133

130-
finished_milestone_text = "Done"
134+
def save_milestone_form(f):
135+
c = f.cleaned_data
131136

132-
form_errors = False
137+
if f.milestone:
138+
m = f.milestone
133139

134-
if request.method == 'POST':
135-
for prefix in request.POST.getlist("prefix"):
136-
if not prefix: # empty form
137-
continue
140+
named_milestone = 'milestone "%s"' % m.desc
141+
if milestone_set == "charter":
142+
named_milestone = "charter " + named_milestone
138143

139-
# new milestones have non-existing ids so instance end up as None
140-
instance = milestones_dict.get(request.POST.get(prefix + "-id", ""), None)
141-
f = MilestoneForm(request.POST, prefix=prefix, instance=instance,
142-
needs_review=needs_review)
143-
forms.append(f)
144+
if c["delete"]:
145+
save_milestone_in_history(m)
144146

145-
form_errors = form_errors or not f.is_valid()
147+
m.state_id = "deleted"
148+
m.save()
146149

147-
if not form_errors:
148-
for f in forms:
149-
c = f.cleaned_data
150+
return 'Deleted %s' % named_milestone
151+
152+
# compute changes
153+
history = None
150154

151-
if f.milestone:
152-
m = f.milestone
155+
changes = ['Changed %s' % named_milestone]
153156

154-
named_milestone = 'milestone "%s"' % m.desc
155-
if milestone_set == "charter":
156-
named_milestone = "charter " + named_milestone
157+
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
158+
if not history:
159+
history = save_milestone_in_history(m)
157160

158-
if c["delete"]:
159-
save_milestone_in_history(m)
161+
if c["accept"] == "accept":
162+
m.state_id = "active"
163+
changes.append("changed state from review to active, accepting new milestone")
164+
elif c["accept"] == "reject":
165+
m.state_id = "deleted"
166+
changes.append("changed state from review to deleted, rejecting new milestone")
160167

161-
m.state_id = "deleted"
162-
m.save()
163168

164-
add_event(m, 'Deleted %s' % named_milestone)
169+
if c["desc"] != m.desc and not needs_review:
170+
if not history:
171+
history = save_milestone_in_history(m)
172+
m.desc = c["desc"]
173+
changes.append('changed description to "%s"' % m.desc)
165174

166-
continue
175+
if c["due"] != m.due:
176+
if not history:
177+
history = save_milestone_in_history(m)
178+
m.due = c["due"]
179+
changes.append('changed due date to %s' % m.due.strftime("%Y-%m-%d"))
167180

168-
# compute changes
169-
history = None
181+
resolved = c["resolved"]
182+
if resolved != m.resolved:
183+
if resolved and not m.resolved:
184+
changes.append('resolved as "%s"' % resolved)
185+
elif not resolved and m.resolved:
186+
changes.append("reverted to not being resolved")
187+
elif resolved and m.resolved:
188+
changes.append('changed resolution to "%s"' % resolved)
170189

171-
changes = ['Changed %s' % named_milestone]
190+
if not history:
191+
history = save_milestone_in_history(m)
172192

173-
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
174-
if not history:
175-
history = save_milestone_in_history(m)
193+
m.resolved = resolved
176194

177-
if c["accept"] == "accept":
178-
m.state_id = "active"
179-
changes.append("changed state from review to active, accepting new milestone")
180-
elif c["accept"] == "reject":
181-
m.state_id = "deleted"
182-
changes.append("changed state from review to deleted, rejecting new milestone")
195+
new_docs = set(c["docs"])
196+
old_docs = set(m.docs.all())
197+
if new_docs != old_docs:
198+
added = new_docs - old_docs
199+
if added:
200+
changes.append('added %s to milestone' % ", ".join(d.name for d in added))
183201

202+
removed = old_docs - new_docs
203+
if removed:
204+
changes.append('removed %s from milestone' % ", ".join(d.name for d in removed))
184205

185-
if c["desc"] != m.desc and not needs_review:
186-
if not history:
187-
history = save_milestone_in_history(m)
188-
m.desc = c["desc"]
189-
changes.append('changed description to "%s"' % m.desc)
206+
if not history:
207+
history = save_milestone_in_history(m)
190208

191-
if c["due"] != m.due:
192-
if not history:
193-
history = save_milestone_in_history(m)
194-
m.due = c["due"]
195-
changes.append('changed due date to %s' % m.due.strftime("%Y-%m-%d"))
209+
m.docs = new_docs
196210

197-
resolved = c["resolved"]
198-
if resolved != m.resolved:
199-
if resolved and not m.resolved:
200-
changes.append('resolved as "%s"' % resolved)
201-
elif not resolved and m.resolved:
202-
changes.append("reverted to not being resolved")
203-
elif resolved and m.resolved:
204-
changes.append('changed resolution to "%s"' % resolved)
211+
if len(changes) > 1:
212+
m.save()
205213

206-
if not history:
207-
history = save_milestone_in_history(m)
214+
return ", ".join(changes)
208215

209-
m.resolved = resolved
216+
else: # new milestone
217+
m = f.milestone = GroupMilestone()
218+
set_attributes_from_form(f, m)
219+
m.save()
210220

211-
new_docs = set(c["docs"])
212-
old_docs = set(m.docs.all())
213-
if new_docs != old_docs:
214-
added = new_docs - old_docs
215-
if added:
216-
changes.append('added %s to milestone' % ", ".join(d.name for d in added))
221+
m.docs = c["docs"]
217222

218-
removed = old_docs - new_docs
219-
if removed:
220-
changes.append('removed %s from milestone' % ", ".join(d.name for d in removed))
223+
named_milestone = 'milestone "%s"' % m.desc
224+
if milestone_set == "charter":
225+
named_milestone = "charter " + named_milestone
221226

222-
if not history:
223-
history = save_milestone_in_history(m)
227+
if m.state_id in ("active", "charter"):
228+
return 'Added %s, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d"))
229+
elif m.state_id == "review":
230+
return 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d"))
224231

225-
m.docs = new_docs
232+
finished_milestone_text = "Done"
233+
234+
form_errors = False
226235

227-
if len(changes) > 1:
228-
add_event(m, ", ".join(changes))
236+
if request.method == 'POST':
237+
# parse out individual milestone forms
238+
for prefix in request.POST.getlist("prefix"):
239+
if not prefix: # empty form
240+
continue
229241

230-
m.save()
242+
# new milestones have non-existing ids so instance end up as None
243+
instance = milestones_dict.get(request.POST.get(prefix + "-id", ""), None)
244+
f = MilestoneForm(request.POST, prefix=prefix, instance=instance,
245+
needs_review=needs_review)
246+
forms.append(f)
231247

232-
else: # new milestone
233-
m = GroupMilestone()
234-
m.group = group
235-
if milestone_set == "current":
236-
if needs_review:
237-
m.state = GroupMilestoneStateName.objects.get(slug="review")
238-
else:
239-
m.state = GroupMilestoneStateName.objects.get(slug="active")
240-
elif milestone_set == "charter":
241-
m.state = GroupMilestoneStateName.objects.get(slug="charter")
242-
m.desc = c["desc"]
243-
m.due = c["due"]
244-
m.resolved = c["resolved"]
245-
m.save()
248+
form_errors = form_errors or not f.is_valid()
246249

247-
m.docs = c["docs"]
250+
action = request.POST.get("action", "review")
251+
if action == "review":
252+
for f in forms:
253+
if not f.is_valid():
254+
continue
255+
256+
# let's fill in the form milestone so we can output it in the template
257+
if not f.milestone:
258+
f.milestone = GroupMilestone()
259+
set_attributes_from_form(f, f.milestone)
260+
elif action == "save" and not form_errors:
261+
for f in forms:
262+
change = save_milestone_form(f)
248263

249-
named_milestone = 'milestone "%s"' % m.desc
250-
if milestone_set == "charter":
251-
named_milestone = "charter " + named_milestone
264+
if not change:
265+
continue
252266

253-
if m.state_id in ("active", "charter"):
254-
add_event(m, 'Added %s, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d")))
255-
elif m.state_id == "review":
256-
add_event(m, 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%Y-%m-%d")))
267+
if milestone_set == "charter":
268+
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
269+
by=login, desc=change)
270+
else:
271+
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
272+
by=login, desc=change, milestone=f.milestone)
257273

258274
if milestone_set == "charter":
259275
return redirect('doc_view', name=group.charter.canonical_name())
@@ -267,6 +283,8 @@ def add_event(m, desc):
267283

268284
empty_form = MilestoneForm(needs_review=needs_review)
269285

286+
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
287+
270288
return render_to_response('wginfo/edit_milestones.html',
271289
dict(group=group,
272290
title=title,

ietf/wginfo/tests.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ def test_add_milestone(self):
316316
'm-1-due': due.strftime("%Y-%m-%d"),
317317
'm-1-resolved': "",
318318
'm-1-docs': ",".join(docs),
319+
'action': "save",
319320
})
320321
self.assertEquals(r.status_code, 200)
321322
q = PyQuery(r.content)
@@ -329,6 +330,7 @@ def test_add_milestone(self):
329330
'm-1-due': due.strftime("%Y-%m-%d"),
330331
'm-1-resolved': "",
331332
'm-1-docs': ",".join(docs),
333+
'action': "save",
332334
})
333335
self.assertEquals(r.status_code, 302)
334336
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
@@ -362,6 +364,7 @@ def test_add_milestone_as_chair(self):
362364
'm-1-due': due.strftime("%Y-%m-%d"),
363365
'm-1-resolved': "",
364366
'm-1-docs': "",
367+
'action': "save",
365368
})
366369
self.assertEquals(r.status_code, 302)
367370
self.assertEquals(GroupMilestone.objects.count(), milestones_before + 1)
@@ -394,6 +397,7 @@ def test_accept_milestone(self):
394397
'm1-resolved': m1.resolved,
395398
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
396399
'm1-accept': "accept",
400+
'action': "save",
397401
})
398402
self.assertEquals(r.status_code, 302)
399403

@@ -419,6 +423,7 @@ def test_delete_milestone(self):
419423
'm1-resolved': "",
420424
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
421425
'm1-delete': "checked",
426+
'action': "save",
422427
})
423428
self.assertEquals(r.status_code, 302)
424429
self.assertEquals(GroupMilestone.objects.count(), milestones_before)
@@ -447,6 +452,7 @@ def test_edit_milestone(self):
447452
'm1-due': due.strftime("%Y-%m-%d"),
448453
'm1-resolved': "",
449454
'm1-docs': ",".join(docs),
455+
'action': "save",
450456
})
451457
self.assertEquals(r.status_code, 200)
452458
q = PyQuery(r.content)
@@ -463,6 +469,7 @@ def test_edit_milestone(self):
463469
'm1-resolved': "Done",
464470
'm1-resolved_checkbox': "checked",
465471
'm1-docs': ",".join(docs),
472+
'action': "save",
466473
})
467474
self.assertEquals(r.status_code, 302)
468475
self.assertEquals(GroupMilestone.objects.count(), milestones_before)

0 commit comments

Comments
 (0)