Merged in [16656] from rjsparks@nostrum.com:

Add IANA expert review tracking.
 - Legacy-Id: 16716
Note: SVN reference [16656] has been migrated to Git commit e45a32e49f
This commit is contained in:
Henrik Levkowetz 2019-09-08 14:34:29 +00:00
commit 3336f4a11b
11 changed files with 239 additions and 34 deletions

View file

@ -12,7 +12,7 @@ from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document
StateDocEvent, ConsensusDocEvent, BallotType, BallotDocEvent, WriteupDocEvent, LastCallDocEvent,
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
ReviewAssignmentDocEvent )
ReviewAssignmentDocEvent, IanaExpertDocEvent )
class StateTypeAdmin(admin.ModelAdmin):
@ -155,7 +155,7 @@ admin.site.register(InitialReviewDocEvent, DocEventAdmin)
admin.site.register(AddedMessageEvent, DocEventAdmin)
admin.site.register(SubmissionDocEvent, DocEventAdmin)
admin.site.register(EditedAuthorsDocEvent, DocEventAdmin)
admin.site.register(IanaExpertDocEvent, DocEventAdmin)
class DeletedEventAdmin(admin.ModelAdmin):
list_display = ['id', 'content_type', 'json', 'by', 'time']

View file

@ -0,0 +1,59 @@
# Copyright The IETF Trust 2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-07 12:07
from __future__ import unicode_literals
from django.db import migrations
def forward(apps, schema_editor):
StateType = apps.get_model('doc','StateType')
State = apps.get_model('doc','State')
StateType.objects.create(slug='draft-iana-experts',label='IANA Experts State')
State.objects.create(type_id='draft-iana-experts',
slug='need-experts',
name='Need IANA Expert(s)',
used=True,
desc='One or more registries need experts assigned',
order=0
)
State.objects.create(type_id='draft-iana-experts',
slug='reviews-assigned',
name='Reviews assigned',
used=True,
desc='One or more expert reviews have been assigned',
order=1
)
State.objects.create(type_id='draft-iana-experts',
slug='expert-issues',
name='Issues identified',
used=True,
desc='Some expert reviewers have identified issues',
order=2
)
State.objects.create(type_id='draft-iana-experts',
slug='reviewers-ok',
name='Expert Reviews OK',
used=True,
desc='All expert reviews have been completed with no blocking issues',
order=2
)
def reverse(apps, schema_editor):
StateType = apps.get_model('doc','StateType')
State = apps.get_model('doc','State')
State.objects.filter(type_id='draft-iana-experts', slug__in=('need-experts','reviews-assigned','reviews-complete')).delete()
StateType.objects.filter(slug='draft-iana-experts').delete()
class Migration(migrations.Migration):
dependencies = [
('doc', '0023_one_to_many_docalias'),
]
operations = [
migrations.RunPython(forward, reverse)
]

View file

@ -0,0 +1,24 @@
# Copyright The IETF Trust 2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-07 12:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('doc', '0024_iana_experts'),
]
operations = [
migrations.CreateModel(
name='IanaExpertDocEvent',
fields=[
('docevent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='doc.DocEvent')),
],
bases=('doc.docevent',),
),
]

View file

@ -1052,6 +1052,9 @@ class DocEvent(models.Model):
class NewRevisionDocEvent(DocEvent):
pass
class IanaExpertDocEvent(DocEvent):
pass
class StateDocEvent(DocEvent):
state_type = ForeignKey(StateType)
state = ForeignKey(State, blank=True, null=True)

View file

@ -16,7 +16,8 @@ from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Documen
TelechatDocEvent, DocReminder, LastCallDocEvent, NewRevisionDocEvent, WriteupDocEvent,
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL)
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
IanaExpertDocEvent )
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
class BallotTypeResource(ModelResource):
@ -713,3 +714,27 @@ class ReviewAssignmentDocEventResource(ModelResource):
"state": ALL_WITH_RELATIONS,
}
api.doc.register(ReviewAssignmentDocEventResource())
from ietf.person.resources import PersonResource
class IanaExpertDocEventResource(ModelResource):
by = ToOneField(PersonResource, 'by')
doc = ToOneField(DocumentResource, 'doc')
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
class Meta:
queryset = IanaExpertDocEvent.objects.all()
serializer = api.Serializer()
cache = SimpleCache()
#resource_name = 'ianaexpertdocevent'
ordering = ['docevent_ptr', ]
filtering = {
"id": ALL,
"time": ALL,
"type": ALL,
"rev": ALL,
"desc": ALL,
"by": ALL_WITH_RELATIONS,
"doc": ALL_WITH_RELATIONS,
"docevent_ptr": ALL_WITH_RELATIONS,
}
api.doc.register(IanaExpertDocEventResource())

View file

@ -19,7 +19,7 @@ import debug # pyflakes:ignore
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory, RgDraftFactory, DocEventFactory
from ietf.doc.models import ( Document, DocReminder, DocEvent,
ConsensusDocEvent, LastCallDocEvent, RelatedDocument, State, TelechatDocEvent,
WriteupDocEvent, DocRelationshipName)
WriteupDocEvent, DocRelationshipName, IanaExpertDocEvent )
from ietf.doc.utils import get_tags_for_stream_id, create_ballot_if_not_open
from ietf.name.models import StreamName, DocTagName
from ietf.group.factories import GroupFactory, RoleFactory
@ -200,6 +200,17 @@ class ChangeStateTests(TestCase):
draft = Document.objects.get(name=draft.name)
self.assertEqual(draft.get_state("draft-iana-review"), next_state)
def test_add_expert_review_comment(self):
draft = WgDraftFactory()
url = urlreverse('ietf.doc.views_draft.add_iana_experts_comment',kwargs=dict(name=draft.name))
login_testing_unauthorized(self, 'iana', url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
r = self.client.post(url,dict(comment='!2ab3x#1'))
self.assertEqual(r.status_code, 302)
self.assertEqual(draft.latest_event(IanaExpertDocEvent,type='comment').desc,'!2ab3x#1')
def test_request_last_call(self):
ad = Person.objects.get(user__username="ad")
draft = WgDraftFactory(ad=ad,states=[('draft-iesg','ad-eval')])

View file

@ -91,7 +91,8 @@ urlpatterns = [
url(r'^%(name)s/email-aliases/$' % settings.URL_REGEXPS, RedirectView.as_view(pattern_name='ietf.doc.views_doc.document_email', permanent=False),name='ietf.doc.urls.redirect.document_email'),
url(r'^%(name)s/edit/state/$' % settings.URL_REGEXPS, views_draft.change_state), # IESG state
url(r'^%(name)s/edit/state/(?P<state_type>iana-action|iana-review)/$' % settings.URL_REGEXPS, views_draft.change_iana_state),
url(r'^%(name)s/edit/state/(?P<state_type>iana-action|iana-review|iana-experts)/$' % settings.URL_REGEXPS, views_draft.change_iana_state),
url(r'^%(name)s/edit/ianaexpertcomment/$' % settings.URL_REGEXPS, views_draft.add_iana_experts_comment),
url(r'^%(name)s/edit/info/$' % settings.URL_REGEXPS, views_draft.edit_info),
url(r'^%(name)s/edit/requestresurrect/$' % settings.URL_REGEXPS, views_draft.request_resurrect),
url(r'^%(name)s/edit/submit-to-iesg/$' % settings.URL_REGEXPS, views_draft.to_iesg),

View file

@ -56,7 +56,7 @@ from django import forms
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent,
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent,
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IanaExpertDocEvent,
IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS )
from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_with_revision,
can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
@ -396,6 +396,9 @@ def document_main(request, name, rev=None):
review_assignments = review_assignments_to_list_for_docs([doc]).get(doc.name, [])
no_review_from_teams = no_review_from_teams_on_doc(doc, rev or doc.rev)
exp_comment = doc.latest_event(IanaExpertDocEvent,type="comment")
iana_experts_comment = exp_comment and exp_comment.desc
return render(request, "doc/document_draft.html",
dict(doc=doc,
group=group,
@ -452,6 +455,8 @@ def document_main(request, name, rev=None):
rfc_editor_state=doc.get_state("draft-rfceditor"),
iana_review_state=doc.get_state("draft-iana-review"),
iana_action_state=doc.get_state("draft-iana-action"),
iana_experts_state=doc.get_state("draft-iana-experts"),
iana_experts_comment=iana_experts_comment,
started_iesg_process=started_iesg_process,
shepherd_writeup=shepherd_writeup,
search_archive=search_archive,

View file

@ -25,7 +25,7 @@ import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocAlias, RelatedDocument, State,
StateType, DocEvent, ConsensusDocEvent, TelechatDocEvent, WriteupDocEvent, StateDocEvent,
IESG_SUBSTATE_TAGS)
IanaExpertDocEvent, IESG_SUBSTATE_TAGS)
from ietf.doc.mails import ( email_pulled_from_rfc_queue, email_resurrect_requested,
email_resurrection_completed, email_state_changed, email_stream_changed,
email_stream_state_changed, email_stream_tags_changed, extra_automation_headers,
@ -188,6 +188,23 @@ def change_state(request, name):
next_states=next_states,
to_iesg_eval=to_iesg_eval))
class AddIanaExpertsCommentForm(forms.Form):
comment = forms.CharField(required=True, widget=forms.Textarea, strip=False)
@role_required('Secretariat', 'IANA')
def add_iana_experts_comment(request, name):
doc = get_object_or_404(Document, docalias__name = name)
if request.method == 'POST':
form = AddIanaExpertsCommentForm(request.POST)
if form.is_valid():
IanaExpertDocEvent.objects.create(doc=doc, rev=doc.rev, by=request.user.person, type="comment", desc=form.cleaned_data['comment'])
return HttpResponseRedirect(doc.get_absolute_url())
else:
form = AddIanaExpertsCommentForm()
return render(request, 'doc/draft/add_iana_experts_comment.html', dict(form=form, doc=doc))
class ChangeIanaStateForm(forms.Form):
state = forms.ModelChoiceField(State.objects.all(), required=False)
@ -197,6 +214,7 @@ class ChangeIanaStateForm(forms.Form):
choices = State.objects.filter(used=True, type=state_type).order_by("order").values_list("pk", "name")
self.fields['state'].choices = [("", "-------")] + list(choices)
@role_required('Secretariat', 'IANA')
def change_iana_state(request, name, state_type):
"""Change IANA review state of Internet Draft. Normally, this is done via

View file

@ -514,34 +514,69 @@
</tbody>
{% endif %}
{% if iana_review_state %}
<tbody class="meta">
<tr>
<th>IANA</th>
<th><a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-review" %}">IANA review state</a></th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-review" %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_review_state }}
</td>
</tr>
{% if can_edit_iana_state or iana_review_state or iana_experts_state or iana_experts_comment %}
<tbody class="meta">
{% if iana_review_state or can_edit_iana_state %}
<tr>
<th>IANA</th>
<th><a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-review" %}">IANA review state</a></th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-review" %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_review_state }}
</td>
</tr>
{% endif %}
<tr>
<th></th>
<th>IANA action state</th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-action" %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_action_state }}
</td>
</tr>
</tbody>
{% if iana_action_state or can_edit_iana_state %}
<tr>
<th>{% if not can_edit_iana_state and not iana_review_state %}IANA{% endif %}</th>
<th><a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-action" %}">IANA action state</a></th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-action" %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_action_state }}
</td>
</tr>
{% endif %}
{% if iana_experts_state or can_edit_iana_state %}
<tr>
<th>{% if not can_edit_iana_state and not iana_review_state and not iana_action_state %}IANA{% endif %}</th>
<th><a href="{% url "ietf.help.views.state" doc=doc.type.slug type="iana-experts" %}">IANA expert review state</a></th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.change_iana_state' name=doc.name state_type="iana-experts" %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_experts_state }}
</td>
</tr>
{% endif %}
{% if iana_experts_comment or can_edit_iana_state %}
<tr>
<th>{% if not can_edit_iana_state and not iana_review_state and not iana_action_state and not iana_experts_state %}IANA{% endif %}</th>
<th>IANA expert review comments</th>
<td class="edit">
{% if can_edit_iana_state and not snapshot %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.doc.views_draft.add_iana_experts_comment' name=doc.name %}">Edit</a>
{% endif %}
</td>
<td>
{{ iana_experts_comment }}
</td>
</tr>
{% endif %}
</tbody>
{% endif %}
<tbody class="meta">

View file

@ -0,0 +1,24 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2019, All Rights Reserved #}
{% load origin %}
{% load bootstrap3 %}
{% block title %}Add IANA Experts Review State comment for {{ doc }}{% endblock %}
{% block content %}
{% origin %}
<h1>Add IANA Experts Review State comment<br><small>{{ doc }}</small></h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<p class="help-block">The comment will be added to the history trail.</p>
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "ietf.doc.views_doc.document_main" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}