Merged in [16126] from housley@vigilsec.com:
Allow Secretariat to handle downrefs when they approve a document
- Legacy-Id: 16134
Note: SVN reference [16126] has been migrated to Git commit 8c7e75101d
This commit is contained in:
commit
33e0f8c35d
|
@ -702,6 +702,60 @@ class ApproveBallotTests(TestCase):
|
|||
self.assertEqual(ballot.ballotpositiondocevent_set.count(),0)
|
||||
self.assertNotEqual(old_ballot_id, ballot.id)
|
||||
|
||||
def test_ballot_downref_approve(self):
|
||||
ad = Person.objects.get(name="Areað Irector")
|
||||
draft = IndividualDraftFactory(ad=ad, intended_std_level_id='ps')
|
||||
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="ann")) # make sure it's approved
|
||||
LastCallDocEvent.objects.create(
|
||||
by=Person.objects.get(name='(System)'),
|
||||
type='sent_last_call',
|
||||
doc=draft,
|
||||
rev=draft.rev,
|
||||
desc='issued last call',
|
||||
expires = datetime.datetime.now()-datetime.timedelta(days=14) )
|
||||
WriteupDocEvent.objects.create(
|
||||
by=Person.objects.get(name='(System)'),
|
||||
doc=draft,
|
||||
rev=draft.rev,
|
||||
type='changed_last_call_text',
|
||||
desc='Last call announcement was changed',
|
||||
text='this is simple last call text.' )
|
||||
rfc = IndividualRfcFactory.create(
|
||||
stream_id='ise',
|
||||
other_aliases=['rfc6666',],
|
||||
states=[('draft','rfc'),('draft-iesg','pub')],
|
||||
std_level_id='inf', )
|
||||
|
||||
url = urlreverse('ietf.doc.views_ballot.approve_downrefs', kwargs=dict(name=draft.name))
|
||||
|
||||
# Only Secretariat can use this URL
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
self.assertTrue("Restricted to role Secretariat" in r.content)
|
||||
|
||||
# There are no downrefs, the page should say so
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("No downward references for" in r.content)
|
||||
|
||||
# Add a downref, the page should ask if it should be added to the registry
|
||||
rel = draft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'),relationship_id='refnorm')
|
||||
d = [rdoc for rdoc in draft.relateddocument_set.all() if rel.is_approved_downref()]
|
||||
original_len = len(d)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("normatively references rfc6666" in r.content)
|
||||
|
||||
# POST with the downref checked
|
||||
r = self.client.post(url, dict(checkboxes=rel.pk))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
# Confirm an entry was added to the downref registry
|
||||
d = [rdoc for rdoc in draft.relateddocument_set.all() if rel.is_approved_downref()]
|
||||
self.assertTrue(len(d) > original_len, "The downref approval was not added")
|
||||
|
||||
class MakeLastCallTests(TestCase):
|
||||
def test_make_last_call(self):
|
||||
ad = Person.objects.get(user__username="ad")
|
||||
|
|
|
@ -118,6 +118,7 @@ urlpatterns = [
|
|||
url(r'^%(name)s/edit/ballotrfceditornote/$' % settings.URL_REGEXPS, views_ballot.ballot_rfceditornote),
|
||||
url(r'^%(name)s/edit/approvaltext/$' % settings.URL_REGEXPS, views_ballot.ballot_approvaltext),
|
||||
url(r'^%(name)s/edit/approveballot/$' % settings.URL_REGEXPS, views_ballot.approve_ballot),
|
||||
url(r'^%(name)s/edit/approvedownrefs/$' % settings.URL_REGEXPS, views_ballot.approve_downrefs),
|
||||
url(r'^%(name)s/edit/makelastcall/$' % settings.URL_REGEXPS, views_ballot.make_last_call),
|
||||
url(r'^%(name)s/edit/urls/$' % settings.URL_REGEXPS, views_draft.edit_document_urls),
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent, BallotPositionDocEvent,
|
||||
LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS )
|
||||
LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS, RelatedDocument )
|
||||
from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ballots,
|
||||
create_ballot_if_not_open, update_telechat )
|
||||
from ietf.doc.mails import ( email_ballot_deferred, email_ballot_undeferred,
|
||||
|
@ -769,6 +769,7 @@ def ballot_approvaltext(request, name):
|
|||
need_intended_status=need_intended_status,
|
||||
))
|
||||
|
||||
|
||||
@role_required('Secretariat')
|
||||
def approve_ballot(request, name):
|
||||
"""Approve ballot, sending out announcement, changing state."""
|
||||
|
@ -872,7 +873,11 @@ def approve_ballot(request, name):
|
|||
msg.save()
|
||||
msg.related_docs.add(doc)
|
||||
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
downrefs = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref()]
|
||||
if not downrefs:
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
else:
|
||||
return HttpResponseRedirect(doc.get_absolute_url()+'edit/approvedownrefs/')
|
||||
|
||||
return render(request, 'doc/ballot/approve_ballot.html',
|
||||
dict(doc=doc,
|
||||
|
@ -880,6 +885,64 @@ def approve_ballot(request, name):
|
|||
announcement=announcement))
|
||||
|
||||
|
||||
class ApproveDownrefsForm(forms.Form):
|
||||
checkboxes = forms.ModelMultipleChoiceField(
|
||||
widget = forms.CheckboxSelectMultiple,
|
||||
queryset = RelatedDocument.objects.none(), )
|
||||
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
super(ApproveDownrefsForm, self).__init__(*args, **kwargs)
|
||||
self.fields['checkboxes'].queryset = queryset
|
||||
|
||||
def clean(self):
|
||||
if 'checkboxes' not in self.cleaned_data:
|
||||
raise forms.ValidationError("No RFCs were selected")
|
||||
|
||||
@role_required('Secretariat')
|
||||
def approve_downrefs(request, name):
|
||||
"""Document ballot was just approved; add the checked downwared references to the downref registry."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if not doc.get_state("draft-iesg"):
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
downrefs_to_rfc = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref() and rel.target.document.is_rfc()]
|
||||
|
||||
downrefs_to_rfc_qs = RelatedDocument.objects.filter(pk__in=[r.pk for r in downrefs_to_rfc])
|
||||
|
||||
last_call_text = doc.latest_event(WriteupDocEvent, type="changed_last_call_text").text.strip()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ApproveDownrefsForm(downrefs_to_rfc_qs, request.POST)
|
||||
if form.is_valid():
|
||||
for rel in form.cleaned_data['checkboxes']:
|
||||
RelatedDocument.objects.create(source=rel.source,
|
||||
target=rel.target, relationship_id='downref-approval')
|
||||
c = DocEvent(type="downref_approved", doc=rel.source,
|
||||
rev=rel.source.rev, by=login)
|
||||
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
|
||||
rel.target.document.rfc_number(), rel.source, rel.source.rev)
|
||||
c.save()
|
||||
c = DocEvent(type="downref_approved", doc=rel.target.document,
|
||||
rev=rel.target.document.rev, by=login)
|
||||
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
|
||||
rel.target.document.rfc_number(), rel.source, rel.source.rev)
|
||||
c.save()
|
||||
|
||||
return HttpResponseRedirect(doc.get_absolute_url())
|
||||
|
||||
else:
|
||||
form = ApproveDownrefsForm(downrefs_to_rfc_qs)
|
||||
|
||||
return render(request, 'doc/ballot/approve_downrefs.html',
|
||||
dict(doc=doc,
|
||||
approve_downrefs_form=form,
|
||||
last_call_text=last_call_text,
|
||||
downrefs_to_rfc=downrefs_to_rfc))
|
||||
|
||||
|
||||
class MakeLastCallForm(forms.Form):
|
||||
last_call_sent_date = forms.DateField(required=True)
|
||||
last_call_expiration_date = forms.DateField(required=True)
|
||||
|
|
|
@ -12,7 +12,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.models import DocEvent, BallotPositionDocEvent, TelechatDocEvent
|
||||
from ietf.doc.models import Document, DocAlias, State, RelatedDocument
|
||||
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictReviewFactory, BaseDocumentFactory, CharterFactory, WgRfcFactory
|
||||
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictReviewFactory, BaseDocumentFactory, CharterFactory, WgRfcFactory, IndividualRfcFactory
|
||||
from ietf.doc.utils import create_ballot_if_not_open
|
||||
from ietf.group.factories import RoleFactory, GroupFactory
|
||||
from ietf.group.models import Group, GroupMilestone, Role
|
||||
|
@ -90,7 +90,9 @@ class IESGTests(TestCase):
|
|||
class IESGAgendaTests(TestCase):
|
||||
def setUp(self):
|
||||
mars = GroupFactory(acronym='mars',parent=Group.objects.get(acronym='farfut'))
|
||||
WgDraftFactory(name='draft-ietf-mars-test',group=mars)
|
||||
wgdraft = WgDraftFactory(name='draft-ietf-mars-test', group=mars, intended_std_level_id='ps')
|
||||
rfc = IndividualRfcFactory.create(stream_id='irtf', other_aliases=['rfc6666',], states=[('draft','rfc'),('draft-iesg','pub')], std_level_id='inf', )
|
||||
wgdraft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'), relationship_id='refnorm')
|
||||
ise_draft = IndividualDraftFactory(name='draft-imaginary-independent-submission')
|
||||
ise_draft.stream = StreamName.objects.get(slug="ise")
|
||||
ise_draft.save_with_history([DocEvent(doc=ise_draft, rev=ise_draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
|
||||
|
@ -364,8 +366,14 @@ class IESGAgendaTests(TestCase):
|
|||
self.assertTrue(d.group.name in unicontent(r), "%s not in response" % k)
|
||||
self.assertTrue(d.group.acronym in unicontent(r), "%s acronym not in response" % k)
|
||||
else:
|
||||
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
|
||||
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
|
||||
if d.type_id == "draft" and d.name == "draft-ietf-mars-test":
|
||||
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
|
||||
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
|
||||
self.assertTrue("Has downref: Yes" in unicontent(r), "%s downref not in response" % k)
|
||||
self.assertTrue("Add rfc6666" in unicontent(r), "%s downref not in response" % k)
|
||||
else:
|
||||
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
|
||||
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
|
||||
|
||||
def test_agenda_package(self):
|
||||
url = urlreverse("ietf.iesg.views.agenda_package")
|
||||
|
|
|
@ -6,7 +6,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from django.urls import reverse
|
||||
|
||||
from ietf.doc.factories import WgDraftFactory, CharterFactory
|
||||
from ietf.doc.factories import WgDraftFactory, IndividualRfcFactory, CharterFactory
|
||||
from ietf.doc.models import BallotDocEvent, BallotType, BallotPositionDocEvent
|
||||
from ietf.doc.utils import update_telechat, create_ballot_if_not_open
|
||||
from ietf.utils.test_utils import TestCase
|
||||
|
@ -58,6 +58,26 @@ class SecrTelechatTestCase(TestCase):
|
|||
self.assertEqual(q("#telechat-positions-table").find("th:contains('Recuse')").length,1)
|
||||
self.assertEqual(q("#telechat-positions-table").find("th:contains('No Record')").length,1)
|
||||
|
||||
def test_doc_detail_draft_with_downref(self):
|
||||
ad = Person.objects.get(user__username="ad")
|
||||
draft = WgDraftFactory(ad=ad, intended_std_level_id='ps', states=[('draft-iesg','pub-req'),])
|
||||
rfc = IndividualRfcFactory.create(stream_id='irtf', other_aliases=['rfc6666',],
|
||||
states=[('draft','rfc'),('draft-iesg','pub')], std_level_id='inf', )
|
||||
draft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'),
|
||||
relationship_id='refnorm')
|
||||
create_ballot_if_not_open(None, draft, ad, 'approve')
|
||||
d = get_next_telechat_date()
|
||||
date = d.strftime('%Y-%m-%d')
|
||||
by=Person.objects.get(name="(System)")
|
||||
update_telechat(None, draft, by, d)
|
||||
url = reverse('ietf.secr.telechat.views.doc_detail', kwargs={'date':date, 'name':draft.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue("Has downref: Yes" in response.content)
|
||||
self.assertTrue("Add rfc6666" in response.content)
|
||||
self.assertTrue("to downref registry" in response.content)
|
||||
|
||||
def test_doc_detail_draft_invalid(self):
|
||||
'''Test using a document not on telechat agenda'''
|
||||
draft = WgDraftFactory(states=[('draft-iesg','pub-req'),])
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
{% buttons %}
|
||||
{% if action == "to_announcement_list" %}
|
||||
<button class="btn btn-warningprimary" type="submit">Notify RFC Editor, send announcement & close ballot</button>
|
||||
<button class="btn btn-primary" type="submit">Notify RFC Editor, send announcement & close ballot</button>
|
||||
{% elif action == "to_rfc_editor" %}
|
||||
<button class="btn btn-warning" type="submit">Email RFC Editor & close ballot</button>
|
||||
<button class="btn btn-primary" type="submit">Email RFC Editor & close ballot</button>
|
||||
{% elif action == "do_not_publish" %}
|
||||
<button class="btn btn-warning" type="submit">Email RFC Editor (DNP) & close ballot"/>
|
||||
<button class="btn btn-primary" type="submit">Email RFC Editor (DNP) & close ballot"/>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
|
34
ietf/templates/doc/ballot/approve_downrefs.html
Normal file
34
ietf/templates/doc/ballot/approve_downrefs.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2019, All Rights Reserved #}
|
||||
{% load origin %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block title %}Approve downward references for {{ doc }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Approve downward references<br><small>The ballot for <a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a> was just approved</small></h1>
|
||||
|
||||
{% if not downrefs_to_rfc %}
|
||||
<p>No downward references for <a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></p>
|
||||
{% else %}
|
||||
<p>Add downward references to RFCs to the downref registry, if they were identified in the IETF Last Call and approved by the Sponsoring Area Director.<p>
|
||||
<p><b>Last Call text for this document:</b><p>
|
||||
<pre>
|
||||
{{ last_call_text }}
|
||||
</pre>
|
||||
<p><b>This document has downward references to the following RFCs.<br>Which downward references, if any, are to be added to the downref registry?</b></p>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form approve_downrefs_form %}
|
||||
{% buttons %}
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">Add no downref entries</a>
|
||||
<button type="submit" class="btn btn-warning" value="Save checked downrefs">Add checked downref entries</button>
|
||||
</p>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in a new issue