Skip to content

Commit 44e1353

Browse files
committed
Add a page for displaying a review request, add support for
withdrawing requests, add tests for these two pages - Legacy-Id: 11217
1 parent 64a6534 commit 44e1353

13 files changed

Lines changed: 247 additions & 30 deletions

File tree

ietf/doc/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ class DocReminder(models.Model):
707707

708708
# review
709709
("requested_review", "Requested review"),
710+
("withdrew_review_request", "Withdrew review"),
710711
]
711712

712713
class DocEvent(models.Model):

ietf/doc/tests_review.py

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,44 @@
11
# -*- coding: utf-8 -*-
22

33
import datetime
4-
from pyquery import PyQuery
54

65
from django.core.urlresolvers import reverse as urlreverse
76

87
import debug # pyflakes:ignore
98

10-
from ietf.review.models import ReviewRequest
9+
from ietf.review.models import ReviewRequest, Reviewer
1110
from ietf.person.models import Person
1211
from ietf.group.models import Group, Role
13-
from ietf.name.models import ReviewResultName
12+
from ietf.name.models import ReviewResultName, ReviewRequestStateName
1413
from ietf.utils.test_utils import TestCase
1514
from ietf.utils.test_data import make_test_data
16-
from ietf.utils.test_utils import login_testing_unauthorized
15+
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
1716

18-
def make_review_data():
19-
review_team = Group.objects.create(state_id="active", acronym="reviewteam", name="Review Team", type_id="team")
20-
review_team.reviewresultname_set.add(ReviewResultName.objects.filter(slug__in=["issues", "ready-issues", "ready", "not-ready"]))
17+
def make_review_data(doc):
18+
team = Group.objects.create(state_id="active", acronym="reviewteam", name="Review Team", type_id="team")
19+
team.reviewresultname_set.add(ReviewResultName.objects.filter(slug__in=["issues", "ready-issues", "ready", "not-ready"]))
2120

2221
p = Person.objects.get(user__username="plain")
23-
Role.objects.create(name_id="reviewer", person=p, email=p.email_set.first(), group=review_team)
22+
role = Role.objects.create(name_id="reviewer", person=p, email=p.email_set.first(), group=team)
23+
reviewer = Reviewer.objects.create(role=role, frequency=14, skip_next=0)
2424

25-
return review_team
25+
review_req = ReviewRequest.objects.create(
26+
doc=doc,
27+
team=team,
28+
type_id="early",
29+
deadline=datetime.datetime.now() + datetime.timedelta(days=20),
30+
state_id="ready",
31+
reviewer=reviewer,
32+
reviewed_rev="01",
33+
)
34+
35+
return review_req
2636

2737
class ReviewTests(TestCase):
2838
def test_request_review(self):
2939
doc = make_test_data()
30-
review_team = make_review_data()
40+
review_req = make_review_data(doc)
41+
review_team = review_req.team
3142

3243
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
3344
login_testing_unauthorized(self, "secretary", url)
@@ -47,17 +58,17 @@ def test_request_review(self):
4758
})
4859
self.assertEqual(r.status_code, 302)
4960

50-
req = ReviewRequest.objects.get(doc=doc)
61+
req = ReviewRequest.objects.get(doc=doc, state="requested")
5162
self.assertEqual(req.deadline.date(), deadline_date)
5263
self.assertEqual(req.deadline.time(), datetime.time(23, 59, 59))
53-
self.assertEqual(req.state_id, "requested")
5464
self.assertEqual(req.team, review_team)
5565
self.assertEqual(req.requested_rev, "01")
5666
self.assertEqual(doc.latest_event().type, "requested_review")
5767

5868
def test_request_review_by_reviewer(self):
5969
doc = make_test_data()
60-
review_team = make_review_data()
70+
review_req = make_review_data(doc)
71+
review_team = review_req.team
6172

6273
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
6374
login_testing_unauthorized(self, "plain", url)
@@ -73,10 +84,40 @@ def test_request_review_by_reviewer(self):
7384
})
7485
self.assertEqual(r.status_code, 302)
7586

76-
req = ReviewRequest.objects.get(doc=doc)
87+
req = ReviewRequest.objects.get(doc=doc, state="requested")
7788
self.assertEqual(req.state_id, "requested")
7889
self.assertEqual(req.team, review_team)
7990

8091
def test_doc_page(self):
92+
# FIXME: fill in
8193
pass
8294

95+
def test_review_request(self):
96+
doc = make_test_data()
97+
review_req = make_review_data(doc)
98+
99+
url = urlreverse('ietf.doc.views_review.review_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
100+
101+
r = self.client.get(url)
102+
self.assertEqual(r.status_code, 200)
103+
self.assertTrue(review_req.team.acronym.upper() in unicontent(r))
104+
105+
def test_withdraw_request(self):
106+
doc = make_test_data()
107+
review_req = make_review_data(doc)
108+
review_req.state = ReviewRequestStateName.objects.get(slug="accepted")
109+
review_req.save()
110+
111+
url = urlreverse('ietf.doc.views_review.withdraw_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
112+
login_testing_unauthorized(self, "secretary", url)
113+
114+
r = self.client.get(url)
115+
self.assertEqual(r.status_code, 200)
116+
117+
# withdraw
118+
r = self.client.post(url, { "action": "withdraw" })
119+
self.assertEqual(r.status_code, 302)
120+
121+
review_req = reload_db_objects(review_req)
122+
self.assertEqual(review_req.state_id, "withdrawn")
123+
self.assertEqual(doc.latest_event().type, "withdrew_review_request")

ietf/doc/urls.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from ietf.doc import views_search, views_draft, views_ballot
3737
from ietf.doc import views_status_change
3838
from ietf.doc import views_doc
39-
from ietf.doc import views_review
4039

4140
session_patterns = [
4241
url(r'^add$', views_doc.add_sessionpresentation),
@@ -74,8 +73,7 @@
7473
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
7574
(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?doc.json$', views_doc.document_json),
7675
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballotpopup/(?P<ballot_id>[0-9]+)/$', views_doc.ballot_popup),
77-
url(r'^(?P<name>[A-Za-z0-9._+-]+)/requestreview/$', views_review.request_review),
78-
url(r'^(?P<name>[A-Za-z0-9._+-]+)/review/(?P<request_id>[0-9]+)/$', views_review.review),
76+
url(r'^(?P<name>[A-Za-z0-9._+-]+)/reviewrequest/', include("ietf.doc.urls_review")),
7977

8078
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
8179

ietf/doc/urls_review.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.conf.urls import patterns, url
2+
from ietf.doc import views_review
3+
4+
urlpatterns = patterns('',
5+
url(r'^$', views_review.request_review),
6+
url(r'^(?P<request_id>[0-9]+)/$', views_review.review_request),
7+
url(r'^(?P<request_id>[0-9]+)/withdraw/$', views_review.withdraw_request),
8+
)
9+

ietf/doc/views_review.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from django.http import HttpResponseForbidden
44
from django.shortcuts import render, get_object_or_404, redirect
5-
from django.core.urlresolvers import reverse as urlreverse
65
from django import forms
76
from django.contrib.auth.decorators import login_required
87

@@ -13,7 +12,6 @@
1312
from ietf.review.utils import active_review_teams
1413
from ietf.utils.fields import DatepickerDateField
1514

16-
1715
class RequestReviewForm(forms.ModelForm):
1816
deadline_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={ "autoclose": "1", "start-date": "+0d" })
1917
deadline_time = forms.TimeField(widget=forms.TextInput(attrs={ 'placeholder': "HH:MM" }), help_text="If time is not specified, end of day is assumed", required=False)
@@ -87,7 +85,8 @@ def request_review(request, name):
8785
type="requested_review",
8886
doc=doc,
8987
by=request.user.person,
90-
desc="{} review by {} requested".format(review_req.type.name, review_req.team.acronym.upper()),
88+
desc="Requested {} review by {}".format(review_req.type.name, review_req.team.acronym.upper()),
89+
time=review_req.time,
9190
)
9291

9392
# FIXME: if I'm a reviewer, auto-assign to myself?
@@ -101,8 +100,41 @@ def request_review(request, name):
101100
'form': form,
102101
})
103102

104-
def review(request, name, request_id):
103+
def review_request(request, name, request_id):
105104
doc = get_object_or_404(Document, name=name)
106-
review_request = get_object_or_404(ReviewRequest, pk=request_id)
105+
review_req = get_object_or_404(ReviewRequest, pk=request_id)
106+
107+
return render(request, 'doc/review/review_request.html', {
108+
'doc': doc,
109+
'review_req': review_req,
110+
'can_withdraw_request': review_req.state_id in ["requested", "accepted"] and is_authorized_in_doc_stream(request.user, doc),
111+
})
112+
113+
def withdraw_request(request, name, request_id):
114+
doc = get_object_or_404(Document, name=name)
115+
review_req = get_object_or_404(ReviewRequest, pk=request_id, state__in=["requested", "accepted"])
116+
117+
if not is_authorized_in_doc_stream(request.user, doc):
118+
return HttpResponseForbidden("You do not have permission to perform this action")
119+
120+
if request.method == "POST" and request.POST.get("action") == "withdraw":
121+
review_req.state = ReviewRequestStateName.objects.get(slug="withdrawn")
122+
review_req.save()
123+
124+
DocEvent.objects.create(
125+
type="withdrew_review_request",
126+
doc=doc,
127+
by=request.user.person,
128+
desc="Withdrew request for {} review by {}".format(review_req.type.name, review_req.team.acronym.upper()),
129+
)
130+
131+
if review_req.state_id != "requested":
132+
# FIXME: handle this case - by emailing?
133+
pass
107134

108-
print doc, review_request
135+
return redirect(review_request, name=review_req.doc.name, request_id=review_req.pk)
136+
137+
return render(request, 'doc/review/withdraw_request.html', {
138+
'doc': doc,
139+
'review_req': review_req,
140+
})

ietf/name/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ class LiaisonStatementTagName(NameModel):
8989
"Action Required, Action Taken"
9090
class ReviewRequestStateName(NameModel):
9191
"""Requested, Accepted, Rejected, Withdrawn, Overtaken By Events,
92-
No Response , Completed"""
92+
No Response, Completed"""
9393
class ReviewTypeName(NameModel):
9494
"""Early Review, Last Call, Telechat"""
9595
class ReviewResultName(NameModel):
9696
"""Almost ready, Has issues, Has nits, Not Ready,
9797
On the right track, Ready, Ready with issues,
9898
Ready with nits, Serious Issues"""
99-
teams = models.ManyToManyField("group.Group", help_text="Which teams this result can be set for. This also implicitly defines which teams are review teams - if there are no possible review results defined for a given team, it can't be a review team.", blank=True)
99+
teams = models.ManyToManyField("group.Group", help_text="Which teams this result can be set for. This also implicitly defines which teams are review teams - if there are no possible review results defined for a given team, it can't be a review team.", blank=True)
100100

ietf/review/migrations/0001_initial.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
2121
('available', models.DateTimeField(help_text=b'When will this reviewer be available again', null=True, blank=True)),
2222
('filter_re', models.CharField(max_length=255, blank=True)),
2323
('skip_next', models.IntegerField(help_text=b'Skip the next N review assignments')),
24-
('role', models.ForeignKey(to='group.Role')),
24+
('role', models.OneToOneField(to='group.Role')),
2525
],
2626
options={
2727
},

ietf/review/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Reviewer(models.Model):
1010
of admin data associated with the reviewer in the particular team.
1111
There will be one record for each combination of reviewer and team.
1212
"""
13-
role = models.ForeignKey(Role)
13+
role = models.OneToOneField(Role)
1414
frequency = models.IntegerField(help_text="Can review every N days")
1515
available = models.DateTimeField(blank=True, null=True, help_text="When will this reviewer be available again")
1616
filter_re = models.CharField(max_length=255, blank=True)

ietf/templates/doc/document_draft.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@
200200
<td>
201201
{% for r in review_requests %}
202202
<div>
203-
<a href="{% url "ietf.doc.views_review.review" doc.name r.pk %}">{{ r.team.acronym|upper }} {{ r.type.name }} Review ({{ r.state.name }})</a>
203+
<a href="{% url "ietf.doc.views_review.review_request" doc.name r.pk %}">{{ r.team.acronym|upper }} {{ r.type.name }} Review ({{ r.state.name }})</a>
204204
</div>
205205
{% endfor %}
206206

ietf/templates/doc/review/request_review.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h1>Request review<br><small>{{ doc.name }}</small></h1>
2323
{% bootstrap_field form.requested_rev layout="horizontal" %}
2424

2525
{% buttons %}
26-
<button type="submit" class="btn btn-primary" name="save_addresses" value="Save">Request review</button>
26+
<button type="submit" class="btn btn-primary">Request review</button>
2727
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.canonical_name %}">Back</a>
2828
{% endbuttons %}
2929
</form>

0 commit comments

Comments
 (0)