diff --git a/ietf/doc/tests_ballot.py b/ietf/doc/tests_ballot.py index a002467af..45f6e939b 100644 --- a/ietf/doc/tests_ballot.py +++ b/ietf/doc/tests_ballot.py @@ -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") diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index 0dfa33382..4e5220646 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -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), diff --git a/ietf/doc/views_ballot.py b/ietf/doc/views_ballot.py index 12a58c642..0fb64e5cf 100644 --- a/ietf/doc/views_ballot.py +++ b/ietf/doc/views_ballot.py @@ -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, @@ -768,6 +768,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.""" @@ -871,7 +872,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, @@ -879,6 +884,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) diff --git a/ietf/templates/doc/ballot/approve_ballot.html b/ietf/templates/doc/ballot/approve_ballot.html index 1639eb5ca..734d1329b 100644 --- a/ietf/templates/doc/ballot/approve_ballot.html +++ b/ietf/templates/doc/ballot/approve_ballot.html @@ -16,11 +16,11 @@ {% buttons %} {% if action == "to_announcement_list" %} - Notify RFC Editor, send announcement & close ballot + Notify RFC Editor, send announcement & close ballot {% elif action == "to_rfc_editor" %} - Email RFC Editor & close ballot + Email RFC Editor & close ballot {% elif action == "do_not_publish" %} - Email RFC Editor (DNP) & close ballot"/> + Email RFC Editor (DNP) & close ballot"/> {% endif %} {% endbuttons %} diff --git a/ietf/templates/doc/ballot/approve_downrefs.html b/ietf/templates/doc/ballot/approve_downrefs.html new file mode 100644 index 000000000..0c7f3e37d --- /dev/null +++ b/ietf/templates/doc/ballot/approve_downrefs.html @@ -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 %} + Approve downward referencesThe ballot for {{ doc }} was just approved + + {% if not downrefs_to_rfc %} + No downward references for {{ doc }} + {% else %} + 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. + Last Call text for this document: + + {{ last_call_text }} + + This document has downward references to the following RFCs.Which downward references, if any, are to be added to the downref registry? + + {% csrf_token %} + {% bootstrap_form approve_downrefs_form %} + {% buttons %} + + Add no downref entries + Add checked downref entries + + {% endbuttons %} + + {% endif %} + +{% endblock %}
No downward references for {{ doc }}
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.
+
Last Call text for this document:
+ {{ last_call_text }} +
This document has downward references to the following RFCs.Which downward references, if any, are to be added to the downref registry?
+ Add no downref entries + Add checked downref entries +