From f5a04263e5144e22f9e7857d8eadeb6668954eb5 Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Wed, 7 Jul 2021 17:49:35 +0000
Subject: [PATCH] Added the notion of responsible leadership.  - Legacy-Id:
 19202

---
 ietf/doc/admin.py                             |   6 +-
 ietf/doc/factories.py                         |  23 +++-
 ietf/doc/mails.py                             |  20 +++-
 ietf/doc/migrations/0043_bofreq_docevents.py  |  36 ++++++
 .../migrations/0043_bofreqeditordocevent.py   |  23 ----
 ietf/doc/models.py                            |   7 +-
 ietf/doc/resources.py                         |  29 ++++-
 ietf/doc/tests_bofreq.py                      | 104 +++++++++++++++---
 ietf/doc/urls_bofreq.py                       |   3 +-
 ietf/doc/utils_bofreq.py                      |  12 ++
 ietf/doc/views_bofreq.py                      |  61 ++++++++--
 ietf/doc/views_doc.py                         |  11 +-
 ietf/group/tests_info.py                      |   2 -
 .../migrations/0023_bofreq_triggers.py        |  17 ++-
 ietf/mailtrigger/models.py                    |  30 ++++-
 ietf/name/fixtures/names.json                 |  36 +++++-
 ietf/templates/doc/bofreq/bof_requests.html   |  51 +++++----
 .../doc/bofreq/change_responsible.html        |  30 +++++
 ietf/templates/doc/document_bofreq.html       |  18 ++-
 .../doc/mail/bofreq_responsible_changed.txt   |   6 +
 20 files changed, 432 insertions(+), 93 deletions(-)
 create mode 100644 ietf/doc/migrations/0043_bofreq_docevents.py
 delete mode 100644 ietf/doc/migrations/0043_bofreqeditordocevent.py
 create mode 100644 ietf/doc/utils_bofreq.py
 create mode 100644 ietf/templates/doc/bofreq/change_responsible.html
 create mode 100644 ietf/templates/doc/mail/bofreq_responsible_changed.txt

diff --git a/ietf/doc/admin.py b/ietf/doc/admin.py
index 6f4f30143..a8c29c5b8 100644
--- a/ietf/doc/admin.py
+++ b/ietf/doc/admin.py
@@ -12,7 +12,7 @@ from .models import (StateType, State, RelatedDocument, DocumentAuthor, Document
     TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
     AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL,
     ReviewAssignmentDocEvent, IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder,
-    BofreqEditorDocEvent )
+    BofreqEditorDocEvent, BofreqResponsibleDocEvent )
 
 from ietf.utils.validators import validate_external_resource_value
 
@@ -198,6 +198,10 @@ class BofreqEditorDocEventAdmin(DocEventAdmin):
     raw_id_fields = ["doc", "by", "editors" ]
 admin.site.register(BofreqEditorDocEvent, BofreqEditorDocEventAdmin)
     
+class BofreqResponsibleDocEventAdmin(DocEventAdmin):
+    raw_id_fields = ["doc", "by", "responsible" ]
+admin.site.register(BofreqResponsibleDocEvent, BofreqResponsibleDocEventAdmin)
+    
 class DocumentUrlAdmin(admin.ModelAdmin):
     list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
     search_fields = ['doc__name', 'url', ]
diff --git a/ietf/doc/factories.py b/ietf/doc/factories.py
index e37e8119d..699f972ea 100644
--- a/ietf/doc/factories.py
+++ b/ietf/doc/factories.py
@@ -13,9 +13,10 @@ from django.conf import settings
 
 from ietf.doc.models import ( Document, DocEvent, NewRevisionDocEvent, DocAlias, State, DocumentAuthor,
     StateDocEvent, BallotPositionDocEvent, BallotDocEvent, BallotType, IRSGBallotDocEvent, TelechatDocEvent,
-    DocumentActionHolder, BofreqEditorDocEvent )
+    DocumentActionHolder, BofreqEditorDocEvent, BofreqResponsibleDocEvent )
 from ietf.group.models import Group
 from ietf.person.factories import PersonFactory
+from ietf.group.factories import RoleFactory
 from ietf.utils.text import xslugify
 
 
@@ -401,12 +402,32 @@ class BofreqEditorDocEventFactory(DocEventFactory):
             obj.editors.set(PersonFactory.create_batch(3))
         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):
     type_id = 'bofreq'
     title = factory.Faker('sentence')
     name = factory.LazyAttribute(lambda o: 'bofreq-%s'%(xslugify(o.title)))
 
     bofreqeditordocevent = factory.RelatedFactory('ietf.doc.factories.BofreqEditorDocEventFactory','doc')
+    bofreqresponsibledocevent = factory.RelatedFactory('ietf.doc.factories.BofreqResponsibleDocEventFactory','doc')
 
     @factory.post_generation
     def states(obj, create, extracted, **kwargs):
diff --git a/ietf/doc/mails.py b/ietf/doc/mails.py
index fc9a2bd26..0f344f015 100644
--- a/ietf/doc/mails.py
+++ b/ietf/doc/mails.py
@@ -18,8 +18,9 @@ from ietf.doc.templatetags.mail_filters import std_level_prompt
 from ietf.utils import log
 from ietf.utils.mail import send_mail, send_mail_text
 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_bofreq import bofreq_editors, bofreq_responsible
 from ietf.group.models import Role
 from ietf.doc.models import Document
 from ietf.mailtrigger.utils import gather_address_lists
@@ -699,14 +700,27 @@ def email_bofreq_title_changed(request, bofreq):
               dict(bofreq=bofreq, request=request),
               cc=addrs.cc)
 
+def plain_names(persons):
+    return [p.plain_name() for p in persons]
+
 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)
 
     send_mail(request, addrs.to, settings.DEFAULT_FROM_EMAIL,
               f'BOF Request editors changed : {bofreq.name}',
               '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)
 
 def email_bofreq_new_revision(request, bofreq):
diff --git a/ietf/doc/migrations/0043_bofreq_docevents.py b/ietf/doc/migrations/0043_bofreq_docevents.py
new file mode 100644
index 000000000..7a300426b
--- /dev/null
+++ b/ietf/doc/migrations/0043_bofreq_docevents.py
@@ -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',),
+        ),
+    ]
diff --git a/ietf/doc/migrations/0043_bofreqeditordocevent.py b/ietf/doc/migrations/0043_bofreqeditordocevent.py
deleted file mode 100644
index e6c3b433a..000000000
--- a/ietf/doc/migrations/0043_bofreqeditordocevent.py
+++ /dev/null
@@ -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',),
-        ),
-    ]
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index cab807c4d..cfcda14fb 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -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)
 
 class BofreqEditorDocEvent(DocEvent):
-    editors = models.ManyToManyField('person.Person',blank=True)
\ No newline at end of file
+    """ 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)
\ No newline at end of file
diff --git a/ietf/doc/resources.py b/ietf/doc/resources.py
index afd2d7949..99e26ac33 100644
--- a/ietf/doc/resources.py
+++ b/ietf/doc/resources.py
@@ -17,7 +17,8 @@ from ietf.doc.models import (BallotType, DeletedEvent, StateType, State, Documen
     InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
     RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
     ReviewRequestDocEvent, ReviewAssignmentDocEvent, EditedAuthorsDocEvent, DocumentURL,
-    IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder, BofreqEditorDocEvent)
+    IanaExpertDocEvent, IRSGBallotDocEvent, DocExtResource, DocumentActionHolder, 
+    BofreqEditorDocEvent,BofreqResponsibleDocEvent)
 
 from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
 class BallotTypeResource(ModelResource):
@@ -832,3 +833,29 @@ class BofreqEditorDocEventResource(ModelResource):
             "editors": ALL_WITH_RELATIONS,
         }
 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())
diff --git a/ietf/doc/tests_bofreq.py b/ietf/doc/tests_bofreq.py
index 5c5a1ac45..de9cb60c3 100644
--- a/ietf/doc/tests_bofreq.py
+++ b/ietf/doc/tests_bofreq.py
@@ -11,14 +11,15 @@ from tempfile import NamedTemporaryFile
 from django.conf import settings
 from django.urls import reverse as urlreverse
 
+from ietf.group.factories import RoleFactory
 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.utils.mail import outbox, empty_outbox
 from ietf.utils.test_utils import TestCase, reload_db_objects, unicontent, login_testing_unauthorized
 
 
-
 class BofreqTests(TestCase):
 
     def setUp(self):
@@ -42,10 +43,12 @@ This test section has some text.
 """)
 
     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')
         self.assertTrue(states.count()>0)
         reqs = BofreqFactory.create_batch(states.count())
-        url = urlreverse('ietf.doc.views_bofreq.bof_requests')
         r = self.client.get(url)
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
@@ -57,6 +60,13 @@ This test section has some text.
         q = PyQuery(r.content)
         for state in states:
             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):
@@ -67,7 +77,8 @@ This test section has some text.
         doc.rev='01'
         doc.save_with_history([nr_event])
         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))
         r = self.client.get(url)
         self.assertContains(r,'Version: 01',status_code=200)
@@ -77,12 +88,15 @@ This test section has some text.
         editor_row = q('#editors').html()
         for editor in editors:
             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'): 
             self.client.login(username=user,password=user+"+password")
             r = self.client.get(url)
             self.assertEqual(r.status_code, 200)
             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.assertNotEqual([],q('#change-request'))
         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,'is for an older version')
 
+
     def test_edit_title(self):
         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))
         title = doc.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):
         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))
         state = doc.get_state('bofreq')
         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):
         doc = BofreqFactory()
-        previous_editors = list(doc.latest_event(BofreqEditorDocEvent).editors.all())
+        previous_editors = list(bofreq_editors(doc))
         acting_editor = previous_editors[0]
         new_editors = set(previous_editors)
         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]))
         r = self.client.post(url, postdict)
         self.assertEqual(r.status_code,302)
-        editors = doc.latest_event(BofreqEditorDocEvent).editors.all()
+        editors = bofreq_editors(doc)
         self.assertEqual(set(previous_editors),set(editors))
         nobody = PersonFactory()
         self.client.login(username=nobody.user.username,password=nobody.user.username+'+password')
         r = self.client.post(url, postdict)
         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.client.logout()
         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')
             r = self.client.get(url)
             self.assertEqual(r.status_code,200)
+            unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
             for editor in previous_editors:
-                unescaped = unicontent(r).encode('utf-8').decode('unicode-escape')
                 self.assertIn(editor.name,unescaped)
             new_editors = set(previous_editors)
             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]))
             r = self.client.post(url,postdict)
             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))
             previous_editors = new_editors
             self.client.logout()
             self.assertEqual(len(outbox),1)
             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):
         doc = BofreqFactory()
         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.client.logout()
 
-        editor = doc.latest_event(BofreqEditorDocEvent).editors.first()
+        editor = bofreq_editors(doc).first()
         for username in ('secretary', 'ad', 'iab-member', editor.user.username):
             self.client.login(username=username, password=username+'+password')
             r = self.client.get(url)
@@ -267,7 +343,7 @@ This test section has some text.
             self.assertEqual(bofreq.title, postdict['title'])
             self.assertEqual(bofreq.rev, '00')
             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.text_or_error(), 'some stuff')
             self.assertEqual(len(outbox),1)
diff --git a/ietf/doc/urls_bofreq.py b/ietf/doc/urls_bofreq.py
index bbc70ce78..856f99e63 100644
--- a/ietf/doc/urls_bofreq.py
+++ b/ietf/doc/urls_bofreq.py
@@ -8,5 +8,6 @@ urlpatterns = [
     url(r'^state/$',                 views_bofreq.change_state),
     url(r'^submit/$',                views_bofreq.submit),
     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),
 ]
\ No newline at end of file
diff --git a/ietf/doc/utils_bofreq.py b/ietf/doc/utils_bofreq.py
new file mode 100644
index 000000000..aec8f60ad
--- /dev/null
+++ b/ietf/doc/utils_bofreq.py
@@ -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()
\ No newline at end of file
diff --git a/ietf/doc/views_bofreq.py b/ietf/doc/views_bofreq.py
index abe011ad7..94e821908 100644
--- a/ietf/doc/views_bofreq.py
+++ b/ietf/doc/views_bofreq.py
@@ -12,10 +12,12 @@ from django.template.loader import render_to_string
 from django.urls import reverse as urlreverse
 
 
-from ietf.doc.mails import ( email_bofreq_title_changed, email_bofreq_editors_changed, 
-    email_bofreq_new_revision, )
-from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, BofreqEditorDocEvent, State
+from ietf.doc.mails import (email_bofreq_title_changed, email_bofreq_editors_changed, 
+    email_bofreq_new_revision, email_bofreq_responsible_changed)
+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_bofreq import bofreq_editors, bofreq_responsible
 from ietf.ietfauth.utils import has_role, role_required
 from ietf.person.fields import SearchablePersonsField
 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
 
 
-
 def bof_requests(request):
     reqs = Document.objects.filter(type_id='bofreq')
     for req in reqs:
         req.latest_revision_event = req.latest_event(NewRevisionDocEvent)
     return render(request, 'doc/bofreq/bof_requests.html',dict(reqs=reqs))
 
+
 def edit_relations(request, name):
     raise NotImplementedError
 
@@ -74,7 +76,7 @@ class BofreqUploadForm(forms.Form):
 @login_required
 def submit(request, 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):
         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
 def change_editors(request, 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):
         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):
     title = forms.CharField(max_length=255, label="Title", required=True)
 
 @login_required
 def edit_title(request, 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):
         permission_denied(request, "You do not have permission to edit this document's title")
 
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 3f10480da..56b021686 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -55,15 +55,15 @@ import debug                            # pyflakes:ignore
 
 from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDocEvent, BallotType,
     ConsensusDocEvent, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent, IanaExpertDocEvent,
-    IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS, DocumentActionHolder, DocumentAuthor,
-    BofreqEditorDocEvent )
+    IESG_BALLOT_ACTIVE_STATES, STATUSCHANGE_RELATIONS, DocumentActionHolder, DocumentAuthor)
 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,
     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,
     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,
-    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.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,
@@ -529,9 +529,9 @@ def document_main(request, name, rev=None):
                                        ))
 
     if doc.type_id == "bofreq":
-#        content = markdown2.markdown(doc.text_or_error(),extras=['tables'])
         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'])
         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,
                                        can_manage=can_manage,
                                        editors=editors,
+                                       responsible=responsible,
                                        is_editor=is_editor,
                                        ))
 
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 8470981cc..dc0396dcb 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -273,8 +273,6 @@ class GroupPagesTests(TestCase):
 
     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', ]
 
         can_edit = {
diff --git a/ietf/mailtrigger/migrations/0023_bofreq_triggers.py b/ietf/mailtrigger/migrations/0023_bofreq_triggers.py
index cfba57644..d9a28d4fd 100644
--- a/ietf/mailtrigger/migrations/0023_bofreq_triggers.py
+++ b/ietf/mailtrigger/migrations/0023_bofreq_triggers.py
@@ -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', 
         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.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.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.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):
     MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
     Recipient = apps.get_model('mailtrigger', 'Recipient')
-    MailTrigger.objects.filter(slug__in=('bofreq_title_changed','bofreq_editors_changed','bofreq_new_revision')).delete()
-    Recipient.objects.filter(slug__in=('bofreq_editors','bofreq_previous_editors')).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_responsible', 'bofreq_previous_responsible')).delete()
 
 
 class Migration(migrations.Migration):
diff --git a/ietf/mailtrigger/models.py b/ietf/mailtrigger/models.py
index 70e70fdc5..3cda3d24e 100644
--- a/ietf/mailtrigger/models.py
+++ b/ietf/mailtrigger/models.py
@@ -6,7 +6,8 @@ from django.db import models
 from django.template import Template, Context
 
 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.group.models import Group
 from ietf.person.models import Email, Alias
@@ -398,8 +399,29 @@ class Recipient(models.Model):
         addrs = []
         if 'doc' in kwargs:
             bofreq = kwargs['doc']
-            editor_event = bofreq.latest_event(BofreqEditorDocEvent)
-            if editor_event:
-                addrs.extend([editor.email_address() for editor in editor_event.editors.all()])
+            editors = bofreq_editors(bofreq)
+            addrs.extend([editor.email_address() for editor in editors])
+        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
 
diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json
index 28b3d9038..3c4ed4eb6 100644
--- a/ietf/name/fixtures/names.json
+++ b/ietf/name/fixtures/names.json
@@ -3535,7 +3535,7 @@
       "to": [
         "bofreq_editors",
         "bofreq_previous_editors",
-        "doc_ad",
+        "bofreq_responsible",
         "doc_notify"
       ]
     },
@@ -3548,20 +3548,34 @@
       "desc": "Recipients when a new revision of a BOF request is uploaded.",
       "to": [
         "bofreq_editors",
-        "doc_ad",
+        "bofreq_responsible",
         "doc_notify"
       ]
     },
     "model": "mailtrigger.mailtrigger",
     "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": {
       "cc": [],
       "desc": "Recipients when the title of a BOF proposal is changed.",
       "to": [
         "bofreq_editors",
-        "doc_ad",
+        "bofreq_responsible",
         "doc_notify"
       ]
     },
@@ -5237,6 +5251,22 @@
     "model": "mailtrigger.recipient",
     "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": {
       "desc": "The person providing a comment to nomcom",
diff --git a/ietf/templates/doc/bofreq/bof_requests.html b/ietf/templates/doc/bofreq/bof_requests.html
index 77e366659..4127de8f0 100644
--- a/ietf/templates/doc/bofreq/bof_requests.html
+++ b/ietf/templates/doc/bofreq/bof_requests.html
@@ -7,26 +7,35 @@
 {% block content %}
   {% origin %}
 
-  {% regroup reqs by get_state_slug as grouped_reqs %}
-  {% for req_group in grouped_reqs %}
-  <div class="panel panel-default">
-    <div class="panel-heading">{{req_group.grouper|capfirst}} BOF Requests</div>
-    <div class="panel-body">
-      <table id="bofreqs-{{req_group.grouper}}" class="table table-condensed table-striped tablesorter">
-        <thead>
-            <tr><th class="col-sm-4">Name</th><th class="col-sm-1">Date</th><th>Title</th></tr>
-        </thead>
-        <tbody>
-              {% for req in req_group.list %}
-                <tr>
-                  <td><a href={% url 'ietf.doc.views_doc.document_main' name=req.name %}>{{req.name}}-{{req.rev}}</a></td>
-                  <td>{{req.latest_revision_event.time|date:"Y-m-d"}}</td>
-                  <td>{{req.title}}</td>
-                </tr>
-              {% endfor %}
-        </tbody>
-      </table>
+  <h1>BOF Requests</h1>
+
+  {% if not reqs %}
+    <p>There are currently no BoF Requests</p>
+  {% else %}
+    {% regroup reqs by get_state_slug as grouped_reqs %}
+    {% for req_group in grouped_reqs %}
+    <div class="panel panel-default">
+      <div class="panel-heading">{{req_group.grouper|capfirst}} BOF Requests</div>
+      <div class="panel-body">
+        <table id="bofreqs-{{req_group.grouper}}" class="table table-condensed table-striped tablesorter">
+          <thead>
+              <tr><th class="col-sm-4">Name</th><th class="col-sm-1">Date</th><th>Title</th></tr>
+          </thead>
+          <tbody>
+                {% for req in req_group.list %}
+                  <tr>
+                    <td><a href={% url 'ietf.doc.views_doc.document_main' name=req.name %}>{{req.name}}-{{req.rev}}</a></td>
+                    <td>{{req.latest_revision_event.time|date:"Y-m-d"}}</td>
+                    <td>{{req.title}}</td>
+                  </tr>
+                {% endfor %}
+          </tbody>
+        </table>
+      </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 %}
\ No newline at end of file
diff --git a/ietf/templates/doc/bofreq/change_responsible.html b/ietf/templates/doc/bofreq/change_responsible.html
new file mode 100644
index 000000000..23b8609bd
--- /dev/null
+++ b/ietf/templates/doc/bofreq/change_responsible.html
@@ -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 %}
\ No newline at end of file
diff --git a/ietf/templates/doc/document_bofreq.html b/ietf/templates/doc/document_bofreq.html
index abd37c23b..4529bcf3d 100644
--- a/ietf/templates/doc/document_bofreq.html
+++ b/ietf/templates/doc/document_bofreq.html
@@ -87,7 +87,7 @@
         <th>Editor{{editors|pluralize}}</th>
         <td class="edit">
           {% 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 %}
             {% endif %}
           {% endif %}
@@ -99,6 +99,22 @@
         </td>
       </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 class="meta">
diff --git a/ietf/templates/doc/mail/bofreq_responsible_changed.txt b/ietf/templates/doc/mail/bofreq_responsible_changed.txt
new file mode 100644
index 000000000..134d9f8f2
--- /dev/null
+++ b/ietf/templates/doc/mail/bofreq_responsible_changed.txt
@@ -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 %}
\ No newline at end of file