Skip to content

Commit e2e6652

Browse files
committed
Add review request page for review teams and first draft of manage
review requests page. Add importer for importing review data from the existing Perl tool (WIP, gets most but not all of the interesting information out). Fix various bugs. - Legacy-Id: 11508
1 parent 1a76c66 commit e2e6652

29 files changed

Lines changed: 1131 additions & 100 deletions

ietf/doc/tests_review.py

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,15 @@
1212

1313
import debug # pyflakes:ignore
1414

15-
from ietf.review.models import ReviewRequest, Reviewer
15+
from ietf.review.models import ReviewRequest, ReviewTeamResult
1616
import ietf.review.mailarch
17-
from ietf.person.models import Person
18-
from ietf.group.models import Group, Role
17+
from ietf.person.models import Email
1918
from ietf.name.models import ReviewResultName, ReviewRequestStateName
2019
from ietf.utils.test_utils import TestCase
21-
from ietf.utils.test_data import make_test_data
20+
from ietf.utils.test_data import make_test_data, make_review_data
2221
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
2322
from ietf.utils.mail import outbox, empty_outbox
2423

25-
def make_review_data(doc):
26-
team = Group.objects.create(state_id="active", acronym="reviewteam", name="Review Team", type_id="team")
27-
team.reviewresultname_set.add(ReviewResultName.objects.filter(slug__in=["issues", "ready-issues", "ready", "not-ready"]))
28-
29-
p = Person.objects.get(user__username="plain")
30-
role = Role.objects.create(name_id="reviewer", person=p, email=p.email_set.first(), group=team)
31-
Reviewer.objects.create(team=team, person=p, frequency=14, skip_next=0)
32-
33-
review_req = ReviewRequest.objects.create(
34-
doc=doc,
35-
team=team,
36-
type_id="early",
37-
deadline=datetime.datetime.now() + datetime.timedelta(days=20),
38-
state_id="ready",
39-
reviewer=role,
40-
reviewed_rev="01",
41-
)
42-
43-
p = Person.objects.get(user__username="marschairman")
44-
role = Role.objects.create(name_id="reviewer", person=p, email=p.email_set.first(), group=team)
45-
46-
p = Person.objects.get(user__username="secretary")
47-
role = Role.objects.create(name_id="secretary", person=p, email=p.email_set.first(), group=team)
48-
49-
return review_req
50-
5124
class ReviewTests(TestCase):
5225
def setUp(self):
5326
self.review_dir = os.path.abspath("tmp-review-dir")
@@ -169,7 +142,7 @@ def test_assign_reviewer(self):
169142

170143
# assign
171144
empty_outbox()
172-
reviewer = Role.objects.filter(name="reviewer", group=review_req.team).first()
145+
reviewer = Email.objects.filter(role__name="reviewer", role__group=review_req.team).first()
173146
r = self.client.post(assign_url, { "action": "assign", "reviewer": reviewer.pk })
174147
self.assertEqual(r.status_code, 302)
175148

@@ -183,7 +156,7 @@ def test_assign_reviewer(self):
183156
empty_outbox()
184157
review_req.state = ReviewRequestStateName.objects.get(slug="accepted")
185158
review_req.save()
186-
reviewer = Role.objects.filter(name="reviewer", group=review_req.team).exclude(pk=reviewer.pk).first()
159+
reviewer = Email.objects.filter(role__name="reviewer", role__group=review_req.team).exclude(pk=reviewer.pk).first()
187160
r = self.client.post(assign_url, { "action": "assign", "reviewer": reviewer.pk })
188161
self.assertEqual(r.status_code, 302)
189162

@@ -335,7 +308,7 @@ def setup_complete_review_test(self):
335308
review_req.save()
336309
review_req.team.list_email = "{}@ietf.org".format(review_req.team.acronym)
337310
for r in ReviewResultName.objects.filter(slug__in=("issues", "ready")):
338-
review_req.team.reviewresultname_set.add(r)
311+
ReviewTeamResult.objects.get_or_create(team=review_req.team, result=r)
339312
review_req.team.save()
340313

341314
url = urlreverse('ietf.doc.views_review.complete_review', kwargs={ "name": doc.name, "request_id": review_req.pk })
@@ -373,7 +346,7 @@ def test_complete_review_upload_content(self):
373346
test_file.name = "unnamed"
374347

375348
r = self.client.post(url, data={
376-
"result": ReviewResultName.objects.get(teams=review_req.team, slug="ready").pk,
349+
"result": ReviewResultName.objects.get(reviewteamresult__team=review_req.team, slug="ready").pk,
377350
"state": ReviewRequestStateName.objects.get(slug="completed").pk,
378351
"reviewed_rev": review_req.doc.rev,
379352
"review_submission": "upload",
@@ -408,7 +381,7 @@ def test_complete_review_enter_content(self):
408381
empty_outbox()
409382

410383
r = self.client.post(url, data={
411-
"result": ReviewResultName.objects.get(teams=review_req.team, slug="ready").pk,
384+
"result": ReviewResultName.objects.get(reviewteamresult__team=review_req.team, slug="ready").pk,
412385
"state": ReviewRequestStateName.objects.get(slug="completed").pk,
413386
"reviewed_rev": review_req.doc.rev,
414387
"review_submission": "enter",
@@ -439,7 +412,7 @@ def test_complete_review_link_to_mailing_list(self):
439412
empty_outbox()
440413

441414
r = self.client.post(url, data={
442-
"result": ReviewResultName.objects.get(teams=review_req.team, slug="ready").pk,
415+
"result": ReviewResultName.objects.get(reviewteamresult__team=review_req.team, slug="ready").pk,
443416
"state": ReviewRequestStateName.objects.get(slug="completed").pk,
444417
"reviewed_rev": review_req.doc.rev,
445418
"review_submission": "link",
@@ -467,7 +440,7 @@ def test_partially_complete_review(self):
467440
empty_outbox()
468441

469442
r = self.client.post(url, data={
470-
"result": ReviewResultName.objects.get(teams=review_req.team, slug="ready").pk,
443+
"result": ReviewResultName.objects.get(reviewteamresult__team=review_req.team, slug="ready").pk,
471444
"state": ReviewRequestStateName.objects.get(slug="part-completed").pk,
472445
"reviewed_rev": review_req.doc.rev,
473446
"review_submission": "enter",
@@ -501,7 +474,7 @@ def test_partially_complete_review(self):
501474
url = urlreverse('ietf.doc.views_review.complete_review', kwargs={ "name": review_req.doc.name, "request_id": review_req.pk })
502475

503476
r = self.client.post(url, data={
504-
"result": ReviewResultName.objects.get(teams=review_req.team, slug="ready").pk,
477+
"result": ReviewResultName.objects.get(reviewteamresult__team=review_req.team, slug="ready").pk,
505478
"state": ReviewRequestStateName.objects.get(slug="completed").pk,
506479
"reviewed_rev": review_req.doc.rev,
507480
"review_submission": "enter",

ietf/doc/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import urllib
44
import math
55
import datetime
6+
from collections import defaultdict
67

78
from django.conf import settings
89
from django.db.models.query import EmptyQuerySet
@@ -556,6 +557,39 @@ def uppercase_std_abbreviated_name(name):
556557
else:
557558
return name
558559

560+
def extract_complete_replaces_ancestor_mapping_for_docs(names):
561+
"""Return dict mapping all replaced by relationships of the
562+
replacement ancestors to docs. So if x is directly replaced by y
563+
and y is in names or replaced by something in names, x in
564+
replaces[y]."""
565+
566+
replaces = defaultdict(set)
567+
568+
checked = set()
569+
front = names
570+
while True:
571+
if not front:
572+
break
573+
574+
relations = RelatedDocument.objects.filter(
575+
source__in=front, relationship="replaces"
576+
).select_related("target").values_list("source", "target__document")
577+
578+
if not relations:
579+
break
580+
581+
checked.update(front)
582+
583+
front = []
584+
for source_doc, target_doc in relations:
585+
replaces[source_doc].add(target_doc)
586+
587+
if target_doc not in checked:
588+
front.append(target_doc)
589+
590+
return replaces
591+
592+
559593
def crawl_history(doc):
560594
# return document history data for inclusion in doc.json (used by timeline)
561595
def get_ancestors(doc):

ietf/doc/views_doc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def document_main(request, name, rev=None):
357357
published = doc.latest_event(type="published_rfc")
358358
started_iesg_process = doc.latest_event(type="started_iesg_process")
359359

360-
review_requests = ReviewRequest.objects.filter(doc=doc).exclude(state__in=["withdrawn", "rejected"])
360+
review_requests = ReviewRequest.objects.filter(doc=doc).exclude(state__in=["withdrawn", "rejected", "overtaken", "no-response"]).order_by("-time", "-id")
361361

362362
return render_to_response("doc/document_draft.html",
363363
dict(doc=doc,

ietf/doc/views_review.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from ietf.doc.models import Document, NewRevisionDocEvent, DocEvent, State, DocAlias
1313
from ietf.ietfauth.utils import is_authorized_in_doc_stream, user_is_person
1414
from ietf.name.models import ReviewRequestStateName, ReviewResultName, DocTypeName
15-
from ietf.group.models import Role
1615
from ietf.review.models import ReviewRequest
16+
from ietf.person.fields import PersonEmailChoiceField
1717
from ietf.review.utils import (active_review_teams, assign_review_request_to_reviewer,
1818
can_request_review_of_doc, can_manage_review_requests_for_team,
1919
email_about_review_request, make_new_review_request_from_existing)
@@ -188,22 +188,13 @@ def withdraw_request(request, name, request_id):
188188
'review_req': review_req,
189189
})
190190

191-
class PersonEmailLabeledRoleModelChoiceField(forms.ModelChoiceField):
192-
def __init__(self, *args, **kwargs):
193-
if not "queryset" in kwargs:
194-
kwargs["queryset"] = Role.objects.select_related("person", "email")
195-
super(PersonEmailLabeledRoleModelChoiceField, self).__init__(*args, **kwargs)
196-
197-
def label_from_instance(self, role):
198-
return u"{} <{}>".format(role.person.name, role.email.address)
199-
200191
class AssignReviewerForm(forms.Form):
201-
reviewer = PersonEmailLabeledRoleModelChoiceField(widget=forms.RadioSelect, empty_label="(None)", required=False)
192+
reviewer = PersonEmailChoiceField(widget=forms.RadioSelect, empty_label="(None)", required=False)
202193

203194
def __init__(self, review_req, *args, **kwargs):
204195
super(AssignReviewerForm, self).__init__(*args, **kwargs)
205196
f = self.fields["reviewer"]
206-
f.queryset = f.queryset.filter(name="reviewer", group=review_req.team)
197+
f.queryset = f.queryset.filter(role__name="reviewer", role__group=review_req.team)
207198
if review_req.reviewer:
208199
f.initial = review_req.reviewer_id
209200

@@ -212,9 +203,7 @@ def assign_reviewer(request, name, request_id):
212203
doc = get_object_or_404(Document, name=name)
213204
review_req = get_object_or_404(ReviewRequest, pk=request_id, state__in=["requested", "accepted"])
214205

215-
can_manage_request = can_manage_review_requests_for_team(request.user, review_req.team)
216-
217-
if not can_manage_request:
206+
if not can_manage_review_requests_for_team(request.user, review_req.team):
218207
return HttpResponseForbidden("You do not have permission to perform this action")
219208

220209
if request.method == "POST" and request.POST.get("action") == "assign":
@@ -322,7 +311,7 @@ def __init__(self, review_req, *args, **kwargs):
322311
" ".join("<a class=\"rev label label-default\">{}</a>".format(r)
323312
for r in known_revisions))
324313

325-
self.fields["result"].queryset = self.fields["result"].queryset.filter(teams=review_req.team)
314+
self.fields["result"].queryset = self.fields["result"].queryset.filter(reviewteamresult__team=review_req.team)
326315
self.fields["review_submission"].choices = [
327316
(k, label.format(mailing_list=review_req.team.list_email or "[error: team has no mailing list set]"))
328317
for k, label in self.fields["review_submission"].choices
@@ -428,10 +417,12 @@ def complete_review(request, name, request_id):
428417
type="changed_review_request",
429418
doc=review_req.doc,
430419
by=request.user.person,
431-
desc="Request for {} review by {} {}".format(
420+
desc="Request for {} review by {} {}: {}. Reviewer: {}".format(
432421
review_req.type.name,
433422
review_req.team.acronym.upper(),
434423
review_req.state.name,
424+
review_req.result.name,
425+
review_req.reviewer,
435426
),
436427
)
437428

ietf/group/features.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class GroupFeatures(object):
66
has_chartering_process = False
77
has_documents = False # i.e. drafts/RFCs
88
has_materials = False
9+
has_reviews = False
910
customize_workflow = False
1011
about_page = "group_about"
1112
default_tab = about_page
@@ -24,3 +25,9 @@ def __init__(self, group):
2425

2526
if self.has_chartering_process:
2627
self.about_page = "group_charter"
28+
29+
from ietf.review.utils import active_review_teams
30+
if group in active_review_teams():
31+
self.has_reviews = True
32+
import ietf.group.views
33+
self.default_tab = ietf.group.views.review_requests

ietf/group/tests_info.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
from ietf.person.models import Person, Email
2626
from ietf.utils.test_utils import TestCase, unicontent
2727
from ietf.utils.mail import outbox, empty_outbox
28-
from ietf.utils.test_data import make_test_data, create_person
28+
from ietf.utils.test_data import make_test_data, create_person, make_review_data
2929
from ietf.utils.test_utils import login_testing_unauthorized
3030
from ietf.group.factories import GroupFactory, RoleFactory, GroupEventFactory
3131
from ietf.meeting.factories import SessionFactory
32+
import ietf.group.views
3233

3334
class GroupPagesTests(TestCase):
3435
def setUp(self):
@@ -313,6 +314,31 @@ def test_materials(self):
313314
self.assertEqual(r.status_code, 200)
314315
self.assertTrue(doc.title not in unicontent(r))
315316

317+
def test_review_requests(self):
318+
doc = make_test_data()
319+
review_req = make_review_data(doc)
320+
321+
group = review_req.team
322+
323+
for url in [ urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym }),
324+
urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym , 'group_type': group.type_id}),
325+
]:
326+
r = self.client.get(url)
327+
self.assertEqual(r.status_code, 200)
328+
self.assertTrue(review_req.doc.name in unicontent(r))
329+
self.assertTrue(unicode(review_req.reviewer.person) in unicontent(r))
330+
331+
url = urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym })
332+
333+
# close request, listed under closed
334+
review_req.state_id = "completed"
335+
review_req.result_id = "ready"
336+
review_req.save()
337+
338+
r = self.client.get(url)
339+
self.assertEqual(r.status_code, 200)
340+
self.assertTrue(review_req.doc.name in unicontent(r))
341+
316342
def test_history(self):
317343
draft = make_test_data()
318344
group = draft.group

ietf/group/tests_review.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import datetime
2+
3+
#from pyquery import PyQuery
4+
5+
from django.core.urlresolvers import reverse as urlreverse
6+
7+
from ietf.utils.test_data import make_test_data, make_review_data
8+
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent, reload_db_objects
9+
from ietf.review.models import ReviewRequest
10+
from ietf.person.models import Email
11+
import ietf.group.views_review
12+
13+
class ReviewTests(TestCase):
14+
def test_manage_review_requests(self):
15+
doc = make_test_data()
16+
review_req1 = make_review_data(doc)
17+
18+
group = review_req1.team
19+
20+
url = urlreverse(ietf.group.views_review.manage_review_requests, kwargs={ 'acronym': group.acronym })
21+
22+
login_testing_unauthorized(self, "secretary", url)
23+
24+
review_req2 = ReviewRequest.objects.create(
25+
doc=review_req1.doc,
26+
team=review_req1.team,
27+
type_id="early",
28+
deadline=datetime.datetime.combine(datetime.date.today() + datetime.timedelta(days=30), datetime.time(23, 59, 59)),
29+
state_id="accepted",
30+
reviewer=review_req1.reviewer,
31+
)
32+
33+
review_req3 = ReviewRequest.objects.create(
34+
doc=review_req1.doc,
35+
team=review_req1.team,
36+
type_id="early",
37+
deadline=datetime.datetime.combine(datetime.date.today() + datetime.timedelta(days=30), datetime.time(23, 59, 59)),
38+
state_id="requested",
39+
)
40+
41+
# get
42+
r = self.client.get(url)
43+
self.assertEqual(r.status_code, 200)
44+
self.assertTrue(review_req1.doc.name in unicontent(r))
45+
46+
# close and assign
47+
new_reviewer = Email.objects.get(role__name="reviewer", role__group=group, person__user__username="marschairman")
48+
r = self.client.post(url, {
49+
# close
50+
"r{}-action".format(review_req1.pk): "close",
51+
"r{}-close".format(review_req1.pk): "no-response",
52+
53+
# assign
54+
"r{}-action".format(review_req2.pk): "assign",
55+
"r{}-reviewer".format(review_req2.pk): new_reviewer.pk,
56+
57+
# no change
58+
"r{}-action".format(review_req3.pk): "",
59+
"r{}-close".format(review_req3.pk): "no-response",
60+
"r{}-reviewer".format(review_req3.pk): "",
61+
})
62+
self.assertEqual(r.status_code, 302)
63+
64+
review_req1, review_req2, review_req3 = reload_db_objects(review_req1, review_req2, review_req3)
65+
self.assertEqual(review_req1.state_id, "no-response")
66+
self.assertEqual(review_req2.state_id, "requested")
67+
self.assertEqual(review_req2.reviewer, new_reviewer)
68+
self.assertEqual(review_req3.state_id, "requested")
69+
70+
# FIXME: test suggested
71+
72+

ietf/group/urls_info.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.conf.urls import patterns, include
44
from django.views.generic import RedirectView
55

6-
from ietf.group import views, views_edit
6+
from ietf.group import views, views_edit, views_review
77

88
urlpatterns = patterns('',
99
(r'^$', views.active_groups),
@@ -20,5 +20,7 @@
2020
(r'^email-aliases/$', 'ietf.group.views.email_aliases'),
2121
(r'^bofs/create/$', views_edit.edit, {'action': "create", }, "bof_create"),
2222
(r'^photos/$', views.chair_photos),
23+
(r'^reviews/$', views.review_requests),
24+
(r'^reviews/manage/$', views_review.manage_review_requests),
2325
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')),
2426
)

0 commit comments

Comments
 (0)