Added the notion of responsible leadership.
- Legacy-Id: 19202
This commit is contained in:
parent
338da98661
commit
f5a04263e5
|
@ -12,7 +12,7 @@ from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document
|
||||||
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
|
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
|
||||||
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
|
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||||
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
|
ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
|
||||||
BofreqEditorDocEvent )
|
BofreqEditorDocEvent, BofreqResponsibleDocEvent )
|
||||||
|
|
||||||
from ietf.utils.validators import validate_external_resource_value
|
from ietf.utils.validators import validate_external_resource_value
|
||||||
|
|
||||||
|
@ -198,6 +198,10 @@ class BofreqEditorDocEventAdmin(DocEventAdmin):
|
||||||
raw_id_fields = ["doc", "by", "editors" ]
|
raw_id_fields = ["doc", "by", "editors" ]
|
||||||
admin.site.register(BofreqEditorDocEvent, BofreqEditorDocEventAdmin)
|
admin.site.register(BofreqEditorDocEvent, BofreqEditorDocEventAdmin)
|
||||||
|
|
||||||
|
class BofreqResponsibleDocEventAdmin(DocEventAdmin):
|
||||||
|
raw_id_fields = ["doc", "by", "responsible" ]
|
||||||
|
admin.site.register(BofreqResponsibleDocEvent, BofreqResponsibleDocEventAdmin)
|
||||||
|
|
||||||
class DocumentUrlAdmin(admin.ModelAdmin):
|
class DocumentUrlAdmin(admin.ModelAdmin):
|
||||||
list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
|
list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
|
||||||
search_fields = ['doc__name', 'url', ]
|
search_fields = ['doc__name', 'url', ]
|
||||||
|
|
|
@ -13,9 +13,10 @@ from django.conf import settings
|
||||||
|
|
||||||
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
|
from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
|
||||||
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
|
StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
|
||||||
DocumentActionHolder, BofreqEditorDocEvent )
|
DocumentActionHolder, BofreqEditorDocEvent, BofreqResponsibleDocEvent )
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.person.factories import PersonFactory
|
from ietf.person.factories import PersonFactory
|
||||||
|
from ietf.group.factories import RoleFactory
|
||||||
from ietf.utils.text import xslugify
|
from ietf.utils.text import xslugify
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,12 +402,32 @@ class BofreqEditorDocEventFactory(DocEventFactory):
|
||||||
obj.editors.set(PersonFactory.create_batch(3))
|
obj.editors.set(PersonFactory.create_batch(3))
|
||||||
obj.desc = f'Changed editors to {", ".join(obj.editors.values_list("name",flat=True)) or "(None)"}'
|
obj.desc = f'Changed editors to {", ".join(obj.editors.values_list("name",flat=True)) or "(None)"}'
|
||||||
|
|
||||||
|
class BofreqResponsibleDocEventFactory(DocEventFactory):
|
||||||
|
class Meta:
|
||||||
|
model = BofreqResponsibleDocEvent
|
||||||
|
|
||||||
|
type = "changed_responsible"
|
||||||
|
doc = factory.SubFactory('ietf.doc.factories.BofreqFactory')
|
||||||
|
|
||||||
|
|
||||||
|
@factory.post_generation
|
||||||
|
def responsible(obj, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
if extracted:
|
||||||
|
obj.responsible.set(extracted)
|
||||||
|
else:
|
||||||
|
ad = RoleFactory(group__type_id='area',name_id='ad').person
|
||||||
|
obj.responsible.set([ad])
|
||||||
|
obj.desc = f'Changed responsible leadership to {", ".join(obj.responsible.values_list("name",flat=True)) or "(None)"}'
|
||||||
|
|
||||||
class BofreqFactory(BaseDocumentFactory):
|
class BofreqFactory(BaseDocumentFactory):
|
||||||
type_id = 'bofreq'
|
type_id = 'bofreq'
|
||||||
title = factory.Faker('sentence')
|
title = factory.Faker('sentence')
|
||||||
name = factory.LazyAttribute(lambda o: 'bofreq-%s'%(xslugify(o.title)))
|
name = factory.LazyAttribute(lambda o: 'bofreq-%s'%(xslugify(o.title)))
|
||||||
|
|
||||||
bofreqeditordocevent = factory.RelatedFactory('ietf.doc.factories.BofreqEditorDocEventFactory','doc')
|
bofreqeditordocevent = factory.RelatedFactory('ietf.doc.factories.BofreqEditorDocEventFactory','doc')
|
||||||
|
bofreqresponsibledocevent = factory.RelatedFactory('ietf.doc.factories.BofreqResponsibleDocEventFactory','doc')
|
||||||
|
|
||||||
@factory.post_generation
|
@factory.post_generation
|
||||||
def states(obj, create, extracted, **kwargs):
|
def states(obj, create, extracted, **kwargs):
|
||||||
|
|
|
@ -18,8 +18,9 @@ from ietf.doc.templatetags.mail_filters import std_level_prompt
|
||||||
from ietf.utils import log
|
from ietf.utils import log
|
||||||
from ietf.utils.mail import send_mail, send_mail_text
|
from ietf.utils.mail import send_mail, send_mail_text
|
||||||
from ietf.ipr.utils import iprs_from_docs, related_docs
|
from ietf.ipr.utils import iprs_from_docs, related_docs
|
||||||
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent, BofreqEditorDocEvent
|
from ietf.doc.models import WriteupDocEvent, LastCallDocEvent, DocAlias, ConsensusDocEvent
|
||||||
from ietf.doc.utils import needed_ballot_positions
|
from ietf.doc.utils import needed_ballot_positions
|
||||||
|
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
|
||||||
from ietf.group.models import Role
|
from ietf.group.models import Role
|
||||||
from ietf.doc.models import Document
|
from ietf.doc.models import Document
|
||||||
from ietf.mailtrigger.utils import gather_address_lists
|
from ietf.mailtrigger.utils import gather_address_lists
|
||||||
|
@ -699,14 +700,27 @@ def email_bofreq_title_changed(request, bofreq):
|
||||||
dict(bofreq=bofreq, request=request),
|
dict(bofreq=bofreq, request=request),
|
||||||
cc=addrs.cc)
|
cc=addrs.cc)
|
||||||
|
|
||||||
|
def plain_names(persons):
|
||||||
|
return [p.plain_name() for p in persons]
|
||||||
|
|
||||||
def email_bofreq_editors_changed(request, bofreq, previous_editors):
|
def email_bofreq_editors_changed(request, bofreq, previous_editors):
|
||||||
editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(bofreq)
|
||||||
addrs = gather_address_lists('bofreq_editors_changed', doc=bofreq, previous_editors=previous_editors)
|
addrs = gather_address_lists('bofreq_editors_changed', doc=bofreq, previous_editors=previous_editors)
|
||||||
|
|
||||||
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||||
f'BOF Request editors changed : {bofreq.name}',
|
f'BOF Request editors changed : {bofreq.name}',
|
||||||
'doc/mail/bofreq_editors_changed.txt',
|
'doc/mail/bofreq_editors_changed.txt',
|
||||||
dict(bofreq=bofreq, request=request, editors=editors, previous_editors=previous_editors),
|
dict(bofreq=bofreq, request=request, editors=plain_names(editors), previous_editors=plain_names(previous_editors)),
|
||||||
|
cc=addrs.cc)
|
||||||
|
|
||||||
|
def email_bofreq_responsible_changed(request, bofreq, previous_responsible):
|
||||||
|
responsible = bofreq_responsible(bofreq)
|
||||||
|
addrs = gather_address_lists('bofreq_responsible_changed', doc=bofreq, previous_responsible=previous_responsible)
|
||||||
|
|
||||||
|
send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
|
||||||
|
f'BOF Request responsible leadership changed : {bofreq.name}',
|
||||||
|
'doc/mail/bofreq_responsible_changed.txt',
|
||||||
|
dict(bofreq=bofreq, request=request, responsible=plain_names(responsible), previous_responsible=plain_names(previous_responsible)),
|
||||||
cc=addrs.cc)
|
cc=addrs.cc)
|
||||||
|
|
||||||
def email_bofreq_new_revision(request, bofreq):
|
def email_bofreq_new_revision(request, bofreq):
|
||||||
|
|
36
ietf/doc/migrations/0043_bofreq_docevents.py
Normal file
36
ietf/doc/migrations/0043_bofreq_docevents.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Generated by Django 2.2.24 on 2021-07-06 13:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('person', '0019_auto_20210604_1443'),
|
||||||
|
('doc', '0042_bofreq_states'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='docevent',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('new_revision', 'Added new revision'), ('new_submission', 'Uploaded new revision'), ('changed_document', 'Changed document metadata'), ('added_comment', 'Added comment'), ('added_message', 'Added message'), ('edited_authors', 'Edited the documents author list'), ('deleted', 'Deleted document'), ('changed_state', 'Changed state'), ('changed_stream', 'Changed document stream'), ('expired_document', 'Expired document'), ('extended_expiry', 'Extended expiry of document'), ('requested_resurrect', 'Requested resurrect'), ('completed_resurrect', 'Completed resurrect'), ('changed_consensus', 'Changed consensus'), ('published_rfc', 'Published RFC'), ('added_suggested_replaces', 'Added suggested replacement relationships'), ('reviewed_suggested_replaces', 'Reviewed suggested replacement relationships'), ('changed_action_holders', 'Changed action holders for document'), ('changed_group', 'Changed group'), ('changed_protocol_writeup', 'Changed protocol writeup'), ('changed_charter_milestone', 'Changed charter milestone'), ('initial_review', 'Set initial review time'), ('changed_review_announcement', 'Changed WG Review text'), ('changed_action_announcement', 'Changed WG Action text'), ('started_iesg_process', 'Started IESG process on document'), ('created_ballot', 'Created ballot'), ('closed_ballot', 'Closed ballot'), ('sent_ballot_announcement', 'Sent ballot announcement'), ('changed_ballot_position', 'Changed ballot position'), ('changed_ballot_approval_text', 'Changed ballot approval text'), ('changed_ballot_writeup_text', 'Changed ballot writeup text'), ('changed_rfc_editor_note_text', 'Changed RFC Editor Note text'), ('changed_last_call_text', 'Changed last call text'), ('requested_last_call', 'Requested last call'), ('sent_last_call', 'Sent last call'), ('scheduled_for_telechat', 'Scheduled for telechat'), ('iesg_approved', 'IESG approved document (no problem)'), ('iesg_disapproved', 'IESG disapproved document (do not publish)'), ('approved_in_minute', 'Approved in minute'), ('iana_review', 'IANA review comment'), ('rfc_in_iana_registry', 'RFC is in IANA registry'), ('rfc_editor_received_announcement', 'Announcement was received by RFC Editor'), ('requested_publication', 'Publication at RFC Editor requested'), ('sync_from_rfc_editor', 'Received updated information from RFC Editor'), ('requested_review', 'Requested review'), ('assigned_review_request', 'Assigned review request'), ('closed_review_request', 'Closed review request'), ('closed_review_assignment', 'Closed review assignment'), ('downref_approved', 'Downref approved'), ('posted_related_ipr', 'Posted related IPR'), ('removed_related_ipr', 'Removed related IPR'), ('changed_editors', 'Changed BOF Request editors')], max_length=50),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BofreqResponsibleDocEvent',
|
||||||
|
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')),
|
||||||
|
('responsible', models.ManyToManyField(blank=True, to='person.Person')),
|
||||||
|
],
|
||||||
|
bases=('doc.docevent',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BofreqEditorDocEvent',
|
||||||
|
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')),
|
||||||
|
('editors', models.ManyToManyField(blank=True, to='person.Person')),
|
||||||
|
],
|
||||||
|
bases=('doc.docevent',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 2.2.23 on 2021-05-25 12:02
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('person', '0018_auto_20201109_0439'),
|
|
||||||
('doc', '0042_bofreq_states'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='BofreqEditorDocEvent',
|
|
||||||
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')),
|
|
||||||
('editors', models.ManyToManyField(blank=True, to='person.Person')),
|
|
||||||
],
|
|
||||||
bases=('doc.docevent',),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1349,4 +1349,9 @@ class EditedAuthorsDocEvent(DocEvent):
|
||||||
basis = models.CharField(help_text="What is the source or reasoning for the changes to the author list",max_length=255)
|
basis = models.CharField(help_text="What is the source or reasoning for the changes to the author list",max_length=255)
|
||||||
|
|
||||||
class BofreqEditorDocEvent(DocEvent):
|
class BofreqEditorDocEvent(DocEvent):
|
||||||
editors = models.ManyToManyField('person.Person',blank=True)
|
""" Capture the proponents of a Bof Request."""
|
||||||
|
editors = models.ManyToManyField('person.Person', blank=True)
|
||||||
|
|
||||||
|
class BofreqResponsibleDocEvent(DocEvent):
|
||||||
|
""" Capture the responsible leadership (IAB and IESG members) for a BoF Request """
|
||||||
|
responsible = models.ManyToManyField('person.Person', blank=True)
|
|
@ -17,7 +17,8 @@ from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Documen
|
||||||
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
|
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
|
||||||
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
|
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
|
||||||
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
|
ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
|
||||||
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder, BofreqEditorDocEvent)
|
IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
|
||||||
|
BofreqEditorDocEvent,BofreqResponsibleDocEvent)
|
||||||
|
|
||||||
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
|
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
|
||||||
class BallotTypeResource(ModelResource):
|
class BallotTypeResource(ModelResource):
|
||||||
|
@ -832,3 +833,29 @@ class BofreqEditorDocEventResource(ModelResource):
|
||||||
"editors": ALL_WITH_RELATIONS,
|
"editors": ALL_WITH_RELATIONS,
|
||||||
}
|
}
|
||||||
api.doc.register(BofreqEditorDocEventResource())
|
api.doc.register(BofreqEditorDocEventResource())
|
||||||
|
|
||||||
|
|
||||||
|
from ietf.person.resources import PersonResource
|
||||||
|
class BofreqResponsibleDocEventResource(ModelResource):
|
||||||
|
by = ToOneField(PersonResource, 'by')
|
||||||
|
doc = ToOneField(DocumentResource, 'doc')
|
||||||
|
docevent_ptr = ToOneField(DocEventResource, 'docevent_ptr')
|
||||||
|
responsible = ToManyField(PersonResource, 'responsible', null=True)
|
||||||
|
class Meta:
|
||||||
|
queryset = BofreqResponsibleDocEvent.objects.all()
|
||||||
|
serializer = api.Serializer()
|
||||||
|
cache = SimpleCache()
|
||||||
|
#resource_name = 'bofreqresponsibledocevent'
|
||||||
|
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,
|
||||||
|
"responsible": ALL_WITH_RELATIONS,
|
||||||
|
}
|
||||||
|
api.doc.register(BofreqResponsibleDocEventResource())
|
||||||
|
|
|
@ -11,14 +11,15 @@ from tempfile import NamedTemporaryFile
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
|
|
||||||
|
from ietf.group.factories import RoleFactory
|
||||||
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
|
from ietf.doc.factories import BofreqFactory, NewRevisionDocEventFactory
|
||||||
from ietf.doc.models import State, BofreqEditorDocEvent, Document, DocAlias, NewRevisionDocEvent
|
from ietf.doc.models import State, Document, DocAlias, NewRevisionDocEvent
|
||||||
|
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
|
||||||
from ietf.person.factories import PersonFactory
|
from ietf.person.factories import PersonFactory
|
||||||
from ietf.utils.mail import outbox, empty_outbox
|
from ietf.utils.mail import outbox, empty_outbox
|
||||||
from ietf.utils.test_utils import TestCase, reload_db_objects, unicontent, login_testing_unauthorized
|
from ietf.utils.test_utils import TestCase, reload_db_objects, unicontent, login_testing_unauthorized
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BofreqTests(TestCase):
|
class BofreqTests(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -42,10 +43,12 @@ This test section has some text.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def test_show_bof_requests(self):
|
def test_show_bof_requests(self):
|
||||||
|
url = urlreverse('ietf.doc.views_bofreq.bof_requests')
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertContains(r, 'There are currently no BoF Requests', status_code=200)
|
||||||
states = State.objects.filter(type_id='bofreq')
|
states = State.objects.filter(type_id='bofreq')
|
||||||
self.assertTrue(states.count()>0)
|
self.assertTrue(states.count()>0)
|
||||||
reqs = BofreqFactory.create_batch(states.count())
|
reqs = BofreqFactory.create_batch(states.count())
|
||||||
url = urlreverse('ietf.doc.views_bofreq.bof_requests')
|
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
|
@ -57,6 +60,13 @@ This test section has some text.
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
for state in states:
|
for state in states:
|
||||||
self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 1)
|
self.assertEqual(len(q(f'#bofreqs-{state.slug} tbody tr')), 1)
|
||||||
|
self.assertFalse(q('#start_button'))
|
||||||
|
PersonFactory(user__username='nobody')
|
||||||
|
self.client.login(username='nobody', password='nobody+password')
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertTrue(q('#start_button'))
|
||||||
|
|
||||||
|
|
||||||
def test_bofreq_main_page(self):
|
def test_bofreq_main_page(self):
|
||||||
|
@ -67,7 +77,8 @@ This test section has some text.
|
||||||
doc.rev='01'
|
doc.rev='01'
|
||||||
doc.save_with_history([nr_event])
|
doc.save_with_history([nr_event])
|
||||||
self.write_bofreq_file(doc)
|
self.write_bofreq_file(doc)
|
||||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(doc)
|
||||||
|
responsible = bofreq_responsible(doc)
|
||||||
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=doc))
|
url = urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=doc))
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertContains(r,'Version: 01',status_code=200)
|
self.assertContains(r,'Version: 01',status_code=200)
|
||||||
|
@ -77,12 +88,15 @@ This test section has some text.
|
||||||
editor_row = q('#editors').html()
|
editor_row = q('#editors').html()
|
||||||
for editor in editors:
|
for editor in editors:
|
||||||
self.assertInHTML(editor.plain_name(),editor_row)
|
self.assertInHTML(editor.plain_name(),editor_row)
|
||||||
|
responsible_row = q('#responsible').html()
|
||||||
|
for leader in responsible:
|
||||||
|
self.assertInHTML(leader.plain_name(),responsible_row)
|
||||||
for user in ('secretary','ad','iab-member'):
|
for user in ('secretary','ad','iab-member'):
|
||||||
self.client.login(username=user,password=user+"+password")
|
self.client.login(username=user,password=user+"+password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
self.assertEqual(4, len(q('td.edit>a.btn')))
|
self.assertEqual(5, len(q('td.edit>a.btn')))
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
self.assertNotEqual([],q('#change-request'))
|
self.assertNotEqual([],q('#change-request'))
|
||||||
editor = editors.first().user.username
|
editor = editors.first().user.username
|
||||||
|
@ -98,9 +112,10 @@ This test section has some text.
|
||||||
self.assertContains(r,'Version: 00',status_code=200)
|
self.assertContains(r,'Version: 00',status_code=200)
|
||||||
self.assertContains(r,'is for an older version')
|
self.assertContains(r,'is for an older version')
|
||||||
|
|
||||||
|
|
||||||
def test_edit_title(self):
|
def test_edit_title(self):
|
||||||
doc = BofreqFactory()
|
doc = BofreqFactory()
|
||||||
editor = doc.latest_event(BofreqEditorDocEvent).editors.first()
|
editor = bofreq_editors(doc).first()
|
||||||
url = urlreverse('ietf.doc.views_bofreq.edit_title', kwargs=dict(name=doc.name))
|
url = urlreverse('ietf.doc.views_bofreq.edit_title', kwargs=dict(name=doc.name))
|
||||||
title = doc.title
|
title = doc.title
|
||||||
r = self.client.post(url,dict(title='New title'))
|
r = self.client.post(url,dict(title='New title'))
|
||||||
|
@ -133,7 +148,7 @@ This test section has some text.
|
||||||
|
|
||||||
def test_edit_state(self):
|
def test_edit_state(self):
|
||||||
doc = BofreqFactory()
|
doc = BofreqFactory()
|
||||||
editor = doc.latest_event(BofreqEditorDocEvent).editors.first()
|
editor = bofreq_editors(doc).first()
|
||||||
url = urlreverse('ietf.doc.views_bofreq.change_state', kwargs=dict(name=doc.name))
|
url = urlreverse('ietf.doc.views_bofreq.change_state', kwargs=dict(name=doc.name))
|
||||||
state = doc.get_state('bofreq')
|
state = doc.get_state('bofreq')
|
||||||
r = self.client.post(url, dict(new_state=self.state_pk_as_str('bofreq','approved')))
|
r = self.client.post(url, dict(new_state=self.state_pk_as_str('bofreq','approved')))
|
||||||
|
@ -162,7 +177,7 @@ This test section has some text.
|
||||||
|
|
||||||
def test_change_editors(self):
|
def test_change_editors(self):
|
||||||
doc = BofreqFactory()
|
doc = BofreqFactory()
|
||||||
previous_editors = list(doc.latest_event(BofreqEditorDocEvent).editors.all())
|
previous_editors = list(bofreq_editors(doc))
|
||||||
acting_editor = previous_editors[0]
|
acting_editor = previous_editors[0]
|
||||||
new_editors = set(previous_editors)
|
new_editors = set(previous_editors)
|
||||||
new_editors.discard(acting_editor)
|
new_editors.discard(acting_editor)
|
||||||
|
@ -171,13 +186,13 @@ This test section has some text.
|
||||||
postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
|
postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
|
||||||
r = self.client.post(url, postdict)
|
r = self.client.post(url, postdict)
|
||||||
self.assertEqual(r.status_code,302)
|
self.assertEqual(r.status_code,302)
|
||||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(doc)
|
||||||
self.assertEqual(set(previous_editors),set(editors))
|
self.assertEqual(set(previous_editors),set(editors))
|
||||||
nobody = PersonFactory()
|
nobody = PersonFactory()
|
||||||
self.client.login(username=nobody.user.username,password=nobody.user.username+'+password')
|
self.client.login(username=nobody.user.username,password=nobody.user.username+'+password')
|
||||||
r = self.client.post(url, postdict)
|
r = self.client.post(url, postdict)
|
||||||
self.assertEqual(r.status_code,403)
|
self.assertEqual(r.status_code,403)
|
||||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(doc)
|
||||||
self.assertEqual(set(previous_editors),set(editors))
|
self.assertEqual(set(previous_editors),set(editors))
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
for username in (previous_editors[0].user.username, 'secretary', 'ad', 'iab-member'):
|
for username in (previous_editors[0].user.username, 'secretary', 'ad', 'iab-member'):
|
||||||
|
@ -185,8 +200,8 @@ This test section has some text.
|
||||||
self.client.login(username=username,password=username+'+password')
|
self.client.login(username=username,password=username+'+password')
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
self.assertEqual(r.status_code,200)
|
self.assertEqual(r.status_code,200)
|
||||||
|
unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
|
||||||
for editor in previous_editors:
|
for editor in previous_editors:
|
||||||
unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
|
|
||||||
self.assertIn(editor.name,unescaped)
|
self.assertIn(editor.name,unescaped)
|
||||||
new_editors = set(previous_editors)
|
new_editors = set(previous_editors)
|
||||||
new_editors.discard(acting_editor)
|
new_editors.discard(acting_editor)
|
||||||
|
@ -194,13 +209,74 @@ This test section has some text.
|
||||||
postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
|
postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
|
||||||
r = self.client.post(url,postdict)
|
r = self.client.post(url,postdict)
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
updated_editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
updated_editors = bofreq_editors(doc)
|
||||||
self.assertEqual(new_editors,set(updated_editors))
|
self.assertEqual(new_editors,set(updated_editors))
|
||||||
previous_editors = new_editors
|
previous_editors = new_editors
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
self.assertEqual(len(outbox),1)
|
self.assertEqual(len(outbox),1)
|
||||||
self.assertIn('BOF Request editors changed',outbox[0]['Subject'])
|
self.assertIn('BOF Request editors changed',outbox[0]['Subject'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_change_responsible(self):
|
||||||
|
doc = BofreqFactory()
|
||||||
|
previous_responsible = list(bofreq_responsible(doc))
|
||||||
|
new_responsible = set(previous_responsible[1:])
|
||||||
|
new_responsible.add(RoleFactory(group__type_id='area',name_id='ad').person)
|
||||||
|
url = urlreverse('ietf.doc.views_bofreq.change_responsible', kwargs=dict(name=doc.name))
|
||||||
|
postdict = dict(responsible=','.join([str(p.pk) for p in new_responsible]))
|
||||||
|
r = self.client.post(url, postdict)
|
||||||
|
self.assertEqual(r.status_code,302)
|
||||||
|
responsible = bofreq_responsible(doc)
|
||||||
|
self.assertEqual(set(previous_responsible), set(responsible))
|
||||||
|
PersonFactory(user__username='nobody')
|
||||||
|
self.client.login(username='nobody',password='nobody+password')
|
||||||
|
r = self.client.post(url, postdict)
|
||||||
|
self.assertEqual(r.status_code,403)
|
||||||
|
responsible = bofreq_responsible(doc)
|
||||||
|
self.assertEqual(set(previous_responsible), set(responsible))
|
||||||
|
self.client.logout()
|
||||||
|
for username in ('secretary', 'ad', 'iab-member'):
|
||||||
|
empty_outbox()
|
||||||
|
self.client.login(username=username,password=username+'+password')
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code,200)
|
||||||
|
unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
|
||||||
|
for responsible in previous_responsible:
|
||||||
|
self.assertIn(responsible.name,unescaped)
|
||||||
|
new_responsible = set(previous_responsible)
|
||||||
|
new_responsible.add(RoleFactory(group__type_id='area',name_id='ad').person)
|
||||||
|
postdict = dict(responsible=','.join([str(p.pk) for p in new_responsible]))
|
||||||
|
r = self.client.post(url,postdict)
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
updated_responsible = bofreq_responsible(doc)
|
||||||
|
self.assertEqual(new_responsible,set(updated_responsible))
|
||||||
|
previous_responsible = new_responsible
|
||||||
|
self.client.logout()
|
||||||
|
self.assertEqual(len(outbox),1)
|
||||||
|
self.assertIn('BOF Request responsible leadership changed',outbox[0]['Subject'])
|
||||||
|
|
||||||
|
def test_change_responsible_validation(self):
|
||||||
|
doc = BofreqFactory()
|
||||||
|
url = urlreverse('ietf.doc.views_bofreq.change_responsible', kwargs=dict(name=doc.name))
|
||||||
|
login_testing_unauthorized(self,'secretary',url)
|
||||||
|
bad_batch = PersonFactory.create_batch(3)
|
||||||
|
good_batch = list()
|
||||||
|
good_batch.append(RoleFactory(group__type_id='area', name_id='ad').person)
|
||||||
|
good_batch.append(RoleFactory(group__acronym='iab', name_id='member').person)
|
||||||
|
pks = set()
|
||||||
|
pks.update([p.pk for p in good_batch])
|
||||||
|
pks.update([p.pk for p in bad_batch])
|
||||||
|
postdict = dict(responsible=','.join([str(pk) for pk in pks]))
|
||||||
|
r = self.client.post(url,postdict)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
error_text = q('.has-error .alert').text()
|
||||||
|
for p in good_batch:
|
||||||
|
self.assertNotIn(p.plain_name(), error_text)
|
||||||
|
for p in bad_batch:
|
||||||
|
self.assertIn(p.plain_name(), error_text)
|
||||||
|
|
||||||
|
|
||||||
def test_submit(self):
|
def test_submit(self):
|
||||||
doc = BofreqFactory()
|
doc = BofreqFactory()
|
||||||
url = urlreverse('ietf.doc.views_bofreq.submit', kwargs=dict(name=doc.name))
|
url = urlreverse('ietf.doc.views_bofreq.submit', kwargs=dict(name=doc.name))
|
||||||
|
@ -219,7 +295,7 @@ This test section has some text.
|
||||||
self.assertEqual(rev, doc.rev)
|
self.assertEqual(rev, doc.rev)
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
editor = doc.latest_event(BofreqEditorDocEvent).editors.first()
|
editor = bofreq_editors(doc).first()
|
||||||
for username in ('secretary', 'ad', 'iab-member', editor.user.username):
|
for username in ('secretary', 'ad', 'iab-member', editor.user.username):
|
||||||
self.client.login(username=username, password=username+'+password')
|
self.client.login(username=username, password=username+'+password')
|
||||||
r = self.client.get(url)
|
r = self.client.get(url)
|
||||||
|
@ -267,7 +343,7 @@ This test section has some text.
|
||||||
self.assertEqual(bofreq.title, postdict['title'])
|
self.assertEqual(bofreq.title, postdict['title'])
|
||||||
self.assertEqual(bofreq.rev, '00')
|
self.assertEqual(bofreq.rev, '00')
|
||||||
self.assertEqual(bofreq.get_state_slug(), 'proposed')
|
self.assertEqual(bofreq.get_state_slug(), 'proposed')
|
||||||
self.assertEqual(list(bofreq.latest_event(BofreqEditorDocEvent).editors.all()), [nobody])
|
self.assertEqual(list(bofreq_editors(bofreq)), [nobody])
|
||||||
self.assertEqual(bofreq.latest_event(NewRevisionDocEvent).rev, '00')
|
self.assertEqual(bofreq.latest_event(NewRevisionDocEvent).rev, '00')
|
||||||
self.assertEqual(bofreq.text_or_error(), 'some stuff')
|
self.assertEqual(bofreq.text_or_error(), 'some stuff')
|
||||||
self.assertEqual(len(outbox),1)
|
self.assertEqual(len(outbox),1)
|
||||||
|
|
|
@ -8,5 +8,6 @@ urlpatterns = [
|
||||||
url(r'^state/$', views_bofreq.change_state),
|
url(r'^state/$', views_bofreq.change_state),
|
||||||
url(r'^submit/$', views_bofreq.submit),
|
url(r'^submit/$', views_bofreq.submit),
|
||||||
url(r'^title/$', views_bofreq.edit_title),
|
url(r'^title/$', views_bofreq.edit_title),
|
||||||
url(r'^editors/$', views_bofreq.change_editors),
|
url(r'^editors/$', views_bofreq.change_editors),
|
||||||
|
url(r'^responsible/$', views_bofreq.change_responsible),
|
||||||
]
|
]
|
12
ietf/doc/utils_bofreq.py
Normal file
12
ietf/doc/utils_bofreq.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Copyright The IETF Trust 2021 All Rights Reserved
|
||||||
|
|
||||||
|
from ietf.doc.models import BofreqEditorDocEvent, BofreqResponsibleDocEvent
|
||||||
|
from ietf.person.models import Person
|
||||||
|
|
||||||
|
def bofreq_editors(bofreq):
|
||||||
|
e = bofreq.latest_event(BofreqEditorDocEvent)
|
||||||
|
return e.editors.all() if e else Person.objects.none()
|
||||||
|
|
||||||
|
def bofreq_responsible(bofreq):
|
||||||
|
e = bofreq.latest_event(BofreqResponsibleDocEvent)
|
||||||
|
return e.responsible.all() if e else Person.objects.none()
|
|
@ -12,10 +12,12 @@ from django.template.loader import render_to_string
|
||||||
from django.urls import reverse as urlreverse
|
from django.urls import reverse as urlreverse
|
||||||
|
|
||||||
|
|
||||||
from ietf.doc.mails import ( email_bofreq_title_changed, email_bofreq_editors_changed,
|
from ietf.doc.mails import (email_bofreq_title_changed, email_bofreq_editors_changed,
|
||||||
email_bofreq_new_revision, )
|
email_bofreq_new_revision, email_bofreq_responsible_changed)
|
||||||
from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, BofreqEditorDocEvent, State
|
from ietf.doc.models import (Document, DocAlias, DocEvent, NewRevisionDocEvent,
|
||||||
|
BofreqEditorDocEvent, BofreqResponsibleDocEvent, State)
|
||||||
from ietf.doc.utils import add_state_change_event
|
from ietf.doc.utils import add_state_change_event
|
||||||
|
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
|
||||||
from ietf.ietfauth.utils import has_role, role_required
|
from ietf.ietfauth.utils import has_role, role_required
|
||||||
from ietf.person.fields import SearchablePersonsField
|
from ietf.person.fields import SearchablePersonsField
|
||||||
from ietf.utils.response import permission_denied
|
from ietf.utils.response import permission_denied
|
||||||
|
@ -23,13 +25,13 @@ from ietf.utils.text import xslugify
|
||||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def bof_requests(request):
|
def bof_requests(request):
|
||||||
reqs = Document.objects.filter(type_id='bofreq')
|
reqs = Document.objects.filter(type_id='bofreq')
|
||||||
for req in reqs:
|
for req in reqs:
|
||||||
req.latest_revision_event = req.latest_event(NewRevisionDocEvent)
|
req.latest_revision_event = req.latest_event(NewRevisionDocEvent)
|
||||||
return render(request, 'doc/bofreq/bof_requests.html',dict(reqs=reqs))
|
return render(request, 'doc/bofreq/bof_requests.html',dict(reqs=reqs))
|
||||||
|
|
||||||
|
|
||||||
def edit_relations(request, name):
|
def edit_relations(request, name):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -74,7 +76,7 @@ class BofreqUploadForm(forms.Form):
|
||||||
@login_required
|
@login_required
|
||||||
def submit(request, name):
|
def submit(request, name):
|
||||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||||
previous_editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
previous_editors = bofreq_editors(bofreq)
|
||||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in previous_editors):
|
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in previous_editors):
|
||||||
permission_denied(request,"You do not have permission to upload a new revision of this BOF Request")
|
permission_denied(request,"You do not have permission to upload a new revision of this BOF Request")
|
||||||
|
|
||||||
|
@ -187,7 +189,7 @@ class ChangeEditorsForm(forms.Form):
|
||||||
@login_required
|
@login_required
|
||||||
def change_editors(request, name):
|
def change_editors(request, name):
|
||||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||||
previous_editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
previous_editors = bofreq_editors(bofreq)
|
||||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in previous_editors):
|
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in previous_editors):
|
||||||
permission_denied(request,"You do not have permission to change this document's editors")
|
permission_denied(request,"You do not have permission to change this document's editors")
|
||||||
|
|
||||||
|
@ -214,13 +216,58 @@ def change_editors(request, name):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeResponsibleForm(forms.Form):
|
||||||
|
responsible = SearchablePersonsField(required=False)
|
||||||
|
def clean_responsible(self):
|
||||||
|
responsible = self.cleaned_data['responsible']
|
||||||
|
not_leadership = list()
|
||||||
|
for person in responsible:
|
||||||
|
if not has_role(person.user, ('Area Director', 'IAB')):
|
||||||
|
not_leadership.append(person)
|
||||||
|
if not_leadership:
|
||||||
|
raise forms.ValidationError('Only current IAB and IESG members are allowed. Please remove: '+', '.join([person.plain_name() for person in not_leadership]))
|
||||||
|
return responsible
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def change_responsible(request,name):
|
||||||
|
if not has_role(request.user,('Secretariat', 'Area Director', 'IAB')):
|
||||||
|
permission_denied(request,"You do not have permission to change this document's responsible leadership")
|
||||||
|
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||||
|
previous_responsible = bofreq_responsible(bofreq)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ChangeResponsibleForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
new_responsible = form.cleaned_data['responsible']
|
||||||
|
if set(new_responsible) != set(previous_responsible):
|
||||||
|
e = BofreqResponsibleDocEvent(type="changed_responsible", doc=bofreq, rev=bofreq.rev, by=request.user.person)
|
||||||
|
e.desc = f'Responsible leadership changed to {", ".join([p.name for p in new_responsible])}'
|
||||||
|
e.save()
|
||||||
|
e.responsible.set(new_responsible)
|
||||||
|
bofreq.save_with_history([e])
|
||||||
|
email_bofreq_responsible_changed(request, bofreq, previous_responsible)
|
||||||
|
return redirect("ietf.doc.views_doc.document_main", name=bofreq.name)
|
||||||
|
else:
|
||||||
|
init = { "responsible" : previous_responsible }
|
||||||
|
form = ChangeResponsibleForm(initial=init)
|
||||||
|
titletext = bofreq.get_base_name()
|
||||||
|
return render(request, 'doc/bofreq/change_responsible.html',
|
||||||
|
{'form': form,
|
||||||
|
'doc': bofreq,
|
||||||
|
'titletext' : titletext,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChangeTitleForm(forms.Form):
|
class ChangeTitleForm(forms.Form):
|
||||||
title = forms.CharField(max_length=255, label="Title", required=True)
|
title = forms.CharField(max_length=255, label="Title", required=True)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_title(request, name):
|
def edit_title(request, name):
|
||||||
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
bofreq = get_object_or_404(Document, type="bofreq", name=name)
|
||||||
editors = bofreq.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(bofreq)
|
||||||
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in editors):
|
if not (has_role(request.user,('Secretariat', 'Area Director', 'IAB')) or request.user.person in editors):
|
||||||
permission_denied(request, "You do not have permission to edit this document's title")
|
permission_denied(request, "You do not have permission to edit this document's title")
|
||||||
|
|
||||||
|
|
|
@ -55,15 +55,15 @@ import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent, BallotType,
|
from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent, BallotType,
|
||||||
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IanaExpertDocEvent,
|
ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IanaExpertDocEvent,
|
||||||
IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS, DocumentActionHolder, DocumentAuthor,
|
IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS, DocumentActionHolder, DocumentAuthor)
|
||||||
BofreqEditorDocEvent )
|
|
||||||
from ietf.doc.utils import (add_links_in_new_revision_events, augment_events_with_revision,
|
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,
|
can_adopt_draft, can_unadopt_draft, get_chartering_type, get_tags_for_stream_id,
|
||||||
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
||||||
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
|
get_initial_notify, make_notify_changed_event, make_rev_history, default_consensus,
|
||||||
add_events_message_info, get_unicode_document_content, build_doc_meta_block,
|
add_events_message_info, get_unicode_document_content, build_doc_meta_block,
|
||||||
augment_docs_and_user_with_user_info, irsg_needed_ballot_positions, add_action_holder_change_event,
|
augment_docs_and_user_with_user_info, irsg_needed_ballot_positions, add_action_holder_change_event,
|
||||||
build_doc_supermeta_block, build_file_urls, update_documentauthors )
|
build_doc_supermeta_block, build_file_urls, update_documentauthors)
|
||||||
|
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
|
||||||
from ietf.group.models import Role, Group
|
from ietf.group.models import Role, Group
|
||||||
from ietf.group.utils import can_manage_group_type, can_manage_materials, group_features_role_filter
|
from ietf.group.utils import can_manage_group_type, can_manage_materials, group_features_role_filter
|
||||||
from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is_person,
|
from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is_person,
|
||||||
|
@ -529,9 +529,9 @@ def document_main(request, name, rev=None):
|
||||||
))
|
))
|
||||||
|
|
||||||
if doc.type_id == "bofreq":
|
if doc.type_id == "bofreq":
|
||||||
# content = markdown2.markdown(doc.text_or_error(),extras=['tables'])
|
|
||||||
content = markdown.markdown(doc.text_or_error(),extensions=['extra'])
|
content = markdown.markdown(doc.text_or_error(),extensions=['extra'])
|
||||||
editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
|
editors = bofreq_editors(doc)
|
||||||
|
responsible = bofreq_responsible(doc)
|
||||||
can_manage = has_role(request.user,['Secretariat', 'Area Director', 'IAB'])
|
can_manage = has_role(request.user,['Secretariat', 'Area Director', 'IAB'])
|
||||||
is_editor = request.user.is_authenticated and request.user.person in editors
|
is_editor = request.user.is_authenticated and request.user.person in editors
|
||||||
|
|
||||||
|
@ -544,6 +544,7 @@ def document_main(request, name, rev=None):
|
||||||
snapshot=snapshot,
|
snapshot=snapshot,
|
||||||
can_manage=can_manage,
|
can_manage=can_manage,
|
||||||
editors=editors,
|
editors=editors,
|
||||||
|
responsible=responsible,
|
||||||
is_editor=is_editor,
|
is_editor=is_editor,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -273,8 +273,6 @@ class GroupPagesTests(TestCase):
|
||||||
|
|
||||||
def test_group_about(self):
|
def test_group_about(self):
|
||||||
|
|
||||||
RoleFactory(group=Group.objects.get(acronym='iab'),name_id='member',person=PersonFactory(user__username='iab-member'))
|
|
||||||
|
|
||||||
interesting_users = [ 'plain','iana','iab-chair','irtf-chair', 'marschairman', 'teamchairman','ad', 'iab-member', 'secretary', ]
|
interesting_users = [ 'plain','iana','iab-chair','irtf-chair', 'marschairman', 'teamchairman','ad', 'iab-member', 'secretary', ]
|
||||||
|
|
||||||
can_edit = {
|
can_edit = {
|
||||||
|
|
|
@ -11,20 +11,27 @@ def forward(apps, schema_editor):
|
||||||
Recipient.objects.create(slug='bofreq_previous_editors',desc='Editors of the prior version of a BOF request',
|
Recipient.objects.create(slug='bofreq_previous_editors',desc='Editors of the prior version of a BOF request',
|
||||||
template='{% for editor in previous_editors %}{{editor.email_address}}{% if not forloop.last %},{% endif %}{% endfor %}')
|
template='{% for editor in previous_editors %}{{editor.email_address}}{% if not forloop.last %},{% endif %}{% endfor %}')
|
||||||
|
|
||||||
|
Recipient.objects.create(slug='bofreq_responsible',desc='BOF request responsible leadership',template='')
|
||||||
|
Recipient.objects.create(slug='bofreq_previous_responsible',desc='BOF request responsible leadership before change', template='')
|
||||||
|
|
||||||
mt = MailTrigger.objects.create(slug='bofreq_title_changed',desc='Recipients when the title of a BOF proposal is changed.')
|
mt = MailTrigger.objects.create(slug='bofreq_title_changed',desc='Recipients when the title of a BOF proposal is changed.')
|
||||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'doc_notify']))
|
mt.to.set(Recipient.objects.filter(slug__in=['bofreq_responsible', 'bofreq_editors', 'doc_notify']))
|
||||||
|
|
||||||
mt = MailTrigger.objects.create(slug='bofreq_editors_changed',desc='Recipients when the editors of a BOF proposal are changed.')
|
mt = MailTrigger.objects.create(slug='bofreq_editors_changed',desc='Recipients when the editors of a BOF proposal are changed.')
|
||||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'bofreq_previous_editors', 'doc_notify']))
|
mt.to.set(Recipient.objects.filter(slug__in=['bofreq_responsible', 'bofreq_editors', 'bofreq_previous_editors', 'doc_notify']))
|
||||||
|
|
||||||
|
mt = MailTrigger.objects.create(slug='bofreq_responsible_changed',desc='Recipients when the responsible leadership of a BOF proposal are changed.')
|
||||||
|
mt.to.set(Recipient.objects.filter(slug__in=['bofreq_responsible', 'bofreq_editors', 'bofreq_previous_responsible', 'doc_notify']))
|
||||||
|
|
||||||
mt = MailTrigger.objects.create(slug='bofreq_new_revision', desc='Recipients when a new revision of a BOF request is uploaded.')
|
mt = MailTrigger.objects.create(slug='bofreq_new_revision', desc='Recipients when a new revision of a BOF request is uploaded.')
|
||||||
mt.to.set(Recipient.objects.filter(slug__in=['doc_ad', 'bofreq_editors', 'doc_notify']))
|
mt.to.set(Recipient.objects.filter(slug__in=['bofreq_responsible', 'bofreq_editors', 'doc_notify']))
|
||||||
|
|
||||||
def reverse(apps, schema_editor):
|
def reverse(apps, schema_editor):
|
||||||
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
|
||||||
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
||||||
MailTrigger.objects.filter(slug__in=('bofreq_title_changed','bofreq_editors_changed','bofreq_new_revision')).delete()
|
MailTrigger.objects.filter(slug__in=('bofreq_title_changed', 'bofreq_editors_changed', 'bofreq_new_revision', 'bofreq_responsible_changed')).delete()
|
||||||
Recipient.objects.filter(slug__in=('bofreq_editors','bofreq_previous_editors')).delete()
|
Recipient.objects.filter(slug__in=('bofreq_editors', 'bofreq_previous_editors')).delete()
|
||||||
|
Recipient.objects.filter(slug__in=('bofreq_responsible', 'bofreq_previous_responsible')).delete()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
|
@ -6,7 +6,8 @@ from django.db import models
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
|
|
||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from ietf.doc.models import BofreqEditorDocEvent
|
|
||||||
|
from ietf.doc.utils_bofreq import bofreq_editors, bofreq_responsible
|
||||||
from ietf.utils.mail import formataddr, get_email_addresses_from_text
|
from ietf.utils.mail import formataddr, get_email_addresses_from_text
|
||||||
from ietf.group.models import Group
|
from ietf.group.models import Group
|
||||||
from ietf.person.models import Email, Alias
|
from ietf.person.models import Email, Alias
|
||||||
|
@ -398,8 +399,29 @@ class Recipient(models.Model):
|
||||||
addrs = []
|
addrs = []
|
||||||
if 'doc' in kwargs:
|
if 'doc' in kwargs:
|
||||||
bofreq = kwargs['doc']
|
bofreq = kwargs['doc']
|
||||||
editor_event = bofreq.latest_event(BofreqEditorDocEvent)
|
editors = bofreq_editors(bofreq)
|
||||||
if editor_event:
|
addrs.extend([editor.email_address() for editor in editors])
|
||||||
addrs.extend([editor.email_address() for editor in editor_event.editors.all()])
|
return addrs
|
||||||
|
|
||||||
|
def gather_bofreq_responsible(self, **kwargs):
|
||||||
|
addrs = []
|
||||||
|
if 'doc' in kwargs:
|
||||||
|
bofreq = kwargs['doc']
|
||||||
|
responsible = bofreq_responsible(bofreq)
|
||||||
|
if responsible:
|
||||||
|
addrs.extend([leader.email_address() for leader in responsible])
|
||||||
|
else:
|
||||||
|
addrs.extend(Recipient.objects.get(slug='iab').gather(**{}))
|
||||||
|
addrs.extend(Recipient.objects.get(slug='iesg').gather(**{}))
|
||||||
|
return addrs
|
||||||
|
|
||||||
|
def gather_bofreq_previous_responsible(self, **kwargs):
|
||||||
|
addrs = []
|
||||||
|
previous_responsible = kwargs['previous_responsible']
|
||||||
|
if previous_responsible:
|
||||||
|
addrs = [p.email_address() for p in previous_responsible]
|
||||||
|
else:
|
||||||
|
addrs.extend(Recipient.objects.get(slug='iab').gather(**{}))
|
||||||
|
addrs.extend(Recipient.objects.get(slug='iesg').gather(**{}))
|
||||||
return addrs
|
return addrs
|
||||||
|
|
||||||
|
|
|
@ -3535,7 +3535,7 @@
|
||||||
"to": [
|
"to": [
|
||||||
"bofreq_editors",
|
"bofreq_editors",
|
||||||
"bofreq_previous_editors",
|
"bofreq_previous_editors",
|
||||||
"doc_ad",
|
"bofreq_responsible",
|
||||||
"doc_notify"
|
"doc_notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3548,20 +3548,34 @@
|
||||||
"desc": "Recipients when a new revision of a BOF request is uploaded.",
|
"desc": "Recipients when a new revision of a BOF request is uploaded.",
|
||||||
"to": [
|
"to": [
|
||||||
"bofreq_editors",
|
"bofreq_editors",
|
||||||
"doc_ad",
|
"bofreq_responsible",
|
||||||
"doc_notify"
|
"doc_notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"model": "mailtrigger.mailtrigger",
|
"model": "mailtrigger.mailtrigger",
|
||||||
"pk": "bofreq_new_revision"
|
"pk": "bofreq_new_revision"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"cc": [],
|
||||||
|
"desc": "Recipients when the responsible leadership of a BOF proposal are changed.",
|
||||||
|
"to": [
|
||||||
|
"bofreq_editors",
|
||||||
|
"bofreq_previous_responsible",
|
||||||
|
"bofreq_responsible",
|
||||||
|
"doc_notify"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"model": "mailtrigger.mailtrigger",
|
||||||
|
"pk": "bofreq_responsible_changed"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": [],
|
"cc": [],
|
||||||
"desc": "Recipients when the title of a BOF proposal is changed.",
|
"desc": "Recipients when the title of a BOF proposal is changed.",
|
||||||
"to": [
|
"to": [
|
||||||
"bofreq_editors",
|
"bofreq_editors",
|
||||||
"doc_ad",
|
"bofreq_responsible",
|
||||||
"doc_notify"
|
"doc_notify"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -5237,6 +5251,22 @@
|
||||||
"model": "mailtrigger.recipient",
|
"model": "mailtrigger.recipient",
|
||||||
"pk": "bofreq_previous_editors"
|
"pk": "bofreq_previous_editors"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "BOF request responsible leadership before change",
|
||||||
|
"template": ""
|
||||||
|
},
|
||||||
|
"model": "mailtrigger.recipient",
|
||||||
|
"pk": "bofreq_previous_responsible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"desc": "BOF request responsible leadership",
|
||||||
|
"template": ""
|
||||||
|
},
|
||||||
|
"model": "mailtrigger.recipient",
|
||||||
|
"pk": "bofreq_responsible"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"desc": "The person providing a comment to nomcom",
|
"desc": "The person providing a comment to nomcom",
|
||||||
|
|
|
@ -7,26 +7,35 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
|
|
||||||
{% regroup reqs by get_state_slug as grouped_reqs %}
|
<h1>BOF Requests</h1>
|
||||||
{% for req_group in grouped_reqs %}
|
|
||||||
<div class="panel panel-default">
|
{% if not reqs %}
|
||||||
<div class="panel-heading">{{req_group.grouper|capfirst}} BOF Requests</div>
|
<p>There are currently no BoF Requests</p>
|
||||||
<div class="panel-body">
|
{% else %}
|
||||||
<table id="bofreqs-{{req_group.grouper}}" class="table table-condensed table-striped tablesorter">
|
{% regroup reqs by get_state_slug as grouped_reqs %}
|
||||||
<thead>
|
{% for req_group in grouped_reqs %}
|
||||||
<tr><th class="col-sm-4">Name</th><th class="col-sm-1">Date</th><th>Title</th></tr>
|
<div class="panel panel-default">
|
||||||
</thead>
|
<div class="panel-heading">{{req_group.grouper|capfirst}} BOF Requests</div>
|
||||||
<tbody>
|
<div class="panel-body">
|
||||||
{% for req in req_group.list %}
|
<table id="bofreqs-{{req_group.grouper}}" class="table table-condensed table-striped tablesorter">
|
||||||
<tr>
|
<thead>
|
||||||
<td><a href={% url 'ietf.doc.views_doc.document_main' name=req.name %}>{{req.name}}-{{req.rev}}</a></td>
|
<tr><th class="col-sm-4">Name</th><th class="col-sm-1">Date</th><th>Title</th></tr>
|
||||||
<td>{{req.latest_revision_event.time|date:"Y-m-d"}}</td>
|
</thead>
|
||||||
<td>{{req.title}}</td>
|
<tbody>
|
||||||
</tr>
|
{% for req in req_group.list %}
|
||||||
{% endfor %}
|
<tr>
|
||||||
</tbody>
|
<td><a href={% url 'ietf.doc.views_doc.document_main' name=req.name %}>{{req.name}}-{{req.rev}}</a></td>
|
||||||
</table>
|
<td>{{req.latest_revision_event.time|date:"Y-m-d"}}</td>
|
||||||
|
<td>{{req.title}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<a id="start_button" class="btn btn-primary" href="{% url 'ietf.doc.views_bofreq.new_bof_request' %}">Start New Bof Request</a>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
30
ietf/templates/doc/bofreq/change_responsible.html
Normal file
30
ietf/templates/doc/bofreq/change_responsible.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2021, All Rights Reserved #}
|
||||||
|
{% load origin %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block title %}Change responsible leadership for {{doc.name}}{% endblock %}
|
||||||
|
|
||||||
|
{% block pagehead %}
|
||||||
|
{{ form.media.css}}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>Change Responsible Leadership<br><small>{{ titletext }}</small></h1>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
|
||||||
|
{% 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.canonical_name %}">Back</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ form.media.js }}
|
||||||
|
{% endblock %}
|
|
@ -87,7 +87,7 @@
|
||||||
<th>Editor{{editors|pluralize}}</th>
|
<th>Editor{{editors|pluralize}}</th>
|
||||||
<td class="edit">
|
<td class="edit">
|
||||||
{% if not snapshot %}
|
{% if not snapshot %}
|
||||||
{% if is_editor or can_manage %}
|
{% if is_editor or can_manage %}
|
||||||
{% doc_edit_button 'ietf.doc.views_bofreq.change_editors' name=doc.name %}
|
{% doc_edit_button 'ietf.doc.views_bofreq.change_editors' name=doc.name %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -99,6 +99,22 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr id="responsible">
|
||||||
|
<td></td>
|
||||||
|
<th>Responsible Leadership</th>
|
||||||
|
<td class="edit">
|
||||||
|
{% if not snapshot %}
|
||||||
|
{% if can_manage %}
|
||||||
|
{% doc_edit_button 'ietf.doc.views_bofreq.change_responsible' name=doc.name %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for leader in responsible %}
|
||||||
|
{% person_link leader %}{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
<tbody class="meta">
|
<tbody class="meta">
|
||||||
|
|
6
ietf/templates/doc/mail/bofreq_responsible_changed.txt
Normal file
6
ietf/templates/doc/mail/bofreq_responsible_changed.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% autoescape off %}{{request.user.person.name}} has changed the responsible leadership for {{bofreq.name}}-{{bofreq.rev}}
|
||||||
|
|
||||||
|
The previous leaders were : {{previous_responsible|join:", "}}
|
||||||
|
|
||||||
|
The new leaders are : {{responsible|join:", "}}
|
||||||
|
{% endautoescape %}
|
Loading…
Reference in a new issue