Skip to content

Commit afb818c

Browse files
committed
Allow review team secretaries and the secretariat to reset the next reviewer in queue for review teams using the RotateAlphabetically policy. Partially addresses ietf-tools#2879. Commit ready for merge.
- Legacy-Id: 17325
1 parent 52d21e2 commit afb818c

5 files changed

Lines changed: 111 additions & 14 deletions

File tree

ietf/group/factories.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,9 @@ class Meta:
1818
list_email = factory.LazyAttribute(lambda a: '%s@ietf.org'% a.acronym)
1919
uses_milestone_dates = True
2020

21-
class ReviewTeamFactory(factory.DjangoModelFactory):
22-
class Meta:
23-
model = Group
21+
class ReviewTeamFactory(GroupFactory):
2422

2523
type_id = 'review'
26-
name = factory.Faker('sentence',nb_words=6)
27-
acronym = factory.Sequence(lambda n: 'acronym%d' %n)
28-
state_id = 'active'
2924

3025
@factory.post_generation
3126
def settings(obj, create, extracted, **kwargs):

ietf/group/tests_review.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2016-2019, All Rights Reserved
1+
# Copyright The IETF Trust 2016-2020, All Rights Reserved
22
# -*- coding: utf-8 -*-
33

44

@@ -19,7 +19,7 @@
1919
from ietf.iesg.models import TelechatDate
2020
from ietf.person.models import Person
2121
from ietf.review.models import ( ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings,
22-
ReviewTeamSettings )
22+
ReviewTeamSettings, NextReviewerInTeam )
2323
from ietf.review.utils import (
2424
suggested_review_requests_for_team,
2525
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
@@ -33,7 +33,7 @@
3333
from ietf.dbtemplate.factories import DBTemplateFactory
3434
from ietf.person.factories import PersonFactory, EmailFactory
3535
from ietf.doc.factories import DocumentFactory
36-
from ietf.group.factories import RoleFactory, ReviewTeamFactory
36+
from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory
3737
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory
3838

3939
class ReviewTests(TestCase):
@@ -929,4 +929,58 @@ def test_rotation_queue_update(self):
929929
self.assertEqual(r.status_code,302)
930930
self.assertEqual(expected_ending_head_of_rotation, policy.default_reviewer_rotation_list()[0])
931931
self.assertMailboxContains(outbox, subject='Last Call assignment', text='Requested by', count=4)
932-
932+
933+
class ResetNextReviewerInTeamTests(TestCase):
934+
935+
def test_reviewer_overview_navigation(self):
936+
group = ReviewTeamFactory(settings__reviewer_queue_policy_id = 'RotateAlphabetically')
937+
url = urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym })
938+
939+
r = self.client.get(url)
940+
self.assertEqual(r.status_code, 200)
941+
q = PyQuery(r.content)
942+
self.assertFalse(q('#reset_next_reviewer'))
943+
944+
self.client.login(username="secretary", password="secretary+password")
945+
r = self.client.get(url)
946+
self.assertEqual(r.status_code, 200)
947+
q = PyQuery(r.content)
948+
self.assertTrue(q('#reset_next_reviewer'))
949+
950+
group.reviewteamsettings.reviewer_queue_policy_id='LeastRecentlyUsed'
951+
group.reviewteamsettings.save()
952+
953+
r = self.client.get(url)
954+
self.assertEqual(r.status_code, 200)
955+
q = PyQuery(r.content)
956+
self.assertFalse(q('#reset_next_reviewer'))
957+
958+
959+
def test_reset_next_reviewer(self):
960+
PersonFactory(user__username='plain')
961+
for group in (GroupFactory(), ReviewTeamFactory(settings__reviewer_queue_policy_id='LeastRecentlyUsed')):
962+
url = urlreverse('ietf.group.views.reset_next_reviewer', kwargs=dict(acronym=group.acronym))
963+
r = self.client.get(url)
964+
self.assertEqual(r.status_code, 302)
965+
self.client.login(username='plain',password='plain+password')
966+
r = self.client.get(url)
967+
self.assertEqual(r.status_code, 404)
968+
self.client.logout()
969+
970+
group = ReviewTeamFactory(settings__reviewer_queue_policy_id='RotateAlphabetically')
971+
secr = RoleFactory(name_id='secr',group=group).person
972+
reviewers = RoleFactory.create_batch(10, name_id='reviewer',group=group)
973+
NextReviewerInTeam.objects.create(team = group, next_reviewer=reviewers[4].person)
974+
975+
target_index = 6
976+
url = urlreverse('ietf.group.views.reset_next_reviewer', kwargs=dict(acronym=group.acronym))
977+
for user in (secr.user.username, 'secretary'):
978+
login_testing_unauthorized(self,user,url)
979+
r = self.client.get(url)
980+
self.assertEqual(r.status_code,200)
981+
r = self.client.post(url,{'next_reviewer':reviewers[target_index].person.pk})
982+
self.assertEqual(r.status_code,302)
983+
self.assertEqual(NextReviewerInTeam.objects.get(team=group).next_reviewer, reviewers[target_index].person)
984+
self.client.logout()
985+
target_index += 2
986+

ietf/group/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright The IETF Trust 2007, All Rights Reserved
1+
# Copyright The IETF Trust 2013-2020, All Rights Reserved
22

33
from django.conf import settings
44
from django.conf.urls import include
@@ -45,6 +45,7 @@
4545
url(r'^reviewers/$', views.reviewer_overview),
4646
url(r'^reviewers/(?P<reviewer_email>[\w%+-.@]+)/settings/$', views.change_reviewer_settings),
4747
url(r'^secretarysettings/$', views.change_review_secretary_settings),
48+
url(r'^reset_next_reviewer/$', views.reset_next_reviewer),
4849
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
4950
]
5051

ietf/group/views.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
from ietf.meeting.helpers import get_meeting
9191
from ietf.meeting.utils import group_sessions, add_event_info_to_session_qs
9292
from ietf.name.models import GroupTypeName, StreamName
93-
from ietf.person.models import Email
93+
from ietf.person.models import Email, Person
9494
from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewerSettings,
9595
ReviewSecretarySettings, UnavailablePeriod )
9696
from ietf.review.policies import get_reviewer_queue_policy
@@ -1405,6 +1405,8 @@ def reviewer_overview(request, acronym, group_type=None):
14051405

14061406
can_manage = can_manage_review_requests_for_team(request.user, group)
14071407

1408+
can_reset_next_reviewer = can_manage and group.reviewteamsettings.reviewer_queue_policy_id == 'RotateAlphabetically'
1409+
14081410
reviewers = get_reviewer_queue_policy(group).default_reviewer_rotation_list(include_unavailable=True)
14091411

14101412
reviewer_settings = { s.person_id: s for s in ReviewerSettings.objects.filter(team=group) }
@@ -1478,7 +1480,8 @@ def reviewer_overview(request, acronym, group_type=None):
14781480
return render(request, 'group/reviewer_overview.html',
14791481
construct_group_menu_context(request, group, "reviewers", group_type, {
14801482
"reviewers": reviewers,
1481-
"can_access_stats": can_access_review_stats_for_team(request.user, group)
1483+
"can_access_stats": can_access_review_stats_for_team(request.user, group),
1484+
"can_reset_next_reviewer": can_reset_next_reviewer,
14821485
}))
14831486

14841487

@@ -1933,3 +1936,42 @@ def add_comment(request, acronym, group_type=None):
19331936
form = AddCommentForm()
19341937

19351938
return render(request, 'group/add_comment.html', { 'group':group, 'form':form, })
1939+
1940+
class ResetNextReviewerForm(forms.Form):
1941+
next_reviewer = forms.ChoiceField()
1942+
1943+
def __init__(self, *args, **kwargs):
1944+
instance = kwargs.pop('instance')
1945+
super(ResetNextReviewerForm, self).__init__(*args, **kwargs)
1946+
self.fields['next_reviewer'].choices = [ (p.pk, p.plain_name()) for p in get_reviewer_queue_policy(instance.team).default_reviewer_rotation_list(include_unavailable=True)]
1947+
1948+
@login_required
1949+
def reset_next_reviewer(request, acronym, group_type=None):
1950+
group = get_group_or_404(acronym, group_type)
1951+
if not group.features.has_reviews:
1952+
raise Http404
1953+
if group.reviewteamsettings.reviewer_queue_policy_id != 'RotateAlphabetically':
1954+
raise Http404
1955+
1956+
if not Role.objects.filter(name="secr", group=group, person__user=request.user).exists() and not has_role(request.user, "Secretariat"):
1957+
return HttpResponseForbidden("You don't have permission to access this view")
1958+
1959+
instance = group.nextreviewerinteam_set.first()
1960+
if not instance:
1961+
raise Http404
1962+
1963+
if request.method == 'POST':
1964+
form = ResetNextReviewerForm(request.POST,instance=instance)
1965+
if form.is_valid():
1966+
instance.next_reviewer = Person.objects.get(pk=form.cleaned_data['next_reviewer'])
1967+
instance.save()
1968+
return redirect('ietf.group.views.reviewer_overview', acronym = group.acronym )
1969+
else:
1970+
form = ResetNextReviewerForm(instance=instance)
1971+
1972+
return render(request, 'group/reset_next_reviewer.html', { 'group':group, 'form': form,})
1973+
1974+
1975+
1976+
1977+

ietf/templates/group/reviewer_overview.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "group/group_base.html" %}
2-
{# Copyright The IETF Trust 2015, All Rights Reserved #}
2+
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
33
{% load origin %}{% origin %}
44

55
{% load ietf_filters staticfiles bootstrap3 %}
@@ -27,6 +27,11 @@ <h2>Reviewers</h2>
2727
<p class="skip-next">Will be skipped the next time at the top of rotation.</p>
2828
<p class="completely-unavailable">Is not available to do reviews at this time.</p>
2929
</div>
30+
{% if can_reset_next_reviewer %}
31+
<div>
32+
<a href="{% url 'ietf.group.views.reset_next_reviewer' acronym=group.acronym %}" class="btn btn-default" id="reset_next_reviewer">Reset head of queue</a>
33+
</div>
34+
{% endif %}
3035

3136
{% if reviewers %}
3237
<table class="table reviewer-overview tablesorter">

0 commit comments

Comments
 (0)