Skip to content

Commit 33e0f8c

Browse files
committed
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 8c7e751
2 parents b92d8ab + 8c7e751 commit 33e0f8c

7 files changed

Lines changed: 190 additions & 10 deletions

File tree

ietf/doc/tests_ballot.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,60 @@ def test_clear_ballot(self):
702702
self.assertEqual(ballot.ballotpositiondocevent_set.count(),0)
703703
self.assertNotEqual(old_ballot_id, ballot.id)
704704

705+
def test_ballot_downref_approve(self):
706+
ad = Person.objects.get(name="Areað Irector")
707+
draft = IndividualDraftFactory(ad=ad, intended_std_level_id='ps')
708+
draft.set_state(State.objects.get(used=True, type="draft-iesg", slug="ann")) # make sure it's approved
709+
LastCallDocEvent.objects.create(
710+
by=Person.objects.get(name='(System)'),
711+
type='sent_last_call',
712+
doc=draft,
713+
rev=draft.rev,
714+
desc='issued last call',
715+
expires = datetime.datetime.now()-datetime.timedelta(days=14) )
716+
WriteupDocEvent.objects.create(
717+
by=Person.objects.get(name='(System)'),
718+
doc=draft,
719+
rev=draft.rev,
720+
type='changed_last_call_text',
721+
desc='Last call announcement was changed',
722+
text='this is simple last call text.' )
723+
rfc = IndividualRfcFactory.create(
724+
stream_id='ise',
725+
other_aliases=['rfc6666',],
726+
states=[('draft','rfc'),('draft-iesg','pub')],
727+
std_level_id='inf', )
728+
729+
url = urlreverse('ietf.doc.views_ballot.approve_downrefs', kwargs=dict(name=draft.name))
730+
731+
# Only Secretariat can use this URL
732+
login_testing_unauthorized(self, "ad", url)
733+
r = self.client.get(url)
734+
self.assertEqual(r.status_code, 403)
735+
self.assertTrue("Restricted to role Secretariat" in r.content)
736+
737+
# There are no downrefs, the page should say so
738+
login_testing_unauthorized(self, "secretary", url)
739+
r = self.client.get(url)
740+
self.assertEqual(r.status_code, 200)
741+
self.assertTrue("No downward references for" in r.content)
742+
743+
# Add a downref, the page should ask if it should be added to the registry
744+
rel = draft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'),relationship_id='refnorm')
745+
d = [rdoc for rdoc in draft.relateddocument_set.all() if rel.is_approved_downref()]
746+
original_len = len(d)
747+
r = self.client.get(url)
748+
self.assertEqual(r.status_code, 200)
749+
self.assertTrue("normatively references rfc6666" in r.content)
750+
751+
# POST with the downref checked
752+
r = self.client.post(url, dict(checkboxes=rel.pk))
753+
self.assertEqual(r.status_code, 302)
754+
755+
# Confirm an entry was added to the downref registry
756+
d = [rdoc for rdoc in draft.relateddocument_set.all() if rel.is_approved_downref()]
757+
self.assertTrue(len(d) > original_len, "The downref approval was not added")
758+
705759
class MakeLastCallTests(TestCase):
706760
def test_make_last_call(self):
707761
ad = Person.objects.get(user__username="ad")

ietf/doc/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
url(r'^%(name)s/edit/ballotrfceditornote/$' % settings.URL_REGEXPS, views_ballot.ballot_rfceditornote),
119119
url(r'^%(name)s/edit/approvaltext/$' % settings.URL_REGEXPS, views_ballot.ballot_approvaltext),
120120
url(r'^%(name)s/edit/approveballot/$' % settings.URL_REGEXPS, views_ballot.approve_ballot),
121+
url(r'^%(name)s/edit/approvedownrefs/$' % settings.URL_REGEXPS, views_ballot.approve_downrefs),
121122
url(r'^%(name)s/edit/makelastcall/$' % settings.URL_REGEXPS, views_ballot.make_last_call),
122123
url(r'^%(name)s/edit/urls/$' % settings.URL_REGEXPS, views_draft.edit_document_urls),
123124

ietf/doc/views_ballot.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import debug # pyflakes:ignore
1717

1818
from ietf.doc.models import ( Document, State, DocEvent, BallotDocEvent, BallotPositionDocEvent,
19-
LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS )
19+
LastCallDocEvent, WriteupDocEvent, IESG_SUBSTATE_TAGS, RelatedDocument )
2020
from ietf.doc.utils import ( add_state_change_event, close_ballot, close_open_ballots,
2121
create_ballot_if_not_open, update_telechat )
2222
from ietf.doc.mails import ( email_ballot_deferred, email_ballot_undeferred,
@@ -769,6 +769,7 @@ def ballot_approvaltext(request, name):
769769
need_intended_status=need_intended_status,
770770
))
771771

772+
772773
@role_required('Secretariat')
773774
def approve_ballot(request, name):
774775
"""Approve ballot, sending out announcement, changing state."""
@@ -872,14 +873,76 @@ def approve_ballot(request, name):
872873
msg.save()
873874
msg.related_docs.add(doc)
874875

875-
return HttpResponseRedirect(doc.get_absolute_url())
876+
downrefs = [rel for rel in doc.relateddocument_set.all() if rel.is_downref() and not rel.is_approved_downref()]
877+
if not downrefs:
878+
return HttpResponseRedirect(doc.get_absolute_url())
879+
else:
880+
return HttpResponseRedirect(doc.get_absolute_url()+'edit/approvedownrefs/')
876881

877882
return render(request, 'doc/ballot/approve_ballot.html',
878883
dict(doc=doc,
879884
action=action,
880885
announcement=announcement))
881886

882887

888+
class ApproveDownrefsForm(forms.Form):
889+
checkboxes = forms.ModelMultipleChoiceField(
890+
widget = forms.CheckboxSelectMultiple,
891+
queryset = RelatedDocument.objects.none(), )
892+
893+
894+
def __init__(self, queryset, *args, **kwargs):
895+
super(ApproveDownrefsForm, self).__init__(*args, **kwargs)
896+
self.fields['checkboxes'].queryset = queryset
897+
898+
def clean(self):
899+
if 'checkboxes' not in self.cleaned_data:
900+
raise forms.ValidationError("No RFCs were selected")
901+
902+
@role_required('Secretariat')
903+
def approve_downrefs(request, name):
904+
"""Document ballot was just approved; add the checked downwared references to the downref registry."""
905+
doc = get_object_or_404(Document, docalias__name=name)
906+
if not doc.get_state("draft-iesg"):
907+
raise Http404
908+
909+
login = request.user.person
910+
911+
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()]
912+
913+
downrefs_to_rfc_qs = RelatedDocument.objects.filter(pk__in=[r.pk for r in downrefs_to_rfc])
914+
915+
last_call_text = doc.latest_event(WriteupDocEvent, type="changed_last_call_text").text.strip()
916+
917+
if request.method == 'POST':
918+
form = ApproveDownrefsForm(downrefs_to_rfc_qs, request.POST)
919+
if form.is_valid():
920+
for rel in form.cleaned_data['checkboxes']:
921+
RelatedDocument.objects.create(source=rel.source,
922+
target=rel.target, relationship_id='downref-approval')
923+
c = DocEvent(type="downref_approved", doc=rel.source,
924+
rev=rel.source.rev, by=login)
925+
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
926+
rel.target.document.rfc_number(), rel.source, rel.source.rev)
927+
c.save()
928+
c = DocEvent(type="downref_approved", doc=rel.target.document,
929+
rev=rel.target.document.rev, by=login)
930+
c.desc = "Downref to RFC %s approved by Last Call for %s-%s" % (
931+
rel.target.document.rfc_number(), rel.source, rel.source.rev)
932+
c.save()
933+
934+
return HttpResponseRedirect(doc.get_absolute_url())
935+
936+
else:
937+
form = ApproveDownrefsForm(downrefs_to_rfc_qs)
938+
939+
return render(request, 'doc/ballot/approve_downrefs.html',
940+
dict(doc=doc,
941+
approve_downrefs_form=form,
942+
last_call_text=last_call_text,
943+
downrefs_to_rfc=downrefs_to_rfc))
944+
945+
883946
class MakeLastCallForm(forms.Form):
884947
last_call_sent_date = forms.DateField(required=True)
885948
last_call_expiration_date = forms.DateField(required=True)

ietf/iesg/tests.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from ietf.doc.models import DocEvent, BallotPositionDocEvent, TelechatDocEvent
1414
from ietf.doc.models import Document, DocAlias, State, RelatedDocument
15-
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictReviewFactory, BaseDocumentFactory, CharterFactory, WgRfcFactory
15+
from ietf.doc.factories import WgDraftFactory, IndividualDraftFactory, ConflictReviewFactory, BaseDocumentFactory, CharterFactory, WgRfcFactory, IndividualRfcFactory
1616
from ietf.doc.utils import create_ballot_if_not_open
1717
from ietf.group.factories import RoleFactory, GroupFactory
1818
from ietf.group.models import Group, GroupMilestone, Role
@@ -90,7 +90,9 @@ def test_photos(self):
9090
class IESGAgendaTests(TestCase):
9191
def setUp(self):
9292
mars = GroupFactory(acronym='mars',parent=Group.objects.get(acronym='farfut'))
93-
WgDraftFactory(name='draft-ietf-mars-test',group=mars)
93+
wgdraft = WgDraftFactory(name='draft-ietf-mars-test', group=mars, intended_std_level_id='ps')
94+
rfc = IndividualRfcFactory.create(stream_id='irtf', other_aliases=['rfc6666',], states=[('draft','rfc'),('draft-iesg','pub')], std_level_id='inf', )
95+
wgdraft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'), relationship_id='refnorm')
9496
ise_draft = IndividualDraftFactory(name='draft-imaginary-independent-submission')
9597
ise_draft.stream = StreamName.objects.get(slug="ise")
9698
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 @@ def test_agenda_moderator_package(self):
364366
self.assertTrue(d.group.name in unicontent(r), "%s not in response" % k)
365367
self.assertTrue(d.group.acronym in unicontent(r), "%s acronym not in response" % k)
366368
else:
367-
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
368-
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
369+
if d.type_id == "draft" and d.name == "draft-ietf-mars-test":
370+
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
371+
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
372+
self.assertTrue("Has downref: Yes" in unicontent(r), "%s downref not in response" % k)
373+
self.assertTrue("Add rfc6666" in unicontent(r), "%s downref not in response" % k)
374+
else:
375+
self.assertTrue(d.name in unicontent(r), "%s not in response" % k)
376+
self.assertTrue(d.title in unicontent(r), "%s title not in response" % k)
369377

370378
def test_agenda_package(self):
371379
url = urlreverse("ietf.iesg.views.agenda_package")

ietf/secr/telechat/tests.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from django.urls import reverse
88

9-
from ietf.doc.factories import WgDraftFactory, CharterFactory
9+
from ietf.doc.factories import WgDraftFactory, IndividualRfcFactory, CharterFactory
1010
from ietf.doc.models import BallotDocEvent, BallotType, BallotPositionDocEvent
1111
from ietf.doc.utils import update_telechat, create_ballot_if_not_open
1212
from ietf.utils.test_utils import TestCase
@@ -58,6 +58,26 @@ def test_doc_detail_draft(self):
5858
self.assertEqual(q("#telechat-positions-table").find("th:contains('Recuse')").length,1)
5959
self.assertEqual(q("#telechat-positions-table").find("th:contains('No Record')").length,1)
6060

61+
def test_doc_detail_draft_with_downref(self):
62+
ad = Person.objects.get(user__username="ad")
63+
draft = WgDraftFactory(ad=ad, intended_std_level_id='ps', states=[('draft-iesg','pub-req'),])
64+
rfc = IndividualRfcFactory.create(stream_id='irtf', other_aliases=['rfc6666',],
65+
states=[('draft','rfc'),('draft-iesg','pub')], std_level_id='inf', )
66+
draft.relateddocument_set.create(target=rfc.docalias_set.get(name='rfc6666'),
67+
relationship_id='refnorm')
68+
create_ballot_if_not_open(None, draft, ad, 'approve')
69+
d = get_next_telechat_date()
70+
date = d.strftime('%Y-%m-%d')
71+
by=Person.objects.get(name="(System)")
72+
update_telechat(None, draft, by, d)
73+
url = reverse('ietf.secr.telechat.views.doc_detail', kwargs={'date':date, 'name':draft.name})
74+
self.client.login(username="secretary", password="secretary+password")
75+
response = self.client.get(url)
76+
self.assertEqual(response.status_code, 200)
77+
self.assertTrue("Has downref: Yes" in response.content)
78+
self.assertTrue("Add rfc6666" in response.content)
79+
self.assertTrue("to downref registry" in response.content)
80+
6181
def test_doc_detail_draft_invalid(self):
6282
'''Test using a document not on telechat agenda'''
6383
draft = WgDraftFactory(states=[('draft-iesg','pub-req'),])

ietf/templates/doc/ballot/approve_ballot.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ <h1>Approve ballot<br><small><a href="{% url "ietf.doc.views_doc.document_main"
1616

1717
{% buttons %}
1818
{% if action == "to_announcement_list" %}
19-
<button class="btn btn-warningprimary" type="submit">Notify RFC Editor, send announcement & close ballot</button>
19+
<button class="btn btn-primary" type="submit">Notify RFC Editor, send announcement & close ballot</button>
2020
{% elif action == "to_rfc_editor" %}
21-
<button class="btn btn-warning" type="submit">Email RFC Editor & close ballot</button>
21+
<button class="btn btn-primary" type="submit">Email RFC Editor & close ballot</button>
2222
{% elif action == "do_not_publish" %}
23-
<button class="btn btn-warning" type="submit">Email RFC Editor (DNP) & close ballot"/>
23+
<button class="btn btn-primary" type="submit">Email RFC Editor (DNP) & close ballot"/>
2424
{% endif %}
2525
{% endbuttons %}
2626
</form>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{% extends "base.html" %}
2+
{# Copyright The IETF Trust 2019, All Rights Reserved #}
3+
{% load origin %}
4+
5+
{% load bootstrap3 %}
6+
7+
{% block title %}Approve downward references for {{ doc }}{% endblock %}
8+
9+
{% block content %}
10+
{% origin %}
11+
<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>
12+
13+
{% if not downrefs_to_rfc %}
14+
<p>No downward references for <a href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">{{ doc }}</a></p>
15+
{% else %}
16+
<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>
17+
<p><b>Last Call text for this document:</b><p>
18+
<pre>
19+
{{ last_call_text }}
20+
</pre>
21+
<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>
22+
<form action="" method="post">
23+
{% csrf_token %}
24+
{% bootstrap_form approve_downrefs_form %}
25+
{% buttons %}
26+
<p>
27+
<a class="btn btn-primary" href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">Add no downref entries</a>
28+
<button type="submit" class="btn btn-warning" value="Save checked downrefs">Add checked downref entries</button>
29+
</p>
30+
{% endbuttons %}
31+
</form>
32+
{% endif %}
33+
34+
{% endblock %}

0 commit comments

Comments
 (0)