From 871a4b653b7599d565a07267e8089c102a14009f Mon Sep 17 00:00:00 2001
From: Sasha Romijn
Date: Thu, 24 Oct 2019 12:37:59 +0000
Subject: [PATCH] Fix #2217 - Allow submission of unsolicited reviews by
secretaries.
- For team secretaries, a button "Submit unsolicited review" will now
appear next to "Request review" on the document's main page.
- If the secretary is a secretary for multiple teams, they are taken
through an intermediate page to select for which team they are
submitting their review.
- The form is similar (and using the same code) as the usual review
completion, with a few extra fields for the review type and reviewer,
which would usually already be known.
- When submitting the review, a ReviewRequest and ReviewAssignment are
automatically created. The assignment is then immediately closed in
the usual way.
- Other workflows are unchanged.
The issues with the review form in #2061 are slightly worse for the
unsolicited review scenario, but that will be improved when #2061 is
fixed.
Commit ready for merge.
- Legacy-Id: 16924
---
ietf/doc/tests_review.py | 87 ++++++++-
ietf/doc/urls_review.py | 8 +-
ietf/doc/views_doc.py | 7 +-
ietf/doc/views_review.py | 169 +++++++++++++-----
ietf/review/mailarch.py | 6 +-
ietf/templates/doc/document_draft.html | 5 +
.../templates/doc/review/complete_review.html | 42 +++--
.../doc/review/submit_unsolicited_review.html | 28 +++
8 files changed, 285 insertions(+), 67 deletions(-)
create mode 100644 ietf/templates/doc/review/submit_unsolicited_review.html
diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py
index 7c16ec2b9..a72344e12 100644
--- a/ietf/doc/tests_review.py
+++ b/ietf/doc/tests_review.py
@@ -27,7 +27,8 @@ from ietf.doc.models import DocumentAuthor, RelatedDocument, DocEvent, ReviewReq
from ietf.group.factories import RoleFactory, ReviewTeamFactory
from ietf.group.models import Group
from ietf.message.models import Message
-from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName
+from ietf.name.models import ReviewResultName, ReviewRequestStateName, ReviewAssignmentStateName, \
+ ReviewTypeName
from ietf.person.models import Email, Person
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
from ietf.review.models import (ReviewRequest, ReviewerSettings,
@@ -563,7 +564,7 @@ class ReviewTests(TestCase):
assignment = ReviewAssignmentFactory(review_request=review_req, reviewer=rev_role.person.email_set.first(), state_id='accepted')
# test URL construction
- query_urls = ietf.review.mailarch.construct_query_urls(review_req)
+ query_urls = ietf.review.mailarch.construct_query_urls(doc, review_team)
self.assertTrue(review_req.doc.name in query_urls["query_data_url"])
# test parsing
@@ -574,9 +575,9 @@ class ReviewTests(TestCase):
# to work, the module (and not the function) must be
# imported in the view
real_fn = ietf.review.mailarch.construct_query_urls
- ietf.review.mailarch.construct_query_urls = lambda review_req, query=None: { "query_data_url": "file://" + os.path.abspath(mbox_path) }
-
+ ietf.review.mailarch.construct_query_urls = lambda doc, team, query=None: { "query_data_url": "file://" + os.path.abspath(mbox_path) }
url = urlreverse('ietf.doc.views_review.search_mail_archive', kwargs={ "name": doc.name, "assignment_id": assignment.pk })
+ url2 = urlreverse('ietf.doc.views_review.search_mail_archive', kwargs={ "name": doc.name, "acronym": review_team.acronym })
login_testing_unauthorized(self, "reviewsecretary", url)
r = self.client.get(url)
@@ -584,6 +585,11 @@ class ReviewTests(TestCase):
messages = r.json()["messages"]
self.assertEqual(len(messages), 2)
+ r = self.client.get(url2)
+ self.assertEqual(r.status_code, 200)
+ messages = r.json()["messages"]
+ self.assertEqual(len(messages), 2)
+
today = datetime.date.today()
self.assertEqual(messages[0]["url"], "https://www.example.com/testmessage")
@@ -604,7 +610,7 @@ class ReviewTests(TestCase):
no_result_path = os.path.join(self.review_dir, "mailarch_no_result.html")
with io.open(no_result_path, "w") as f:
f.write('Content-Type: text/html\n\n
No results found
')
- ietf.review.mailarch.construct_query_urls = lambda review_req, query=None: { "query_data_url": "file://" + os.path.abspath(no_result_path) }
+ ietf.review.mailarch.construct_query_urls = lambda doc, team, query=None: { "query_data_url": "file://" + os.path.abspath(no_result_path) }
url = urlreverse('ietf.doc.views_review.search_mail_archive', kwargs={ "name": doc.name, "assignment_id": assignment.pk })
@@ -617,6 +623,27 @@ class ReviewTests(TestCase):
finally:
ietf.review.mailarch.construct_query_urls = real_fn
+ def test_submit_unsolicited_review_choose_team(self):
+ doc = WgDraftFactory(group__acronym='mars', rev='01')
+ review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
+ secretary = RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com', name_id='secr')
+
+ url = urlreverse('ietf.doc.views_review.submit_unsolicited_review_choose_team',
+ kwargs={'name': doc.name})
+ redirect_url = urlreverse("ietf.doc.views_review.complete_review",
+ kwargs={'name': doc.name, 'acronym': review_team.acronym})
+ login_testing_unauthorized(self, secretary.person.user.username, url)
+
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 200)
+ self.assertContains(r, review_team.name)
+
+ r = self.client.post(url, data={'team': review_team.pk})
+ self.assertRedirects(r, redirect_url)
+
+ r = self.client.post(url, data={'team': review_team.pk + 5})
+ self.assertEqual(r.status_code, 200)
+
def setup_complete_review_test(self):
doc = WgDraftFactory(group__acronym='mars',rev='01')
NewRevisionDocEventFactory(doc=doc,rev='01')
@@ -872,6 +899,56 @@ class ReviewTests(TestCase):
self.assertEqual(len(outbox), 0)
self.assertTrue("http://example.com" in assignment.review.external_url)
+ @patch('requests.get')
+ def test_complete_unsolicited_review_link_to_mailing_list_by_secretary(self, mock):
+ # Mock up the url response for the request.get() call to retrieve the mailing list url
+ response = Response()
+ response.status_code = 200
+ response._content = b"This is a review\nwith two lines"
+ mock.return_value = response
+
+ # Run the test
+ doc = WgDraftFactory(group__acronym='mars', rev='01')
+ NewRevisionDocEventFactory(doc=doc, rev='01')
+ review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
+ rev_role = RoleFactory(group=review_team, person__user__username='reviewer', person__user__email='reviewer@example.com', name_id='reviewer')
+ secretary_role = RoleFactory(group=review_team, person__user__username='reviewsecretary', person__user__email='reviewsecretary@example.com', name_id='secr')
+
+ url = urlreverse('ietf.doc.views_review.complete_review',
+ kwargs={"name": doc.name, "acronym": review_team.acronym})
+
+ login_testing_unauthorized(self, secretary_role.person.user.username, url)
+
+ empty_outbox()
+
+ r = self.client.post(url, data={
+ "result": ReviewResultName.objects.get(slug="ready").pk,
+ "state": ReviewAssignmentStateName.objects.get(slug="completed").pk,
+ "reviewed_rev": '01',
+ "review_submission": "link",
+ "review_content": response.content.decode(),
+ "review_url": "http://example.com/testreview/",
+ "review_file": "",
+ "review_type": ReviewTypeName.objects.get(slug="early").pk,
+ "reviewer": rev_role.person.pk,
+ "completion_date": "2012-12-24",
+ "completion_time": "12:13:14",
+ })
+ self.assertEqual(r.status_code, 302)
+
+ review_req = doc.reviewrequest_set.get()
+ assignment = review_req.reviewassignment_set.get()
+ self.assertEqual(review_req.type_id, "early")
+ self.assertEqual(review_req.requested_by, secretary_role.person)
+ self.assertEqual(assignment.reviewer, rev_role.person.role_email('reviewer'))
+ self.assertEqual(assignment.state_id, "completed")
+
+ with io.open(os.path.join(self.review_subdir, assignment.review.name + ".txt")) as f:
+ self.assertEqual(f.read(), "This is a review\nwith two lines")
+
+ self.assertEqual(len(outbox), 0)
+ self.assertTrue("http://example.com" in assignment.review.external_url)
+
def test_partially_complete_review(self):
assignment, url = self.setup_complete_review_test()
diff --git a/ietf/doc/urls_review.py b/ietf/doc/urls_review.py
index 23b217c50..7adce5145 100644
--- a/ietf/doc/urls_review.py
+++ b/ietf/doc/urls_review.py
@@ -1,3 +1,6 @@
+# Copyright The IETF Trust 2016-2019, All Rights Reserved
+from django.conf import settings
+
from ietf.doc import views_review
from ietf.utils.urls import url
@@ -9,9 +12,12 @@ urlpatterns = [
url(r'^(?P[0-9]+)/assignreviewer/$', views_review.assign_reviewer),
url(r'^(?P[0-9]+)/rejectreviewerassignment/$', views_review.reject_reviewer_assignment),
url(r'^(?P[0-9]+)/complete/$', views_review.complete_review),
+ url(r'^%(acronym)s/submitunsolicitedreview/$' % settings.URL_REGEXPS, views_review.complete_review),
+ url(r'^submitunsolicitedreview/$', views_review.submit_unsolicited_review_choose_team),
url(r'^(?P[0-9]+)/withdraw/$', views_review.withdraw_reviewer_assignment),
url(r'^(?P[0-9]+)/noresponse/$', views_review.mark_reviewer_assignment_no_response),
- url(r'^(?P[0-9]+)/searchmailarchive/$', views_review.search_mail_archive),
+ url(r'^assignment/(?P[0-9]+)/searchmailarchive/$', views_review.search_mail_archive),
+ url(r'^team/%(acronym)s/searchmailarchive/$' % settings.URL_REGEXPS, views_review.search_mail_archive),
url(r'^(?P[0-9]+)/editcomment/$', views_review.edit_comment),
url(r'^(?P[0-9]+)/editdeadline/$', views_review.edit_deadline),
]
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 3f1ea00a5..cffe273aa 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -64,7 +64,7 @@ from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_wi
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
add_events_message_info, get_unicode_document_content, build_doc_meta_block)
from ietf.community.utils import augment_docs_with_tracking_info
-from ietf.group.models import Role
+from ietf.group.models import Role, Group
from ietf.group.utils import can_manage_group_type, can_manage_materials, group_features_role_filter
from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is_person,
role_required, is_individual_draft_author)
@@ -317,6 +317,10 @@ def document_main(request, name, rev=None):
consensus = nice_consensus(e and e.consensus)
can_request_review = can_request_review_of_doc(request.user, doc)
+ can_submit_unsolicited_review_for_teams = None
+ if request.user.is_authenticated:
+ can_submit_unsolicited_review_for_teams = Group.objects.filter(
+ reviewteamsettings__isnull=False, role__person__user=request.user, role__name='secr')
# mailing list search archive
search_archive = "www.ietf.org/mail-archive/web/"
@@ -419,6 +423,7 @@ def document_main(request, name, rev=None):
can_edit_replaces=can_edit_replaces,
can_view_possibly_replaces=can_view_possibly_replaces,
can_request_review=can_request_review,
+ can_submit_unsolicited_review_for_teams=can_submit_unsolicited_review_for_teams,
rfc_number=rfc_number,
draft_name=draft_name,
diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py
index d74adde1c..6568a900f 100644
--- a/ietf/doc/views_review.py
+++ b/ietf/doc/views_review.py
@@ -24,7 +24,9 @@ from django.urls import reverse as urlreverse
from ietf.doc.models import (Document, NewRevisionDocEvent, State, DocAlias,
LastCallDocEvent, ReviewRequestDocEvent, ReviewAssignmentDocEvent, DocumentAuthor)
-from ietf.name.models import ReviewRequestStateName, ReviewAssignmentStateName, ReviewResultName, DocTypeName
+from ietf.name.models import ReviewRequestStateName, ReviewAssignmentStateName, ReviewResultName, \
+ DocTypeName, ReviewTypeName
+from ietf.person.models import Person
from ietf.review.models import ReviewRequest, ReviewAssignment
from ietf.group.models import Group
from ietf.ietfauth.utils import is_authorized_in_doc_stream, user_is_person, has_role
@@ -451,10 +453,43 @@ def mark_reviewer_assignment_no_response(request, name, assignment_id):
})
+class SubmitUnsolicitedReviewTeamChoiceForm(forms.Form):
+ team = forms.ModelChoiceField(queryset=Group.objects.filter(reviewteamsettings__isnull=False), widget=forms.RadioSelect, empty_label=None)
+
+ def __init__(self, user, *args, **kwargs):
+ super(SubmitUnsolicitedReviewTeamChoiceForm, self).__init__(*args, **kwargs)
+ self.fields['team'].queryset = self.fields['team'].queryset.filter(role__person__user=user, role__name='secr')
+
+
+def submit_unsolicited_review_choose_team(request, name):
+ """
+ If a user is submitting an unsolicited review, and is allowed to do this for more
+ than one team, they are routed through this small view to pick a team.
+ This is needed as the complete review form needs to be specific for a team.
+ """
+ if not request.user.is_authenticated:
+ # This view only produces a redirect, so it's open for any user
+ return HttpResponseForbidden("You do not have permission to perform this action")
+ doc = get_object_or_404(Document, name=name)
+ if request.method == "POST":
+ form = SubmitUnsolicitedReviewTeamChoiceForm(request.user, request.POST)
+ if form.is_valid():
+ return redirect("ietf.doc.views_review.complete_review",
+ name=doc.name, acronym=form.cleaned_data['team'].acronym)
+ else:
+ form = SubmitUnsolicitedReviewTeamChoiceForm(user=request.user)
+ return render(request, 'doc/review/submit_unsolicited_review.html', {
+ 'doc': doc,
+ 'form': form,
+ })
+
class CompleteReviewForm(forms.Form):
state = forms.ModelChoiceField(queryset=ReviewAssignmentStateName.objects.filter(slug__in=("completed", "part-completed")).order_by("-order"), widget=forms.RadioSelect, initial="completed")
reviewed_rev = forms.CharField(label="Reviewed revision", max_length=4)
result = forms.ModelChoiceField(queryset=ReviewResultName.objects.filter(used=True), widget=forms.RadioSelect, empty_label=None)
+ review_type = forms.ModelChoiceField(queryset=ReviewTypeName.objects.filter(used=True), widget=forms.RadioSelect, empty_label=None)
+ reviewer = forms.ModelChoiceField(queryset=Person.objects.all(), widget=forms.Select)
+
ACTIONS = [
("enter", "Enter review content (automatically posts to {mailing_list})"),
("upload", "Upload review content in text file (automatically posts to {mailing_list})"),
@@ -470,16 +505,15 @@ class CompleteReviewForm(forms.Form):
cc = MultiEmailField(required=False, help_text="Email addresses to send to in addition to the review team list")
email_ad = forms.BooleanField(label="Send extra email to the responsible AD suggesting early attention", required=False)
- def __init__(self, assignment, is_reviewer, *args, **kwargs):
+ def __init__(self, assignment, doc, team, is_reviewer, *args, **kwargs):
self.assignment = assignment
+ self.doc = doc
super(CompleteReviewForm, self).__init__(*args, **kwargs)
- doc = self.assignment.review_request.doc
-
known_revisions = NewRevisionDocEvent.objects.filter(doc=doc).order_by("time", "id").values_list("rev", "time", flat=False)
- revising_review = assignment.state_id not in ["assigned", "accepted"]
+ revising_review = assignment.state_id not in ["assigned", "accepted"] if assignment else False
if not revising_review:
self.fields["state"].choices = [
@@ -487,7 +521,7 @@ class CompleteReviewForm(forms.Form):
for slug, label in self.fields["state"].choices
]
- if 'initial' in kwargs:
+ if 'initial' in kwargs and assignment:
reviewed_rev_class = []
for r in known_revisions:
last_version = r[0]
@@ -515,16 +549,24 @@ class CompleteReviewForm(forms.Form):
" ".join("{1}".format('', *r)
for i, r in enumerate(known_revisions)))
- self.fields["result"].queryset = self.fields["result"].queryset.filter(reviewteamsettings_review_results_set__group=assignment.review_request.team)
+ self.fields["result"].queryset = self.fields["result"].queryset.filter(reviewteamsettings_review_results_set__group=team)
def format_submission_choice(label):
if revising_review:
label = label.replace(" (automatically posts to {mailing_list})", "")
- return label.format(mailing_list=assignment.review_request.team.list_email or "[error: team has no mailing list set]")
+ return label.format(mailing_list=team.list_email or "[error: team has no mailing list set]")
+
+ if assignment:
+ del self.fields["review_type"]
+ del self.fields["reviewer"]
+ else:
+ self.fields["review_type"].queryset = self.fields["review_type"].queryset.filter(
+ reviewteamsettings__group=team)
+ self.fields["reviewer"].queryset = self.fields["reviewer"].queryset.filter(role__name="reviewer", role__group=team)
self.fields["review_submission"].choices = [ (k, format_submission_choice(label)) for k, label in self.fields["review_submission"].choices]
-
+
if revising_review:
del self.fields["cc"]
elif is_reviewer:
@@ -532,7 +574,7 @@ class CompleteReviewForm(forms.Form):
del self.fields["completion_time"]
def clean_reviewed_rev(self):
- return clean_doc_revision(self.assignment.review_request.doc, self.cleaned_data.get("reviewed_rev"))
+ return clean_doc_revision(self.doc, self.cleaned_data.get("reviewed_rev"))
def clean_review_content(self):
return self.cleaned_data["review_content"].replace("\r", "")
@@ -550,7 +592,7 @@ class CompleteReviewForm(forms.Form):
return url
def clean(self):
- if "@" in self.assignment.reviewer.person.ascii:
+ if self.assignment and "@" in self.assignment.reviewer.person.ascii:
raise forms.ValidationError("Reviewer name must be filled in (the ASCII version is currently \"{}\" - since it contains an @ sign the name is probably still the original email address).".format(self.review_req.reviewer.person.ascii))
def require_field(f):
@@ -566,35 +608,67 @@ class CompleteReviewForm(forms.Form):
require_field("review_url")
@login_required
-def complete_review(request, name, assignment_id):
+def complete_review(request, name, assignment_id=None, acronym=None):
doc = get_object_or_404(Document, name=name)
- assignment = get_object_or_404(ReviewAssignment, pk=assignment_id)
-
- revising_review = assignment.state_id not in ["assigned", "accepted"]
-
- is_reviewer = user_is_person(request.user, assignment.reviewer.person)
- can_manage_request = can_manage_review_requests_for_team(request.user, assignment.review_request.team)
-
- if not (is_reviewer or can_manage_request):
- return HttpResponseForbidden("You do not have permission to perform this action")
-
- team_acronym = assignment.review_request.team.acronym.lower()
- request_type = assignment.review_request.type
- mailtrigger_slug = 'review_completed_{}_{}'.format(team_acronym, request_type.slug)
- # Description is only used if the mailtrigger does not exist yet.
- mailtrigger_desc = 'Recipients when a {} {} review is completed'.format(team_acronym, request_type)
- to, cc = gather_address_lists(
- mailtrigger_slug,
- create_from_slug_if_not_exists='review_completed',
- desc_if_not_exists=mailtrigger_desc,
- review_req=assignment.review_request
- )
+ if assignment_id:
+ assignment = get_object_or_404(ReviewAssignment, pk=assignment_id)
+
+ revising_review = assignment.state_id not in ["assigned", "accepted"]
+
+ is_reviewer = user_is_person(request.user, assignment.reviewer.person)
+ can_manage_request = can_manage_review_requests_for_team(request.user, assignment.review_request.team)
+
+ if not (is_reviewer or can_manage_request):
+ return HttpResponseForbidden("You do not have permission to perform this action")
+
+ team = assignment.review_request.team
+ team_acronym = assignment.review_request.team.acronym.lower()
+ request_type = assignment.review_request.type
+ mailtrigger_slug = 'review_completed_{}_{}'.format(team_acronym, request_type.slug)
+ # Description is only used if the mailtrigger does not exist yet.
+ mailtrigger_desc = 'Recipients when a {} {} review is completed'.format(team_acronym, request_type)
+ to, cc = gather_address_lists(
+ mailtrigger_slug,
+ create_from_slug_if_not_exists='review_completed',
+ desc_if_not_exists=mailtrigger_desc,
+ review_req=assignment.review_request
+ )
+ else:
+ team = get_object_or_404(Group, acronym=acronym)
+ if not can_manage_review_requests_for_team(request.user, team):
+ return HttpResponseForbidden("You do not have permission to perform this action")
+ assignment = None
+ is_reviewer = False
+ revising_review = False
+ request_type = None
+ to, cc = [], []
if request.method == "POST":
- form = CompleteReviewForm(assignment, is_reviewer,
+ form = CompleteReviewForm(assignment, doc, team, is_reviewer,
request.POST, request.FILES)
if form.is_valid():
review_submission = form.cleaned_data['review_submission']
+
+ if not assignment:
+ # If this is an unsolicited review, create a new request and assignment.
+ # The assignment will be immediately closed after, sharing the usual
+ # processes for regular assigned reviews.
+ review_request = ReviewRequest.objects.create(
+ state_id='assigned',
+ type=form.cleaned_data['review_type'],
+ doc=doc,
+ team=team,
+ deadline=datetime.date.today(),
+ requested_by=Person.objects.get(user=request.user),
+ requested_rev=form.cleaned_data['reviewed_rev'],
+ )
+ assignment = ReviewAssignment.objects.create(
+ review_request=review_request,
+ state_id='assigned',
+ reviewer=form.cleaned_data['reviewer'].role_email('reviewer', group=team),
+ assigned_on=datetime.datetime.now(),
+ )
+ request_type = form.cleaned_data['review_type']
review = assignment.review
if not review:
@@ -772,23 +846,24 @@ def complete_review(request, name, assignment_id):
return redirect("ietf.doc.views_doc.document_main", name=assignment.review.name)
else:
initial={
- "reviewed_rev": assignment.reviewed_rev,
- "result": assignment.result_id,
+ "reviewed_rev": assignment.reviewed_rev if assignment else None,
+ "result": assignment.result_id if assignment else None,
"cc": ", ".join(cc),
}
try:
initial['review_content'] = render_to_string('/group/%s/review/content_templates/%s.txt' % (assignment.review_request.team.acronym,
request_type.slug), {'assignment':assignment, 'today':datetime.date.today()})
- except TemplateDoesNotExist:
+ except (TemplateDoesNotExist, AttributeError):
pass
- form = CompleteReviewForm(assignment, is_reviewer, initial=initial)
+ form = CompleteReviewForm(assignment, doc, team, is_reviewer, initial=initial)
- mail_archive_query_urls = mailarch.construct_query_urls(assignment.review_request)
+ mail_archive_query_urls = mailarch.construct_query_urls(doc, team)
return render(request, 'doc/review/complete_review.html', {
'doc': doc,
+ 'team': team,
'assignment': assignment,
'form': form,
'mail_archive_query_urls': mail_archive_query_urls,
@@ -797,16 +872,22 @@ def complete_review(request, name, assignment_id):
'review_cc': cc,
})
-def search_mail_archive(request, name, assignment_id):
- assignment = get_object_or_404(ReviewAssignment, pk=assignment_id)
+def search_mail_archive(request, name, acronym=None, assignment_id=None):
+ if assignment_id:
+ assignment = get_object_or_404(ReviewAssignment, pk=assignment_id)
+ team = assignment.review_request.team
+ else:
+ assignment = None
+ team = get_object_or_404(Group, acronym=acronym)
+ doc = get_object_or_404(Document, name=name)
- is_reviewer = user_is_person(request.user, assignment.reviewer.person)
- can_manage_request = can_manage_review_requests_for_team(request.user, assignment.review_request.team)
+ is_reviewer = assignment and user_is_person(request.user, assignment.reviewer.person)
+ can_manage_request = can_manage_review_requests_for_team(request.user, team)
if not (is_reviewer or can_manage_request):
return HttpResponseForbidden("You do not have permission to perform this action")
- res = mailarch.construct_query_urls(assignment.review_request, query=request.GET.get("query"))
+ res = mailarch.construct_query_urls(doc, team, query=request.GET.get("query"))
if not res:
return JsonResponse({ "error": "Couldn't do lookup in mail archive - don't know where to look"})
diff --git a/ietf/review/mailarch.py b/ietf/review/mailarch.py
index fc50f1075..3ad1dbd30 100644
--- a/ietf/review/mailarch.py
+++ b/ietf/review/mailarch.py
@@ -42,13 +42,13 @@ def hash_list_message_id(list_name, msgid):
sha.update(force_bytes(list_name))
return base64.urlsafe_b64encode(sha.digest()).rstrip(b"=")
-def construct_query_urls(review_req, query=None):
- list_name = list_name_from_email(review_req.team.list_email)
+def construct_query_urls(doc, team, query=None):
+ list_name = list_name_from_email(team.list_email)
if not list_name:
return None
if not query:
- query = review_req.doc.name
+ query = doc.name
encoded_query = "?" + urlencode({
"qdr": "c", # custom time frame
diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html
index f74b3b8c8..fc70217ea 100644
--- a/ietf/templates/doc/document_draft.html
+++ b/ietf/templates/doc/document_draft.html
@@ -224,6 +224,11 @@
{% if can_request_review %}
+ You are submitting an unsolicited review for this document for the {{ team }}.
+ This process should only be used for unsolicited reviews.
+ A review request and assignment will be created automatically upon submitting this review.
+
+ {% endif %}
- {% if not revising_review %}
+ {% if assignment and not revising_review %}
The review findings should be made available here and the review
posted to the mailing list. If you enter the findings below, the
system will post the review for you. If you already have posted
@@ -30,7 +38,7 @@
If you enter the review below, the review will be sent
to {{ review_to|join:", " }}
{% if review_cc %}, with a Cc to {{ review_cc|join:", " }}{% endif %}.
- {% else %}
+ {% elif assignment %}
You can revise this review by entering the results below.