Allow review team secretaries and the secretariat to reset the next reviewer in queue for review teams using the RotateAlphabetically policy. Partially addresses #2879. Commit ready for merge.
- Legacy-Id: 17325
This commit is contained in:
parent
52d21e29cb
commit
afb818c298
|
@ -18,14 +18,9 @@ class GroupFactory(factory.DjangoModelFactory):
|
|||
list_email = factory.LazyAttribute(lambda a: '%s@ietf.org'% a.acronym)
|
||||
uses_milestone_dates = True
|
||||
|
||||
class ReviewTeamFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = Group
|
||||
class ReviewTeamFactory(GroupFactory):
|
||||
|
||||
type_id = 'review'
|
||||
name = factory.Faker('sentence',nb_words=6)
|
||||
acronym = factory.Sequence(lambda n: 'acronym%d' %n)
|
||||
state_id = 'active'
|
||||
|
||||
@factory.post_generation
|
||||
def settings(obj, create, extracted, **kwargs):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2016-2019, All Rights Reserved
|
||||
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ from ietf.group.models import Role
|
|||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.person.models import Person
|
||||
from ietf.review.models import ( ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings,
|
||||
ReviewTeamSettings )
|
||||
ReviewTeamSettings, NextReviewerInTeam )
|
||||
from ietf.review.utils import (
|
||||
suggested_review_requests_for_team,
|
||||
review_assignments_needing_reviewer_reminder, email_reviewer_reminder,
|
||||
|
@ -33,7 +33,7 @@ from ietf.utils.mail import outbox, empty_outbox
|
|||
from ietf.dbtemplate.factories import DBTemplateFactory
|
||||
from ietf.person.factories import PersonFactory, EmailFactory
|
||||
from ietf.doc.factories import DocumentFactory
|
||||
from ietf.group.factories import RoleFactory, ReviewTeamFactory
|
||||
from ietf.group.factories import RoleFactory, ReviewTeamFactory, GroupFactory
|
||||
from ietf.review.factories import ReviewRequestFactory, ReviewerSettingsFactory, ReviewAssignmentFactory
|
||||
|
||||
class ReviewTests(TestCase):
|
||||
|
@ -929,4 +929,58 @@ class BulkAssignmentTests(TestCase):
|
|||
self.assertEqual(r.status_code,302)
|
||||
self.assertEqual(expected_ending_head_of_rotation, policy.default_reviewer_rotation_list()[0])
|
||||
self.assertMailboxContains(outbox, subject='Last Call assignment', text='Requested by', count=4)
|
||||
|
||||
|
||||
class ResetNextReviewerInTeamTests(TestCase):
|
||||
|
||||
def test_reviewer_overview_navigation(self):
|
||||
group = ReviewTeamFactory(settings__reviewer_queue_policy_id = 'RotateAlphabetically')
|
||||
url = urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym })
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertFalse(q('#reset_next_reviewer'))
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(q('#reset_next_reviewer'))
|
||||
|
||||
group.reviewteamsettings.reviewer_queue_policy_id='LeastRecentlyUsed'
|
||||
group.reviewteamsettings.save()
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertFalse(q('#reset_next_reviewer'))
|
||||
|
||||
|
||||
def test_reset_next_reviewer(self):
|
||||
PersonFactory(user__username='plain')
|
||||
for group in (GroupFactory(), ReviewTeamFactory(settings__reviewer_queue_policy_id='LeastRecentlyUsed')):
|
||||
url = urlreverse('ietf.group.views.reset_next_reviewer', kwargs=dict(acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.client.login(username='plain',password='plain+password')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
self.client.logout()
|
||||
|
||||
group = ReviewTeamFactory(settings__reviewer_queue_policy_id='RotateAlphabetically')
|
||||
secr = RoleFactory(name_id='secr',group=group).person
|
||||
reviewers = RoleFactory.create_batch(10, name_id='reviewer',group=group)
|
||||
NextReviewerInTeam.objects.create(team = group, next_reviewer=reviewers[4].person)
|
||||
|
||||
target_index = 6
|
||||
url = urlreverse('ietf.group.views.reset_next_reviewer', kwargs=dict(acronym=group.acronym))
|
||||
for user in (secr.user.username, 'secretary'):
|
||||
login_testing_unauthorized(self,user,url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
r = self.client.post(url,{'next_reviewer':reviewers[target_index].person.pk})
|
||||
self.assertEqual(r.status_code,302)
|
||||
self.assertEqual(NextReviewerInTeam.objects.get(team=group).next_reviewer, reviewers[target_index].person)
|
||||
self.client.logout()
|
||||
target_index += 2
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
# Copyright The IETF Trust 2013-2020, All Rights Reserved
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
|
@ -45,6 +45,7 @@ info_detail_urls = [
|
|||
url(r'^reviewers/$', views.reviewer_overview),
|
||||
url(r'^reviewers/(?P<reviewer_email>[\w%+-.@]+)/settings/$', views.change_reviewer_settings),
|
||||
url(r'^secretarysettings/$', views.change_review_secretary_settings),
|
||||
url(r'^reset_next_reviewer/$', views.reset_next_reviewer),
|
||||
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
|
||||
]
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ from ietf.mailtrigger.utils import gather_relevant_expansions
|
|||
from ietf.meeting.helpers import get_meeting
|
||||
from ietf.meeting.utils import group_sessions, add_event_info_to_session_qs
|
||||
from ietf.name.models import GroupTypeName, StreamName
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewerSettings,
|
||||
ReviewSecretarySettings, UnavailablePeriod )
|
||||
from ietf.review.policies import get_reviewer_queue_policy
|
||||
|
@ -1405,6 +1405,8 @@ def reviewer_overview(request, acronym, group_type=None):
|
|||
|
||||
can_manage = can_manage_review_requests_for_team(request.user, group)
|
||||
|
||||
can_reset_next_reviewer = can_manage and group.reviewteamsettings.reviewer_queue_policy_id == 'RotateAlphabetically'
|
||||
|
||||
reviewers = get_reviewer_queue_policy(group).default_reviewer_rotation_list(include_unavailable=True)
|
||||
|
||||
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):
|
|||
return render(request, 'group/reviewer_overview.html',
|
||||
construct_group_menu_context(request, group, "reviewers", group_type, {
|
||||
"reviewers": reviewers,
|
||||
"can_access_stats": can_access_review_stats_for_team(request.user, group)
|
||||
"can_access_stats": can_access_review_stats_for_team(request.user, group),
|
||||
"can_reset_next_reviewer": can_reset_next_reviewer,
|
||||
}))
|
||||
|
||||
|
||||
|
@ -1933,3 +1936,42 @@ def add_comment(request, acronym, group_type=None):
|
|||
form = AddCommentForm()
|
||||
|
||||
return render(request, 'group/add_comment.html', { 'group':group, 'form':form, })
|
||||
|
||||
class ResetNextReviewerForm(forms.Form):
|
||||
next_reviewer = forms.ChoiceField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.pop('instance')
|
||||
super(ResetNextReviewerForm, self).__init__(*args, **kwargs)
|
||||
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)]
|
||||
|
||||
@login_required
|
||||
def reset_next_reviewer(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
if group.reviewteamsettings.reviewer_queue_policy_id != 'RotateAlphabetically':
|
||||
raise Http404
|
||||
|
||||
if not Role.objects.filter(name="secr", group=group, person__user=request.user).exists() and not has_role(request.user, "Secretariat"):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
instance = group.nextreviewerinteam_set.first()
|
||||
if not instance:
|
||||
raise Http404
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ResetNextReviewerForm(request.POST,instance=instance)
|
||||
if form.is_valid():
|
||||
instance.next_reviewer = Person.objects.get(pk=form.cleaned_data['next_reviewer'])
|
||||
instance.save()
|
||||
return redirect('ietf.group.views.reviewer_overview', acronym = group.acronym )
|
||||
else:
|
||||
form = ResetNextReviewerForm(instance=instance)
|
||||
|
||||
return render(request, 'group/reset_next_reviewer.html', { 'group':group, 'form': form,})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "group/group_base.html" %}
|
||||
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
|
||||
{% load origin %}{% origin %}
|
||||
|
||||
{% load ietf_filters staticfiles bootstrap3 %}
|
||||
|
@ -27,6 +27,11 @@
|
|||
<p class="skip-next">Will be skipped the next time at the top of rotation.</p>
|
||||
<p class="completely-unavailable">Is not available to do reviews at this time.</p>
|
||||
</div>
|
||||
{% if can_reset_next_reviewer %}
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if reviewers %}
|
||||
<table class="table reviewer-overview tablesorter">
|
||||
|
|
Loading…
Reference in a new issue