Skip to content

Commit 334f013

Browse files
committed
Merged in [17325] from rjsparks@nostrum.com:
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. - Legacy-Id: 17358 Note: SVN reference [17325] has been migrated to Git commit afb818c
2 parents 07747b2 + 92b2f06 commit 334f013

6 files changed

Lines changed: 131 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+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% extends "base.html" %}
2+
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
3+
{% load origin %}{% origin %}
4+
5+
{% load ietf_filters staticfiles bootstrap3 %}
6+
7+
{% block content %}
8+
<h2>Set next reviewer in queue for {{ group.acronym }}</h2>
9+
<form id="dbtemplate-edit" role="form" method="post">
10+
{% csrf_token %}
11+
12+
{% bootstrap_form form %}
13+
14+
{% buttons %}
15+
<button class="btn btn-default" type="submit">Save</button>
16+
<a class="btn btn-default" href="{% url 'ietf.group.views.reviewer_overview' acronym=group.acronym %}">Cancel</a>
17+
{% endbuttons %}
18+
19+
</form>
20+
{% endblock %}

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)