Skip to content

Commit e171aa6

Browse files
committed
Add support for revising a closed review, mostly to allow reviewers to
correct historic entries with missing data - Legacy-Id: 12314
1 parent 39d674b commit e171aa6

5 files changed

Lines changed: 156 additions & 59 deletions

File tree

ietf/doc/tests_review.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from ietf.person.models import Email, Person
2020
from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewTypeName, DocRelationshipName
2121
from ietf.group.models import Group
22-
from ietf.doc.models import DocumentAuthor, Document, DocAlias, RelatedDocument, DocEvent
22+
from ietf.doc.models import DocumentAuthor, Document, DocAlias, RelatedDocument, DocEvent, ReviewRequestDocEvent
2323
from ietf.utils.test_utils import TestCase
2424
from ietf.utils.test_data import make_test_data, make_review_data, create_person
2525
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
@@ -553,7 +553,6 @@ def test_complete_review_enter_content(self):
553553

554554
login_testing_unauthorized(self, review_req.reviewer.person.user.username, url)
555555

556-
# complete by uploading file
557556
empty_outbox()
558557

559558
r = self.client.post(url, data={
@@ -584,7 +583,6 @@ def test_complete_review_link_to_mailing_list(self):
584583

585584
login_testing_unauthorized(self, review_req.reviewer.person.user.username, url)
586585

587-
# complete by uploading file
588586
empty_outbox()
589587

590588
r = self.client.post(url, data={
@@ -664,3 +662,58 @@ def test_partially_complete_review(self):
664662
second_review = review_req.review
665663
self.assertTrue(first_review.name != second_review.name)
666664
self.assertTrue(second_review.name.endswith("-2")) # uniquified
665+
666+
def test_revise_review_enter_content(self):
667+
review_req, url = self.setup_complete_review_test()
668+
review_req.state = ReviewRequestStateName.objects.get(slug="no-response")
669+
review_req.save()
670+
671+
login_testing_unauthorized(self, review_req.reviewer.person.user.username, url)
672+
673+
empty_outbox()
674+
675+
r = self.client.post(url, data={
676+
"result": ReviewResultName.objects.get(resultusedinreviewteam__team=review_req.team, slug="ready").pk,
677+
"state": ReviewRequestStateName.objects.get(slug="completed").pk,
678+
"reviewed_rev": review_req.doc.rev,
679+
"review_submission": "enter",
680+
"review_content": "This is a review\nwith two lines",
681+
"review_url": "",
682+
"review_file": "",
683+
"completion_date": "2012-12-24",
684+
"completion_time": "12:13:14",
685+
})
686+
self.assertEqual(r.status_code, 302)
687+
688+
review_req = reload_db_objects(review_req)
689+
self.assertEqual(review_req.state_id, "completed")
690+
event = ReviewRequestDocEvent.objects.get(type="closed_review_request", review_request=review_req)
691+
self.assertEqual(event.time, datetime.datetime(2012, 12, 24, 12, 13, 14))
692+
693+
with open(os.path.join(self.review_subdir, review_req.review.name + ".txt")) as f:
694+
self.assertEqual(f.read(), "This is a review\nwith two lines")
695+
696+
self.assertEqual(len(outbox), 0)
697+
698+
# revise again
699+
empty_outbox()
700+
r = self.client.post(url, data={
701+
"result": ReviewResultName.objects.get(resultusedinreviewteam__team=review_req.team, slug="ready").pk,
702+
"state": ReviewRequestStateName.objects.get(slug="part-completed").pk,
703+
"reviewed_rev": review_req.doc.rev,
704+
"review_submission": "enter",
705+
"review_content": "This is a revised review",
706+
"review_url": "",
707+
"review_file": "",
708+
"completion_date": "2013-12-24",
709+
"completion_time": "11:11:11",
710+
})
711+
self.assertEqual(r.status_code, 302)
712+
713+
review_req = reload_db_objects(review_req)
714+
self.assertEqual(review_req.review.rev, "01")
715+
event = ReviewRequestDocEvent.objects.get(type="closed_review_request", review_request=review_req)
716+
self.assertEqual(event.time, datetime.datetime(2013, 12, 24, 11, 11, 11))
717+
718+
self.assertEqual(len(outbox), 0)
719+

ietf/doc/views_review.py

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@ def review_request(request, name, request_id):
165165
and review_req.reviewer
166166
and (is_reviewer or can_manage_request))
167167

168-
can_complete_review = (review_req.state_id in ["requested", "accepted"]
168+
can_complete_review = (review_req.state_id in ["requested", "accepted", "overtaken", "no-response", "part-completed", "completed"]
169169
and review_req.reviewer
170170
and (is_reviewer or can_manage_request))
171-
171+
172172
if request.method == "POST" and request.POST.get("action") == "accept" and can_accept_reviewer_assignment:
173173
review_req.state = ReviewRequestStateName.objects.get(slug="accepted")
174174
review_req.save()
@@ -331,6 +331,8 @@ class CompleteReviewForm(forms.Form):
331331
review_url = forms.URLField(label="Link to message", required=False)
332332
review_file = forms.FileField(label="Text file to upload", required=False)
333333
review_content = forms.CharField(widget=forms.Textarea, required=False)
334+
completion_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={ "autoclose": "1" }, initial=datetime.date.today, help_text="Date of announcement of the results of this review")
335+
completion_time = forms.TimeField(widget=forms.HiddenInput, initial=datetime.time.min)
334336
cc = forms.CharField(required=False, help_text="Email addresses to send to in addition to the review team list")
335337

336338
def __init__(self, review_req, *args, **kwargs):
@@ -342,20 +344,33 @@ def __init__(self, review_req, *args, **kwargs):
342344

343345
known_revisions = NewRevisionDocEvent.objects.filter(doc=doc).order_by("time", "id").values_list("rev", flat=True)
344346

345-
self.fields["state"].choices = [
346-
(slug, "{} - extra reviewer is to be assigned".format(label)) if slug == "part-completed" else (slug, label)
347-
for slug, label in self.fields["state"].choices
348-
]
347+
revising_review = review_req.state_id not in ["requested", "accepted"]
348+
349+
if not revising_review:
350+
self.fields["state"].choices = [
351+
(slug, "{} - extra reviewer is to be assigned".format(label)) if slug == "part-completed" else (slug, label)
352+
for slug, label in self.fields["state"].choices
353+
]
349354

350355
self.fields["reviewed_rev"].help_text = mark_safe(
351356
" ".join("<a class=\"rev label label-default\">{}</a>".format(r)
352357
for r in known_revisions))
353358

354359
self.fields["result"].queryset = self.fields["result"].queryset.filter(resultusedinreviewteam__team=review_req.team)
355-
self.fields["review_submission"].choices = [
356-
(k, label.format(mailing_list=review_req.team.list_email or "[error: team has no mailing list set]"))
357-
for k, label in self.fields["review_submission"].choices
358-
]
360+
361+
def format_submission_choice(label):
362+
if revising_review:
363+
label = label.replace(" (automatically posts to {mailing_list})", "")
364+
365+
return label.format(mailing_list=review_req.team.list_email or "[error: team has no mailing list set]")
366+
367+
self.fields["review_submission"].choices = [ (k, format_submission_choice(label)) for k, label in self.fields["review_submission"].choices]
368+
369+
if revising_review:
370+
del self.fields["cc"]
371+
else:
372+
del self.fields["completion_date"]
373+
del self.fields["completion_time"]
359374

360375
def clean_reviewed_rev(self):
361376
return clean_doc_revision(self.review_req.doc, self.cleaned_data.get("reviewed_rev"))
@@ -386,7 +401,9 @@ def require_field(f):
386401
@login_required
387402
def complete_review(request, name, request_id):
388403
doc = get_object_or_404(Document, name=name)
389-
review_req = get_object_or_404(ReviewRequest, pk=request_id, state__in=["requested", "accepted"])
404+
review_req = get_object_or_404(ReviewRequest, pk=request_id)
405+
406+
revising_review = review_req.state_id not in ["requested", "accepted"]
390407

391408
if not review_req.reviewer:
392409
return redirect(review_request, name=review_req.doc.name, request_id=review_req.pk)
@@ -402,30 +419,34 @@ def complete_review(request, name, request_id):
402419
if form.is_valid():
403420
review_submission = form.cleaned_data['review_submission']
404421

405-
# create review doc
406-
for i in range(1, 100):
407-
name_components = [
408-
"review",
409-
strip_prefix(review_req.doc.name, "draft-"),
410-
form.cleaned_data["reviewed_rev"],
411-
review_req.team.acronym,
412-
review_req.type.slug,
413-
xslugify(review_req.reviewer.person.ascii_parts()[3]),
414-
datetime.date.today().isoformat(),
415-
]
416-
if i > 1:
417-
name_components.append(str(i))
418-
419-
name = "-".join(c for c in name_components if c).lower()
420-
if not Document.objects.filter(name=name).exists():
421-
review = Document.objects.create(name=name)
422-
DocAlias.objects.create(document=review, name=review.name)
423-
break
424-
425-
review.type = DocTypeName.objects.get(slug="review")
426-
review.rev = "00"
422+
review = review_req.review
423+
if not review:
424+
# create review doc
425+
for i in range(1, 100):
426+
name_components = [
427+
"review",
428+
strip_prefix(review_req.doc.name, "draft-"),
429+
form.cleaned_data["reviewed_rev"],
430+
review_req.team.acronym,
431+
review_req.type.slug,
432+
xslugify(review_req.reviewer.person.ascii_parts()[3]),
433+
datetime.date.today().isoformat(),
434+
]
435+
if i > 1:
436+
name_components.append(str(i))
437+
438+
name = "-".join(c for c in name_components if c).lower()
439+
if not Document.objects.filter(name=name).exists():
440+
review = Document.objects.create(name=name)
441+
DocAlias.objects.create(document=review, name=review.name)
442+
break
443+
444+
review.type = DocTypeName.objects.get(slug="review")
445+
review.group = review_req.team
446+
447+
review.rev = "00" if not review.rev else "{:02}".format(int(review.rev) + 1)
427448
review.title = "{} Review of {}-{}".format(review_req.type.name, review_req.doc.name, form.cleaned_data["reviewed_rev"])
428-
review.group = review_req.team
449+
review.time = datetime.datetime.now()
429450
if review_submission == "link":
430451
review.external_url = form.cleaned_data['review_url']
431452

@@ -459,7 +480,7 @@ def complete_review(request, name, request_id):
459480
review_req.review = review
460481
review_req.save()
461482

462-
need_to_email_review = review_submission != "link" and review_req.team.list_email
483+
need_to_email_review = review_submission != "link" and review_req.team.list_email and not revising_review
463484

464485
desc = "Request for {} review by {} {}: {}. Reviewer: {}.".format(
465486
review_req.type.name,
@@ -471,16 +492,22 @@ def complete_review(request, name, request_id):
471492
if need_to_email_review:
472493
desc += " " + "Sent review to list."
473494

474-
close_event = ReviewRequestDocEvent.objects.create(
475-
type="closed_review_request",
476-
doc=review_req.doc,
477-
by=request.user.person,
478-
desc=desc,
479-
review_request=review_req,
480-
state=review_req.state,
481-
)
495+
completion_datetime = datetime.datetime.now()
496+
if "completion_date" in form.cleaned_data:
497+
completion_datetime = datetime.datetime.combine(form.cleaned_data["completion_date"], form.cleaned_data.get("completion_time") or datetime.time.min)
498+
499+
close_event = ReviewRequestDocEvent.objects.filter(type="closed_review_request", review_request=review_req).first()
500+
if not close_event:
501+
close_event = ReviewRequestDocEvent(type="closed_review_request", review_request=review_req)
502+
503+
close_event.doc = review_req.doc
504+
close_event.by = request.user.person
505+
close_event.desc = desc
506+
close_event.state = review_req.state
507+
close_event.time = completion_datetime
508+
close_event.save()
482509

483-
if review_req.state_id == "part-completed":
510+
if review_req.state_id == "part-completed" and not revising_review:
484511
existing_open_reqs = ReviewRequest.objects.filter(doc=review_req.doc, team=review_req.team, state__in=("requested", "accepted"))
485512

486513
new_review_req_url = new_review_req = None
@@ -519,7 +546,10 @@ def complete_review(request, name, request_id):
519546

520547
return redirect("doc_view", name=review_req.review.name)
521548
else:
522-
form = CompleteReviewForm(review_req)
549+
form = CompleteReviewForm(review_req, initial={
550+
"reviewed_rev": review_req.reviewed_rev,
551+
"result": review_req.result_id
552+
})
523553

524554
mail_archive_query_urls = mailarch.construct_query_urls(review_req)
525555

@@ -528,11 +558,12 @@ def complete_review(request, name, request_id):
528558
'review_req': review_req,
529559
'form': form,
530560
'mail_archive_query_urls': mail_archive_query_urls,
561+
'revising_review': revising_review,
531562
})
532563

533564
def search_mail_archive(request, name, request_id):
534565
#doc = get_object_or_404(Document, name=name)
535-
review_req = get_object_or_404(ReviewRequest, pk=request_id, state__in=["requested", "accepted"])
566+
review_req = get_object_or_404(ReviewRequest, pk=request_id)
536567

537568
is_reviewer = user_is_person(request.user, review_req.reviewer.person)
538569
can_manage_request = can_manage_review_requests_for_team(request.user, review_req.team)

ietf/static/ietf/js/complete-review.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ $(document).ready(function () {
7272
row.find(".from").text(msg.splitfrom[0]);
7373
row.data("url", msg.url);
7474
row.data("content", msg.content);
75+
row.data("date", msg.utcdate[0]);
76+
row.data("time", msg.utcdate[1]);
7577
results.append(row);
7678
}
7779
}
@@ -99,6 +101,8 @@ $(document).ready(function () {
99101

100102
form.find("[name=review_url]").val(row.data("url"));
101103
form.find("[name=review_content]").val(row.data("content")).prop("scrollTop", 0);
104+
form.find("[name=completion_date]").val(row.data("date"));
105+
form.find("[name=completion_time]").val(row.data("time"));
102106
});
103107

104108

ietf/templates/doc/review/complete_review.html

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,34 @@
22
{# Copyright The IETF Trust 2016, All Rights Reserved #}
33
{% load origin bootstrap3 static %}
44

5-
{% block title %}Complete review of {{ review_req.doc.name }}{% endblock %}
5+
{% block title %}{% if revising_review %}Revise{% else %}Complete{% endif %} review of {{ review_req.doc.name }}{% endblock %}
6+
7+
{% block pagehead %}
8+
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
9+
{% endblock %}
610

711
{% block content %}
812
{% origin %}
9-
<h1>Complete review<br><small>{{ review_req.doc.name }}</small></h1>
10-
11-
<p>The review findings should be made available here and the review
12-
posted to the mailing list. If you enter the findings below, the
13-
system will post the review for you. If you already have posted
14-
the review, you can try to let the system find the link to the
15-
archive and retrieve the email body.</p>
13+
<h1>{% if revising_review %}Revise{% else %}Complete{% endif %} review<br><small>{{ review_req.doc.name }}</small></h1>
1614

15+
{% if not revising_review %}
16+
<p>The review findings should be made available here and the review
17+
posted to the mailing list. If you enter the findings below, the
18+
system will post the review for you. If you already have posted
19+
the review, you can try to let the system find the link to the
20+
archive and retrieve the email body.</p>
21+
{% else %}
22+
<p>You can revise this review by entering the results below.</p>
23+
{% endif %}
24+
1725
<form class="complete-review form-horizontal" method="post" enctype="multipart/form-data">
1826
{% csrf_token %}
1927

2028
{% bootstrap_form form layout="horizontal" %}
2129

2230
{% buttons %}
2331
<a class="btn btn-default" href="{% url "ietf.doc.views_review.review_request" name=doc.canonical_name request_id=review_req.pk %}">Cancel</a>
24-
<button type="submit" class="btn btn-primary">Complete review</button>
32+
<button type="submit" class="btn btn-primary">{% if revising_review %}Revise{% else %}Complete{% endif %} review</button>
2533
{% endbuttons %}
2634

2735
<div class="template" style="display:none">
@@ -76,6 +84,7 @@ <h1>Complete review<br><small>{{ review_req.doc.name }}</small></h1>
7684
{% endblock %}
7785

7886
{% block js %}
87+
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
7988
<script>
8089
var searchMailArchiveUrl = "{% url "ietf.doc.views_review.search_mail_archive" name=review_req.doc.name request_id=review_req.pk %}";
8190
</script>

ietf/templates/doc/review/review_request.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ <h1>Review request<br><small>{{ review_req.doc.name }}</small></h1>
124124
{% endif %}
125125

126126
{% if can_complete_review %}
127-
<a class="btn btn-primary btn-xs" href="{% url "ietf.doc.views_review.complete_review" name=doc.name request_id=review_req.pk %}"><span class="fa fa-pencil-square-o"></span> Complete review</a>
127+
<a class="btn btn-primary btn-xs" href="{% url "ietf.doc.views_review.complete_review" name=doc.name request_id=review_req.pk %}"><span class="fa fa-pencil-square-o"></span> {% if review_req.state_id == "requested" or review_req.state_id == "accepted" %}Complete review{% else %}Correct review{% endif %}</a>
128128
{% endif %}
129129
</td>
130130
</tr>

0 commit comments

Comments
 (0)