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
This commit is contained in:
Sasha Romijn 2019-10-24 12:37:59 +00:00
parent 93797b3fc1
commit 871a4b653b
8 changed files with 285 additions and 67 deletions

View file

@ -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<html><body><div class="xtr"><div class="xtd no-results">No results found</div></div>')
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()

View file

@ -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<request_id>[0-9]+)/assignreviewer/$', views_review.assign_reviewer),
url(r'^(?P<assignment_id>[0-9]+)/rejectreviewerassignment/$', views_review.reject_reviewer_assignment),
url(r'^(?P<assignment_id>[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<assignment_id>[0-9]+)/withdraw/$', views_review.withdraw_reviewer_assignment),
url(r'^(?P<assignment_id>[0-9]+)/noresponse/$', views_review.mark_reviewer_assignment_no_response),
url(r'^(?P<assignment_id>[0-9]+)/searchmailarchive/$', views_review.search_mail_archive),
url(r'^assignment/(?P<assignment_id>[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<request_id>[0-9]+)/editcomment/$', views_review.edit_comment),
url(r'^(?P<request_id>[0-9]+)/editdeadline/$', views_review.edit_deadline),
]

View file

@ -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,

View file

@ -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("<a class=\"rev label label-default {0}\" title=\"{2:%Y-%m-%d}\">{1}</a>".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"})

View file

@ -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

View file

@ -224,6 +224,11 @@
{% if can_request_review %}
<div>
<a class="btn btn-default btn-xs" href="{% url "ietf.doc.views_review.request_review" doc.name %}"><span class="fa fa-check-circle-o"></span> Request review</a>
{% if can_submit_unsolicited_review_for_teams|length == 1 %}
<a class="btn btn-default btn-xs" href="{% url "ietf.doc.views_review.complete_review" doc.name can_submit_unsolicited_review_for_teams.0.acronym %}"><span class="fa fa-pencil-square-o"></span> Submit unsolicited review</a>
{% elif can_submit_unsolicited_review_for_teams %}
<a class="btn btn-default btn-xs" href="{% url "ietf.doc.views_review.submit_unsolicited_review_choose_team" doc.name %}"><span class="fa fa-pencil-square-o"></span> Submit unsolicited review</a>
{% endif %}
</div>
{% endif %}
</td>

View file

@ -2,7 +2,7 @@
{# Copyright The IETF Trust 2016, All Rights Reserved #}
{% load origin bootstrap3 static %}
{% block title %}{% if revising_review %}Revise{% else %}Complete{% endif %} review of {{ review_req.doc.name }}{% endblock %}
{% block title %}{% if revising_review %}Revise{% elif assignment %}Complete{% else %}Submit unsolicited{% endif %} review of {{ review_req.doc.name }}{% endblock %}
{% block pagehead %}
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
@ -10,18 +10,26 @@
{% block content %}
{% origin %}
<h1>{% if revising_review %}Revise{% else %}Complete{% endif %} review<br>
<small>{{ assignment.review_request.doc.name }}</small>
<h1>{% if revising_review %}Revise{% elif assignment %}Complete{% else %}Submit unsolicited{% endif %} review<br>
<small>{{ doc.name }}</small>
</h1>
<p>
<div><strong>Review type:</strong> {{ assignment.review_request.team.acronym }} - {{ assignment.review_request.type }} review </div>
<div><strong>Requested version for review:</strong> {{ assignment.review_request.requested_rev|default:"Current" }} </div>
<div><strong>Requested:</strong> {{ assignment.review_request.time|date:"Y-m-d" }} </div>
<div><strong>Reviewer:</strong> {{ assignment.reviewer.person.name }}</div>
</p>
{% if assignment %}
<p>
<div><strong>Review type:</strong> {{ assignment.review_request.team.acronym }} - {{ assignment.review_request.type }} review </div>
<div><strong>Requested version for review:</strong> {{ assignment.review_request.requested_rev|default:"Current" }} </div>
<div><strong>Requested:</strong> {{ assignment.review_request.time|date:"Y-m-d" }} </div>
<div><strong>Reviewer:</strong> {{ assignment.reviewer.person.name }}</div>
</p>
{% else %}
<p>
You are submitting an <strong>unsolicited</strong> review for this document for the {{ team }}.
<strong>This process should only be used for unsolicited reviews.</strong>
A review request and assignment will be created automatically upon submitting this review.
</p>
{% endif %}
{% if not revising_review %}
{% if assignment and not revising_review %}
<p>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 @@
<p>If you enter the review below, the review will be sent
to <em>{{ review_to|join:", " }}</em>
{% if review_cc %}, with a Cc to <em>{{ review_cc|join:", " }}</em>{% endif %}.</p>
{% else %}
{% elif assignment %}
<p>You can revise this review by entering the results below.</p>
{% endif %}
@ -40,7 +48,11 @@
{% bootstrap_form form layout="horizontal" %}
{% buttons %}
<a class="btn btn-default" href="{% url "ietf.doc.views_review.review_request" name=doc.canonical_name request_id=assignment.review_request.pk %}">Cancel</a>
{% if assignment %}
<a class="btn btn-default" href="{% url "ietf.doc.views_review.review_request" name=doc.canonical_name request_id=assignment.review_request.pk %}">Cancel</a>
{% else %}
<a class="btn btn-default" href="{{ doc.get_absolute_url }}">Cancel</a>
{% endif %}
<button type="submit" class="btn btn-primary">{% if revising_review %}Revise{% else %}Complete{% endif %} review</button>
{% endbuttons %}
@ -98,7 +110,11 @@
{% block js %}
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
<script>
var searchMailArchiveUrl = "{% url "ietf.doc.views_review.search_mail_archive" name=assignment.review_request.doc.name assignment_id=assignment.pk %}";
{% if assignment %}
var searchMailArchiveUrl = "{% url "ietf.doc.views_review.search_mail_archive" name=doc.name assignment_id=assignment.pk %}";
{% else %}
var searchMailArchiveUrl = "{% url "ietf.doc.views_review.search_mail_archive" name=doc.name acronym=team.acronym %}";
{% endif %}
</script>
<script src="{% static 'ietf/js/complete-review.js' %}"></script>
{% endblock %}

View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2016, All Rights Reserved #}
{% load origin bootstrap3 static %}
{% block title %}Submit an unsolicited review of {{ review_req.doc.name }}{% endblock %}
{% block content %}
{% origin %}
<h1>Submit unsolicited review<br>
<small>{{ doc.name }}</small>
</h1>
<p>
You are submitting an <strong>unsolicited</strong> review for this document.
First, select the team for which you will be submitting this review.
</p>
<form class="form-horizontal" method="post">
{% csrf_token %}
{% bootstrap_form form layout="horizontal" %}
{% buttons %}
<button type="submit" class="btn btn-primary">Continue</button>
{% endbuttons %}
</form>
{% endblock %}