Rework closing a review request so the logic is reusable, add the more

specific close reasons to the database migration, add
ReviewRequest.requested_by so it's possible to notify the requester of
a review that it has been dropped.
 - Legacy-Id: 11520
This commit is contained in:
Ole Laursen 2016-07-05 16:05:00 +00:00
parent ebc37dede9
commit 19fff81a4f
15 changed files with 171 additions and 93 deletions

View file

@ -14,7 +14,7 @@ import debug # pyflakes:ignore
from ietf.review.models import ReviewRequest, ReviewTeamResult
import ietf.review.mailarch
from ietf.person.models import Email
from ietf.person.models import Email, Person
from ietf.name.models import ReviewResultName, ReviewRequestStateName
from ietf.utils.test_utils import TestCase
from ietf.utils.test_data import make_test_data, make_review_data
@ -57,7 +57,8 @@ class ReviewTests(TestCase):
"type": "early",
"team": review_team.pk,
"deadline_date": deadline_date.isoformat(),
"requested_rev": "01"
"requested_rev": "01",
"requested_by": Person.objects.get(user__username="plain").pk,
})
self.assertEqual(r.status_code, 302)
@ -82,13 +83,13 @@ class ReviewTests(TestCase):
self.assertEqual(r.status_code, 200)
self.assertTrue(review_req.team.acronym.upper() in unicontent(r))
def test_withdraw_request(self):
def test_close_request(self):
doc = make_test_data()
review_req = make_review_data(doc)
review_req.state = ReviewRequestStateName.objects.get(slug="accepted")
review_req.save()
withdraw_url = urlreverse('ietf.doc.views_review.withdraw_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
close_url = urlreverse('ietf.doc.views_review.close_request', kwargs={ "name": doc.name, "request_id": review_req.pk })
# follow link
@ -96,26 +97,26 @@ class ReviewTests(TestCase):
self.client.login(username="secretary", password="secretary+password")
r = self.client.get(req_url)
self.assertEqual(r.status_code, 200)
self.assertTrue(withdraw_url in unicontent(r))
self.assertTrue(close_url in unicontent(r))
self.client.logout()
# get withdraw page
login_testing_unauthorized(self, "secretary", withdraw_url)
r = self.client.get(withdraw_url)
# get close page
login_testing_unauthorized(self, "secretary", close_url)
r = self.client.get(close_url)
self.assertEqual(r.status_code, 200)
# withdraw
# close
empty_outbox()
r = self.client.post(withdraw_url, { "action": "withdraw" })
r = self.client.post(close_url, { "close_reason": "withdrawn" })
self.assertEqual(r.status_code, 302)
review_req = reload_db_objects(review_req)
self.assertEqual(review_req.state_id, "withdrawn")
e = doc.latest_event()
self.assertEqual(e.type, "changed_review_request")
self.assertTrue("Withdrew" in e.desc)
self.assertTrue("closed" in e.desc.lower())
self.assertEqual(len(outbox), 1)
self.assertTrue("withdrawn" in unicode(outbox[0]))
self.assertTrue("closed" in unicode(outbox[0]).lower())
def test_assign_reviewer(self):
doc = make_test_data()

View file

@ -4,7 +4,7 @@ from ietf.doc import views_review
urlpatterns = patterns('',
url(r'^$', views_review.request_review),
url(r'^(?P<request_id>[0-9]+)/$', views_review.review_request),
url(r'^(?P<request_id>[0-9]+)/withdraw/$', views_review.withdraw_request),
url(r'^(?P<request_id>[0-9]+)/close/$', views_review.close_request),
url(r'^(?P<request_id>[0-9]+)/assignreviewer/$', views_review.assign_reviewer),
url(r'^(?P<request_id>[0-9]+)/rejectreviewerassignment/$', views_review.reject_reviewer_assignment),
url(r'^(?P<request_id>[0-9]+)/complete/$', views_review.complete_review),

View file

@ -10,13 +10,14 @@ from django.core.exceptions import ValidationError
from django.template.loader import render_to_string
from ietf.doc.models import Document, NewRevisionDocEvent, DocEvent, State, DocAlias
from ietf.ietfauth.utils import is_authorized_in_doc_stream, user_is_person
from ietf.ietfauth.utils import is_authorized_in_doc_stream, user_is_person, has_role
from ietf.name.models import ReviewRequestStateName, ReviewResultName, DocTypeName
from ietf.review.models import ReviewRequest
from ietf.person.fields import PersonEmailChoiceField
from ietf.person.fields import PersonEmailChoiceField, SearchablePersonField
from ietf.review.utils import (active_review_teams, assign_review_request_to_reviewer,
can_request_review_of_doc, can_manage_review_requests_for_team,
email_about_review_request, make_new_review_request_from_existing)
email_review_request_change, make_new_review_request_from_existing,
close_review_request_states, close_review_request)
from ietf.review import mailarch
from ietf.utils.fields import DatepickerDateField
from ietf.utils.text import skip_prefix
@ -38,7 +39,7 @@ class RequestReviewForm(forms.ModelForm):
class Meta:
model = ReviewRequest
fields = ('type', 'team', 'deadline', 'requested_rev')
fields = ('requested_by', 'type', 'team', 'deadline', 'requested_rev')
def __init__(self, user, doc, *args, **kwargs):
super(RequestReviewForm, self).__init__(*args, **kwargs)
@ -57,6 +58,12 @@ class RequestReviewForm(forms.ModelForm):
self.fields["deadline"].required = False
self.fields["requested_rev"].label = "Document revision"
if has_role(user, "Secretariat"):
self.fields["requested_by"] = SearchablePersonField()
else:
self.fields["requested_by"].widget = forms.HiddenInput()
self.fields["requested_by"].initial = user.person.pk
def clean_deadline_date(self):
v = self.cleaned_data.get('deadline_date')
if v < datetime.date.today():
@ -119,9 +126,9 @@ def review_request(request, name, request_id):
is_reviewer = review_req.reviewer and user_is_person(request.user, review_req.reviewer.person)
can_manage_request = can_manage_review_requests_for_team(request.user, review_req.team)
can_withdraw_request = (review_req.state_id in ["requested", "accepted"]
and (is_authorized_in_doc_stream(request.user, doc)
or can_manage_request))
can_close_request = (review_req.state_id in ["requested", "accepted"]
and (is_authorized_in_doc_stream(request.user, doc)
or can_manage_request))
can_assign_reviewer = (review_req.state_id in ["requested", "accepted"]
and can_manage_request)
@ -147,47 +154,55 @@ def review_request(request, name, request_id):
return render(request, 'doc/review/review_request.html', {
'doc': doc,
'review_req': review_req,
'can_withdraw_request': can_withdraw_request,
'can_close_request': can_close_request,
'can_reject_reviewer_assignment': can_reject_reviewer_assignment,
'can_assign_reviewer': can_assign_reviewer,
'can_accept_reviewer_assignment': can_accept_reviewer_assignment,
'can_complete_review': can_complete_review,
})
class CloseReviewRequestForm(forms.Form):
close_reason = forms.ModelChoiceField(queryset=close_review_request_states(), widget=forms.RadioSelect, empty_label=None)
def __init__(self, can_manage_request, *args, **kwargs):
super(CloseReviewRequestForm, self).__init__(*args, **kwargs)
if not can_manage_request:
self.fields["close_reason"].queryset = self.fields["close_reason"].queryset.filter(slug__in=["withdrawn"])
if len(self.fields["close_reason"].queryset) == 1:
self.fields["close_reason"].initial = self.fields["close_reason"].queryset.first().pk
self.fields["close_reason"].widget = forms.HiddenInput()
@login_required
def withdraw_request(request, name, request_id):
def close_request(request, name, request_id):
doc = get_object_or_404(Document, name=name)
review_req = get_object_or_404(ReviewRequest, pk=request_id, state__in=["requested", "accepted"])
if not is_authorized_in_doc_stream(request.user, doc):
can_request = is_authorized_in_doc_stream(request.user, doc)
can_manage_request = can_manage_review_requests_for_team(request.user, review_req.team)
if not (can_request or can_manage_request):
return HttpResponseForbidden("You do not have permission to perform this action")
if request.method == "POST" and request.POST.get("action") == "withdraw":
prev_state = review_req.state
review_req.state = ReviewRequestStateName.objects.get(slug="withdrawn")
review_req.save()
DocEvent.objects.create(
type="changed_review_request",
doc=doc,
by=request.user.person,
desc="Withdrew request for {} review by {}".format(review_req.type.name, review_req.team.acronym.upper()),
)
if prev_state.slug != "requested":
email_about_review_request(
request, review_req,
"Withdrew review request for %s" % review_req.doc.name,
"Review request has been withdrawn by %s." % request.user.person,
by=request.user.person, notify_secretary=False, notify_reviewer=True)
if request.method == "POST":
form = CloseReviewRequestForm(can_manage_request, request.POST)
if form.is_valid():
close_review_request(request, review_req, form.cleaned_data["close_reason"])
return redirect(review_request, name=review_req.doc.name, request_id=review_req.pk)
else:
form = CloseReviewRequestForm(can_manage_request)
return render(request, 'doc/review/withdraw_request.html', {
return render(request, 'doc/review/close_request.html', {
'doc': doc,
'review_req': review_req,
'form': form,
})
class AssignReviewerForm(forms.Form):
reviewer = PersonEmailChoiceField(widget=forms.RadioSelect, empty_label="(None)", required=False)
@ -266,7 +281,7 @@ def reject_reviewer_assignment(request, name, request_id):
"message_to_secretary": form.cleaned_data.get("message_to_secretary")
})
email_about_review_request(request, review_req, "Reviewer assignment rejected", msg, by=request.user.person, notify_secretary=True, notify_reviewer=True)
email_review_request_change(request, review_req, "Reviewer assignment rejected", msg, by=request.user.person, notify_secretary=True, notify_reviewer=True, notify_requested_by=False)
return redirect(review_request, name=new_review_req.doc.name, request_id=new_review_req.pk)
else:
@ -438,7 +453,7 @@ def complete_review(request, name, request_id):
"new_review_req": new_review_req,
})
email_about_review_request(request, review_req, subject, msg, request.user.person, notify_secretary=True, notify_reviewer=False)
email_review_request_change(request, review_req, subject, msg, request.user.person, notify_secretary=True, notify_reviewer=False, notify_requested_by=False)
if review_submission != "link" and review_req.team.list_email:
# email the review

View file

@ -7,7 +7,7 @@ from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.test_data import make_test_data, make_review_data
from ietf.utils.test_utils import login_testing_unauthorized, TestCase, unicontent, reload_db_objects
from ietf.review.models import ReviewRequest
from ietf.person.models import Email
from ietf.person.models import Email, Person
import ietf.group.views_review
class ReviewTests(TestCase):
@ -28,6 +28,7 @@ class ReviewTests(TestCase):
deadline=datetime.datetime.combine(datetime.date.today() + datetime.timedelta(days=30), datetime.time(23, 59, 59)),
state_id="accepted",
reviewer=review_req1.reviewer,
requested_by=Person.objects.get(user__username="plain"),
)
review_req3 = ReviewRequest.objects.create(
@ -36,6 +37,7 @@ class ReviewTests(TestCase):
type_id="early",
deadline=datetime.datetime.combine(datetime.date.today() + datetime.timedelta(days=30), datetime.time(23, 59, 59)),
state_id="requested",
requested_by=Person.objects.get(user__username="plain"),
)
# get

View file

@ -4,10 +4,11 @@ from django.contrib.auth.decorators import login_required
from django import forms
from ietf.review.models import ReviewRequest, ReviewRequestStateName
from ietf.review.utils import (can_manage_review_requests_for_team,
from ietf.review.utils import (can_manage_review_requests_for_team, close_review_request_states,
extract_revision_ordered_review_requests_for_documents,
assign_review_request_to_reviewer,
# email_about_review_request, make_new_review_request_from_existing,
close_review_request,
# email_review_request_change, make_new_review_request_from_existing,
suggested_review_requests_for_team)
from ietf.group.utils import get_group_or_404
from ietf.person.fields import PersonEmailChoiceField
@ -21,14 +22,7 @@ class ManageReviewRequestForm(forms.Form):
action = forms.ChoiceField(choices=ACTIONS, widget=forms.HiddenInput, required=False)
CLOSE_OPTIONS = [
("noreviewversion", "No review of this version"),
("noreviewdocument", "No review of document"),
("withdraw", "Withdraw request"),
("no-response", "No response"),
("overtaken", "Overtaken by events"),
]
close = forms.ChoiceField(choices=CLOSE_OPTIONS, required=False)
close = forms.ModelChoiceField(queryset=close_review_request_states(), required=False)
reviewer = PersonEmailChoiceField(empty_label="(None)", required=False, label_with="person")
@ -44,9 +38,9 @@ class ManageReviewRequestForm(forms.Form):
close_initial = None
if review_req.pk is None:
if review_req.latest_reqs:
close_initial = "noreviewversion"
close_initial = "no-review-version"
else:
close_initial = "noreviewdocument"
close_initial = "no-review-document"
elif review_req.reviewer:
close_initial = "no-response"
else:
@ -55,6 +49,9 @@ class ManageReviewRequestForm(forms.Form):
if close_initial:
self.fields["close"].initial = close_initial
if review_req.pk is None:
self.fields["close"].queryset = self.fields["close"].queryset.filter(slug__in=["noreviewversion", "noreviewdocument"])
self.fields["close"].widget.attrs["class"] = "form-control input-sm"
self.fields["reviewer"].queryset = self.fields["reviewer"].queryset.filter(
@ -115,18 +112,13 @@ def manage_review_requests(request, acronym, group_type=None):
form_results.append(req.form.is_valid())
if all(form_results):
for req in review_requests:
action = req.form.cleaned_data.get("action")
for review_req in review_requests:
action = review_req.form.cleaned_data.get("action")
if action == "assign":
assign_review_request_to_reviewer(request, req, req.form.cleaned_data["reviewer"])
assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"])
elif action == "close":
close_reason = req.form.cleaned_data["close"]
if close_reason in ("withdraw", "no-response", "overtaken"):
req.state = ReviewRequestStateName.objects.get(slug=close_reason, used=True)
req.save()
# FIXME: notify?
else:
FIXME
close_review_request(request, review_req, review_req.form.cleaned_data["close"])
kwargs = { "acronym": group.acronym }
if group_type:

View file

@ -12,8 +12,10 @@ def insert_initial_review_data(apps, schema_editor):
ReviewRequestStateName.objects.get_or_create(slug="withdrawn", name="Withdrawn", order=4)
ReviewRequestStateName.objects.get_or_create(slug="overtaken", name="Overtaken By Events", order=5)
ReviewRequestStateName.objects.get_or_create(slug="no-response", name="No Response", order=6)
ReviewRequestStateName.objects.get_or_create(slug="part-completed", name="Partially Completed", order=7)
ReviewRequestStateName.objects.get_or_create(slug="completed", name="Completed", order=8)
ReviewRequestStateName.objects.get_or_create(slug="no-review-version", name="No Review of Version", order=7)
ReviewRequestStateName.objects.get_or_create(slug="no-review-document", name="No Review of Document", order=8)
ReviewRequestStateName.objects.get_or_create(slug="part-completed", name="Partially Completed", order=9)
ReviewRequestStateName.objects.get_or_create(slug="completed", name="Completed", order=10)
ReviewTypeName = apps.get_model("name", "ReviewTypeName")
ReviewTypeName.objects.get_or_create(slug="early", name="Early", order=1)

View file

@ -89,7 +89,7 @@ class LiaisonStatementTagName(NameModel):
"Action Required, Action Taken"
class ReviewRequestStateName(NameModel):
"""Requested, Accepted, Rejected, Withdrawn, Overtaken By Events,
No Response, Partially Completed, Completed"""
No Response, No Review of Version, No Review of Document, Partially Completed, Completed"""
class ReviewTypeName(NameModel):
"""Early Review, Last Call, Telechat"""
class ReviewResultName(NameModel):

View file

@ -161,6 +161,8 @@ with db_con.cursor() as c:
doc_metadata[(row.docname, row.version)] = doc_metadata[row.docname] = (parse_timestamp(row.deadline), parse_timestamp(row.telechat), parse_timestamp(row.lcend), row.status)
system_person = Person.objects.get(name="(System)")
with db_con.cursor() as c:
c.execute("select * from reviews order by reviewid;")
@ -207,6 +209,7 @@ with db_con.cursor() as c:
"state": states["requested"],
"type": type_name,
"deadline": deadline,
"requested_by": system_person,
}
)

View file

@ -42,6 +42,7 @@ class Migration(migrations.Migration):
('result', models.ForeignKey(blank=True, to='name.ReviewResultName', null=True)),
('review', models.OneToOneField(null=True, blank=True, to='doc.Document')),
('reviewer', models.ForeignKey(blank=True, to='person.Email', null=True)),
('requested_by', models.ForeignKey(to='person.Person')),
('state', models.ForeignKey(to='name.ReviewRequestStateName')),
('team', models.ForeignKey(to='group.Group')),
('type', models.ForeignKey(to='name.ReviewTypeName')),

View file

@ -42,6 +42,7 @@ class ReviewRequest(models.Model):
doc = models.ForeignKey(Document, related_name='review_request_set')
team = models.ForeignKey(Group, limit_choices_to=~models.Q(reviewteamresult=None))
deadline = models.DateTimeField()
requested_by = models.ForeignKey(Person)
requested_rev = models.CharField(verbose_name="requested revision", max_length=16, blank=True, help_text="Fill in if a specific revision is to be reviewed, e.g. 02")
# Fields filled in as reviewer is assigned and as the review is

View file

@ -6,6 +6,7 @@ from django.contrib.sites.models import Site
from ietf.group.models import Group, Role
from ietf.doc.models import Document, DocEvent, State, LastCallDocEvent
from ietf.iesg.models import TelechatDate
from ietf.person.models import Person
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream
from ietf.review.models import ReviewRequest, ReviewRequestStateName, ReviewTypeName
from ietf.utils.mail import send_mail
@ -15,6 +16,9 @@ def active_review_teams():
# if there's a ReviewTeamResult defined, it's a review team
return Group.objects.filter(state="active").exclude(reviewteamresult=None)
def close_review_request_states():
return ReviewRequestStateName.objects.filter(used=True).exclude(slug__in=["requested", "accepted", "rejected", "part-completed", "completed"])
def can_request_review_of_doc(user, doc):
if not user.is_authenticated():
return False
@ -35,30 +39,44 @@ def make_new_review_request_from_existing(review_req):
obj.team = review_req.team
obj.deadline = review_req.deadline
obj.requested_rev = review_req.requested_rev
obj.requested_by = review_req.requested_by
obj.state = ReviewRequestStateName.objects.get(slug="requested")
return obj
def email_about_review_request(request, review_req, subject, msg, by, notify_secretary, notify_reviewer):
def email_review_request_change(request, review_req, subject, msg, by, notify_secretary, notify_reviewer, notify_requested_by):
"""Notify possibly both secretary and reviewer about change, skipping
a party if the change was done by that party."""
def extract_email_addresses(objs):
if any(o.person == by for o in objs if o):
return []
else:
return [o.formatted_email() for o in objs if o]
system_email = Person.objects.get(name="(System)").formatted_email()
to = []
if notify_secretary:
to += extract_email_addresses(Role.objects.filter(name__in=["secretary", "delegate"], group=review_req.team).distinct())
if notify_reviewer:
to += extract_email_addresses([review_req.reviewer])
def extract_email_addresses(objs):
if any(o.person == by for o in objs if o):
l = []
else:
l = []
for o in objs:
if o:
e = o.formatted_email()
if e != system_email:
l.append(e)
for e in l:
if e not in to:
to.append(e)
if notify_secretary:
extract_email_addresses(Role.objects.filter(name__in=["secretary", "delegate"], group=review_req.team).distinct())
if notify_reviewer:
extract_email_addresses([review_req.reviewer])
if notify_requested_by:
extract_email_addresses([review_req.requested_by.email()])
if not to:
return
send_mail(request, list(set(to)), None, subject, "doc/mail/review_request_changed.txt", {
send_mail(request, to, None, subject, "doc/mail/review_request_changed.txt", {
"domain": Site.objects.get_current().domain,
"review_req": review_req,
"msg": msg,
@ -71,11 +89,11 @@ def assign_review_request_to_reviewer(request, review_req, reviewer):
return
if review_req.reviewer:
email_about_review_request(
email_review_request_change(
request, review_req,
"Unassigned from review of %s" % review_req.doc.name,
"%s has cancelled your assignment to the review." % request.user.person,
by=request.user.person, notify_secretary=False, notify_reviewer=True)
by=request.user.person, notify_secretary=False, notify_reviewer=True, notify_requested_by=False)
review_req.state = ReviewRequestStateName.objects.get(slug="requested")
review_req.reviewer = reviewer
@ -92,11 +110,36 @@ def assign_review_request_to_reviewer(request, review_req, reviewer):
),
)
email_about_review_request(
email_review_request_change(
request, review_req,
"Assigned to review of %s" % review_req.doc.name,
"%s has assigned you to review the document." % request.user.person,
by=request.user.person, notify_secretary=False, notify_reviewer=True)
by=request.user.person, notify_secretary=False, notify_reviewer=True, notify_requested_by=False)
def close_review_request(request, review_req, close_state):
suggested_req = review_req.pk is None
prev_state = review_req.state
review_req.state = close_state
if close_state.slug == "no-review-version":
review_req.reviewed_rev = review_req.doc.rev # save rev for later reference
review_req.save()
if not suggested_req:
DocEvent.objects.create(
type="changed_review_request",
doc=review_req.doc,
by=request.user.person,
desc="Closed request for {} review by {} with state '{}'".format(
review_req.type.name, review_req.team.acronym.upper(), close_state.name),
)
if prev_state.slug != "requested":
email_review_request_change(
request, review_req,
"Closed review request for {}: {}".format(review_req.doc.name, close_state.name),
"Review request has been closed by {}.".format(request.user.person),
by=request.user.person, notify_secretary=False, notify_reviewer=True, notify_requested_by=True)
def suggested_review_requests_for_team(team):
def fixup_deadline(d):
@ -104,11 +147,13 @@ def suggested_review_requests_for_team(team):
d = d - datetime.timedelta(seconds=1) # 23:59:59 is treated specially in the view code
return d
system_person = Person.objects.get(name="(System)")
seen_deadlines = {}
requests = {}
if True:
if True: # FIXME
# in Last Call
last_call_type = ReviewTypeName.objects.get(slug="lc")
last_call_docs = Document.objects.filter(states=State.objects.get(type="draft-iesg", slug="lc", used=True))
@ -125,12 +170,13 @@ def suggested_review_requests_for_team(team):
doc=doc,
team=team,
deadline=deadline,
requested_by=system_person,
)
seen_deadlines[doc.pk] = deadline
if True:
if True: # FIXME
# on Telechat Agenda
telechat_dates = list(TelechatDate.objects.active().order_by('date').values_list("date", flat=True)[:4])
@ -153,6 +199,7 @@ def suggested_review_requests_for_team(team):
doc=doc,
team=team,
deadline=deadline,
requested_by=system_person,
)
seen_deadlines[doc.pk] = deadline

View file

@ -2,20 +2,22 @@
{# Copyright The IETF Trust 2016, All Rights Reserved #}
{% load origin bootstrap3 static %}
{% block title %}Withdraw review request for {{ review_req.doc.name }}{% endblock %}
{% block title %}Close review request for {{ review_req.doc.name }}{% endblock %}
{% block content %}
{% origin %}
<h1>Withdraw review request<br><small>{{ review_req.doc.name }}</small></h1>
<h1>Close review request<br><small>{{ review_req.doc.name }}</small></h1>
<p>Do you want to withdraw the review request?</p>
<p>Do you want to close the review request?</p>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-default" href="{% url "ietf.doc.views_review.review_request" name=doc.canonical_name request_id=review_req.pk %}">Cancel</a>
<button type="submit" class="btn btn-primary" name="action" value="withdraw">Withdraw request</button>
<button type="submit" class="btn btn-primary">Close request</button>
{% endbuttons %}
</form>

View file

@ -3,6 +3,8 @@
{% load origin bootstrap3 static %}
{% block pagehead %}
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
{% endblock %}
@ -16,6 +18,7 @@
<form class="form-horizontal" method="post">
{% csrf_token %}
{% bootstrap_field form.requested_by layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.team layout="horizontal" %}
{% bootstrap_field form.deadline_date layout="horizontal" %}
@ -31,4 +34,6 @@
{% block js %}
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'select2/select2.min.js' %}"></script>
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
{% endblock %}

View file

@ -51,6 +51,12 @@
<th>Requested</th>
<td>{{ review_req.time|date:"Y-m-d" }}</td>
</tr>
<tr>
<th></th>
<th>Requested by</th>
<td>{{ review_req.requested_by }}</td>
</tr>
</tbody>
<tbody class="meta">
@ -143,8 +149,8 @@
</table>
<div>
{% if can_withdraw_request %}
<a class="btn btn-danger btn-xs" href="{% url "ietf.doc.views_review.withdraw_request" name=doc.name request_id=review_req.pk %}"><span class="fa fa-ban"></span> Withdraw request</a>
{% if can_close_request %}
<a class="btn btn-danger btn-xs" href="{% url "ietf.doc.views_review.close_request" name=doc.name request_id=review_req.pk %}"><span class="fa fa-ban"></span> Close request</a>
{% endif %}
</div>

View file

@ -375,6 +375,7 @@ def make_review_data(doc):
type_id="early",
deadline=datetime.datetime.now() + datetime.timedelta(days=20),
state_id="accepted",
requested_by=p,
reviewer=email,
)